good-mth.lua /size: 17 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['good-mth'] = {
2    version   = 1.000,
3    comment   = "companion to font-lib.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 type, next, tonumber, unpack = type, next, tonumber, unpack
10local ceil = math.ceil
11local match = string.match
12
13local fonts = fonts
14
15local trace_goodies      = false  trackers.register("fonts.goodies", function(v) trace_goodies = v end)
16local report_goodies     = logs.reporter("fonts","goodies")
17
18local registerotffeature = fonts.handlers.otf.features.register
19
20local fontgoodies        = fonts.goodies or { }
21
22local fontcharacters     = fonts.hashes.characters
23
24local trace_defining     = false  trackers.register("math.defining",   function(v) trace_defining   = v end)
25
26local report_math        = logs.reporter("mathematics","initializing")
27
28local nuts               = nodes.nuts
29
30local setlink            = nuts.setlink
31
32local nodepool           = nuts.pool
33
34local new_kern           = nodepool.kern
35local new_glyph          = nodepool.glyph
36local new_hlist          = nodepool.hlist
37local new_vlist          = nodepool.vlist
38
39local insertnodeafter    = nuts.insertafter
40
41local helpers            = fonts.helpers
42local upcommand          = helpers.commands.up
43local rightcommand       = helpers.commands.right
44local charcommand        = helpers.commands.char
45local prependcommands    = helpers.prependcommands
46
47-- experiment, we have to load the definitions immediately as they precede
48-- the definition so they need to be initialized in the typescript
49
50local function withscriptcode(tfmdata,unicode,data,action)
51    if type(unicode) == "string" then
52        local p, u = match(unicode,"^(.-):(.-)$")
53        if u then
54            u = tonumber(u)
55            if u then
56                local slots = fonts.helpers.mathscriptslots(tfmdata,u)
57                if slots then
58                    if p == "*" then
59                        action(u,data)
60                        for i=1,#slots do
61                            action(slots[i],data)
62                        end
63                    else
64                        p = tonumber(p)
65                        if p then
66                            action(slots[p],data)
67                        end
68                    end
69                end
70            end
71        end
72    else
73        action(unicode,data)
74    end
75end
76
77local function finalize(tfmdata,feature,value)
78--     if tfmdata.mathparameters then -- funny, cambria text has this
79    local goodies = tfmdata.goodies
80    if goodies then
81        local virtualized = mathematics.virtualized
82        for i=1,#goodies do
83            local goodie = goodies[i]
84            local mathematics = goodie.mathematics
85            local dimensions  = mathematics and mathematics.dimensions
86            if dimensions then
87                if trace_defining then
88                    report_math("overloading dimensions in %a @ %p",tfmdata.properties.fullname,tfmdata.parameters.size)
89                end
90                local characters   = tfmdata.characters
91                local descriptions = tfmdata.descriptions
92                local parameters   = tfmdata.parameters
93                local factor       = parameters.factor
94                local hfactor      = parameters.hfactor
95                local vfactor      = parameters.vfactor
96                local function overloadone(unicode,data)
97                    local character = characters[unicode]
98                    if not character then
99                        local c = virtualized[unicode]
100                        if c then
101                            character = characters[c]
102                        end
103                    end
104                    if character then
105                        local width  = data.width
106                        local height = data.height
107                        local depth  = data.depth
108                        if trace_defining and (width or height or depth) then
109                            report_math("overloading dimensions of %C, width %p, height %p, depth %p",
110                                unicode,width or 0,height or 0,depth or 0)
111                        end
112                        if width  then character.width  = width  * hfactor end
113                        if height then character.height = height * vfactor end
114                        if depth  then character.depth  = depth  * vfactor end
115                        --
116                        local xoffset = data.xoffset
117                        local yoffset = data.yoffset
118                        if xoffset == "llx" then
119                            local d = descriptions[unicode]
120                            if d then
121                                xoffset         = - d.boundingbox[1] * hfactor
122                                character.width = character.width + xoffset
123                                xoffset         = rightcommand[xoffset]
124                            else
125                                xoffset = nil
126                            end
127                        elseif xoffset and xoffset ~= 0 then
128                            xoffset = rightcommand[xoffset * hfactor]
129                        else
130                            xoffset = nil
131                        end
132                        if yoffset and yoffset ~= 0 then
133                            yoffset = upcommand[yoffset * vfactor]
134                        else
135                            yoffset = nil
136                        end
137                        if xoffset or yoffset then
138                            local commands = character.commands
139                            if commands then
140                                prependcommands(commands,yoffset,xoffset)
141                            else
142                                local slot = charcommand[unicode]
143                                if xoffset and yoffset then
144                                    character.commands = { xoffset, yoffset, slot }
145                                elseif xoffset then
146                                    character.commands = { xoffset, slot }
147                                else
148                                    character.commands = { yoffset, slot }
149                                end
150                            end
151                        end
152                    elseif trace_defining then
153                        report_math("no overloading dimensions of %C, not in font",unicode)
154                    end
155                end
156                local function overload(dimensions)
157                    for unicode, data in next, dimensions do
158                        withscriptcode(tfmdata,unicode,data,overloadone)
159                    end
160                end
161                if value == nil then
162                    value = { "default" }
163                end
164                if value == "all" or value == true then
165                    for name, value in next, dimensions do
166                        overload(value)
167                    end
168                else
169                    if type(value) == "string" then
170                        value = utilities.parsers.settings_to_array(value)
171                    end
172                    if type(value) == "table" then
173                        for i=1,#value do
174                            local d = dimensions[value[i]]
175                            if d then
176                                overload(d)
177                            end
178                        end
179                    end
180                end
181            end
182        end
183    end
184end
185
186registerotffeature {
187    name         = "mathdimensions",
188    description  = "manipulate math dimensions",
189 -- default      = true,
190    manipulators = {
191        base = finalize,
192        node = finalize,
193    }
194}
195
196local function initialize(goodies)
197    local mathgoodies = goodies.mathematics
198    if mathgoodies then
199        local virtuals = mathgoodies.virtuals
200        local mapfiles = mathgoodies.mapfiles
201        local maplines = mathgoodies.maplines
202        if virtuals then
203            for name, specification in next, virtuals do
204                -- beware, they are all constructed ... we should be more selective
205                mathematics.makefont(name,specification,goodies)
206            end
207        end
208        if mapfiles then
209            for i=1,#mapfiles do
210                fonts.mappings.loadfile(mapfiles[i]) -- todo: backend function
211            end
212        end
213        if maplines then
214            for i=1,#maplines do
215                fonts.mappings.loadline(maplines[i]) -- todo: backend function
216            end
217        end
218    end
219end
220
221fontgoodies.register("mathematics", initialize)
222
223-- local enabled = false   directives.register("fontgoodies.mathkerning",function(v) enabled = v end)
224
225local function initialize(tfmdata)
226--     if enabled and tfmdata.mathparameters then -- funny, cambria text has this
227    if tfmdata.mathparameters then -- funny, cambria text has this
228        local goodies = tfmdata.goodies
229        if goodies then
230            local characters = tfmdata.characters
231            if characters[0x1D44E] then -- 119886
232                -- we have at least an italic a
233                for i=1,#goodies do
234                    local mathgoodies = goodies[i].mathematics
235                    if mathgoodies then
236                        local kerns = mathgoodies.kerns
237                        if kerns then
238                            local function kernone(unicode,data)
239                                local chardata = characters[unicode]
240                                if chardata and (not chardata.mathkerns or data.force) then
241                                    chardata.mathkerns = data
242                                end
243                            end
244                            for unicode, data in next, kerns do
245                                withscriptcode(tfmdata,unicode,data,kernone)
246                            end
247                            return
248                        end
249                    end
250                end
251            else
252                return -- no proper math font anyway
253            end
254        end
255    end
256end
257
258registerotffeature {
259    name         = "mathkerns",
260    description  = "math kerns",
261 -- default      = true,
262    initializers = {
263        base = initialize,
264        node = initialize,
265    }
266}
267
268-- math italics (not really needed)
269--
270-- it would be nice to have a \noitalics\font option
271
272local function initialize(tfmdata)
273    local goodies = tfmdata.goodies
274    if goodies then
275        local shared = tfmdata.shared
276        for i=1,#goodies do
277            local mathgoodies = goodies[i].mathematics
278            if mathgoodies then
279                local mathitalics = mathgoodies.italics
280                if mathitalics then
281                    local properties = tfmdata.properties
282                    if properties.setitalics then
283                        mathitalics = mathitalics[file.nameonly(properties.name)] or mathitalics
284                        if mathitalics then
285                            if trace_goodies then
286                                report_goodies("loading mathitalics for font %a",properties.name)
287                            end
288                            local corrections   = mathitalics.corrections
289                            local defaultfactor = mathitalics.defaultfactor
290                         -- properties.mathitalic_defaultfactor = defaultfactor -- we inherit outer one anyway (name will change)
291                            if corrections then
292                                fontgoodies.registerpostprocessor(tfmdata, function(tfmdata) -- this is another tfmdata (a copy)
293                                    -- better make a helper so that we have less code being defined
294                                    local properties = tfmdata.properties
295                                    local parameters = tfmdata.parameters
296                                    local characters = tfmdata.characters
297                                    properties.mathitalic_defaultfactor = defaultfactor
298                                    properties.mathitalic_defaultvalue  = defaultfactor * parameters.quad
299                                    if trace_goodies then
300                                        report_goodies("assigning mathitalics for font %a",properties.name)
301                                    end
302                                    local quad    = parameters.quad
303                                    local hfactor = parameters.hfactor
304                                    for k, v in next, corrections do
305                                        local c = characters[k]
306                                        if c then
307                                            if v > -1 and v < 1 then
308                                                c.italic = v * quad
309                                            else
310                                                c.italic = v * hfactor
311                                            end
312                                        else
313                                            report_goodies("invalid mathitalics entry %U for font %a",k,properties.name)
314                                        end
315                                    end
316                                end)
317                            end
318                            return -- maybe not as these can accumulate
319                        end
320                    end
321                end
322            end
323        end
324    end
325end
326
327registerotffeature {
328    name         = "mathitalics",
329    description  = "additional math italic corrections",
330 -- default      = true,
331    initializers = {
332        base = initialize,
333        node = initialize,
334    }
335}
336
337-- fontgoodies.register("mathitalics", initialize)
338
339local function mathradicalaction(n,h,v,font,mchar,echar)
340    local characters = fontcharacters[font]
341    local mchardata  = characters[mchar]
342    local echardata  = characters[echar]
343    local ewidth     = echardata.width
344    local mwidth     = mchardata.width
345    local delta      = h - ewidth
346    local glyph      = new_glyph(font,echar)
347    local head       = glyph
348    if delta > 0 then
349        local count = ceil(delta/mwidth)
350        local kern  = (delta - count * mwidth) / count
351        for i=1,count do
352            local k = new_kern(kern)
353            local g = new_glyph(font,mchar)
354            setlink(k,head)
355            setlink(g,k)
356            head = g
357        end
358    end
359    local height = mchardata.height
360    local list   = new_hlist(head)
361    local kern   = new_kern(height-v)
362    list = setlink(kern,list)
363    local list = new_vlist(kern)
364    insertnodeafter(n,n,list)
365end
366
367local function mathhruleaction(n,h,v,font,bchar,mchar,echar)
368    local characters = fontcharacters[font]
369    local bchardata  = characters[bchar]
370    local mchardata  = characters[mchar]
371    local echardata  = characters[echar]
372    local bwidth     = bchardata.width
373    local mwidth     = mchardata.width
374    local ewidth     = echardata.width
375    local delta      = h - ewidth - bwidth
376    local glyph      = new_glyph(font,echar)
377    local head       = glyph
378    if delta > 0 then
379        local count = ceil(delta/mwidth)
380        local kern  = (delta - count * mwidth) / (count+1)
381        for i=1,count do
382            local k = new_kern(kern)
383            local g = new_glyph(font,mchar)
384            setlink(k,head)
385            setlink(g,k)
386            head = g
387        end
388        local k = new_kern(kern)
389        setlink(k,head)
390        head = k
391    end
392    local g = new_glyph(font,bchar)
393    setlink(g,head)
394    head = g
395    local height = mchardata.height
396    local list   = new_hlist(head)
397    local kern   = new_kern(height-v)
398    list = setlink(kern,list)
399    local list = new_vlist(kern)
400    insertnodeafter(n,n,list)
401end
402
403local function initialize(tfmdata)
404    local goodies = tfmdata.goodies
405    if goodies then
406        local resources = tfmdata.resources
407        local ruledata  = { }
408        for i=1,#goodies do
409            local mathematics = goodies[i].mathematics
410            if mathematics then
411                local rules = mathematics.rules
412                if rules then
413                    for tag, name in next, rules do
414                        ruledata[tag] = name
415                    end
416                end
417            end
418        end
419        if next(ruledata) then
420            local characters = tfmdata.characters
421            local unicodes   = resources.unicodes
422            if characters and unicodes then
423                local mathruleactions = resources.mathruleactions
424                if not mathruleactions then
425                    mathruleactions = { }
426                    resources.mathruleactions = mathruleactions
427                end
428                --
429                local mchar = unicodes[ruledata["radical.extender"] or false]
430                local echar = unicodes[ruledata["radical.end"]      or false]
431                if mchar and echar then
432                    mathruleactions.radicalaction = function(n,h,v,font)
433                        mathradicalaction(n,h,v,font,mchar,echar)
434                    end
435                end
436                --
437                local bchar = unicodes[ruledata["hrule.begin"]    or false]
438                local mchar = unicodes[ruledata["hrule.extender"] or false]
439                local echar = unicodes[ruledata["hrule.end"]      or false]
440                if bchar and mchar and echar then
441                    mathruleactions.hruleaction = function(n,h,v,font)
442                        mathhruleaction(n,h,v,font,bchar,mchar,echar)
443                    end
444                end
445                -- not that nice but we need to register it at the tex end
446             -- context.enablemathrules("\\fontclass")
447            end
448        end
449    end
450end
451
452registerotffeature {
453    name         = "mathrules",
454    description  = "check math rules",
455    default      = true,
456    initializers = {
457        base = initialize,
458        node = initialize,
459    }
460}
461