math-act.lmt /size: 17 Kb    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['math-act'] = {
2    version   = 1.001,
3    comment   = "companion to math-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
9-- Here we tweak some font properties (if needed). The commented sections
10-- have been removed (no longer viable) but can be found in the .lua variant.
11
12local type, next = type, next
13local fastcopy, insert, remove = table.fastcopy, table.insert, table.remove
14local formatters = string.formatters
15
16local trace_defining   = false  trackers.register("math.defining",   function(v) trace_defining   = v end)
17local trace_collecting = false  trackers.register("math.collecting", function(v) trace_collecting = v end)
18
19local report_math      = logs.reporter("mathematics","initializing")
20
21local context          = context
22local commands         = commands
23local mathematics      = mathematics
24local texsetdimen      = tex.setdimen
25local abs              = math.abs
26
27local helpers          = fonts.helpers
28local upcommand        = helpers.commands.up
29local rightcommand     = helpers.commands.right
30local charcommand      = helpers.commands.char
31local prependcommands  = helpers.prependcommands
32
33local sequencers       = utilities.sequencers
34local appendgroup      = sequencers.appendgroup
35local appendaction     = sequencers.appendaction
36
37local fontchars        = fonts.hashes.characters
38local fontproperties   = fonts.hashes.properties
39
40local mathfontparameteractions = sequencers.new {
41    name      = "mathparameters",
42    arguments = "target,original",
43}
44
45appendgroup("mathparameters","before") -- user
46appendgroup("mathparameters","system") -- private
47appendgroup("mathparameters","after" ) -- user
48
49function fonts.constructors.assignmathparameters(original,target)
50    local runner = mathfontparameteractions.runner
51    if runner then
52        runner(original,target)
53    end
54end
55
56function mathematics.initializeparameters(target,original)
57    local mathparameters = original.mathparameters
58    if mathparameters and next(mathparameters) then
59        mathparameters = mathematics.dimensions(mathparameters)
60        if not mathparameters.SpaceBeforeScript then
61            mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript
62        end
63        target.mathparameters = mathparameters
64    end
65end
66
67sequencers.appendaction("mathparameters","system","mathematics.initializeparameters")
68
69local how = {
70 -- RadicalKernBeforeDegree         = "horizontal",
71 -- RadicalKernAfterDegree          = "horizontal",
72    ScriptPercentScaleDown          = "unscaled",
73    ScriptScriptPercentScaleDown    = "unscaled",
74    RadicalDegreeBottomRaisePercent = "unscaled",
75    NoLimitSupFactor                = "unscaled",
76    NoLimitSubFactor                = "unscaled",
77}
78
79function mathematics.scaleparameters(target,original)
80    if not target.properties.math_is_scaled then
81        local mathparameters = target.mathparameters
82        if mathparameters and next(mathparameters) then
83            local parameters = target.parameters
84            local factor  = parameters.factor
85            local hfactor = parameters.hfactor
86            local vfactor = parameters.vfactor
87            for name, value in next, mathparameters do
88                local h = how[name]
89                if h == "unscaled" then
90                    -- kept
91                elseif h == "horizontal" then
92                    value = value * hfactor
93                elseif h == "vertical"then
94                    value = value * vfactor
95                else
96                    value = value * factor
97                end
98               mathparameters[name] = value
99            end
100        end
101        target.properties.math_is_scaled = true
102    end
103end
104
105-- AccentBaseHeight vs FlattenedAccentBaseHeight
106
107function mathematics.checkaccentbaseheight(target,original)
108    local mathparameters = target.mathparameters
109    if mathparameters and mathparameters.AccentBaseHeight == 0 then
110        mathparameters.AccentBaseHeight = target.parameters.x_height -- needs checking
111    end
112end
113
114function mathematics.checkprivateparameters(target,original)
115    local mathparameters = target.mathparameters
116    if mathparameters then
117        local parameters = target.parameters
118        local properties = target.properties
119        if parameters then
120            local size = parameters.size
121            if size then
122                if not mathparameters.FractionDelimiterSize then
123                    mathparameters.FractionDelimiterSize = 1.01 * size
124                end
125                if not mathparameters.FractionDelimiterDisplayStyleSize then
126                    mathparameters.FractionDelimiterDisplayStyleSize = 2.40 * size
127                end
128            elseif properties then
129                report_math("invalid parameters in font %a",properties.fullname or "?")
130            else
131                report_math("invalid parameters in font")
132            end
133        elseif properties then
134            report_math("no parameters in font %a",properties.fullname or "?")
135        else
136            report_math("no parameters and properties in font")
137        end
138    end
139end
140
141function mathematics.overloadparameters(target,original)
142    local mathparameters = target.mathparameters
143    if mathparameters and next(mathparameters) then
144        local goodies = target.goodies
145        if goodies then
146            for i=1,#goodies do
147                local goodie = goodies[i]
148                local mathematics = goodie.mathematics
149                local parameters  = mathematics and mathematics.parameters
150                if parameters then
151                    if trace_defining then
152                        report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size)
153                    end
154                    for name, value in next, parameters do
155                        local tvalue = type(value)
156                        if tvalue == "string" then
157                            report_math("comment for math parameter %a: %s",name,value)
158                        else
159                            local oldvalue = mathparameters[name]
160                            local newvalue = oldvalue
161                            if oldvalue then
162                                if tvalue == "number" then
163                                    newvalue = value
164                                elseif tvalue == "function" then
165                                    newvalue = value(oldvalue,target,original)
166                                elseif not tvalue then
167                                    newvalue = nil
168                                end
169                                if trace_defining and oldvalue ~= newvalue then
170                                    report_math("overloading math parameter %a: %S => %S",name,oldvalue,newvalue)
171                                end
172                            else
173                                report_math("invalid math parameter %a",name)
174                            end
175                            mathparameters[name] = newvalue
176                        end
177                    end
178                end
179            end
180        end
181    end
182end
183
184local function applytweaks(when,target,original)
185    local goodies = original.goodies
186    if goodies then
187        for i=1,#goodies do
188            local goodie = goodies[i]
189            local mathematics = goodie.mathematics
190            local tweaks = mathematics and mathematics.tweaks
191            if type(tweaks) == "table" then
192                tweaks = tweaks[when]
193                if type(tweaks) == "table" then
194                    if trace_defining then
195                        report_math("tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when)
196                    end
197                    for i=1,#tweaks do
198                        local tweak= tweaks[i]
199                        local tvalue = type(tweak)
200                        if tvalue == "function" then
201                            tweak(target,original)
202                        end
203                    end
204                end
205            end
206        end
207    end
208end
209
210function mathematics.tweakbeforecopyingfont(target,original)
211    local mathparameters = target.mathparameters -- why not hasmath
212    if mathparameters then
213        applytweaks("beforecopying",target,original)
214    end
215end
216
217function mathematics.tweakaftercopyingfont(target,original)
218    local mathparameters = target.mathparameters -- why not hasmath
219    if mathparameters then
220        applytweaks("aftercopying",target,original)
221    end
222end
223
224sequencers.appendaction("mathparameters","system","mathematics.scaleparameters")
225sequencers.appendaction("mathparameters","system","mathematics.checkaccentbaseheight")  -- should go in lfg instead
226sequencers.appendaction("mathparameters","system","mathematics.checkprivateparameters") -- after scaling !
227sequencers.appendaction("mathparameters","system","mathematics.overloadparameters")
228
229sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont")
230sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont")
231
232-- no, it's a feature now (see good-mth):
233--
234-- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions")
235
236-- a couple of predefined tweaks:
237
238local tweaks       = { }
239mathematics.tweaks = tweaks
240
241-- helpers
242
243local setmetatableindex  = table.setmetatableindex
244
245local getfontoffamily    = tex.getfontoffamily
246
247local fontcharacters     = fonts.hashes.characters
248local extensibles        = utilities.storage.allocate()
249fonts.hashes.extensibles = extensibles
250
251local chardata           = characters.data
252local extensibles        = mathematics.extensibles
253
254-- we use numbers at the tex end (otherwise we could stick to chars)
255
256local e_left       = extensibles.left
257local e_right      = extensibles.right
258local e_horizontal = extensibles.horizontal
259local e_mixed      = extensibles.mixed
260local e_unknown    = extensibles.unknown
261
262local unknown      = { e_unknown, false, false }
263
264local function extensiblecode(font,unicode)
265    local characters = fontcharacters[font]
266    local character = characters[unicode]
267    if not character then
268        return unknown
269    end
270    local first = character.next
271    local code = unicode
272    local next = first
273    while next do
274        code = next
275        character = characters[next]
276        next = character.next
277    end
278    local char = chardata[unicode]
279    if not char then
280        return unknown
281    end
282    if character.horiz_variants then
283        if character.vert_variants then
284            return { e_mixed, code, character }
285        else
286            local m = char.mathextensible
287            local e = m and extensibles[m]
288            return e and { e, code, character } or unknown
289        end
290    elseif character.vert_variants then
291        local m = char.mathextensible
292        local e = m and extensibles[m]
293        return e and { e, code, character } or unknown
294    elseif first then
295        -- assume accent (they seldom stretch .. sizes)
296        local m = char.mathextensible or char.mathstretch
297        local e = m and extensibles[m]
298        return e and { e, code, character } or unknown
299    else
300        return unknown
301    end
302end
303
304setmetatableindex(extensibles,function(extensibles,font)
305    local codes = { }
306    setmetatableindex(codes, function(codes,unicode)
307        local status = extensiblecode(font,unicode)
308        codes[unicode] = status
309        return status
310    end)
311    extensibles[font] = codes
312    return codes
313end)
314
315local function extensiblecode(family,unicode)
316    return extensibles[getfontoffamily(family or 0)][unicode][1]
317end
318
319-- left       : [head] ...
320-- right      : ... [head]
321-- horizontal : [head] ... [head]
322--
323-- abs(right["start"] - right["end"]) | right.advance | characters[right.glyph].width
324
325local function horizontalcode(family,unicode)
326    local font    = getfontoffamily(family or 0)
327    local data    = extensibles[font][unicode]
328    local kind    = data[1]
329    local loffset = 0
330    local roffset = 0
331    if kind == e_left then
332        local charlist = data[3].horiz_variants
333        if charlist then
334            local left = charlist[1]
335            loffset = abs((left["start"] or 0) - (left["end"] or 0))
336        end
337    elseif kind == e_right then
338        local charlist = data[3].horiz_variants
339        if charlist then
340            local right = charlist[#charlist]
341            roffset = abs((right["start"] or 0) - (right["end"] or 0))
342        end
343     elseif kind == e_horizontal then
344        local charlist = data[3].horiz_variants
345        if charlist then
346            local left  = charlist[1]
347            local right = charlist[#charlist]
348            loffset = abs((left ["start"] or 0) - (left ["end"] or 0))
349            roffset = abs((right["start"] or 0) - (right["end"] or 0))
350        end
351    end
352    return kind, loffset, roffset
353end
354
355mathematics.extensiblecode = extensiblecode
356mathematics.horizontalcode = horizontalcode
357
358interfaces.implement { -- can be public with two times "integerargument"
359    name      = "extensiblecode",
360    arguments = { "integer", "integer" },
361    actions   = { extensiblecode, context }
362}
363
364interfaces.implement { -- can be public with two times "integerargument"
365    name      = "horizontalcode",
366    arguments = { "integer", "integer" },
367    actions   = function(family,unicode)
368        local kind, loffset, roffset = horizontalcode(family,unicode)
369        texsetdimen("scratchleftoffset", loffset)
370        texsetdimen("scratchrightoffset",roffset)
371        context(kind)
372    end
373}
374
375
376local stack = { }
377
378function mathematics.registerfallbackid(n,id,name)
379    if trace_collecting then
380        report_math("resolved fallback font %i, name %a, id %a, used %a",
381            n,name,id,fontproperties[id].fontname)
382    end
383    stack[#stack][n] = id
384end
385
386interfaces.implement { -- will be shared with text
387    name      = "registerfontfallbackid",
388    arguments = { "integer", "integer", "string" },
389    actions   = mathematics.registerfallbackid,
390}
391
392function mathematics.resolvefallbacks(target,specification,fallbacks)
393    local definitions = fonts.collections.definitions[fallbacks]
394    if definitions then
395        local size = specification.size -- target.size
396        local list = { }
397        insert(stack,list)
398        context.pushcatcodes("prt") -- context.unprotect()
399        for i=1,#definitions do
400            local definition = definitions[i]
401            local name       = definition.font
402            local features   = definition.features or ""
403            local size       = size * (definition.rscale or 1)
404            context.font_fallbacks_register_math(i,name,features,size)
405            if trace_collecting then
406                report_math("registering fallback font %i, name %a, size %a, features %a",i,name,size,features)
407            end
408        end
409        context.popcatcodes()
410    end
411end
412
413function mathematics.finishfallbacks(target,specification,fallbacks)
414    local list = remove(stack)
415    if list and #list > 0 then
416        local definitions = fonts.collections.definitions[fallbacks]
417        if definitions and #definitions > 0 then
418            if trace_collecting then
419                report_math("adding fallback characters to font %a",specification.hash)
420            end
421            local definedfont = fonts.definers.internal
422            local copiedglyph = fonts.handlers.vf.math.copy_glyph
423            local fonts       = target.fonts
424            local size        = specification.size -- target.size
425            local characters  = target.characters
426            if not fonts then
427                fonts = { }
428                target.fonts = fonts
429            end
430            if #fonts == 0 then
431                fonts[1] = { id = 0, size = size } -- self, will be resolved later
432            end
433            local done = { }
434            for i=1,#definitions do
435                local definition = definitions[i]
436                local name   = definition.font
437                local start  = definition.start
438                local stop   = definition.stop
439                local gaps   = definition.gaps
440                local check  = definition.check
441                local force  = definition.force
442                local rscale = definition.rscale or 1
443                local offset = definition.offset or start
444                local id     = list[i]
445                if id then
446                    local index  = #fonts + 1
447                    fonts[index] = { id = id, size = size }
448                    local chars  = fontchars[id]
449                    local function remap(unic,unicode,gap)
450                        if check and not chars[unicode] then
451                            return
452                        end
453                        if force or (not done[unic] and not characters[unic]) then
454                            if trace_collecting then
455                                report_math("replacing math character %C by %C using vector %a and font id %a for %a%s%s",
456                                    unic,unicode,fallbacks,id,fontproperties[id].fontname,check and ", checked",gap and ", gap plugged")
457                            end
458                            characters[unic] = copiedglyph(target,characters,chars,unicode,index)
459                            done[unic] = true
460                        end
461                    end
462                    local step = offset - start
463                    for unicode = start, stop do
464                        remap(unicode + step,unicode,false)
465                    end
466                    if gaps then
467                        for unic, unicode in next, gaps do
468                            remap(unic,unicode,true)
469                            remap(unicode,unicode,true)
470                        end
471                    end
472                end
473            end
474        elseif trace_collecting then
475            report_math("no fallback characters added to font %a",specification.hash)
476        end
477    end
478end
479