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