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