font-fbk.lmt /size: 19 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","composing")
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
49-- \starttext
50--     \definefontfeature[default][default][fakecombining=yes,compose=yes]
51--     \setupbodyfont[minion]
52--     [x][\char"2D9][x][\char"323] ṭḍṃḥ
53-- \stoptext
54
55local missing = {
56 -- [0x323] = { kind = "bottom", top = 0x307 }, -- dot below
57    [0x323] = { kind = "bottom", top = 0x2D9 }, -- dot below
58    --
59    -- TODO
60    --
61}
62
63local function fakecharacters(tfmdata,value)
64    if value then
65        local characters   = tfmdata.characters
66        local descriptions = tfmdata.descriptions
67        for unicode, detail in next, missing do
68            local c = characters[k]
69            if not c then
70                local kind = detail.kind
71                if kind == "bottom" then
72                    local u = detail.top
73                    local d = characters[u]
74                    if d then
75                        local ex = tfmdata.parameters.xheight
76                        characters[unicode] = {
77                            width    = d.width,
78                            height   = 0,
79                            depth    = ex/2,
80                            commands = { { "offset", 0, -d.height - ex/5 , u } },
81                            unicode  = unicode,
82                        }
83                        -- we need this for the composer
84                        local d = descriptions[u]
85                        local x = descriptions[0x78]
86                        if d and x then
87                            local t = table.copy(d)
88                            local x = -x.boundingbox[4]/2
89                            local b = t.boundingbox
90                            b[2] = x
91                            b[4] = x
92                            descriptions[unicode] = t
93                        end
94                        if trace then
95                            report("deriving %C from %C",unicode,u)
96                        end
97                    end
98                end
99            end
100        end
101    end
102end
103
104local specification = {
105    name        = "fakecombining",
106    description = "add missing combining accents",
107    manipulators = {
108     -- position = 1, -- todo: just before compose
109        base     = fakecharacters,
110        node     = fakecharacters,
111    }
112}
113
114fonts.handlers.otf.features.register(specification)
115
116local function composecharacters(tfmdata)
117    -- this assumes that slot 1 is self, there will be a proper self some day
118    local characters   = tfmdata.characters
119    local descriptions = tfmdata.descriptions
120    local parameters   = tfmdata.parameters
121    local properties   = tfmdata.properties
122    local Xdesc        = descriptions[utfbyte("X")]
123    local xdesc        = descriptions[utfbyte("x")]
124    if Xdesc and xdesc then
125        local scale        = parameters.factor or 1
126        local deltaxheight = scale * (Xdesc.boundingbox[4] - xdesc.boundingbox[4])
127        local extraxheight = fraction * deltaxheight -- maybe use compose value
128        local italicfactor = parameters.italicfactor or 0
129        local red, green, blue, black
130        if trace_visualize then
131            red   = { "startcolor", "red" }
132            green = { "startcolor", "green" }
133            blue  = { "startcolor", "blue" }
134            black = { "stopcolor" }
135        end
136        local compose = fonts.goodies.getcompositions(tfmdata)
137        if compose and trace_visualize then
138            report("using compose information from goodies file")
139        end
140        local done = false
141        for i, c in next, unicodecharacters do -- loop over all characters ... not that efficient but a specials hash takes memory
142            if force_combining or not characters[i] then
143                local s = c.specials
144                if s and s[1] == 'char' then
145                    local chr = s[2]
146                    local charschr = characters[chr]
147                    if charschr then
148                        local cc = c.category
149                        if cc == 'll' or cc == 'lu' or cc == 'lt' then -- characters.is_letter[cc]
150                            local acc = s[3]
151                            local t = { }
152                            for k, v in next, charschr do
153                                if k ~= "commands" then
154                                    t[k] = v
155                                end
156                            end
157                            local charsacc = characters[acc]
158                         -- local ca = charsacc.category
159                         -- if ca == "mn" then
160                         --     -- mark nonspacing
161                         -- elseif ca == "ms" then
162                         --     -- mark spacing combining
163                         -- elseif ca == "me" then
164                         --     -- mark enclosing
165                         -- else
166                            if not charsacc then -- fallback accents
167                                acc = unicodefallbacks[acc]
168                                charsacc = acc and characters[acc]
169                            end
170                            local chr_t = charcommand[chr]
171                            if charsacc then
172                                if trace_define then
173                                    report("composed %C, base %C, accent %C",i,chr,acc)
174                                end
175                                local acc_t = charcommand[acc]
176                                local cb = descriptions[chr]
177                                local ab = descriptions[acc]
178                                local cb = cb and cb.boundingbox
179                                local ab = ab and ab.boundingbox
180                                -- todo: adapt height
181                                if cb and ab then
182                                    local c_llx = scale*cb[1]
183                                    local c_lly = scale*cb[2]
184                                    local c_urx = scale*cb[3]
185                                    local c_ury = scale*cb[4]
186                                    local a_llx = scale*ab[1]
187                                    local a_lly = scale*ab[2]
188                                    local a_urx = scale*ab[3]
189                                    local a_ury = scale*ab[4]
190                                    local done  = false
191                                    if compose then
192                                        local i_compose = compose[i]
193                                        local i_anchored = i_compose and i_compose.anchored
194                                        if i_anchored then
195                                            local c_compose = compose[chr]
196                                            local a_compose = compose[acc]
197                                            local c_anchors = c_compose and c_compose.anchors
198                                            local a_anchors = a_compose and a_compose.anchors
199                                            if c_anchors and a_anchors then
200                                                local c_anchor = c_anchors[i_anchored]
201                                                local a_anchor = a_anchors[i_anchored]
202                                                if c_anchor and a_anchor then
203                                                    local cx = c_anchor.x or 0
204                                                    local cy = c_anchor.y or 0
205                                                    local ax = a_anchor.x or 0
206                                                    local ay = a_anchor.y or 0
207                                                    local dx = cx - ax
208                                                    local dy = cy - ay
209                                                    if trace_define then
210                                                        report("building %C from %C and %C",i,chr,acc)
211                                                        report("  boundingbox:")
212                                                        report("    chr: %3i %3i %3i %3i",unpack(cb))
213                                                        report("    acc: %3i %3i %3i %3i",unpack(ab))
214                                                        report("  anchors:")
215                                                        report("    chr: %3i %3i",cx,cy)
216                                                        report("    acc: %3i %3i",ax,ay)
217                                                        report("  delta:")
218                                                        report("    %s: %3i %3i",i_anchored,dx,dy)
219                                                    end
220                                                    local right = rightcommand[scale*dx]
221                                                    local down  = upcommand[scale*dy]
222                                                    if trace_visualize then
223                                                        t.commands = {
224                                                            push, right, down,
225                                                            green, acc_t, black,
226                                                            pop, chr_t,
227                                                        }
228                                                    else
229                                                        t.commands = {
230                                                            push, right, down,
231                                                            acc_t, pop, chr_t,
232                                                        }
233                                                    end
234                                                    done = true
235                                                end
236                                            end
237                                        end
238                                    end
239                                    if not done then
240                                        -- can be sped up for scale == 1
241                                        local dx = (c_urx - a_urx - a_llx + c_llx)/2
242                                        local dd = (c_urx - c_llx)*italicfactor
243                                        if a_ury < 0  then
244                                            local right = rightcommand[dx-dd]
245                                            if trace_visualize then
246                                                t.commands = {
247                                                    push, right, red, acc_t,
248                                                    black, pop, chr_t,
249                                                }
250                                            else
251                                                t.commands = {
252                                                    push, right, acc_t, pop,
253                                                    chr_t,
254                                                }
255                                            end
256                                            t.depth = a_ury
257                                        elseif c_ury > a_lly then -- messy test
258                                            local dy
259                                            if compose then
260                                                -- experimental: we could use sx but all that testing
261                                                -- takes time and code
262                                                dy = compose[i]
263                                                if dy then
264                                                    dy = dy.dy
265                                                end
266                                                if not dy then
267                                                    dy = compose[acc]
268                                                    if dy then
269                                                        dy = dy and dy.dy
270                                                    end
271                                                end
272                                                if not dy then
273                                                    dy = compose.dy
274                                                end
275                                                if not dy then
276                                                    dy = - deltaxheight + extraxheight
277                                                elseif dy > -1.5 and dy < 1.5 then
278                                                    -- we assume a fraction of (percentage)
279                                                    dy = - dy * deltaxheight
280                                                else
281                                                    -- we assume fontunits (value smaller than 2 make no sense)
282                                                    dy = - dy * scale
283                                                end
284                                            else
285                                                dy = - deltaxheight + extraxheight
286                                            end
287                                            t.height = a_ury-dy
288                                            local right = rightcommand[dx+dd]
289                                            local down  = downcommand[dy]
290                                            if trace_visualize then
291                                                t.commands = {
292                                                    push, right, down, green,
293                                                    acc_t, black, pop, chr_t,
294                                                }
295                                            else
296                                                t.commands = {
297                                                    push, right, down, acc_t,
298                                                    pop, chr_t,
299                                                }
300                                            end
301                                        else
302                                            local right = rightcommand[dx+dd]
303                                            if trace_visualize then
304                                                t.commands = {
305                                                    push, right, blue, acc_t,
306                                                    black, pop, chr_t,
307                                                }
308                                            else
309                                                t.commands = {
310                                                    push, right, acc_t, pop,
311                                                    chr_t,
312                                                }
313                                            end
314                                            t.height = a_ury
315                                        end
316                                    end
317                                else
318                                    t.commands = {
319                                        chr_t, -- else index mess
320                                    }
321                                end
322                            else
323                                if trace_define then
324                                    report("%C becomes simplified %C",i,chr)
325                                end
326                                t.commands = {
327                                    chr_t, -- else index mess
328                                }
329                            end
330                            done = true
331                            characters[i] = t
332                            local d = { }
333                            for k, v in next, descriptions[chr] do
334                                d[k] = v
335                            end
336                            descriptions[i] = d
337                        end
338                    end
339                end
340            end
341        end
342    end
343end
344
345local specification = {
346    name        = "compose",
347    description = "additional composed characters",
348    manipulators = {
349        base = composecharacters,
350        node = composecharacters,
351    }
352}
353
354registerotffeature(specification)
355registerafmfeature(specification)
356
357addotffeature {
358    name     = "char-ligatures",
359    type     = "ligature",
360    data     = characters.splits.char,
361    order    = { "char-ligatures" },
362    prepend  = true,
363}
364
365addotffeature {
366    name     = "compat-ligatures",
367    type     = "ligature",
368    data     = characters.splits.compat,
369    order    = { "compat-ligatures" },
370    prepend  = true,
371}
372
373registerotffeature {
374    name        = 'char-ligatures',
375    description = 'unicode char specials to ligatures',
376}
377
378registerotffeature {
379    name        = 'compat-ligatures',
380    description = 'unicode compat specials to ligatures',
381}
382
383-- We now provide the composer in the helpers namespace (too):
384
385fonts.helpers.composecharacters = composecharacters
386
387-- We keep this just because we have a few demos but there often are other ways to achieve
388-- the same. This code installs the builder into the regular virtual font builder, which
389-- only makes sense as demo. It also shows a bit what the evolution is.
390
391do
392
393    local vf       = handlers.vf
394    local commands = vf.combiner.commands
395
396    vf.helpers.composecharacters = composecharacters
397
398    commands["compose.trace.enable"]  = function() trace_visualize = true  end
399    commands["compose.trace.disable"] = function() trace_visualize = false end
400    commands["compose.force.enable"]  = function() force_combining = true  end
401    commands["compose.force.disable"] = function() force_combining = false end
402
403    commands["compose.trace.set"] = function(g,v)
404        if v[2] == nil then
405            trace_visualize = true
406        else
407            trace_visualize = v[2]
408        end
409    end
410
411    commands["compose.apply"] = function(g,v)
412        composecharacters(g)
413    end
414
415end
416