font-fbk.lua /size: 16 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['font-fbk'] = {
2    version   = 1.001,
3    comment   = "companion to font-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local cos, tan, rad, format = math.cos, math.tan, math.rad, string.format
10local utfbyte, utfchar = utf.byte, utf.char
11local next = next
12
13--[[ldx--
14<p>This is very experimental code!</p>
15--ldx]]--
16
17local trace_visualize    = false  trackers.register("fonts.composing.visualize", function(v) trace_visualize = v end)
18local trace_define       = false  trackers.register("fonts.composing.define",    function(v) trace_define    = v end)
19
20local report             = logs.reporter("fonts","combining")
21
22local allocate           = utilities.storage.allocate
23
24local fonts              = fonts
25local handlers           = fonts.handlers
26local constructors       = fonts.constructors
27local helpers            = fonts.helpers
28
29local otf                = handlers.otf
30local afm                = handlers.afm
31local registerotffeature = otf.features.register
32local registerafmfeature = afm.features.register
33
34local addotffeature      = otf.addfeature
35
36local unicodecharacters  = characters.data
37local unicodefallbacks   = characters.fallbacks
38
39local vfcommands         = helpers.commands
40local charcommand        = vfcommands.char
41local rightcommand       = vfcommands.right
42local downcommand        = vfcommands.down
43local upcommand          = vfcommands.up
44local push               = vfcommands.push
45local pop                = vfcommands.pop
46
47local force_combining    = false -- just for demo purposes (see mk)
48local fraction           = 0.15  -- 30 units for lucida
49
50-- todo: we also need to update the feature hashes ... i'll do that when i'm in the mood
51-- and/or when i need it
52
53local function composecharacters(tfmdata)
54    -- this assumes that slot 1 is self, there will be a proper self some day
55    local characters   = tfmdata.characters
56    local descriptions = tfmdata.descriptions
57    local parameters   = tfmdata.parameters
58    local properties   = tfmdata.properties
59    local Xdesc        = descriptions[utfbyte("X")]
60    local xdesc        = descriptions[utfbyte("x")]
61    if Xdesc and xdesc then
62        local scale        = parameters.factor or 1
63        local deltaxheight = scale * (Xdesc.boundingbox[4] - xdesc.boundingbox[4])
64        local extraxheight = fraction * deltaxheight -- maybe use compose value
65        local italicfactor = parameters.italicfactor or 0
66        local vfspecials   = backends.tables.vfspecials --brr
67        local red, green, blue, black
68        if trace_visualize then
69            red   = vfspecials.startcolor("red")
70            green = vfspecials.startcolor("green")
71            blue  = vfspecials.startcolor("blue")
72            black = vfspecials.stopcolor
73        end
74        local compose = fonts.goodies.getcompositions(tfmdata)
75        if compose and trace_visualize then
76            report("using compose information from goodies file")
77        end
78        local done = false
79        for i, c in next, unicodecharacters do -- loop over all characters ... not that efficient but a specials hash takes memory
80            if force_combining or not characters[i] then
81                local s = c.specials
82                if s and s[1] == 'char' then
83                    local chr = s[2]
84                    local charschr = characters[chr]
85                    if charschr then
86                        local cc = c.category
87                        if cc == 'll' or cc == 'lu' or cc == 'lt' then -- characters.is_letter[cc]
88                            local acc = s[3]
89                            local t = { }
90                            for k, v in next, charschr do
91                                if k ~= "commands" then
92                                    t[k] = v
93                                end
94                            end
95                            local charsacc = characters[acc]
96                         -- local ca = charsacc.category
97                         -- if ca == "mn" then
98                         --     -- mark nonspacing
99                         -- elseif ca == "ms" then
100                         --     -- mark spacing combining
101                         -- elseif ca == "me" then
102                         --     -- mark enclosing
103                         -- else
104                            if not charsacc then -- fallback accents
105                                acc = unicodefallbacks[acc]
106                                charsacc = acc and characters[acc]
107                            end
108                            local chr_t = charcommand[chr]
109                            if charsacc then
110                                if trace_define then
111                                    report("composed %C, base %C, accent %C",i,chr,acc)
112                                end
113                                local acc_t = charcommand[acc]
114                                local cb = descriptions[chr].boundingbox
115                                local ab = descriptions[acc].boundingbox
116                                -- todo: adapt height
117                                if cb and ab then
118                                    local c_llx = scale*cb[1]
119                                    local c_lly = scale*cb[2]
120                                    local c_urx = scale*cb[3]
121                                    local c_ury = scale*cb[4]
122                                    local a_llx = scale*ab[1]
123                                    local a_lly = scale*ab[2]
124                                    local a_urx = scale*ab[3]
125                                    local a_ury = scale*ab[4]
126                                    local done  = false
127                                    if compose then
128                                        local i_compose = compose[i]
129                                        local i_anchored = i_compose and i_compose.anchored
130                                        if i_anchored then
131                                            local c_compose = compose[chr]
132                                            local a_compose = compose[acc]
133                                            local c_anchors = c_compose and c_compose.anchors
134                                            local a_anchors = a_compose and a_compose.anchors
135                                            if c_anchors and a_anchors then
136                                                local c_anchor = c_anchors[i_anchored]
137                                                local a_anchor = a_anchors[i_anchored]
138                                                if c_anchor and a_anchor then
139                                                    local cx = c_anchor.x or 0
140                                                    local cy = c_anchor.y or 0
141                                                    local ax = a_anchor.x or 0
142                                                    local ay = a_anchor.y or 0
143                                                    local dx = cx - ax
144                                                    local dy = cy - ay
145                                                    if trace_define then
146                                                        report("building %C from %C and %C",i,chr,acc)
147                                                        report("  boundingbox:")
148                                                        report("    chr: %3i %3i %3i %3i",unpack(cb))
149                                                        report("    acc: %3i %3i %3i %3i",unpack(ab))
150                                                        report("  anchors:")
151                                                        report("    chr: %3i %3i",cx,cy)
152                                                        report("    acc: %3i %3i",ax,ay)
153                                                        report("  delta:")
154                                                        report("    %s: %3i %3i",i_anchored,dx,dy)
155                                                    end
156                                                    local right = rightcommand[scale*dx]
157                                                    local down  = upcommand[scale*dy]
158                                                    if trace_visualize then
159                                                        t.commands = {
160                                                            push, right, down,
161                                                            green, acc_t, black,
162                                                            pop, chr_t,
163                                                        }
164                                                    else
165                                                        t.commands = {
166                                                            push, right, down,
167                                                            acc_t, pop, chr_t,
168                                                        }
169                                                    end
170                                                    done = true
171                                                end
172                                            end
173                                        end
174                                    end
175                                    if not done then
176                                        -- can be sped up for scale == 1
177                                        local dx = (c_urx - a_urx - a_llx + c_llx)/2
178                                        local dd = (c_urx - c_llx)*italicfactor
179                                        if a_ury < 0  then
180                                            local right = rightcommand[dx-dd]
181                                            if trace_visualize then
182                                                t.commands = {
183                                                    push, right, red, acc_t,
184                                                    black, pop, chr_t,
185                                                }
186                                            else
187                                                t.commands = {
188                                                    push, right, acc_t, pop,
189                                                    chr_t,
190                                                }
191                                            end
192t.depth = a_ury
193                                        elseif c_ury > a_lly then -- messy test
194                                            local dy
195                                            if compose then
196                                                -- experimental: we could use sx but all that testing
197                                                -- takes time and code
198                                                dy = compose[i]
199                                                if dy then
200                                                    dy = dy.dy
201                                                end
202                                                if not dy then
203                                                    dy = compose[acc]
204                                                    if dy then
205                                                        dy = dy and dy.dy
206                                                    end
207                                                end
208                                                if not dy then
209                                                    dy = compose.dy
210                                                end
211                                                if not dy then
212                                                    dy = - deltaxheight + extraxheight
213                                                elseif dy > -1.5 and dy < 1.5 then
214                                                    -- we assume a fraction of (percentage)
215                                                    dy = - dy * deltaxheight
216                                                else
217                                                    -- we assume fontunits (value smaller than 2 make no sense)
218                                                    dy = - dy * scale
219                                                end
220                                            else
221                                                dy = - deltaxheight + extraxheight
222                                            end
223t.height = a_ury-dy
224                                            local right = rightcommand[dx+dd]
225                                            local down  = downcommand[dy]
226                                            if trace_visualize then
227                                                t.commands = {
228                                                    push, right, down, green,
229                                                    acc_t, black, pop, chr_t,
230                                                }
231                                            else
232                                                t.commands = {
233                                                    push, right, down, acc_t,
234                                                    pop, chr_t,
235                                                }
236                                            end
237                                        else
238                                            local right = rightcommand[dx+dd]
239                                            if trace_visualize then
240                                                t.commands = {
241                                                    push, right, blue, acc_t,
242                                                    black, pop, chr_t,
243                                                }
244                                            else
245                                                t.commands = {
246                                                    push, right, acc_t, pop,
247                                                    chr_t,
248                                                }
249                                            end
250t.height = a_ury
251                                        end
252                                    end
253                                else
254                                    t.commands = {
255                                        chr_t, -- else index mess
256                                    }
257                                end
258                            else
259                                if trace_define then
260                                    report("%C becomes simplified %C",i,chr)
261                                end
262                                t.commands = {
263                                    chr_t, -- else index mess
264                                }
265                            end
266                            done = true
267                            characters[i] = t
268                            local d = { }
269                            for k, v in next, descriptions[chr] do
270                                d[k] = v
271                            end
272                            descriptions[i] = d
273                        end
274                    end
275                end
276            end
277        end
278        if done then
279            properties.virtualized = true
280        end
281    end
282end
283
284local specification = {
285    name        = "compose",
286    description = "additional composed characters",
287    manipulators = {
288        base = composecharacters,
289        node = composecharacters,
290    }
291}
292
293registerotffeature(specification)
294registerafmfeature(specification)
295
296addotffeature {
297    name     = "char-ligatures",
298    type     = "ligature",
299    data     = characters.splits.char,
300    order    = { "char-ligatures" },
301    prepend  = true,
302}
303
304addotffeature {
305    name     = "compat-ligatures",
306    type     = "ligature",
307    data     = characters.splits.compat,
308    order    = { "compat-ligatures" },
309    prepend  = true,
310}
311
312registerotffeature {
313    name        = 'char-ligatures',
314    description = 'unicode char specials to ligatures',
315}
316
317registerotffeature {
318    name        = 'compat-ligatures',
319    description = 'unicode compat specials to ligatures',
320}
321
322do
323
324    -- This installs the builder into the regular virtual font builder,
325    -- which only makes sense as demo.
326
327    local vf       = handlers.vf
328    local commands = vf.combiner.commands
329
330    vf.helpers.composecharacters = composecharacters
331
332    commands["compose.trace.enable"] = function()
333        trace_visualize = true
334    end
335
336    commands["compose.trace.disable"] = function()
337        trace_visualize = false
338    end
339
340    commands["compose.force.enable"] = function()
341        force_combining = true
342    end
343
344    commands["compose.force.disable"] = function()
345        force_combining = false
346    end
347
348    commands["compose.trace.set"] = function(g,v)
349        if v[2] == nil then
350            trace_visualize = true
351        else
352            trace_visualize = v[2]
353        end
354    end
355
356    commands["compose.apply"] = function(g,v)
357        composecharacters(g)
358    end
359
360end
361