font-fbk.lmt /size: 16 Kb    last modification: 2021-10-28 13:51
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 red, green, blue, black
67        if trace_visualize then
68            red   = { "startcolor", "red" }
69            green = { "startcolor", "green" }
70            blue  = { "startcolor", "blue" }
71            black = { "stopcolor" }
72        end
73        local compose = fonts.goodies.getcompositions(tfmdata)
74        if compose and trace_visualize then
75            report("using compose information from goodies file")
76        end
77        local done = false
78        for i, c in next, unicodecharacters do -- loop over all characters ... not that efficient but a specials hash takes memory
79            if force_combining or not characters[i] then
80                local s = c.specials
81                if s and s[1] == 'char' then
82                    local chr = s[2]
83                    local charschr = characters[chr]
84                    if charschr then
85                        local cc = c.category
86                        if cc == 'll' or cc == 'lu' or cc == 'lt' then -- characters.is_letter[cc]
87                            local acc = s[3]
88                            local t = { }
89                            for k, v in next, charschr do
90                                if k ~= "commands" then
91                                    t[k] = v
92                                end
93                            end
94                            local charsacc = characters[acc]
95                         -- local ca = charsacc.category
96                         -- if ca == "mn" then
97                         --     -- mark nonspacing
98                         -- elseif ca == "ms" then
99                         --     -- mark spacing combining
100                         -- elseif ca == "me" then
101                         --     -- mark enclosing
102                         -- else
103                            if not charsacc then -- fallback accents
104                                acc = unicodefallbacks[acc]
105                                charsacc = acc and characters[acc]
106                            end
107                            local chr_t = charcommand[chr]
108                            if charsacc then
109                                if trace_define then
110                                    report("composed %C, base %C, accent %C",i,chr,acc)
111                                end
112                                local acc_t = charcommand[acc]
113                                local cb = descriptions[chr].boundingbox
114                                local ab = descriptions[acc].boundingbox
115                                -- todo: adapt height
116                                if cb and ab then
117                                    local c_llx = scale*cb[1]
118                                    local c_lly = scale*cb[2]
119                                    local c_urx = scale*cb[3]
120                                    local c_ury = scale*cb[4]
121                                    local a_llx = scale*ab[1]
122                                    local a_lly = scale*ab[2]
123                                    local a_urx = scale*ab[3]
124                                    local a_ury = scale*ab[4]
125                                    local done  = false
126                                    if compose then
127                                        local i_compose = compose[i]
128                                        local i_anchored = i_compose and i_compose.anchored
129                                        if i_anchored then
130                                            local c_compose = compose[chr]
131                                            local a_compose = compose[acc]
132                                            local c_anchors = c_compose and c_compose.anchors
133                                            local a_anchors = a_compose and a_compose.anchors
134                                            if c_anchors and a_anchors then
135                                                local c_anchor = c_anchors[i_anchored]
136                                                local a_anchor = a_anchors[i_anchored]
137                                                if c_anchor and a_anchor then
138                                                    local cx = c_anchor.x or 0
139                                                    local cy = c_anchor.y or 0
140                                                    local ax = a_anchor.x or 0
141                                                    local ay = a_anchor.y or 0
142                                                    local dx = cx - ax
143                                                    local dy = cy - ay
144                                                    if trace_define then
145                                                        report("building %C from %C and %C",i,chr,acc)
146                                                        report("  boundingbox:")
147                                                        report("    chr: %3i %3i %3i %3i",unpack(cb))
148                                                        report("    acc: %3i %3i %3i %3i",unpack(ab))
149                                                        report("  anchors:")
150                                                        report("    chr: %3i %3i",cx,cy)
151                                                        report("    acc: %3i %3i",ax,ay)
152                                                        report("  delta:")
153                                                        report("    %s: %3i %3i",i_anchored,dx,dy)
154                                                    end
155                                                    local right = rightcommand[scale*dx]
156                                                    local down  = upcommand[scale*dy]
157                                                    if trace_visualize then
158                                                        t.commands = {
159                                                            push, right, down,
160                                                            green, acc_t, black,
161                                                            pop, chr_t,
162                                                        }
163                                                    else
164                                                        t.commands = {
165                                                            push, right, down,
166                                                            acc_t, pop, chr_t,
167                                                        }
168                                                    end
169                                                    done = true
170                                                end
171                                            end
172                                        end
173                                    end
174                                    if not done then
175                                        -- can be sped up for scale == 1
176                                        local dx = (c_urx - a_urx - a_llx + c_llx)/2
177                                        local dd = (c_urx - c_llx)*italicfactor
178                                        if a_ury < 0  then
179                                            local right = rightcommand[dx-dd]
180                                            if trace_visualize then
181                                                t.commands = {
182                                                    push, right, red, acc_t,
183                                                    black, pop, chr_t,
184                                                }
185                                            else
186                                                t.commands = {
187                                                    push, right, acc_t, pop,
188                                                    chr_t,
189                                                }
190                                            end
191                                            t.depth = a_ury
192                                        elseif c_ury > a_lly then -- messy test
193                                            local dy
194                                            if compose then
195                                                -- experimental: we could use sx but all that testing
196                                                -- takes time and code
197                                                dy = compose[i]
198                                                if dy then
199                                                    dy = dy.dy
200                                                end
201                                                if not dy then
202                                                    dy = compose[acc]
203                                                    if dy then
204                                                        dy = dy and dy.dy
205                                                    end
206                                                end
207                                                if not dy then
208                                                    dy = compose.dy
209                                                end
210                                                if not dy then
211                                                    dy = - deltaxheight + extraxheight
212                                                elseif dy > -1.5 and dy < 1.5 then
213                                                    -- we assume a fraction of (percentage)
214                                                    dy = - dy * deltaxheight
215                                                else
216                                                    -- we assume fontunits (value smaller than 2 make no sense)
217                                                    dy = - dy * scale
218                                                end
219                                            else
220                                                dy = - deltaxheight + extraxheight
221                                            end
222                                            t.height = a_ury-dy
223                                            local right = rightcommand[dx+dd]
224                                            local down  = downcommand[dy]
225                                            if trace_visualize then
226                                                t.commands = {
227                                                    push, right, down, green,
228                                                    acc_t, black, pop, chr_t,
229                                                }
230                                            else
231                                                t.commands = {
232                                                    push, right, down, acc_t,
233                                                    pop, chr_t,
234                                                }
235                                            end
236                                        else
237                                            local right = rightcommand[dx+dd]
238                                            if trace_visualize then
239                                                t.commands = {
240                                                    push, right, blue, acc_t,
241                                                    black, pop, chr_t,
242                                                }
243                                            else
244                                                t.commands = {
245                                                    push, right, acc_t, pop,
246                                                    chr_t,
247                                                }
248                                            end
249                                            t.height = a_ury
250                                        end
251                                    end
252                                else
253                                    t.commands = {
254                                        chr_t, -- else index mess
255                                    }
256                                end
257                            else
258                                if trace_define then
259                                    report("%C becomes simplified %C",i,chr)
260                                end
261                                t.commands = {
262                                    chr_t, -- else index mess
263                                }
264                            end
265                            done = true
266                            characters[i] = t
267                            local d = { }
268                            for k, v in next, descriptions[chr] do
269                                d[k] = v
270                            end
271                            descriptions[i] = d
272                        end
273                    end
274                end
275            end
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
318-- We now provide the composer in the helpers namespace (too):
319
320fonts.helpers.composecharacters = composecharacters
321
322-- We keep this just because we have a few demos but there often are other ways to achieve
323-- the same. This code installs the builder into the regular virtual font builder, which
324-- only makes sense as demo. It also shows a bit what the evolution is.
325
326do
327
328    local vf       = handlers.vf
329    local commands = vf.combiner.commands
330
331    vf.helpers.composecharacters = composecharacters
332
333    commands["compose.trace.enable"]  = function() trace_visualize = true  end
334    commands["compose.trace.disable"] = function() trace_visualize = false end
335    commands["compose.force.enable"]  = function() force_combining = true  end
336    commands["compose.force.disable"] = function() force_combining = false end
337
338    commands["compose.trace.set"] = function(g,v)
339        if v[2] == nil then
340            trace_visualize = true
341        else
342            trace_visualize = v[2]
343        end
344    end
345
346    commands["compose.apply"] = function(g,v)
347        composecharacters(g)
348    end
349
350end
351