math-act.lmt /size: 178 Kb    last modification: 2024-01-16 09:02
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
12-- The tweaks here evolved from experiments with, discussions about and upgrades of
13-- the math subsystem, a project that Mikael Sundvist and I started end 2021 and
14-- that is still ongoing in 2023 (and probably beyond as we find new challenges as
15-- we go).
16
17local type, next, tonumber = type, next, tonumber
18local fastcopy, copytable, insert, remove, concat = table.fastcopy, table.copy, table.insert, table.remove, table.concat
19local formatters = string.formatters
20local byte = string.byte
21local max = math.max
22local setmetatableindex, sortedkeys, sortedhash  = table.setmetatableindex, table.sortedkeys, table.sortedhash
23local lpegmatch = lpeg.match
24
25local trace_defining   = false  trackers.register("math.defining",   function(v) trace_defining   = v end)
26local trace_collecting = false  trackers.register("math.collecting", function(v) trace_collecting = v end)
27local trace_tweaking   = false  trackers.register("math.tweaks",     function(v) trace_tweaking   = v end)
28
29local report_math      = logs.reporter("mathematics","initializing")
30local report_mathtweak = logs.reporter("mathematics","tweak")
31
32local getfontoffamily  = tex.getfontoffamily
33local texget           = tex.get
34local fontcharacters   = fonts.hashes.characters
35local chardata         = characters.data
36local extensibles      = mathematics.extensibles
37
38local context          = context
39local commands         = commands
40local mathematics      = mathematics
41local texsetdimen      = tex.setdimen
42local texisdimen       = tex.isdimen
43local abs              = math.abs
44
45local blocks           = characters.blocks
46local stepper          = utilities.parsers.stepper
47
48local helpers          = fonts.helpers
49local prependcommands  = helpers.prependcommands
50
51local vfcommands       = helpers.commands
52local upcommand        = vfcommands.up
53local downcommand      = vfcommands.down
54local rightcommand     = vfcommands.right
55local leftcommand      = vfcommands.left
56local slotcommand      = vfcommands.slot
57local charcommand      = vfcommands.char
58local push             = vfcommands.push
59local pop              = vfcommands.pop
60
61local sequencers       = utilities.sequencers
62local appendgroup      = sequencers.appendgroup
63local appendaction     = sequencers.appendaction
64
65local fontchars        = fonts.hashes.characters
66local fontproperties   = fonts.hashes.properties
67
68local mathgaps         = mathematics.gaps
69
70local d_scratchleftoffset  = texisdimen("scratchleftoffset")
71local d_scratchrightoffset = texisdimen("scratchrightoffset")
72
73local use_math_goodies = true   directives.register("math.nogoodies",    function(v) use_math_goodies = not v end)
74local checkitalics     = false  trackers  .register("math.checkitalics", function(v) checkitalics     =     v end)
75
76local function registerdone(done,unicode)
77    if not trace_tweaking then
78        done = true
79    elseif done then
80        done[unicode] = true
81    else
82        done = { [unicode] = true }
83    end
84    return done
85end
86
87local mathfontparameteractions = sequencers.new {
88    name      = "mathparameters",
89    arguments = "target,original",
90}
91
92appendgroup("mathparameters","before") -- user
93appendgroup("mathparameters","system") -- private
94appendgroup("mathparameters","after" ) -- user
95
96function fonts.constructors.assignmathparameters(original,target) -- wrong way around
97    local runner = mathfontparameteractions.runner
98    if runner then
99        runner(original,target)
100    end
101end
102
103-- we need a better reset because the following will scale
104
105local undefined <const> = 0x3FFFFFFF -- maxdimen or undefined_math_parameter
106
107function mathematics.initializeparameters(target,original,nodimensions)
108    local mathparameters = original.mathparameters
109    if mathparameters and next(mathparameters) then
110        if nodimensions ~= "noscale" then
111            mathparameters = mathematics.dimensions(mathparameters)
112        end
113        --
114     -- if not mathparameters.MinConnectorOverlap               then mathparameters.MinConnectorOverlap               = undefined end
115        if not mathparameters.SubscriptShiftDownWithSuperscript then mathparameters.SubscriptShiftDownWithSuperscript = mathparameters.SubscriptShiftDown * 1.5 end
116     -- if not mathparameters.FractionDelimiterSize             then mathparameters.FractionDelimiterSize             = undefined end
117     -- if not mathparameters.FractionDelimiterDisplayStyleSize then mathparameters.FractionDelimiterDisplayStyleSize = undefined end
118     -- if not mathparameters.SkewedDelimiterTolerance          then mathparameters.SkewedDelimiterTolerance          = undefined end
119        -- some more can be undefined:
120        if not mathparameters.PrimeRaisePercent                 then mathparameters.PrimeRaisePercent                 = 0 end
121        if not mathparameters.PrimeRaiseComposedPercent         then mathparameters.PrimeRaiseComposedPercent         = 0 end
122        if not mathparameters.PrimeShiftUp                      then mathparameters.PrimeShiftUp                      = mathparameters.SuperscriptShiftUp end
123        if not mathparameters.PrimeBaselineDropMax              then mathparameters.PrimeBaselineDropMax              = mathparameters.SuperscriptBaselineDropMax end
124        if not mathparameters.PrimeShiftUpCramped               then mathparameters.PrimeShiftUpCramped               = mathparameters.SuperscriptShiftUpCramped end
125        if not mathparameters.PrimeSpaceAfter                   then mathparameters.PrimeSpaceAfter                   = 0 end
126        if not mathparameters.PrimeWidthPercent                 then mathparameters.PrimeWidthPercent                 = 50 end
127        if not mathparameters.SpaceBeforeScript                 then mathparameters.SpaceBeforeScript                 = mathparameters.SpaceAfterScript end
128        if not mathparameters.NoLimitSupFactor                  then mathparameters.NoLimitSupFactor                  = 0 end
129        if not mathparameters.NoLimitSubFactor                  then mathparameters.NoLimitSubFactor                  = 0 end
130        if not mathparameters.AccentTopShiftUp                  then mathparameters.AccentTopShiftUp                  = 0 end
131        if not mathparameters.AccentBottomShiftDown             then mathparameters.AccentBottomShiftDown             = 0 end
132        if not mathparameters.FlattenedAccentTopShiftUp         then mathparameters.AccentTopShiftUp                  = 0 end
133        if not mathparameters.FlattenedAccentBottomShiftDown    then mathparameters.AccentBottomShiftDown             = 0 end
134        if not mathparameters.AccentBaseDepth                   then mathparameters.AccentBaseDepth                   = 0 end
135        if not mathparameters.AccentFlattenedBaseDepth          then mathparameters.AccentFlattenedBaseDepth          = 0 end
136        if not mathparameters.AccentTopOvershoot                then mathparameters.AccentTopOvershoot                = 0 end
137        if not mathparameters.AccentBottomOvershoot             then mathparameters.AccentBottomOvershoot             = 0 end
138        if not mathparameters.AccentSuperscriptDrop             then mathparameters.AccentSuperscriptDrop             = 0 end
139        if not mathparameters.AccentSuperscriptPercent          then mathparameters.AccentSuperscriptPercent          = 0 end
140        if not mathparameters.AccentExtendMargin                then mathparameters.AccentExtendMargin                = 50 end
141        if not mathparameters.DelimiterPercent                  then mathparameters.DelimiterPercent                  = 100 end
142        if not mathparameters.DelimiterShortfall                then mathparameters.DelimiterShortfall                = 0 end
143        if not mathparameters.DelimiterDisplayPercent           then mathparameters.DelimiterDisplayPercent           = 100 end
144        if not mathparameters.DelimiterDisplayShortfall         then mathparameters.DelimiterDisplayShortfall         = 0 end
145        if not mathparameters.RadicalKernAfterExtensible        then mathparameters.RadicalKernAfterExtensible        = 0 end
146        if not mathparameters.RadicalKernBeforeExtensible       then mathparameters.RadicalKernBeforeExtensible       = 0 end
147        if not mathparameters.SuperscriptSnap                   then mathparameters.SuperscriptSnap                   = 750 end
148        if not mathparameters.SubscriptSnap                     then mathparameters.SubscriptSnap                     = 250 end
149        --
150-- if mathparameters.RadicalDisplayStyleVerticalGap then
151--     mathparameters.RadicalDisplayStyleVerticalGap = mathparameters.RadicalDisplayStyleVerticalGap - 100
152-- end
153-- if mathparameters.RadicalVerticalGap then
154--     mathparameters.RadicalVerticalGap = mathparameters.RadicalVerticalGap - 100
155-- end
156        --
157        target.mathparameters = mathparameters
158    end
159end
160
161sequencers.appendaction("mathparameters","system","mathematics.initializeparameters")
162
163local how = {
164 -- RadicalKernBeforeDegree         = "horizontal",
165 -- RadicalKernAfterDegree          = "horizontal",
166    ScriptPercentScaleDown          = "unscaled",
167    ScriptScriptPercentScaleDown    = "unscaled",
168    RadicalDegreeBottomRaisePercent = "unscaled",
169    NoLimitSupFactor                = "unscaled",
170    NoLimitSubFactor                = "unscaled",
171    PrimeRaisePercent               = "unscaled",
172    PrimeRaiseComposedPercent       = "unscaled",
173    PrimeWidthPercent               = "unscaled",
174    AccentTopOvershoot              = "unscaled",
175    AccentBottomOvershoot           = "unscaled",
176    AccentSuperscriptPercent        = "unscaled",
177    DelimiterPercent                = "unscaled",
178    DelimiterDisplayPercent         = "unscaled",
179    --
180    RadicalRuleThickness            = "vertical",
181    OverbarRuleThickness            = "vertical",
182    FractionRuleThickness           = "vertical",
183    UnderbarRuleThickness           = "vertical",
184}
185
186local function scaleparameters(mathparameters,parameters)
187    if mathparameters and next(mathparameters) and parameters then
188        local factor  = parameters.factor
189        local hfactor = parameters.hfactor
190        local vfactor = parameters.vfactor
191        for name, value in next, mathparameters do
192            local h = how[name]
193            if h == "unscaled" then
194                -- kept
195            elseif h == "horizontal" then
196                value = value * hfactor
197            elseif h == "vertical" then
198                value = value * vfactor
199            else
200                value = value * factor
201            end
202            mathparameters[name] = value
203        end
204    end
205end
206
207function mathematics.scaleparameters(target,original)
208    if not target.properties.math_is_scaled then
209        scaleparameters(target.mathparameters,target.parameters)
210        target.properties.math_is_scaled = true
211    end
212end
213
214-- AccentBaseHeight vs FlattenedAccentBaseHeight
215
216-- function mathematics.checkaccentbaseheight(target,original)
217--     local mathparameters = target.mathparameters
218--     if mathparameters and mathparameters.AccentBaseHeight == 0 then
219--         if trace_defining then
220--             report_math("zero AccentBaseHeight corrected %a @ %p",target.properties.fullname,target.parameters.size)
221--         end
222--         mathparameters.AccentBaseHeight = target.parameters.xheight -- needs checking
223--     end
224-- end
225
226function mathematics.overloadparameters(target,original)
227    if use_math_goodies then
228        local mathparameters = target.mathparameters
229        if mathparameters and next(mathparameters) then
230            local goodies = target.goodies
231            if goodies then
232                for i=1,#goodies do
233                    local goodie = goodies[i]
234                    local mathematics = goodie.mathematics
235                    if mathematics then
236                        local parameters = mathematics.parameters
237                        local bigslots   = mathematics.bigslots or mathematics.bigs
238                        if parameters then
239                            if trace_defining then
240                                report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size)
241                            end
242                            for name, value in next, parameters do
243                                local tvalue   = type(value)
244                                local oldvalue = mathparameters[name]
245                                local newvalue = oldvalue
246                                if tvalue == "number" then
247                                    newvalue = value
248                                elseif tvalue == "string" then
249                                    -- delay till all set
250                                elseif tvalue == "function" then
251                                    newvalue = value(oldvalue,target,original)
252                                elseif not tvalue then
253                                    newvalue = nil
254                                end
255                                if trace_defining and oldvalue ~= newvalue then
256                                    report_math("overloading math parameter %a: %S => %S",name,oldvalue or 0,newvalue)
257                                end
258                                mathparameters[name] = newvalue
259                            end
260                            for name, value in next, parameters do
261                                local tvalue = type(value)
262                                if tvalue == "string" then
263                                    local newvalue = mathparameters[value]
264                                    if not newvalue then
265                                        local code = loadstring("return " .. value,"","t",mathparameters)
266                                        if type(code) == "function" then
267                                            local okay, v = pcall(code)
268                                            if okay then
269                                                newvalue = v
270                                            end
271                                        end
272                                    end
273                                    if newvalue then
274                                        -- split in number and string
275                                        mathparameters[name] = newvalue
276                                    elseif trace_defining then
277                                        report_math("ignoring math parameter %a: %S",name,value)
278                                    end
279                                end
280                            end
281                        end
282                        if bigslots then
283                            target.bigslots = bigslots
284                        end
285                    end
286                end
287            end
288        end
289    end
290end
291
292-- a couple of predefined tweaks:
293
294local datasets       = { }
295local mathtweaks     = { datasets = datasets }
296mathematics.tweaks   = mathtweaks
297
298-- can be a common helper:
299
300local f_u = formatters["%U"]
301
302local function unicodecharlist(t)
303    local r = { }
304    local n = 0
305    for u in sortedhash(t) do
306        n = n + 1 ; r[n] = f_u(u)
307    end
308    return concat(r," ")
309end
310
311local function report_tweak(fmt,target,original,...)
312    if fmt then
313        local metadata   = (original and original.shared.rawdata.metadata) or
314                           (target   and target  .shared.rawdata.metadata)
315        local parameters = target.parameters
316        if parameters then
317            report_mathtweak(
318                "%a, size %P, math size %i, %s",
319                metadata and metadata.fontname or "unknown",
320                parameters.size or 655360,
321                parameters.mathsize or 1,
322                formatters[fmt](...)
323            )
324        else
325            print("something is wrong")
326        end
327    else
328        report_mathtweak("")
329    end
330end
331
332local function feedback_tweak(tweak,target,original,done)
333    if not done or (type(done) == "table" and not next(done)) then
334if trace_tweaking then -- for now
335        report_tweak("no need for %a",target,original,tweak)
336end
337    elseif trace_tweaking then
338        report_tweak("tweak %a applied to: %s",target,original,tweak,unicodecharlist(done))
339    end
340end
341
342mathtweaks.subsets = {
343    acenorsuvxz      = { 0x1D44E, 0x1D450, 0x1D452, 0x1D45B, 0x1D45C, 0x1D45F, 0x1D460, 0x1D462, 0x1D463, 0x1D465, 0x1D467 },
344    bhklt            = { 0x1D44F, 0x1D455, 0x1D458, 0x1D459, 0x1D461 },
345    d                = { 0x1D451 },
346    f                = { 0x1D453 },
347    gjqy             = { 0x1D454, 0x1D457, 0x1D45E, 0x1D466 },
348    i                = { 0x1D456 },
349    mw               = { 0x1D45A, 0x1D464 },
350    p                = { 0x1D45D },
351    letterlike       = { 0x02202 },
352    dotless          = { 0x00049, 0x0004A, 0x00131, 0x00237, 0x1D6A4, 0x1D6A5 },
353    integrals        = { 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233, 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13, 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C, 0x02320, 0x02321 },
354    horizontalfences = { 0x0203E, 0x023B4, 0x023B5, 0x023DC, 0x023DD, 0x023DE, 0x023DF, 0x023E0, 0x023E1 }, -- not really used
355}
356
357local function getalso(target,original)
358    local also = target.tweakalso -- maybe
359    if not also then
360        also = { }
361     -- for k, v in sortedhash(target.characters) do
362        for k, v in next, target.characters do
363            local u = v.unicode
364            if u and k ~= u then
365                local a = also[u]
366                if a then
367                    a[#a+1] = k
368                else
369                    also[u] = { k }
370                end
371            end
372        end
373        target.tweakalso = also
374    end
375    return also
376end
377
378-- {
379--     tweak = "dimensions",
380--     list  = {
381--         ["lowercasegreeksansserifbolditalic"] = {
382--          -- delta    = 0x003B1 - 0x1D7AA,
383--             slant    = -0.2,
384--             line     = 0.1,
385--             mode     = 1,
386--             width    = 0.675,
387--          -- scale    = 0.975,
388--             squeeze  = 0.975,
389--             extend   = .7,
390--         },
391--     },
392-- },
393
394 -- ["0x7C.variants.*"] = { squeeze = 0.10, height = 0.10, depth = 0.10 },
395
396local detail  do
397
398    local splitter = lpeg.tsplitat(".")
399    local private  = fonts.helpers.privateslot
400
401    detail = function(characters,k)
402        if type(k) == "string" then
403            local t = lpegmatch(splitter,k)
404            local n = #t
405            if n > 0 then
406                local slot = t[1]
407                local base = tonumber(slot) or tonumber(slot,16) or private(slot)
408                if base then
409                    local c = characters[base]
410                    if c and n > 1 then
411                        local list = t[2]
412                        if list == "parts" then
413                            local nxt = c.next
414                            while nxt do
415                                c = characters[nxt]
416                                nxt = c.next
417                            end
418                            c = c.parts
419                            if c then
420                                local index = t[3]
421                                if index == "*" then
422                                    return t
423                                else
424                                    if index == "top" then
425                                        index = #c
426                                    elseif index == "bottom" then
427                                        index = 1
428                                    else
429                                        index = tonumber(index)
430                                    end
431                                    if index then
432                                        c = c[index]
433                                        if c then
434                                            return c.glyph
435                                        end
436                                    end
437                                end
438                            end
439                        elseif list == "variants" then
440                            local index = t[3]
441                            if index == "*" then
442                                local t = { }
443                                local nxt = c.next
444                                while nxt do
445                                    t[#t+1] = nxt
446                                    c = characters[nxt]
447                                    nxt = c.next
448                                end
449                                return t
450                            else
451                                index = tonumber(index)
452                                if index then
453                                    local nxt = c.next
454                                    while nxt and index > 1 do
455                                        c = characters[nxt]
456                                        nxt = c.next
457                                        index = index - 1
458                                    end
459                                    return nxt
460                                end
461                            end
462                        elseif list == "flataccent" then
463                            return c.flataccent
464                        end
465                    end
466                end
467            end
468        else
469            return k
470        end
471    end
472
473end
474
475-- This temporary tweak was used when we (MS & HH) were fixing the Latin Modern
476-- parameters that relate to script placement. We started from the original cmr
477-- ratios combined with the formal specification and ended up with the following
478-- values. In the end we rejected this tweak and setteled for checking and fixing:
479--
480-- SubscriptShiftDown
481-- SubscriptShiftDownWithSuperscript
482-- SuperscriptShiftUp
483-- SuperscriptShiftUpCramped
484--
485-- because it looks a bit arbitrary what values are set. We keep the code below
486-- as documentation.
487
488-- do
489--     -- modern
490--     --
491--     -- local factors = {
492--     --     scripts = {
493--     --         SubscriptBaselineDropMin          = 0.116,
494--     --         SubscriptShiftDown                = 0.348,
495--     --         SubscriptShiftDownWithSuperscript = 0.573,
496--     --         SubscriptTopMax                   = 0.800,
497--     --         SuperscriptBaselineDropMax        = 0.896,
498--     --         SuperscriptBottomMaxWithSubscript = 0.800,
499--     --         SuperscriptBottomMin              = 0.250,
500--     --         SuperscriptShiftUp                = 0.958,
501--     --         SuperscriptShiftUpCramped         = 0.958,
502--     --     }
503--     -- }
504--
505--     -- -- cambria
506--     --
507--     -- local factors = {
508--     --     scripts = {
509--     --         SubscriptBaselineDropMin          = 0.279,
510--     --         SubscriptShiftDown                = 0.364,
511--     --         SubscriptShiftDownWithSuperscript = 0.547,
512--     --         SubscriptTopMax                   = 0.662,
513--     --         SuperscriptBaselineDropMax        = 0.401,
514--     --         SuperscriptBottomMaxWithSubscript = 0.667,
515--     --         SuperscriptBottomMin              = 0.208,
516--     --         SuperscriptShiftUp                = 0.654,
517--     --         SuperscriptShiftUpCramped         = 0.654,
518--     --     }
519--     -- }
520--
521--     -- after some tests and inspection
522--     --
523--
524--     local factors = {
525--         scripts = {
526--             SubscriptBaselineDropMin          = 0.100, -- harmless but small (seldom triggered)
527--             SubscriptShiftDown                = 0.400, -- by inspection in several files
528--             SubscriptShiftDownWithSuperscript = 0.400, -- as above
529--             SubscriptTopMax                   = 0.800, -- Microsoft recommendation
530--             SuperscriptBaselineDropMax        = 0.100, -- see SubscriptBaselineDropMin
531--             SuperscriptBottomMaxWithSubscript = 0.800, -- Microsoft recommendation
532--             SuperscriptBottomMin              = 0.250, -- Microsoft recommendation
533--             SuperscriptShiftUp                = 0.650, -- by inspection, but also a bit gamble
534--             SuperscriptShiftUpCramped         = 0.650, -- see above, non-TeX
535--         }
536--     }
537--
538--     datasets.fixparameters = factors
539--
540--     function mathtweaks.fixparameters(target,original,parameters)
541--         local mathparameters = target.mathparameters
542--         if mathparameters and next(mathparameters) then
543--             local xheight = target.parameters.xheight
544--             -- todo : options
545--             for k, v in next, factors.scripts do
546--                 mathparameters[k] = v * xheight
547--             end
548--         end
549--     end
550--
551-- end
552
553do
554
555    local stepper     = utilities.parsers.stepper
556    local count       = 0
557    local toeffect    = fonts.toeffect
558    local privateslot = fonts.helpers.privateslot
559
560    local function adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,n)
561        k = mathgaps[k] or k
562        local character = targetcharacters[k]
563        if character then
564         -- if not character.tweaked then -- todo: add a force
565                local t = type(v)
566                if t == "number" then
567                    v = list[v]
568                    t = type(v)
569                end
570                if t == "table" and next(v) then
571
572                    local axis = tonumber(v.axis)
573                    if axis then
574                        axis = target.mathparameters.AxisHeight * axis
575                    end
576
577                    local factor = v.factor
578                    if factor then
579                        local m = v
580                        v = setmetatableindex({
581                            width   = factor,
582                            height  = factor,
583                            depth   = factor,
584                            squeeze = factor,
585                            extend  = factor,
586                        }, v)
587                    end
588                    local originalslot = v.original
589                    if not originalslot then
590                        local delta = v.delta
591                        if delta then
592                            originalslot = k + delta
593                        end
594                    end
595                    if originalslot then
596                        originalslot = mathgaps[originalslot] or originalslot
597                        local data = targetcharacters[originalslot]
598                        if data then
599                            data = copytable(data)
600                            data.unicode = originalslot
601                            targetcharacters[k] = data
602                            character = data
603                        else
604                            report_mathtweak("no slot %U",originalslot)
605                            return
606                        end
607                    end
608                    --
609                    local width        = character.width
610                    local height       = character.height
611                    local depth        = character.depth
612                    local italic       = character.italic
613                    local topanchor    = character.topanchor
614                    local bottomanchor = character.bottomanchor
615                    --
616                    local widthfactor   = v.width
617                    local heightfactor  = v.height
618                    local depthfactor   = v.depth
619                    local italicfactor  = v.italic
620                    local anchorfactor  = v.anchor
621                    local advancefactor = v.advance
622                    local xoffsetfactor = v.xoffset
623                    local yoffsetfactor = v.yoffset
624                    local scalefactor   = v.scale
625                    local total = (height or 0) + (depth or 0)
626                    if scalefactor ~= 1 then
627                        character.scale = scalefactor
628                    end
629                    if width and width ~= 0 then
630                        if advancefactor then
631                            character.advance = advancefactor * width
632                        else
633                            character.advance = character.advance or width -- so advance is oldwidth
634                        end
635                        if widthfactor then
636                            character.width = widthfactor * width
637                        end
638                        if xoffsetfactor then
639                            character.xoffset = xoffsetfactor * width
640                        end
641                    end
642                    if height and height ~= 0 then
643                        if heightfactor then
644                            character.height  = heightfactor * height
645                        end
646                    end
647                    if depth and depthfactor then
648                        character.depth = depthfactor * depth
649                    end
650                    if yoffsetfactor then
651                        character.yoffset = yoffsetfactor * total
652                    end
653
654                    if axis then
655                        character.height  = (character.height  or 0) - axis
656                        character.depth   = (character.depth   or 0) + axis
657                        character.yoffset = (character.yoffset or 0) + axis
658                    end
659
660                    if italicfactor then
661                        if italic then
662                            character.italic = italicfactor * italic
663                        elseif width and italicfactor ~= 1 then
664                            character.italic = italicfactor * width
665                        end
666                    end
667                    if anchorfactor then
668                        character.topanchor = anchorfactor * (topanchor or width)
669                    end
670--                     if anchorfactor then
671--                         character.bottomaccent = anchorfactor * (bottomanchor or width)
672--                     end
673                 -- begin experiment
674                    local line = v.wline
675                    if line then
676                        local parameters = target.parameters
677                        v.line = parameters.hfactor * line / parameters.units
678                    end
679                 -- end experiment
680                    character.effect = toeffect(v) -- todo: move wline test inside here
681                 -- begin experiment
682                    v.line = line
683                 -- end experiment
684                    if trace_tweaking then
685                        report_tweak("adapting dimensions of %U ",target,original,k)
686                    end
687                     -- missing when private
688                    local originaldata = originalcharacters[k] -- or targetcharacters[k]
689                    local smaller = originaldata and originaldata.smaller
690                    if compact and smaller and smaller ~= k then
691                        adapt(list,target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1)
692                    end
693                    count = count + 1
694                else
695                    report_mathtweak("invalid dimension entry %U",k)
696                end
697             -- character.tweaked = true
698                if v.all then
699                    local nxt = character.next
700                    if nxt then
701                        adapt(list,target,original,targetcharacters,originalcharacters,nxt,v,compact,n)
702                    else
703                        local parts = character.parts
704                        if parts then
705                            for i=1,#parts do
706                                adapt(list,target,original,targetcharacters,originalcharacters,parts[i],v,compact,n)
707                            end
708                        end
709                    end
710                end
711         -- end
712        else
713            report_tweak("no character %U",target,original,k)
714        end
715    end
716
717    function mathtweaks.dimensions(target,original,parameters)
718        local list = parameters.list
719        if list then
720            local targetcharacters   = target.characters
721            local originalcharacters = original.characters
722            local compact = target.parameters.textscale and true or false
723            count = 0
724            for k, v in sortedhash(list) do
725                local t = type(k)
726                if t == "number" then
727                    adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,1)
728                elseif t == "string" then
729                    local d = privateslot(k) or detail(targetcharacters,k) -- watch the private here
730                    local t = type(d)
731                    if t == "table" then
732                        for i=1,#d do
733                            adapt(list,target,original,targetcharacters,originalcharacters,d[i],v,compact,1)
734                        end
735                    elseif t == "number" then
736                        adapt(list,target,original,targetcharacters,originalcharacters,d,v,compact,1)
737                    elseif d then
738                        -- some kind of error
739                    else
740                        local r = blocks[k]
741                        if r then
742                            local done = false
743                            for i=r.first,r.last do
744                                adapt(list,target,original,targetcharacters,originalcharacters,i,v,compact,1)
745                            end
746                        else
747                            stepper(k,function(n)
748                                adapt(list,target,original,targetcharacters,originalcharacters,n,v,compact,1)
749                            end)
750                        end
751                    end
752             -- elseif t == "table" then
753             --     for i=1,#t do
754             --         adapt(list,target,original,targetcharacters,originalcharacters,t[i],v,compact,1)
755             --     end
756                end
757            end
758            if trace_tweaking and count > 0 then
759                report_mathtweak("%i dimensions adapted",count)
760            end
761        end
762    end
763
764end
765
766do
767
768    function mathtweaks.message(target,original,parameters)
769        report_mathtweak(parameters.text or "no message")
770    end
771
772    function mathtweaks.showinfo(target,original,parameters)
773        local mathparameters = target.mathparameters
774        for k, v in sortedhash(mathparameters) do
775            report_mathtweak("%s : %s",k,v)
776        end
777    end
778
779end
780
781do
782
783    function mathtweaks.wipevariants(target,original,parameters)
784        local list = parameters.list
785        if list then
786            local targetcharacters   = target.characters
787         -- local originalcharacters = original.characters
788            local count = 0
789         -- local also  = getalso(target,original)
790            local done  = false
791            for k, v in sortedhash(list) do
792                local ori = targetcharacters[k]
793                local nxt = ori.next
794                local cnt = v
795                if nxt then
796                    local prt = nil
797                    local pro = nil
798                    local lst = { }
799                    while nxt do
800                        local chr = targetcharacters[nxt]
801                        lst[#lst+1] = chr
802                        nxt = chr.next
803                        if not nxt then
804                            prt = chr.parts
805                            pro = chr.partsorientation
806                            break
807                        end
808                    end
809                    if prt then
810                        count = count + 1
811                        if cnt ~= "*" then
812                            if #lst < cnt then
813                                cnt = #lst
814                            end
815                            ori = lst[cnt]
816                        end
817                        ori.parts = prt
818                        ori.partsorientation = pro
819                    end
820                    done = registerdone(done,k)
821                end
822            end
823            feedback_tweak("wipevariants",target,original,done)
824        end
825    end
826
827end
828
829do
830
831    -- This is a horrible tweak for lm that has bars inconsistent with other fences.
832
833    local function find(targetcharacters,slot,n)
834        local chr = targetcharacters[slot]
835        while chr do
836            slot = chr.next
837            if not slot then
838                break
839            elseif n == 1 then
840                return slot
841            end
842            n = n - 1
843            chr = targetcharacters[slot]
844        end
845        return nil
846    end
847
848    function mathtweaks.fixvariants(target,original,parameters)
849        local list = parameters.list
850        if list then
851            local targetcharacters   = target.characters
852            local done  = false
853            for k, v in sortedhash(list) do
854                local tmp = v.template
855                local idx = v.index
856                local dy  = v.yoffset or 0
857                if tmp and idx then
858                    local olduni = find(targetcharacters,k,idx)
859                    local tmpuni = find(targetcharacters,tmp,idx)
860                    local axis   = target.mathparameters.AxisHeight
861                    if axis then
862                        while olduni and tmpuni do
863                            local oldchr  = targetcharacters[olduni]
864                            local tmpchr  = targetcharacters[tmpuni]
865                            local oldht   = oldchr.height or 0
866                            local olddp   = oldchr.depth  or 0
867                            local tmpht   = tmpchr.height or 0
868                            local tmpdp   = tmpchr.depth  or 0
869                            local scale   = (tmpht + tmpdp) / (oldht + olddp)
870                            local total   = (oldht - olddp) * scale
871                            local yoffset = axis - total/2
872                            oldchr.effect = { squeeze = scale }
873                            oldchr.height  = scale * oldht + yoffset
874                            oldchr.depth   = scale * olddp - yoffset
875                            oldchr.yoffset = yoffset
876                            olduni = oldchr.next
877                            tmpuni = tmpchr.next
878                        end
879                        done = registerdone(done,k)
880                    end
881                end
882            end
883            feedback_tweak("addvariants",target,original,done)
884        end
885    end
886
887end
888
889do
890
891    function mathtweaks.replace(target,original,parameters)
892        local list = parameters.list
893        if list then
894            local targetcharacters   = target.characters
895            local originalcharacters = original.characters
896            local unicodes = original.resources.unicodes
897            if unicodes then
898                local count = 0
899                for k, v in sortedhash(list) do
900                    if type(v) == "string" then
901                        v = unicodes[v]
902                    end
903                    if type(v) == "number" then
904                        targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[v] or v]
905                        count = count + 1
906                    end
907                end
908                if trace_tweaking and count > 0 then
909                    report_tweak("%i permanent replacements",target,original,count)
910                end
911            end
912        end
913    end
914
915    function mathtweaks.substitute(target,original,parameters)
916        local list = parameters.list
917        if list then
918            local targetcharacters   = target.characters
919            local originalcharacters = original.characters
920            local getsubstitution    = fonts.handlers.otf.getsubstitution
921            local count              = 0
922            for k, v in next, list do -- no need for sortedhash(list) unless we report
923                local sub = getsubstitution(original,k,v,true)
924                if sub then
925                    targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[sub] or sub]
926                    count = count + 1
927                end
928            end
929            if trace_tweaking and count > 0 then
930                report_tweak("%i permanent substitutions",target,original,count)
931            end
932        end
933    end
934
935end
936
937do
938
939    -- maybe we'll have a different name
940
941    function mathtweaks.kernpairs(target,original,parameters)
942        local list = parameters.list
943        if list then
944            local targetcharacters   = target.characters
945            local originalcharacters = original.characters
946            local done               = false
947
948            local function add(v,n)
949                local chardata = targetcharacters[mathgaps[n] or n]
950                if chardata then
951                    local width = chardata.width
952                    if width then
953                        local kerns = chardata.kerns or { }
954                        for kk, vv in next, v do
955--                         for kk, vv in sortedhash(v) do
956                            stepper(kk,function(nn) -- todo: also make stepper accept a table
957                                local t = mathgaps[nn] or nn
958                                if t then
959                                    kerns[t] = vv * width
960                                    done = registerdone(done,t)
961                                end
962                            end)
963                        end
964                        chardata.kerns = kerns
965                    end
966                end
967            end
968
969            for k, v in next, list do -- no need for sortedhash(list) unless we report
970                stepper(k,function(n) -- todo: also make stepper accept a table
971                    add(v,n)
972                end)
973            end
974
975--             for k, v in next, list do -- no need for sortedhash(list) unless we report
976--                 local chardata = targetcharacters[mathgaps[k] or k]
977--                 if chardata then
978--                     local width = chardata.width
979--                     if width then
980--                         local kerns = chardata.kerns or { }
981--                         for kk, vv in next, v do
982--                             local t = mathgaps[kk] or kk
983--                             if t then
984--                                 kerns[t] = vv * width
985--                                 count = count + 1
986--                             end
987--                         end
988--                         chardata.kerns = kerns
989--                     end
990--                 end
991--             end
992
993            feedback_tweak("kernpairs",target,original,done)
994        end
995    end
996
997end
998
999do
1000
1001    local list = {
1002        { 0x2032, 1 },
1003        { 0x2033, 2, 0x2032 },
1004        { 0x2034, 3, 0x2032 },
1005        { 0x2057, 4, 0x2032 },
1006        { 0x2035, 1 },
1007        { 0x2036, 2, 0x2035 },
1008        { 0x2037, 3, 0x2035 },
1009    }
1010
1011    datasets.fixprimes = list
1012
1013    function mathtweaks.fixprimes(target,original,parameters)
1014        local targetcharacters = target.characters
1015        local factor = parameters.factor or 1
1016        local fake   = tonumber(parameters.fake)
1017        for i=1,#list do
1018            local entry   = list[i]
1019            local unicode = entry[1]
1020            local count   = entry[2]
1021            local used    = fonts.handlers.otf.getsubstitution(target,unicode,"ssty",true,"math","dflt") or unicode
1022            local data    = targetcharacters[used]
1023            if data then
1024                targetcharacters[unicode] = data
1025                local oldheight = data.height or 0
1026                local newheight = factor * oldheight
1027                data.yoffset = newheight - (oldheight or 0)
1028                data.height  = newheight
1029                data.smaller = nil
1030            elseif not fake then
1031                report_tweak("missing %i prime %U",target,original,count,unicode)
1032            end
1033        end
1034        if fake then
1035            for i=1,#list do
1036                local entry = list[i]
1037                local count = entry[2]
1038                if count > 1 then
1039                    local unicode  = entry[1]
1040                    local original = entry[3]
1041                    local data     = targetcharacters[original]
1042                    if data then
1043                        local oldwidth = data.width
1044                        local xoffset  = fake * oldwidth
1045                        local newwidth = oldwidth + (count - 1) * xoffset
1046                        targetcharacters[unicode] = {
1047                            width    = newwidth,
1048                            height   = data.height,
1049                            unicode  = unicode,
1050                            commands = {
1051                                              { "offset",           0, 0, original },
1052                                              { "offset",     xoffset, 0, original },
1053                                count > 2 and { "offset", 2 * xoffset, 0, original } or nil,
1054                                count > 3 and { "offset", 3 * xoffset, 0, original } or nil,
1055                            },
1056                        }
1057                    end
1058                end
1059            end
1060        end
1061    end
1062
1063end
1064
1065do
1066
1067    local nps = fonts.helpers.newprivateslot
1068
1069    local privates = {
1070        [0x2212] = nps("unary minus"),
1071        [0x002B] = nps("unary plus"),
1072        [0x00B1] = nps("unary plus minus"),
1073        [0x2213] = nps("unary minus plus"),
1074    }
1075
1076    -- these are the values tested with texgyre-bonum
1077
1078    local predefined  = {
1079        ["unary minus"] = {
1080            original = 0x2212,
1081            extend   = .5,
1082            width    = .5,
1083            unicode  = 0x002D, -- hyphen minus
1084        },
1085        ["unary plus"] = {
1086            original = 0x002B,
1087            extend   = .5,
1088            squeeze  = .5,
1089            width    = .5,
1090            height   = .5,
1091            yoffset  = .2,
1092            mode     = 2,
1093            wline    = .5,
1094            unicode  = 0x002B,
1095        },
1096        ["unary plus minus"] = {
1097            original = 0x00B1,
1098            extend   = .5,
1099            squeeze  = .5,
1100            width    = .5,
1101            height   = .5,
1102            yoffset  = .2,
1103            mode     = 2,
1104            wline    = .5,
1105        },
1106        ["unary minus plus"] = {
1107            original = 0x2213,
1108            extend   = .5,
1109            squeeze  = .5,
1110            width    = .5,
1111            height   = .5,
1112            yoffset  = .2,
1113            mode     = 2,
1114            wline    = .5,
1115        },
1116    }
1117
1118 -- {
1119 --     tweak = "addprivates",
1120 --     list  = {
1121 --         -- for specific parameters see act file
1122 --         ["unary minus"]      = { preset = "unary minus"      },
1123 --         ["unary plus"]       = { preset = "unary plus"       },
1124 --         ["unary plus minus"] = { preset = "unary plus minus" },
1125 --         ["unary minus plus"] = { preset = "unary minus plus" },
1126 --     },
1127 -- },
1128
1129    function mathtweaks.addprivates(target,original,parameters)
1130        local list = parameters.list or predefined
1131        if list then
1132            local targetcharacters   = target.characters
1133            local targetparameters   = target.parameters
1134            local originalcharacters = original.characters
1135            local processedprivates  = { }
1136            for name, v in sortedhash(list) do
1137                if type(v) == "table" then
1138                    local preset = v.preset
1139                    if preset then
1140                        local p = predefined[preset]
1141                        if p then
1142                            v = table.combine(p,v)
1143                            p.preset = nil
1144                        else
1145                            goto next
1146                        end
1147                    end
1148                    local charslot = v.original
1149                    if charslot then
1150                        local chardata = targetcharacters[charslot]
1151                        if chardata then
1152                            local clonedata = copytable(chardata)
1153                            local cloneslot = nps(name)
1154                            local unicode   = v.unicode or clonedata.unicode
1155                            clonedata.uncode = unicode
1156                            targetcharacters[cloneslot] = clonedata
1157                            if trace_tweaking then
1158                                report_tweak("cloning %a from %C into %U with tounicode %U",target,original,name,charslot,cloneslot,unicode)
1159                            end
1160                        end
1161                        processedprivates[name] = v
1162                    end
1163                  ::next::
1164                end
1165            end
1166            mathtweaks.dimensions(target,original,{
1167                tweak = parameters.tweak,
1168                list  = processedprivates,
1169            })
1170        end
1171    end
1172
1173end
1174
1175-- do
1176--
1177--     function mathtweaks.fixanchors(target,original,parameters)
1178--         local targetcharacters= target.characters
1179--         local factor = tonumber(parameters.factor) or 0
1180--         if factor ~= 0 then
1181--             local done = false
1182--             for k, v in next, targetcharacters do
1183--                 local a = v.topanchor
1184--                 if a and a > 0 then
1185--                     v.topanchor = a * factor
1186--                     count = count + 1
1187--                     done = registerdone(done,u)
1188--                 end
1189--             end
1190--         end
1191--         feedback_tweak("fixanchors",target,original,done)
1192--     end
1193--
1194-- end
1195
1196-- do
1197--
1198--     -- actually this should be a an engine feature driven by category because we don't
1199--     -- want this in display mode .. only a test for MS and HH
1200--
1201--     local issymbol = characters.is_symbol
1202--
1203--     function mathtweaks.oldstylemath(target,original,parameters)
1204--         local chardata = characters.data
1205--         local characters = target.characters
1206--         local axis       = target.mathparameters.AxisHeight
1207--         local delta      = (parameters.factor or .1) * axis
1208--         target.mathparameters.AxisHeight = (axis - delta)
1209--         for k, v in sortedhash(characters) do
1210--             if issymbol[k] then -- quick hack, engine knows
1211--                 v.yoffset = -delta
1212--                 v.height  = (v.height or 0) - delta
1213--                 v.depth   = (v.depth  or 0) - delta
1214--             end
1215--         end
1216--     end
1217--
1218--     function mathtweaks.oldstylemath(target,original,parameters)
1219--         -- not relevant
1220--     end
1221--
1222-- end
1223
1224do
1225
1226    function mathtweaks.simplifykerns(target,original,parameters)
1227        local characters = target.characters
1228        local done       = false
1229     -- for u, v in sortedhash(characters) do
1230        for u, v in next, characters do
1231            local mathkerns = v.mathkerns
1232            if mathkerns then
1233                local k = mathkerns.topleft
1234                if k then
1235                    k = k[#k].kern
1236                    if k then
1237                        v.topleft = k
1238                    end
1239                end
1240                local k = mathkerns.topright
1241                if k then
1242                    k = k[#k].kern
1243                    if k then
1244                        v.topright = k
1245                    end
1246                end
1247                local k = mathkerns.bottomleft
1248                if k then
1249                    k = k[1].kern -- todo get value at baseline
1250                    if k then
1251                        v.bottomleft = k
1252                    end
1253                end
1254                local k = mathkerns.bottomright
1255                if k then
1256                    k = k[1].kern -- todo get value at baseline
1257                    if k then
1258                        v.bottomright = k
1259                    end
1260                end
1261                v.mathkerns = nil
1262                done = registerdone(done,u)
1263            end
1264        end
1265        feedback_tweak("simplifykerns",target,original,done)
1266    end
1267
1268end
1269
1270
1271-- In TeX The Program we find in section 543 a remark about the second use of math
1272-- italic: it is always added to the width except with respect to the position of
1273-- the subscript. That paragraph also mentions that the number of different widths
1274-- is normally small so they can be shared (there is an eight bit index into a width
1275-- array). Actually the limits of at most 16 heights and depths has as side effect
1276-- that we get some snapping of sizes. Now, because many math characters have an
1277-- italic correction, the saving on shared widths is negated by the amount of (at
1278-- most 64) italics. So in practice there is no gain and the italic correction could
1279-- have served as bottom kern. We think that the following approach (that we actualy
1280-- came to by a different reasonsing: inconsistent open type fonts) is quite valid
1281-- and robust. It's just that the opentype math fonts should never have gone that
1282-- TeX italic route. Italic usage is more clear from section 543 than from the math
1283-- rendering code.
1284
1285do
1286
1287     local function wipe(whatever,target,original,parameters,field,move,integrals)
1288        local targetcharacters   = target.characters
1289        local targetdescriptions = target.descriptions
1290        local factor             = target.parameters.factor
1291        local correct            = parameters.correct
1292        local done               = false
1293        local function getllx(u)
1294            local d = targetdescriptions[u]
1295            if d then
1296                local b = d.boundingbox
1297                if b then
1298                    local llx = b[1]
1299                    if llx < 0 then
1300                        return - llx
1301                    end
1302                end
1303            end
1304            return false
1305        end
1306        local function step(s)
1307            while s do
1308                local u = mathgaps[s] or s
1309                local c = targetcharacters[u]
1310                if c then
1311                    if field == "topanchor" then
1312                        if c.topanchor then
1313                            c.topanchor = nil
1314                        else
1315                            goto smaller
1316                        end
1317                    else
1318                        local okay   = false
1319                        local italic = c.italic
1320                        if move and not c.advance then -- advance check prevents double move
1321                            local width  = c.width or 0
1322                            c.advance = width
1323                            if correct then
1324                                local llx = getllx(u)
1325                                if llx then
1326                                    local topanchor = c.topanchor
1327                                    llx   = llx * factor
1328                                    width = width + llx
1329                                    c.xoffset = llx
1330                                    if topanchor then
1331                                        c.topanchor = topanchor + llx
1332                                    end
1333                                    -- too bad (schola e^x):
1334                                 -- c.bottomleft = (c.bottomleft or 0) - llx
1335                                 -- c.topleft    = (c.topleft    or 0) - llx
1336                                    okay = true
1337                                end
1338                            end
1339                            if italic and italic ~= 0 then
1340                                c.width       = width + italic
1341                                c.bottomright = - italic
1342                                okay = true
1343                            else
1344                                c.width = width
1345                            end
1346c.bottomanchor = width/2 -- maybe optional
1347                        end
1348                        if italic then
1349                            c.italic = nil
1350                            okay = true
1351                        end
1352                        if okay then
1353                            done = registerdone(done,u)
1354                        else
1355                            goto smaller
1356                        end
1357                    end
1358                    goto smaller
1359                  ::smaller::
1360                    s = c.smaller
1361                  ::variants::
1362                    -- no italics here anyway but we could check them some day
1363                else
1364                    break
1365                end
1366            end
1367        end
1368        local list = parameters.list -- todo: ranges
1369        if list == "letters" or parameters.letters then
1370            local chardata = characters.data
1371         -- for k, v in sortedhash(targetcharacters) do
1372            for k, v in next, targetcharacters do
1373                if v.italic then
1374                    local d = chardata[v.unicode]
1375                    local c = d and d.category
1376                    if c == "ll" or c == "lu" then
1377                        step(k)
1378                    end
1379                end
1380            end
1381            goto done
1382        end
1383        if not list or list == "all" or list == true or parameters.all then
1384            list = sortedkeys(targetcharacters)
1385        elseif type(list) == "string" then
1386            list = { list }
1387        end
1388        for i=1,#list do
1389            local l = list[i]
1390            local t = type(l)
1391            if not l then
1392                -- can be false
1393            elseif t == "table" then
1394                for i=1,#l do
1395                    step(l[i])
1396                end
1397            elseif t == "number" then
1398                step(l)
1399            else
1400                local r = blocks[l]
1401                if r then
1402                    for i=r.first,r.last do
1403                        step(i)
1404                    end
1405                else
1406                    stepper(l,step)
1407                end
1408            end
1409        end
1410       ::done::
1411        feedback_tweak(whatever,target,original,done)
1412    end
1413
1414    function mathtweaks.wipeanchors(target,original,parameters)
1415        wipe("wipeanchors",target,original,parameters,"topanchor")
1416    end
1417
1418    function mathtweaks.wipeitalics(target,original,parameters)
1419        if not checkitalics then
1420            wipe("wipeitalics",target,original,parameters,"italic")
1421        end
1422    end
1423
1424    function mathtweaks.moveitalics(target,original,parameters)
1425        wipe("moveitalics",target,original,parameters,"italic",true)
1426    end
1427
1428 -- function mathtweaks.fixdigits(target,original,parameters)
1429 --     mathtweaks.fixanchors(target,original,{ list = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } })
1430 -- end
1431
1432end
1433
1434do
1435
1436    function mathtweaks.topanchors(target,original,parameters)
1437        local characters = target.characters
1438        local list       = parameters.list
1439        if list then
1440            local done = false
1441            for u, v in sortedhash(list) do
1442                local c = characters[k]
1443                if c then
1444                    local w = c.width
1445                    if w and w ~= 0 then
1446                        c.topanchor = v * w
1447                        done = registerdone(done,u)
1448                    end
1449                end
1450            end
1451            feedback_tweak("topanchors",target,original,done)
1452        end
1453    end
1454
1455    function mathtweaks.movelimits(target,original,parameters)
1456        local characters = target.characters
1457        local list       = parameters.list
1458        if list then
1459            local factor = parameters.factor or 1
1460            local also   = getalso(target,original)
1461            local done   = { }
1462            local function relocate(u,factor)
1463                if done[u] then
1464                    return
1465                end
1466                done[u] = true
1467                local c = characters[u]
1468                if c then
1469                    local italic = c.italic
1470                    if italic then
1471                        if italic ~= 0 then
1472                            local width = c.width or 0
1473                            local half  = (italic/2) * factor
1474                            -- kind of weird ... needs to be in sync with engine .. needs checking
1475                            c.topanchor    = width + half
1476                            c.bottomanchor = width - half
1477-- print(width/65536,c.topanchor/65536)
1478                            c.bottomright  = - italic * (parameters.icfactor or 1)
1479                            if trace_tweaking then
1480                                -- todo
1481                            end
1482                        end
1483                        c.italic = nil
1484                    end
1485                    local s = c.smaller
1486                    if s then
1487                        relocate(s,factor)
1488                    end
1489                    local n = c.next
1490                    if n then
1491                        relocate(n,factor)
1492                    end
1493                    -- Kind of tricky: we configure the engine to use the vitalic
1494                    -- so when we tweak we need to set that to zero.
1495                    local parts  = c.parts
1496                    local italic = c.partsitalic
1497                    if parts and italic then
1498                        if italic ~= 0 then
1499                            local tchar = characters[parts[#parts].glyph]
1500                            local bchar = characters[parts[1].glyph]
1501                            local width = tchar.width or 0
1502                            local half  = (italic/2) * factor
1503                            tchar.topanchor    = width + half
1504                            bchar.bottomanchor = width - half
1505                            bchar.bottomright  = - italic
1506                            if trace_tweaking then
1507                                -- todo
1508                            end
1509                            tchar.italic = nil
1510                            bchar.italic = nil
1511                        end
1512                        c.vitalic = nil
1513                    end
1514                    if also then
1515                        local a = also[u]
1516                        if a then
1517                            for i=1,#a do
1518                                relocate(a[i],factor)
1519                            end
1520                        end
1521                    end
1522                end
1523            end
1524            if #list > 0 then
1525                for i=1,#list do
1526                    relocate(list[i],factor)
1527                end
1528            else
1529                for k, v in sortedhash(list) do
1530                    relocate(k,tonumber(v) or factor)
1531                end
1532            end
1533            feedback_tweak("movelimits",target,original,done)
1534        end
1535    end
1536
1537end
1538
1539do
1540
1541    -- musical timestamp: March 2022, Antonio Sanches (Bad Hombre), live performance in NL
1542
1543    function mathtweaks.kerns(target,original,parameters)
1544        local kerns = parameters.list
1545        if kerns then
1546            local characters = target.characters
1547            local done       = false
1548            local function setone(uc,data)
1549                local function set(unicode)
1550                    unicode = mathgaps[unicode] or unicode
1551                    local chardata = characters[unicode]
1552                    if chardata then
1553                        local width = chardata.width  or 0
1554                        local k = data.topleft     ; if k and k ~= 0 then chardata.topleft     = k * width end
1555                        local k = data.topright    ; if k and k ~= 0 then chardata.topright    = k * width end
1556                        local k = data.bottomleft  ; if k and k ~= 0 then chardata.bottomleft  = k * width end
1557                        local k = data.bottomright ; if k and k ~= 0 then chardata.bottomright = k * width end
1558                        done = registerdone(done,unicode)
1559                    end
1560                end
1561                local unicode  = detail(characters,uc)
1562                if type(unicode) == "table" then
1563                    for i=1,#unicode do
1564                        set(unicode[i])
1565                    end
1566                elseif unicode then
1567                    set(unicode)
1568                end
1569            end
1570            for unicode, data in next, kerns do
1571                setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
1572                -- also smaller
1573            end
1574            feedback_tweak("kerns",target,original,done)
1575        end
1576    end
1577
1578end
1579
1580do
1581
1582--     function mathtweaks.margins(target,original,parameters)
1583--         local margins = parameters.list
1584--         if margins then
1585--             local characters = target.characters
1586--             local done       = false
1587--             local function setone(unicode,data)
1588--                 unicode = mathgaps[unicode] or unicode
1589--                 local chardata = characters[unicode]
1590--                 if chardata then
1591--                     local width    = chardata.width or 0
1592--                     local total    = (chardata.height or 0) + (chardata.depth or 0)
1593--                     local k = data.left   ; if k and k ~= 0 then chardata.leftmargin   = k * width end
1594--                     local k = data.right  ; if k and k ~= 0 then chardata.rightmargin  = k * width end
1595--                     local k = data.top    ; if k and k ~= 0 then chardata.topmargin    = k * total end
1596--                     local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * total end
1597--                     done = registerdone(done,unicode)
1598--                 end
1599--             end
1600--             for unicode, data in next, margins do
1601--                 setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
1602--                 -- also smaller
1603--             end
1604--             feedback_tweak("margins",target,original,done)
1605--         end
1606--     end
1607
1608    function mathtweaks.margins(target,original,parameters)
1609        local kerns = parameters.list
1610        if kerns then
1611            local characters = target.characters
1612            local done       = false
1613            local function setone(uc,data)
1614                local function set(unicode)
1615                    unicode = mathgaps[unicode] or unicode
1616                    local chardata = characters[unicode]
1617                    if chardata then
1618                        local width = chardata.width  or 0
1619                        local k = data.left   ; if k and k ~= 0 then chardata.leftmargin   = k * width end
1620                        local k = data.right  ; if k and k ~= 0 then chardata.rightmargin  = k * width end
1621                        local k = data.top    ; if k and k ~= 0 then chardata.topmargin    = k * width end
1622                        local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * width end
1623                        done = registerdone(done,unicode)
1624                    end
1625                end
1626                local unicode  = detail(characters,uc)
1627                if type(unicode) == "table" then
1628                    for i=1,#unicode do
1629                        set(unicode[i])
1630                    end
1631                elseif unicode then
1632                    set(unicode)
1633                end
1634            end
1635            for unicode, data in next, kerns do
1636                setone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
1637                -- also smaller
1638            end
1639            feedback_tweak("margins",target,original,done)
1640        end
1641    end
1642
1643
1644end
1645
1646do
1647
1648    -- musical timestamp: June 2022, Porcupine Tree - Rats Return
1649
1650    -- we can actually share these and flag them as being tweaked
1651
1652    local function scale(t,width,total)
1653        local r = { }
1654        for i=1,#t do
1655            local ti = t[i]
1656            local kern   = ti.kern
1657            local height = ti.height
1658            if kern then
1659                kern = width * kern
1660            end
1661            if height then
1662                height = total * height
1663            end
1664            r[i] = {
1665                kern   = kern or 0,
1666                height = height or 0,
1667            }
1668        end
1669        return r
1670    end
1671
1672    function mathtweaks.staircase(target,original,parameters)
1673        local kerns = parameters.list
1674        if kerns then
1675            local characters = target.characters
1676            local function kernone(unicode,data)
1677                local chardata = characters[mathgaps[unicode] or unicode]
1678                local total = (chardata.height or 0) + (chardata.depth or 0)
1679                local width = chardata.width or 0
1680                if data then
1681                    local tl = data.topleft     ; if tl then tl = scale(tl,width,total) end
1682                    local tr = data.topright    ; if tr then tr = scale(tr,width,total) end
1683                    local bl = data.bottomleft  ; if bl then bl = scale(bl,width,total) end
1684                    local br = data.bottomright ; if br then br = scale(br,width,total) end
1685                    chardata.mathkerns = {
1686                        topleft     = tl,
1687                        ropright    = tr,
1688                        bottomleft  = bl,
1689                        bottomright = br,
1690                    }
1691                else
1692                    chardata.mathkerns = nil
1693                end
1694            end
1695            for unicode, data in next, kerns do
1696                kernone(unicode,data) -- withscriptcode(tfmdata,unicode,data,kernone)
1697                -- also smaller
1698            end
1699        end
1700    end
1701
1702end
1703
1704do
1705
1706 -- local list = {
1707 --     [0x203E] = { factor = .4 }, -- overbar
1708 --     [0x203E] = { factor = .7 }, -- underbar
1709 --     [0x23DE] = { factor = .4 }, -- overbrace
1710 --     [0x23DF] = { factor = .7 }, -- underbrace
1711 --     [0x23DC] = { factor = .4 }, -- overparent
1712 --     [0x23DD] = { factor = .7 }, -- underparent
1713 --     [0x23B4] = { factor = .4 }, -- overbracket
1714 --     [0x23B5] = { factor = .7 }, -- underbracket
1715 -- }
1716
1717    -- We can patch the dimensions in-place or we can use additional characters in
1718    -- the private namespace.
1719
1720    -- local addprivate = fonts.helpers.addprivate
1721    -- local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata)
1722
1723    local nps = fonts.helpers.newprivateslot
1724
1725    local umbracepiece = nps("um brace piece") -- will be created
1726    local lmbracepiece = nps("lm brace piece") -- will be created
1727    local cmbracepiece = nps("cm brace piece") -- will be created : center piece for brace builder hack
1728
1729    local ulbracepiece = nps("ul brace piece")
1730    local urbracepiece = nps("ur brace piece")
1731    local llbracepiece = nps("ll brace piece")
1732    local lrbracepiece = nps("lr brace piece")
1733
1734    local over  = { factor = "over"   }
1735    local under = { factor = "under"  }
1736
1737    local candidates = {
1738        over = {
1739            [0x203E] = over,  -- overbar
1740            [0x23DE] = over,  -- overbrace
1741            [0x23DC] = over,  -- overparent
1742            [0x23B4] = over,  -- overbracket
1743        },
1744        under = {
1745            [0x23DF] = under, -- underbrace
1746            [0x23DD] = under, -- underparent
1747            [0x23B5] = under, -- underbracket
1748        },
1749        accent = {
1750            [0x0300] = over,  -- widegrave
1751            [0x0308] = over,  -- wideddot
1752            [0x0304] = over,  -- macron (bar)
1753            [0x0305] = over,  -- widebar
1754            [0x0301] = over,  -- wideacute
1755            [0x0302] = over,  -- widehat
1756            [0x030C] = over,  -- widecheck
1757            [0x0306] = over,  -- widebreve
1758            [0x0307] = over,  -- widedot
1759            [0x030A] = over,  -- widering
1760            [0x0303] = over,  -- widetilde
1761            [0x20DB] = over,  -- widedddot
1762        },
1763    }
1764
1765    datasets.accentdimensions = candidates
1766
1767    local function adapt(c,factor,baseheight,basedepth)
1768        if not c.tweaked then
1769            local height  = c.height or 0
1770            local depth   = c.depth  or 0
1771            local yoffset = 0
1772            if factor == "over" then
1773                local h = height - baseheight
1774                yoffset = h - height
1775                height  = h
1776                depth   = depth - baseheight
1777            elseif factor == "under" then
1778                local d = depth - basedepth
1779                yoffset = depth - d
1780                depth   = d
1781                height  = height - baseheight
1782            elseif height > 0 then
1783                local h = tonumber(factor) * height
1784                yoffset = h - height
1785                height  = h
1786            elseif depth > 0 then
1787                local d = tonumber(factor) * depth
1788                yoffset = depth - d
1789                depth   = d
1790            end
1791            c.yoffset = yoffset ~= 0 and yoffset or nil
1792            c.height  = height   > 0 and height  or nil
1793            c.depth   = depth    > 0 and depth   or nil
1794            c.tweaked = true
1795        end
1796    end
1797
1798    local function process(target,original,characters,list,baseheight,basedepth)
1799        if list then
1800            for k, v in sortedhash(list) do -- sort for tracing
1801                local c = characters[k]
1802                if c and not c.yoffset then
1803                    local factor = v.factor
1804                    if factor then
1805                        adapt(c,factor,baseheight,basedepth)
1806                        local nc  = c.next
1807                        local nv = 0
1808                        local ns = 0
1809                        while nc do
1810                            local c = characters[nc]
1811                            if c then
1812                                adapt(c,factor,baseheight,basedepth)
1813                                nv = nv + 1
1814                                nc = c.next
1815                                if not nc then
1816                                    local hv = c.parts
1817                                    if hv then
1818                                        for i=1,#hv do
1819                                            local c = characters[hv[i].glyph]
1820                                            if c then
1821                                                adapt(c,factor,baseheight,basedepth)
1822                                                ns = ns + 1
1823                                            end
1824                                        end
1825                                    end
1826                                    break
1827                                end
1828                            else
1829                                break
1830                            end
1831                        end
1832                        if trace_tweaking then
1833                            report_tweak("adapting extensible (%i sizes, %i parts) %U",target,original,k,nv,ns)
1834                        end
1835                    end
1836                end
1837            end
1838        end
1839    end
1840
1841    function mathtweaks.accentdimensions(target,original,parameters)
1842        local list = parameters.list or { "over", "under" }
1843        if list then
1844            local characters = target.characters
1845            local baseheight = target.mathparameters.AccentBaseHeight or 0
1846            local basedepth  = target.mathparameters.AccentBaseDepth  or 0
1847            for k, v in sortedhash(list) do -- sort for tracing
1848                local t = type(v)
1849                if t == "string" then
1850                    v = candidates[v]
1851                    t = type(v)
1852                end
1853                if t == "table" then
1854                    process(target,original,characters,v,baseheight,basedepth)
1855                end
1856            end
1857        end
1858    end
1859
1860end
1861
1862do
1863
1864    local addprivate     = fonts.helpers.addprivate
1865    local privateslot    = fonts.helpers.privateslot
1866    local newprivateslot = fonts.helpers.newprivateslot
1867
1868    -- no checking for present extensibles
1869
1870    function mathtweaks.addrules(target,original,parameters)
1871        local characters = target.characters
1872        local thickness  = target.mathparameters.OverbarRuleThickness
1873        local width      = target.parameters.emwidth / 3
1874        local step       = width / 2
1875        local quarter    = thickness / 4
1876        local half       = thickness / 2
1877        local double     = thickness * 2
1878        local done       = false
1879        characters[0x203E] = { -- middle used for all kind
1880            width    = width,
1881            height   = half,
1882            depth    = half,
1883            yoffset  = - half,
1884            unicode  = 0x203E,
1885            commands = { { "rule", thickness, width } },
1886            parts    = {
1887                { advance = width, ["end"] = step, glyph = 0x203E, start = 0 },
1888                { advance = width, ["end"] = 0,    glyph = 0x203E, start = step, extender = 1 },
1889            },
1890            partsorientation = "horizontal",
1891        }
1892        local function build(target,leftarrow,rightarrow)
1893            if leftarrow and rightarrow then
1894                -- actually the same sort of code as we have for antykwa
1895                local left  = leftarrow.parts
1896                local right = rightarrow.parts
1897                if left and right then
1898                    local leftline   = right[1].glyph
1899                    local rightline  = left[#left].glyph
1900                    local leftdata   = characters[leftline]
1901                    local rightdata  = characters[rightline]
1902                    local leftwidth  = leftdata.width
1903                    local rightwidth = rightdata.width
1904                    local result     = characters[target] -- copytable(leftdata)
1905                    if not result or result.width == 0 then
1906                        result = {
1907                            height   = leftdata.height,
1908                            depth    = leftdata.depth,
1909                            width    = 0.9*leftwidth + rightwidth,
1910                            unicode  = target,
1911                            commands = {
1912                                slotcommand[0][leftline],
1913                                leftcommand[0.1*leftwidth],
1914                                slotcommand[0][rightline],
1915                            },
1916                        }
1917                        characters[target] = result
1918                    end
1919                    result.parts = {
1920                        { advance = leftwidth, glyph = leftline, ["end"] = .9*leftwidth, start = 0 },
1921                        { advance = rightwidth, glyph = rightline, ["end"] = .1*leftwidth, start = .9*rightwidth, extender = 1 },
1922                    }
1923                    result.partsorientation = "horizontal"
1924                    done = registerdone(done,target)
1925                end
1926            end
1927        end
1928        build(0x305,characters[0x20D6],characters[0x20D7]) -- overbar  accent
1929        build(0x332,characters[0x20EE],characters[0x20EF]) -- underbar accent
1930        --
1931        -- lucida lacks them ...
1932        --
1933        if not characters[0x23B4] then
1934            local depth  = 0
1935            local height = 5 * thickness
1936            local tpiece = addprivate(target,"bracket-piece-top",{
1937                width    = thickness,
1938                height   = height,
1939                depth    = depth,
1940                commands = { upcommand[thickness], { "rule", 4 * thickness, thickness } },
1941            })
1942            local mpiece = addprivate(target,"bracket-piece-top-middle",{
1943                width    = width,
1944                height   = height,
1945                depth    = depth,
1946                commands = { upcommand[4*thickness], { "rule", thickness, width } },
1947            })
1948            characters[0x23B4] = { -- over
1949                width    = double + width,
1950                height   = height,
1951                depth    = depth,
1952                unicode  = 0x23B4,
1953extensible = false,
1954                commands = {
1955                    slotcommand[0][tpiece],
1956                    slotcommand[0][mpiece],
1957                    slotcommand[0][tpiece],
1958                },
1959                parts    = {
1960                    { advance = thickness, glyph = tpiece, ["end"] = 0, start = half },
1961                    { advance = width,     glyph = mpiece, ["end"] = step, start = step, extender = 1 },
1962                    { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 },
1963                },
1964                partsorientation = "horizontal",
1965            }
1966            done = registerdone(done,0x23B4)
1967        end
1968        if not characters[0x23B5] then
1969            local depth  = 0
1970            local height = 5 * thickness
1971            local bpiece = addprivate(target,"bracket-piece-bottom",{
1972                width    = thickness,
1973                height   = height,
1974                depth    = depth,
1975                yoffset  = depth,
1976                commands = { { "rule", 4 * thickness, thickness } },
1977            })
1978            local mpiece = addprivate(target,"bracket-piece-bottom-middle",{
1979                width    = width,
1980                height   = height,
1981                depth    = depth,
1982                commands = { { "rule", thickness, width } },
1983            })
1984            characters[0x23B5] = { -- under
1985                width    = double + width,
1986                height   = height,
1987                depth    = depth,
1988                unicode  = 0x23B5,
1989extensible = false,
1990                commands = {
1991                    slotcommand[0][bpiece],
1992                    slotcommand[0][mpiece],
1993                    slotcommand[0][bpiece],
1994                },
1995                parts    = {
1996                    { advance = thickness, glyph = bpiece, ["end"] = 0, start = half },
1997                    { advance = width,     glyph = mpiece, ["end"] = step, start = step, extender = 1 },
1998                    { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 },
1999                },
2000                partsorientation = "horizontal",
2001            }
2002            done = registerdone(done,0x23B5)
2003        end
2004        --
2005        feedback_tweak("rules",target,original,done)
2006    end
2007
2008    -- vfmath.builders.extension(target)
2009
2010    local rbe = newprivateslot("radical bar extender")
2011    local fbe = newprivateslot("fraction bar extender")
2012
2013    local frp = {
2014        newprivateslot("flat rule left piece"),
2015        newprivateslot("flat rule middle piece"),
2016        newprivateslot("flat rule right piece"),
2017    }
2018
2019    local rrp = {
2020        newprivateslot("radical rule left piece"),
2021        newprivateslot("radical rule middle piece"),
2022        newprivateslot("radical rule right piece"),
2023    }
2024
2025    local mrp = {
2026        newprivateslot("minus rule left piece"),
2027        newprivateslot("minus rule middle piece"),
2028        newprivateslot("minus rule right piece"),
2029    }
2030
2031    local forceextensible_tag = tex.charactertagcodes.forceextensible
2032
2033    local function useminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
2034        local minus   = characters[0x2212]
2035        local parts   = minus.parts
2036        if parameters == true then
2037            parameters = { }
2038        end
2039        if parts then
2040            minus.tag = forceextensible_tag
2041            what  = copytable(what)
2042            parts = copytable(parts)
2043            local xscale   = parameters.xscale  or 1 -- why not applied to width ?
2044            local yscale   = parameters.yscale  or 1
2045            local mwidth   = minus.width
2046            local mheight  = minus.height
2047            local height   = (parameters.height  or 1) * mheight
2048            local yshift   = (parameters.yoffset or 0) * mheight
2049            local loverlap = parameters.leftoverlap  or 0
2050            local roverlap = parameters.rightoverlap or 0
2051            local loffset  = parameters.leftoffset   or 0
2052            local roffset  = parameters.rightoffset  or 0
2053            if skipfirst then
2054                remove(parts,1)
2055                remove(what,1)
2056            end
2057            height = height / 2
2058            yshift = yshift + height
2059            for i=1,#parts do
2060                local part   = parts[i]
2061                local glyph  = part.glyph
2062                local gdata  = characters[glyph]
2063                local width  = gdata.width
2064                local xshift = 0
2065                if i == 1 and loverlap ~= 0 then
2066                    xshift = loverlap * width
2067                    width  = width - xshift
2068                elseif i == #parts and roverlap ~= 0 then
2069                    width  = width - roverlap * width
2070                end
2071                characters[what[i]] = {
2072                    height   = height,
2073                    depth    = height,
2074                    width    = width,
2075                    advance  = gdata.width,
2076                    unicode  = 0xFFFD, -- we have no unicode ignore ... maybe zws
2077                    commands = {
2078                        leftcommand[xshift],
2079                        downcommand[yshift],
2080                        { "slot", 0, glyph, xscale, yscale },
2081                    },
2082                }
2083                -- we should overlap more ...
2084                if part["start"] >= width then
2085                    part["start"] = width
2086                end
2087                if part["end"] >= width then
2088                    part["end"] = width
2089                end
2090                part.advance = width
2091                part.glyph   = what[i]
2092            end
2093            characters[what[1]].unicode  = unicode -- nice for tagging
2094            xshift = loffset * mwidth + loverlap * mwidth
2095            width  = mwidth - xshift - roffset * mwidth - roverlap * mwidth
2096            characters[unicode] = {
2097                -- base character
2098                height   = height,
2099                depth    = height,
2100                width    = width,
2101                tag      = forceextensible_tag,
2102                -- not needed as we force:
2103                commands = {
2104                    leftcommand[xshift],
2105                    downcommand[yshift],
2106                    { "slot", 0, 0x2212, xscale, yscale },
2107                },
2108                unicode          = tounicode or unicode,
2109                -- extensibles
2110                parts            = parts,
2111                partsorientation = "horizontal",
2112            }
2113        end
2114    end
2115
2116    -- add minus parts of not there and create clipped clone
2117
2118    local function checkminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
2119        local minus = characters[unicode]
2120        local parts = minus.parts
2121        if parameters == true then
2122            parameters = { }
2123        end
2124        local p_normal = 0
2125        local p_flat   = 0
2126        local mwidth   = minus.width
2127        local height   = minus.height
2128        local depth    = minus.depth
2129        local loffset  = parameters.leftoffset  or 0
2130        local roffset  = parameters.rightoffset or 0
2131        local lshift   = mwidth * loffset
2132        local rshift   = mwidth * roffset
2133        local width    = mwidth - lshift - rshift
2134        if parts then
2135         -- print("minus has parts")
2136            if lshift ~= 0 or width ~= mwidth then
2137                parts = copytable(parts)
2138                for i=1,#parts do
2139                    local part    = parts[i]
2140                    local glyph   = part.glyph
2141                    local gdata   = characters[glyph]
2142                    local width   = gdata.width
2143                    local advance = part.advance
2144                    local lshift = 0
2145                    if i == 1 and left ~= 0 then
2146                        lshift  = loffset * width
2147                        width   = width - lshift
2148                        advance = advance - lshift
2149                    elseif i == #parts and roffset ~= 0 then
2150                        width   = width - rshift
2151                        advance = advance - rshift
2152                    end
2153                    characters[what[i]] = {
2154                        height   = height,
2155                        depth    = depth,
2156                        width    = width,
2157                        commands = {
2158                            leftcommand[lshift],
2159                            slotcommand[0][glyph],
2160-- { "offset", lshift, 0, glyph },
2161                        },
2162                    }
2163                    part.glyph   = what[i]
2164                    part.advance = advance
2165                end
2166                minus.parts = parts
2167                minus.partsorientation = "horizontal"
2168
2169            end
2170        else
2171            local f_normal = formatters["M-NORMAL-%H"](unicode)
2172         -- local p_normal = hasprivate(main,f_normal)
2173            p_normal = addprivate(target,f_normal,{
2174                height   = height,
2175                width    = width,
2176                commands = {
2177                    push,
2178                    leftcommand[lshift],
2179                    slotcommand[0][unicode],
2180                    pop,
2181-- { "offset", lshift, 0, unicode },
2182                },
2183            })
2184--             local step = width/2
2185            local step = .8*width
2186            minus.parts = {
2187                { extender = 0, glyph = p_normal, ["end"] = step, start = 0,    advance = width },
2188                { extender = 1, glyph = p_normal, ["end"] = step, start = step, advance = width },
2189                { extender = 0, glyph = p_normal, ["end"] = 0,    start = step, advance = width },
2190            }
2191            minus.partsorientation = "horizontal"
2192        end
2193        minus.unicode = tounicode or unicode
2194    end
2195
2196    function mathtweaks.replacerules(target,original,parameters)
2197        local characters = target.characters
2198        local minus      = parameters.minus
2199        local fraction   = parameters.fraction
2200        local radical    = parameters.radical
2201        local stacker    = parameters.stacker
2202        if minus then
2203            checkminus(target,0x2212,characters,minus,false,mrp)
2204        end
2205        if fraction then
2206            useminus(target,fbe,characters,fraction,false,frp,0x2044) -- division slash
2207        end
2208        if radical then
2209if not characters[rbe] then
2210            local skipfirst = true
2211            if radical.skipfirst == false then -- explicit
2212                skipfirst = false
2213            end
2214            useminus(target,rbe,characters,radical,skipfirst,rrp,0x2061)   -- apply function
2215end
2216        end
2217        if stacker then
2218            useminus(target,0x203E,characters,stacker,false,frp)
2219        end
2220    end
2221
2222    local force = false  experiments.register("math.arrows", function(v) force = v end)
2223
2224    local function tighten(target,unicode,left,right,squeeze,yoffset)
2225        local name = formatters["math tightened %U %.3N %.3N %.3N %.3N"](unicode,left,right,squeeze,yoffset)
2226        local slot = privateslot(target,name)
2227        if not slot then
2228            local characters = target.characters
2229            local data   = copytable(characters[unicode])
2230            local width  = data.width
2231            data.advance = width
2232            data.width   = width * (1-left-right)
2233            data.xoffset = width * -left
2234            if squeeze ~= 1 then
2235                data.effect  = { squeeze = squeeze }
2236            end
2237            if yoffset ~= 0 then
2238                data.yoffset = (data.height or 0) * yoffset
2239            end
2240            slot = addprivate(target,name,data)
2241        end
2242        return slot
2243    end
2244
2245    local function create(target,unicode,list,overloads)
2246        local characters = target.characters
2247        local chardata   = characters[unicode]
2248        if chardata then
2249            local endpoint = unicode
2250            while chardata.next do
2251                chardata = characters[chardata.next]
2252            end
2253            if chardata and (force or overloads[unicode] == false or not chardata.parts) then
2254                if not list then
2255                 -- chardata.parts = nil -- when we test
2256--                     chardata.parts = { { glyph = unicode } }
2257                else
2258                    local overload = overloads[unicode]
2259                    local parts    = { }
2260                    for i=1,#list do
2261                        local part    = list[i]
2262                        local glyph   = part.glyph or unicode
2263                        local check   = overloads[glyph]
2264                        local left    = (check and check.left   ) or part.left    or 0
2265                        local right   = (check and check.right  ) or part.right   or 0
2266                        local squeeze =  check and check.squeeze or 1
2267                        local yoffset =  check and check.yoffset or 0
2268                        if left~= 0 or right ~= 0 or squeeze ~= 1 or yoffset ~= 0 then
2269                            glyph = tighten(target,glyph,left,right,squeeze,yoffset)
2270                        end
2271                        local width = characters[glyph].width
2272                        local step  = width/2
2273                        if part.extensible then
2274                            parts[#parts+1] = {
2275                                advance  = width,
2276                                glyph    = glyph,
2277                                ["end"]  = step,
2278                                start    = step,
2279                                extender = 1,
2280                            }
2281                        else
2282                            parts[#parts+1] = {
2283                                advance = width,
2284                                glyph   = glyph,
2285                                ["end"] = 0,
2286                                start   = step,
2287                            }
2288                        end
2289                    end
2290                    if #parts == #list then
2291                        chardata.parts            = parts
2292                        chardata.partsorientation = "horizontal"
2293                    end
2294                end
2295            end
2296        end
2297    end
2298
2299    -- Unicode math lacks the arrow snippet while it does have fence snippets. Also, some
2300    -- fonts have a relbar that doesn't match the double arrow.
2301    --
2302    -- {
2303    --     tweak = "addarrows",
2304    --     list  = { [0x3D] = { squeeze = .85, yoffset = .0975 } }
2305    -- },
2306    --
2307    -- We have no begin and end snippet, so I played with centering and rules at the edges
2308    --
2309    -- [0x21A9] = { -- hookleftarrow
2310    --     { glyph = 0x2212, left = slack, extensible = true },
2311    --     { glyph = 0x21A9, right = slack },
2312    --     { glyph = 0x2212, right = slack, extensible = true },
2313    -- }
2314    --
2315    -- but in the end rejected it.
2316
2317    local function initialize(left, right, slack)
2318        -- We save some space with locals. When no glyph is given the unicode itself is
2319        -- used which also saves some.
2320        local single = { glyph = 0x2212, left = slack, right = slack, extensible = true }
2321        local double = { glyph = 0x003D, left = slack, right = slack, extensible = true }
2322        local triple = { glyph = 0x2261, left = slack, right = slack, extensible = true }
2323        ----- spacer = { glyph = 0x0020, left = slack, right = slack, extensible = true }
2324        local slackslack  = { left = slack, right = slack }
2325        local leftslack   = { left = left,  right = slack }
2326        local slackright  = { left = slack, right = right }
2327        ----- centered    = { spacer, { }, spacer }
2328        local centered    = false -- the luametatex engine does this
2329        local singleright = { single, slackright }
2330        local leftsingle  = { leftslack,  single }
2331        return {
2332            --
2333            [0x002D] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel
2334-- [0x2212] = { { left = slack, right = slack, glyph = 0x2212 }, single }, -- rel
2335            --
2336            [0x2190] = leftsingle, -- leftarrow
2337            [0x219E] = leftsingle, -- twoheadleftarrow
2338            [0x21BC] = leftsingle, -- leftharpoonup
2339            [0x21BD] = leftsingle, -- leftharpoondown
2340            --
2341            [0x2192] = singleright, -- rightarrow
2342            [0x21A0] = singleright, -- twoheadrightarrow
2343            [0x21C0] = singleright, -- rightharpoonup
2344            [0x21C1] = singleright, -- rightharpoondown
2345            --
2346            [0x003D] = { slackslack, double }, -- equaltext
2347            [0x2261] = { slackslack, triple }, -- triplerel
2348            [0x27F8] = { leftslack,  double }, -- Leftarrow
2349            [0x27F9] = { double, slackright }, -- Rightarrow
2350            --
2351            [0x21A9] = centered, -- hookleftarrow
2352            [0x21AA] = centered, -- hookrightarrow
2353            [0x21CB] = centered, -- leftrightharpoons
2354            [0x21CC] = centered, -- rightleftharpoons
2355            [0x21C4] = centered, -- rightoverleftarrow
2356            [0x21C6] = centered, -- leftoverrightarrow
2357            [0x21A6] = centered, -- mapsto
2358            --
2359            [0x203E] = { slackslack, { left = slack, right = slack, extensible = true } }, -- bar
2360            --
2361            [0x27F7] = { { glyph = 0x2190, left = left, right = slack }, single, { glyph = 0x2192, left = slack, right = right } }, -- leftrightarrow rightleftarrow
2362            [0x27FA] = { { glyph = 0x27F8, left = left, right = slack }, double, { glyph = 0x27F9, left = slack, right = right } }, -- Leftrightarrow Rightleftarrow
2363        }
2364    end
2365
2366    datasets.addarrows = { }
2367
2368    function mathtweaks.addarrows(target,original,parameters)
2369        local overloads = parameters.list  or { } -- { [unicode] = { left = .1, right = .1 } }
2370        local left      = parameters.left  or 0.05
2371        local right     = parameters.right or 0.05
2372        local slack     = parameters.slack or 0.1
2373        local arrows    = initialize(left,right,slack)
2374         -- inspect(arrows)
2375        for unicode, list in sortedhash(arrows) do
2376            create(target,unicode,list,overloads)
2377        end
2378        datasets.addarrows = sortedkeys(arrows)
2379        if trace_tweaking then
2380            report_tweak("arrows added",target,original)
2381        end
2382
2383    end
2384
2385end
2386
2387
2388
2389do -- this could be combined with the previous
2390
2391    function mathtweaks.addparts(target,original,parameters)
2392        local characters = target.characters
2393        local list       = parameters.list
2394        if list then
2395            for unicode, data in sortedhash(list) do
2396                local template = data.template
2397                if template then
2398                    local source = characters[template]
2399                    local target = characters[unicode]
2400                    if source and target then
2401                        local sequence = data.sequence
2402                        if sequence then
2403                            -- we should follow the next chain first
2404                            local parts = source.parts
2405                            if parts then
2406                                local p = { }
2407                                for i=1,#sequence do
2408                                    local step  = sequence[i]
2409                                    local glyph = step.glyph
2410                                    if glyph == "first" or glyph == "last" then
2411                                        local g = glyph == "first" and 1 or #parts
2412                                        local c = fastcopy(parts[g])
2413                                        local f = step.factor
2414                                        if f then
2415                                            c["end"]  = f * (c["end"] or 0)
2416                                            c.start   = f * (c.start  or 0)
2417                                        end
2418                                        p[#p+1] = c
2419                                    else
2420                                        local c = characters[glyph]
2421                                        if c then
2422                                            p[#p+1] = {
2423                                                glyph   = glyph,
2424                                                advance = c.width,
2425                                                start   = 0,
2426                                                ["end"] = 0,
2427                                            }
2428                                        end
2429                                    end
2430                                end
2431                                if #p > 0 then
2432                                    target.parts = p
2433                                    if not data.horizontal then
2434                                        target.partsorientation = "vertical"
2435                                    end
2436                                end
2437                            end
2438                        end
2439                    end
2440                end
2441            end
2442        end
2443    end
2444
2445end
2446
2447function mathtweaks.action(target,original,parameters)
2448    local action = parameters.action
2449    if type(action) == "function" then
2450        action(target,original,parameters)
2451    end
2452end
2453
2454do
2455
2456    local list = {
2457        { 0x00A0, "s", 1         }, -- nbsp
2458        { 0x2000, "q", 1/2       }, -- enquad
2459        { 0x2001, "q", 1         }, -- emquad
2460        { 0x2002, "q", 1/2       }, -- enspace
2461        { 0x2003, "q", 1         }, -- emspace
2462        { 0x2004, "q", 1/3       }, -- threeperemspace
2463        { 0x2005, "q", 1/4       }, -- fourperemspace
2464        { 0x2006, "q", 1/6       }, -- sixperemspace
2465        { 0x2007, "c", byte('0') }, -- figurespace
2466        { 0x2008, "c", byte('.') }, -- punctuationspace
2467        { 0x2009, "q", 1/8       }, -- breakablethinspace
2468        { 0x200A, "q", 1/8       }, -- hairspace
2469        { 0x200B, "q", 0         }, -- zerowidthspace
2470        { 0x202F, "q", 1/8       }, -- narrownobreakspace
2471        { 0x205F, "s", 1/2       }, -- math thinspace
2472    }
2473
2474    datasets.checkspacing = list
2475
2476    function mathtweaks.checkspacing(target,original,parameters)
2477        local characters = target.characters
2478        local parameters = target.parameters
2479        for i=1,#list do
2480            local entry   = list[i]
2481            local unicode = entry[1]
2482            local data    = characters[unicode]
2483            if not data then
2484                local method   = entry[2]
2485                local fraction = entry[3]
2486                local width    = 0
2487                local height   = 0
2488             -- local depth    = 0
2489                if method == "c" then
2490                    local template = characters[fraction]
2491                    width  = template.width
2492                    height = template.height
2493                 -- depth  = template.depth
2494                elseif method == "s" then
2495                    width  = fraction * parameters.space -- space
2496                    height = 0
2497                 -- depth  = 0
2498                else
2499                    width = fraction * parameters.quad  -- quad
2500                    height = 0
2501                 -- depth  = 0
2502                end
2503                if trace_tweaking then
2504                    report_tweak("setting width of %U to %p",target,original,unicode,width)
2505                end
2506                characters[unicode] = {
2507                    width    = width,
2508                 -- advance  = width,
2509                    height   = height,
2510                 -- depth    = depth,
2511                    unicode  = unicode,
2512                    commands = {
2513                     -- { "slot", 0, 32 },
2514                    },
2515                }
2516            end
2517        end
2518    end
2519
2520end
2521
2522do
2523
2524    -- mirror
2525    -- smaller
2526
2527    local nps = fonts.helpers.newprivateslot
2528
2529    local radicalbarextender = nps("radical bar extender") -- we reserve it here
2530
2531    local list = {
2532        0x221A,
2533    }
2534
2535--     local function fix(target,original,characters,unicode)
2536--         local data = characters[unicode]
2537--         if data then
2538--             local height = data.height or 0
2539--             local depth  = data.depth or 0
2540--             if depth > height then
2541--                 if trace_tweaking then
2542--                     report_tweak("swapping height and depth of radical %U",target,original,unicode)
2543--                 end
2544--                 data.height  = depth
2545--                 data.depth   = height
2546--                 if data.rorrim then
2547--                     -- the original does the magic
2548--                 else
2549--                     data.yoffset = depth - height
2550--                 end
2551--             end
2552--             local smaller = data.smaller
2553--             if smaller then
2554--                 fix(target,original,characters,smaller)
2555--             end
2556--             local mirror = data.mirror
2557--             if mirror then
2558--                 fix(target,original,characters,mirror)
2559--             end
2560--         end
2561--     end
2562
2563    local function fix(target,original,characters,unicode)
2564        local data = characters[unicode]
2565        if data then
2566            local height = data.height or 0
2567            local depth  = data.depth or 0
2568            if depth > height then
2569                if trace_tweaking then
2570                    report_tweak("swapping height and depth of radical %U",target,original,unicode)
2571                end
2572                if data.rorrim then
2573                    -- the original does the magic
2574                else
2575                    data.height  = (data.height or 0) + (data.depth or 0)
2576                    data.yoffset = data.depth
2577                    data.depth   = 0
2578                end
2579            end
2580            local smaller = data.smaller
2581            if smaller then
2582                fix(target,original,characters,smaller)
2583            end
2584         -- local mirror = data.mirror
2585         -- if mirror then
2586         --     fix(target,original,characters,mirror)
2587         -- end
2588            local next = data.next
2589            if next then
2590                fix(target,original,characters,next)
2591            else
2592             -- -- assume no depth
2593             -- local parts = data.parts
2594             -- if parts then
2595             --     fix(target,original,characters,parts[1].glyph)
2596             -- end
2597            end
2598        end
2599    end
2600
2601    function mathtweaks.fixradicals(target,original,parameters)
2602        local characters = target.characters
2603        for i=1,#list do
2604            local unicode = list[i]
2605            fix(target,original,characters,unicode)
2606        end
2607    end
2608
2609    local function fix(target,original,characters,u,l)
2610        local data = characters[u]
2611        if data then
2612         -- data.innerlocation = l.location == "right" and 2 or 1
2613            data.innerlocation = l.location == "right" and "right" or "left"
2614            data.innerxoffset  = (l.hfactor or 1) *  (data.width  or 0)
2615            data.inneryoffset  = (l.vfactor or 1) * ((data.height or 0) + (data.depth or 0))
2616        end
2617    end
2618
2619    function mathtweaks.radicaldegreeanchors(target,original,parameters)
2620        local list = parameters.list
2621        if list then
2622            local characters = target.characters
2623            for unicode, l in sortedhash(list) do -- resolve variants
2624                local u = detail(characters,unicode) or unicode
2625                if type(u) == "table" then
2626                    for i=1,#u do
2627                        fix(target,original,characters,u[i],l)
2628                    end
2629                else
2630                    fix(target,original,characters,u,l)
2631                end
2632            end
2633        end
2634    end
2635
2636    local function fix(target,original,characters,u,l)
2637        local data = characters[u]
2638        if data then
2639            data.leftmargin  = (l.left  or 1) * (data.width  or 0)
2640            data.rightmargin = (l.right or 1) * (data.width  or 0)
2641        end
2642    end
2643
2644    function mathtweaks.radicalbodymargins(target,original,parameters)
2645        local list = parameters.list
2646        if list then
2647            local characters = target.characters
2648            for unicode, l in sortedhash(list) do -- resolve variants
2649                local u = detail(characters,unicode) or unicode
2650                if type(u) == "table" then
2651                    for i=1,#u do
2652                        fix(target,original,characters,u[i],l)
2653                    end
2654                else
2655                    fix(target,original,characters,u,l)
2656                end
2657            end
2658        end
2659    end
2660
2661end
2662
2663do
2664
2665    local done = nil
2666
2667    local function fix(target,original,characters,unicode,axis)
2668        if done[unicode] then
2669            return
2670        end
2671        done[unicode] = true
2672        local data = characters[unicode]
2673        if data then
2674            local height = data.height or 0
2675            local depth  = data.depth or 0
2676            if trace_tweaking then
2677                report_tweak("swapping height and depth of %U",target,original,unicode)
2678            end
2679            local half = (height + depth)/2
2680            if data.rorrim then
2681                -- the original does the magic
2682            else
2683                data.yoffset = depth - (half - axis)
2684            end
2685            height = half + axis
2686            depth  = half - axis
2687            data.height  = height
2688            data.depth   = depth
2689            local smaller = data.smaller
2690            if smaller then
2691                fix(target,original,characters,smaller,axis)
2692            end
2693            local mirror = data.mirror
2694            if mirror then
2695                fix(target,original,characters,mirror,axis)
2696            end
2697            local next = data.next
2698            if next then
2699                fix(target,original,characters,next,axis)
2700            end
2701        end
2702    end
2703
2704    function mathtweaks.fixoldschool(target,original,parameters)
2705        local characters = target.characters
2706        local list       = mathtweaks.subsets.integrals
2707        local also       = getalso(target,original)
2708        local axis       = target.mathparameters.AxisHeight
2709        done = { }
2710        for i=1,#list do
2711            local unicode = list[i]
2712            fix(target,original,characters,unicode,axis)
2713        end
2714        if also then
2715            local a = also[u]
2716            if a then
2717                for i=1,#a do
2718                    fix(target,original,characters,a[i],axis)
2719                end
2720            end
2721        end
2722        done = nil
2723    end
2724
2725    -- After the next one I rewarded myself by (again) watching Joe Parrish interpretation
2726    -- of Shostakovich 10 Mvmt. II - Metal several times (video on yt, track on bandcamp)
2727    -- ... timestamp: awaiting the new Albion (Official) single; their work comes in parts.
2728
2729    function mathtweaks.fixintegrals(target,original,parameters)
2730        local characters = target.characters
2731        local integral   = characters[0x222B]
2732        if integral and not integral.parts then
2733            local top = characters[0x2320]
2734            local mid = characters[0x23AE]
2735            local bot = characters[0x2321]
2736            if top and mid and bot then
2737                top = top.height
2738                mid = mid.height
2739                bot = bot.height
2740                integral.partsitalic = integral.italic
2741                integral.parts       = {
2742                    { advance = bot, ["end"] = bot/3, glyph = 0x2321, start = bot/3  },
2743                    { advance = mid, ["end"] = mid/2, glyph = 0x23AE, start = mid/2, extender = 1 },
2744                    { advance = top, ["end"] = top/3, glyph = 0x2320, start = top/3  },
2745                }
2746                integral.partsorientation = "vertical"
2747                if trace_tweaking then
2748                    report_tweak("fixing the integral extensible",target,original)
2749                end
2750            end
2751        else
2752            report_tweak("no need to fix the integral extensible",target,original)
2753        end
2754    end
2755
2756end
2757
2758do
2759
2760    local list = { 0x2061, 0x2062, 0x2063, 0x2064 }
2761
2762    datasets.wipecues = list
2763
2764    function mathtweaks.wipecues(target,original,parameters)
2765        local characters = target.characters
2766        local tobewiped  = parameters.list or list
2767        local done       = false
2768        for i=1,#tobewiped do
2769            local unicode = tobewiped[i]
2770            characters[unicode] = {
2771                width   = 0,
2772                height  = 0,
2773                depth   = 0,
2774                unicode = unicode,
2775            }
2776            done = registerdone(done,unicode)
2777        end
2778        feedback_tweak("wipecues",target,original,done)
2779    end
2780
2781end
2782
2783do
2784
2785    local mapping = {
2786        [0x002F] = 0x2044,
2787    }
2788
2789    datasets.fixslashes = mapping
2790
2791    function mathtweaks.fixslashes(target,original,parameters)
2792        local characters = target.characters
2793     -- local done       = false
2794        for normal, weird in sortedhash(mapping) do
2795            local normalone  = characters[normal]
2796            local weirdone   = characters[weird]
2797            if normalone and weirdone and not normalone.next then
2798                normalone.next = weirdone.next
2799             -- done = registerdone(done,normal)
2800            end
2801            weirdone = copytable(normalone)
2802            characters[weird] = weirdone
2803            weirdone.unicode = weird
2804        end
2805     -- feedback_tweak("fixslashes",target,original,done)
2806        if trace_tweaking then
2807            report_tweak("slashes fixed",target,original)
2808        end
2809     end
2810
2811end
2812
2813do -- see pagella for an extensive example
2814
2815    local nps = fonts.helpers.newprivateslot
2816
2817    local mapping = {
2818        [0x0300] = { 0x0060, false },
2819        [0x0308] = { 0x00A8, false },
2820        [0x0304] = { 0x00AF, false }, -- 305
2821        [0x0301] = { 0x00B4, false },
2822        [0x0302] = { 0x02C6, true  },
2823        [0x030C] = { 0x02C7, true  },
2824        [0x0306] = { 0x02D8, false },
2825        [0x0307] = { 0x02D9, false },
2826        [0x030A] = { 0x02DA, false },
2827        [0x0303] = { 0x02DC, true  },
2828        [0x20DB] = { 0x20DB, false },
2829-- [0x20EF] = { 0x20EF, false },
2830    }
2831
2832    datasets.fixaccents     = mapping
2833    datasets.extendaccents  = mapping
2834    datasets.flattenaccents = mapping
2835    datasets.copyaccents    = mapping
2836
2837    -- local flat = stretchingdata.flataccent
2838    -- if flat then
2839    --     -- Nasty! xoffset needed. Check this when we patch vf.
2840    --     local flatdata = characters[flat]
2841    --     flatdata.width     = width
2842    --     flatdata.advance   = 0
2843    --     flatdata.topanchor = topanchor
2844    --     flatdata.xoffset   = width + topanchor
2845    -- end
2846
2847local cdata = characters.data
2848
2849    function mathtweaks.fixaccents(target,original,parameters)
2850        local characters = target.characters
2851        local done       = false
2852        for stretching, entry in sortedhash(mapping) do
2853            local alias  = entry[1]
2854            local stretchingdata = characters[stretching]
2855            if stretchingdata and stretchingdata.width == 0 then
2856if false then
2857    local b = target.descriptions[stretching].boundingbox
2858    if b then
2859        local llx = b[1] * target.parameters.hfactor
2860        local urx = b[3] * target.parameters.hfactor
2861        width = urx - llx
2862        stretchingdata.width        = width
2863        stretchingdata.xoffset      = - llx
2864        stretchingdata.advance      = urx
2865        stretchingdata.topanchor    = width/2
2866        stretchingdata.bottomanchor = width/2
2867    end
2868else
2869                local topanchor = stretchingdata.topanchor or 0
2870                local width     = -topanchor
2871                topanchor = width/2
2872                stretchingdata.width     = width
2873                stretchingdata.advance   = 0
2874                stretchingdata.topanchor = topanchor
2875                stretchingdata.commands  = { rightcommand[width + topanchor], charcommand[stretching] }
2876end
2877                done = registerdone(done,stretching)
2878            end
2879        end
2880        feedback_tweak("fixaccents",target,original,done)
2881    end
2882
2883    function mathtweaks.checkaccents(target,original,parameters)
2884        local characters = target.characters
2885        local done       = false
2886        local factor     = target.parameters.hfactor
2887        for unicode, data in sortedhash(characters) do
2888            local width = data.width
2889            if width == 0 then
2890                local d = chardata[data.unicode or unicode]
2891                local c = d and d.category
2892                if c == "mn" or c == "sk" or c == "lm" then -- we can probably can go
2893                    local b = target.descriptions[unicode]
2894                    if b then
2895                        b = b.boundingbox
2896                    end
2897                    if b then
2898                        local topanchor = data.topanchor or 0
2899                        local llx       = b[1] * factor
2900                        local urx       = b[3] * factor
2901-- if topanchor < 0 then
2902data.advance = data.width
2903if true then
2904                     -- width         = - topanchor
2905                        width = 2 * (topanchor - llx)
2906--                         data.commands = {
2907--                          -- rightcommand[width+width/2],
2908--                             rightcommand[-llx],
2909--                             slotcommand[0][unicode]
2910--                         }
2911data.xoffset = -llx
2912else
2913                        width         = urx - llx
2914                        data.commands = {
2915                            leftcommand[llx],
2916                            slotcommand[0][unicode]
2917                        }
2918end
2919                        data.width        = width
2920                        data.topanchor    = width/2
2921                        data.bottomanchor = width/2
2922                    end
2923                    done = registerdone(done,unicode)
2924                end
2925            end
2926        end
2927        feedback_tweak("checkaccents",target,original,done)
2928    end
2929
2930    -- all  true|number  false
2931
2932    function mathtweaks.extendaccents(target,original,parameters)
2933        local characters = target.characters
2934        local all        = parameters.all
2935        local count      = tonumber(all)
2936        local done       = false
2937        for stretching, entry in sortedhash(mapping) do
2938            local extend = entry[2]
2939            if extend then
2940                local last = characters[stretching]
2941                local cnt  = 1
2942                local okay = false
2943                while last do
2944                    if all or (count and cnt > count) then
2945                        last.extensible = true
2946                        local flataccent = last.flataccent
2947                        if flataccent then
2948                            characters[flataccent].extensible = true
2949                            okay = true
2950                        end
2951                    end
2952                    local n = last.next
2953                    if n then
2954                        last = characters[n]
2955                    else
2956                        last.extensible = true
2957                        local flataccent = last.flataccent
2958                        if flataccent then
2959                            characters[flataccent].extensible = true
2960                            okay = true
2961                        end
2962                        break
2963                    end
2964                    cnt = cnt + 1
2965                end
2966                if okay then
2967                    done = registerdone(done,stretching)
2968                end
2969            end
2970        end
2971        feedback_tweak("extendaccents",target,original,done)
2972    end
2973
2974    -- force     true     false
2975    -- height    factor   0.8
2976    -- offset    factor   0.9|calculated
2977    -- squeeze   factor   0.1|calculated
2978
2979    local f_flat = formatters["flat accent %05X"]
2980
2981    function mathtweaks.flattenaccents(target,original,parameters)
2982        local characters = target.characters
2983        local force   = parameters.force
2984        local squeeze = parameters.squeeze or 0.85
2985        local ofactor = parameters.offset  or (squeeze/8.5)
2986        local hfactor = parameters.height  or 0.95 -- (1 - ofactor)
2987        local done    = false
2988        for stretching, entry in sortedhash(mapping) do
2989            local code  = stretching
2990            local last  = characters[stretching]
2991            while last do
2992                if force or not last.flataccent then
2993                    local slot   = nps(f_flat(code))
2994                    local height = last.height or 0
2995-- print(last.width/65536,code,slot)
2996--                     data.effect  = { squeeze = squeeze }
2997characters[slot] = {
2998    width        = last.width,
2999    depth        = last.depth,
3000    height       = last.height * hfactor,
3001    topanchor    = last.topanchor,
3002    bottomanchor = last.bottomanchor,
3003    commands     = { { "offset", 0, ofactor * height, code, 1, squeeze } },
3004-- commands     = { slotcommand[0][code] },
3005-- effect = { squeeze = squeeze },
3006--     next         = last.next,
3007    unicode      = last.unicode,
3008}
3009-- if code == 770 then
3010--     inspect(last)
3011-- end
3012
3013                    last.flataccent  = slot
3014                    done = registerdone(done,stretching)
3015                end
3016                code = last.next
3017                if code then
3018                    last = characters[code]
3019                else
3020                    break
3021                end
3022            end
3023        end
3024        feedback_tweak("flattenaccents",target,original,done)
3025    end
3026
3027    function mathtweaks.copyaccents(target,original,parameters)
3028        local characters = target.characters
3029        local done       = false
3030        for stretching, entry in sortedhash(mapping) do
3031            local alias = entry[1]
3032            if alias ~= stretching then
3033                local stretchingdata = characters[stretching]
3034                if stretchingdata then
3035                    -- we need to nil [x|y]offsets
3036                    characters[alias] = {
3037                        width     = stretchingdata.width,
3038                        height    = stretchingdata.height,
3039                        depth     = stretchingdata.depth,
3040                        next      = stretchingdata.next,
3041--                         commands  = { charcommand[stretching] },
3042                        commands  = stretchingdata.commands or { charcommand[stretching] },
3043                        topanchor = stretchingdata.topanchor,
3044                     -- unicode   = stretching,  -- when we alias to combiners
3045                        unicode   = alias, -- when we keep the original
3046                    }
3047                    done = registerdone(done,stretching)
3048                end
3049            end
3050        end
3051        feedback_tweak("copyaccents",target,original,done)
3052    end
3053
3054    function mathtweaks.keepbases(target,original,parameters)
3055        local characters = target.characters
3056        local done       = false
3057        local list       = parameters.list
3058        if list == "default" then
3059            list = sortedkeys(mapping)
3060        end
3061        if list and #list > 0 then
3062            for i=1,#list do -- assumes sorting
3063                local unicode  = list[i]
3064                local chardata = characters[unicode]
3065                if chardata then
3066                    chardata.keepbase = true
3067                    done = registerdone(done,unicode)
3068                end
3069            end
3070        else
3071            -- maybe also hash
3072        end
3073        feedback_tweak("keepbases",target,original,done)
3074    end
3075
3076end
3077
3078do
3079
3080    function mathtweaks.inspect(target,original,parameters)
3081        local characters = target.characters
3082        local slot = parameters.slot or parameters.unicode
3083        if slot then
3084            -- todo: show unicode
3085            report_math(formatters["%C data:"](slot))
3086            inspect(characters[slot])
3087        end
3088    end
3089
3090end
3091
3092-- do
3093--
3094--     local single <const> = 0x003D
3095--     local double <const> = 0x2A75
3096--     local triple <const> = 0x2A76
3097--
3098--     function mathtweaks.addequals(target,original,parameters)
3099--         local characters = target.characters
3100--         local basechar   = characters[single]
3101--         local width      = basechar.width
3102--         local height     = basechar.height
3103--         local depth      = basechar.depth
3104--         local advance    = (parameters.advance or 1/20) * width
3105--         local char       = charcommand[single]
3106--         local left       = leftcommand[advance]
3107--         characters[double] = {
3108--             unicode  = double,
3109--             width    = 2*width - 1*advance,
3110--             height   = height,
3111--             depth    = depth,
3112--             commands = { char, left, char },
3113--         }
3114--         characters[triple] = {
3115--             unicode  = triple,
3116--             width    = 3*width - 2*advance,
3117--             height   = height,
3118--             depth    = depth,
3119--             commands = { char, left, char, left, char },
3120--         }
3121--         if trace_tweaking then
3122--             report_tweak("double %U and triple %U equals added",target,original,double,triple)
3123--         end
3124--     end
3125--
3126-- end
3127
3128do
3129
3130    local function jointwo(characters,force,unicode,ds,u1,d12,u2)
3131        if force or not characters[unicode] then
3132            local c1 = characters[u1]
3133            local c2 = characters[u2]
3134            if c1 and c2 then
3135                local w1 = c1.width
3136                local w2 = c2.width
3137                local width
3138                if d12 == false then
3139                    d12   = 0
3140                    width = w2
3141                elseif d12 < 0 then
3142                    d12   = d12 * w2
3143                    width = w2
3144                else
3145                    d12   = d12 * ds
3146                    width = w1 + w2 - d12
3147                end
3148                characters[unicode] = {
3149                    unicode  = unicode,
3150                    width    = width,
3151                    height   = max(c1.height or 0, c2.height or 0),
3152                    depth    = max(c1.depth  or 0, c2.depth  or 0),
3153keepvirtual = true,
3154                    commands = {
3155                     -- { "inspect" },
3156                     -- { "trace" },
3157                        slotcommand[0][u1],
3158                     -- { "trace" },
3159                        d12 ~= 0 and leftcommand[d12] or false,
3160                        slotcommand[0][u2],
3161                     -- { "trace" },
3162                    },
3163                }
3164            end
3165        end
3166    end
3167
3168    local function jointhree(characters,force,unicode,ds,u1,d12,u2,d23,u3)
3169        if force or not characters[unicode] then
3170            local c1 = characters[u1]
3171            local c2 = characters[u2]
3172            local c3 = characters[u3]
3173            if c1 and c2 and c3 then
3174                local w1 = c1.width
3175                local w2 = c2.width
3176                local w3 = c3.width
3177                d12 = d12 * ds
3178                d23 = d23 * ds
3179                characters[unicode] = {
3180                    unicode  = unicode,
3181                    width    = w1 + w2 + w3 - d12 - d23,
3182                    height   = max(c1.height or 0, c2.height or 0, c3.height or 0),
3183                    depth    = max(c1.depth  or 0, c2.depth  or 0, c3.depth  or 0),
3184                    commands = {
3185                        slotcommand[0][u1],
3186                        d12 ~= 0 and leftcommand[d12] or false,
3187                        slotcommand[0][u2],
3188                        d23 ~= 0 and leftcommand[d23] or false,
3189                        slotcommand[0][u3],
3190                    }
3191                }
3192            end
3193        end
3194    end
3195
3196    function mathtweaks.addequals(target,original,parameters)
3197        local characters = target.characters
3198        local step       = target.parameters.size/18
3199        local force      = parameters.force
3200force = true
3201        jointwo  (characters,force,0x2254,step,0x03A,0,0x03D)         -- :=
3202        jointhree(characters,force,0x2A74,step,0x03A,0,0x03A,0,0x03D) -- ::=
3203        jointwo  (characters,force,0x2A75,step,0x03D,0,0x03D)         -- ==
3204        jointhree(characters,force,0x2A76,step,0x03D,0,0x03D,0,0x03D) -- ===
3205    end
3206
3207end
3208
3209do
3210
3211    -- If we really want, we can have variants that also match radicals but in practice
3212    -- radicals and actuarians are never seen together. We could also have a smaller
3213    -- extender.
3214
3215    local nps = fonts.helpers.newprivateslot
3216
3217    local radical             <const> = 0x0221A
3218    local actuarianrightlong  <const> = nps("delimited right annuity long") -- 0x020E7
3219    local actuarianrightshort <const> = nps("delimited right annuity short")
3220    local actuarianleftlong   <const> = nps("delimited left annuity long" )
3221    local actuarianleftshort  <const> = nps("delimited left annuity short" )
3222    local placehold           <const> = nps("delimited ghost annuity")
3223
3224    local actuarianbottomrightlong  <const> = nps("delimited bottom right annuity long") -- 0x020E7
3225    local actuarianbottomrightshort <const> = nps("delimited bottom right annuity short")
3226    local actuarianbottomleftlong   <const> = nps("delimited bottom left annuity long" )
3227    local actuarianbottomleftshort  <const> = nps("delimited bottom left annuity short" )
3228
3229    function mathtweaks.addactuarian(target,original,parameters)
3230        local characters = target.characters
3231        local parameters = target.parameters
3232        local linewidth  = target.MathConstants.RadicalRuleThickness -- make option
3233        local basechar   = characters[radical]
3234        local baseheight = (basechar.height or 0)/2
3235        local basedepth  = (basechar.depth  or 0)/2
3236        local basetotal  = baseheight + basedepth
3237        local used       = baseheight
3238        --
3239        characters[placehold] = {
3240            width    = 2*linewidth,
3241            height   = baseheight,
3242            depth    = basedepth,
3243            unicode  = actuarian, -- whatever
3244            callback = "devirtualize",
3245            commands = {
3246                rightcommand[linewidth],
3247                downcommand[basedepth],
3248                { "rule", basetotal, 0 },
3249            },
3250        }
3251        --
3252        characters[0x020E7] = {
3253            width    = 6*linewidth,
3254            height   = baseheight,
3255            depth    = basedepth,
3256            unicode  = actuarian,
3257            callback = "devirtualize",
3258            commands = {
3259                upcommand[baseheight-4*linewidth],
3260                { "rule", linewidth, 4*linewidth },
3261                downcommand[basetotal/2-linewidth],
3262                { "rule", basetotal/2, linewidth },
3263            },
3264        }
3265        --
3266        characters[actuarianrightlong] = {
3267            width    = 2*linewidth,
3268            height   = baseheight,
3269            depth    = basedepth,
3270            unicode  = actuarian,
3271            callback = "devirtualize",
3272            commands = {
3273                downcommand[basedepth],
3274                { "rule", basetotal, linewidth },
3275            },
3276            parts = {
3277                {
3278                    advance  = basetotal,
3279                    ["end"]  = used,
3280                    glyph    = actuarianrightlong,
3281                    start    = 0,
3282                },
3283                {
3284                    advance  = basetotal,
3285                    ["end"]  = 0,
3286                    extender = 1,
3287                    glyph    = actuarianrightlong,
3288                    start    = used,
3289                },
3290            }
3291        }
3292        characters[actuarianbottomrightlong] =
3293            characters[actuarianrightlong]
3294        characters[actuarianrightshort] = {
3295            width    = 2*linewidth,
3296            height   = baseheight,
3297            depth    = basedepth,
3298            unicode  = actuarian,
3299            callback = "devirtualize",
3300            commands = {
3301                upcommand[baseheight-4*linewidth],
3302                { "rule", 4*linewidth, linewidth },
3303            },
3304            parts = {
3305                {
3306                    advance  = basetotal,
3307                    ["end"]  = used,
3308                    glyph    = actuarianrightshort,
3309                    start    = 0,
3310                },
3311                {
3312                    advance  = basetotal,
3313                    ["end"]  = 0,
3314                    extender = 1,
3315                    glyph    = actuarianrightshort,
3316                    start    = used,
3317                },
3318            }
3319        }
3320        characters[actuarianbottomrightshort] = {
3321            width    = 2*linewidth,
3322            height   = linewidth,
3323            depth    = basedepth,
3324            unicode  = actuarian,
3325            callback = "devirtualize",
3326            commands = {
3327                downcommand[basedepth],
3328                { "rule", 4*linewidth, linewidth },
3329            },
3330            parts = {
3331                {
3332                    advance  = basetotal,
3333                    ["end"]  = used,
3334                    glyph    = actuarianrightshort,
3335                    start    = 0,
3336                },
3337                {
3338                    advance  = basetotal,
3339                    ["end"]  = 0,
3340                    extender = 1,
3341                    glyph    = actuarianrightshort,
3342                    start    = used,
3343                },
3344            }
3345        }
3346
3347        characters[actuarianleftlong] = {
3348            width    = 2*linewidth,
3349            height   = baseheight,
3350            depth    = basedepth,
3351            unicode  = actuarian, -- whatever
3352            callback = "devirtualize",
3353            commands = {
3354                rightcommand[linewidth],
3355                downcommand[basedepth],
3356                { "rule", basetotal, linewidth },
3357            },
3358            parts = {
3359                {
3360                    advance  = basetotal,
3361                    ["end"]  = used,
3362                    extender = 1,
3363                    glyph    = placehold,
3364                    start    = 0,
3365                },
3366                {
3367                    advance  = basetotal,
3368                    ["end"]  = 0,
3369                    glyph    = actuarianleftlong,
3370                    start    = used,
3371                },
3372            }
3373        }
3374        characters[actuarianbottomleftlong] =
3375            characters[actuarianleftlong]
3376        characters[actuarianleftshort] = {
3377            width    = 2*linewidth,
3378            height   = baseheight,
3379            depth    = basedepth,
3380            unicode  = actuarian, -- whatever
3381            callback = "devirtualize",
3382            commands = {
3383                rightcommand[linewidth],
3384                upcommand[baseheight-4*linewidth],
3385                { "rule", 4*linewidth, linewidth },
3386            },
3387            parts = {
3388                {
3389                    advance  = basetotal,
3390                    ["end"]  = used,
3391                    extender = 1,
3392                    glyph    = placehold,
3393                    start    = 0,
3394                },
3395                {
3396                    advance  = basetotal,
3397                    ["end"]  = 0,
3398                    glyph    = actuarianleftshort,
3399                    start    = used,
3400                },
3401            }
3402        }
3403        characters[actuarianbottomleftshort] = {
3404            width    = 2*linewidth,
3405         -- height   = baseheight,
3406            height   = linewidth,
3407            depth    = basedepth,
3408            unicode  = actuarian, -- whatever
3409            callback = "devirtualize",
3410            commands = {
3411                rightcommand[linewidth],
3412                downcommand[basedepth],
3413                { "rule", 4*linewidth, linewidth },
3414            },
3415            parts = {
3416                {
3417                    advance  = basetotal,
3418                    ["end"]  = used,
3419                    extender = 1,
3420                    glyph    = placehold,
3421                    start    = 0,
3422                },
3423                {
3424                    advance  = basetotal,
3425                    ["end"]  = 0,
3426                    glyph    = actuarianleftshort,
3427                    start    = used,
3428                },
3429            }
3430        }
3431        --
3432        if trace_tweaking then
3433            report_tweak("actuarian %U added",target,original,actuarian)
3434        end
3435    end
3436
3437end
3438
3439do
3440
3441    -- todo: make callback because we can delay it but then we need to stack
3442    -- callbacks
3443
3444    local nps = fonts.helpers.newprivateslot
3445
3446    local list = {
3447     -- { 0x0300, nps("delimited right grave"),   nps("delimited ghost grave")   },
3448        { 0x0308, nps("delimited right ddot"),    nps("delimited ghost ddot")    },
3449        { 0x0304, nps("delimited right bar"),     nps("delimited ghost bar")     },
3450     -- { 0x0301, nps("delimited right acute"),   nps("delimited ghost acute")   },
3451        { 0x0302, nps("delimited right hat"),     nps("delimited ghost hat")     },
3452        { 0x030C, nps("delimited right check"),   nps("delimited ghost check")   },
3453        { 0x0306, nps("delimited right breve"),   nps("delimited ghost breve")   },
3454        { 0x0307, nps("delimited right dot"),     nps("delimited ghost dot")     },
3455        { 0x030A, nps("delimited right ring"),    nps("delimited ghost ring")    },
3456        { 0x0303, nps("delimited right tilde"),   nps("delimited ghost tilde")   },
3457        { 0x20DB, nps("delimited right dddot"),   nps("delimited ghost dddot")   },
3458
3459        { 0x2032, nps("delimited right prime"),   nps("delimited ghost prime"),   false, 1 },
3460        { 0x2033, nps("delimited right dprime"),  nps("delimited ghost dprime"),  false, 1 },
3461        { 0x2034, nps("delimited right tprime"),  nps("delimited ghost tprime"),  false, 1 },
3462        { 0x2057, nps("delimited right qprime"),  nps("delimited ghost qprime"),  false, 1 },
3463        { 0x2035, nps("delimited right rprime"),  nps("delimited ghost rprime"),  false, 1 },
3464        { 0x2036, nps("delimited right drprime"), nps("delimited ghost rdprime"), false, 1 },
3465        { 0x2037, nps("delimited right dtprime"), nps("delimited ghost rtprime"), false, 1 },
3466
3467        { 0x231C, nps("delimited left upper corner"),  nps("delimited ghost upper corner") },
3468        { 0x231D, nps("delimited right upper corner"), nps("delimited ghost upper corner") },
3469        { 0x231E, nps("delimited left lower corner"),  nps("delimited ghost lower corner"), true  },
3470        { 0x231F, nps("delimited right lower corner"), nps("delimited ghost lower corner"), true  },
3471
3472        -- If needed we can have an installer:
3473
3474        { 0x2020, nps("delimited right dagger"),  nps("delimited ghost dagger")  },
3475        { 0x2021, nps("delimited right ddagger"), nps("delimited ghost ddagger") },
3476        { 0x2217, nps("delimited right ast"),     nps("delimited ghost ast")     },
3477        { 0x22C6, nps("delimited right star"),    nps("delimited ghost star")    },
3478
3479        { 0x2020, nps("delimited right dagger 1"),  nps("delimited ghost dagger 1"),  false, 1 },
3480        { 0x2021, nps("delimited right ddagger 1"), nps("delimited ghost ddagger 1"), false, 1 },
3481        { 0x2217, nps("delimited right ast 1"),     nps("delimited ghost ast 1"),     false, 1 },
3482        { 0x22C6, nps("delimited right star 1"),    nps("delimited ghost star 1"),    false, 1 },
3483    }
3484
3485    function mathtweaks.addfourier(target,original,parameters)
3486        local characters = target.characters
3487        for i=1,#list do
3488            local entry        = list[i]
3489            local basecode     = entry[1]
3490            local fouriercode  = entry[2]
3491            local movecode     = entry[3]
3492            local reverse      = entry[4]
3493            local size         = entry[5] or 0
3494            local basechar     = characters[basecode]
3495            local compactscale = 1
3496            -- bah ...
3497            if basechar and target.properties.compactmath and size > 0 then
3498                compactscale = target.parameters[size > 1 and "scriptscriptscale" or "scriptscale"] / 1000
3499                for i=1,size do
3500                    basecode = basechar.smaller or basecode
3501                    basechar = characters[basecode]
3502                end
3503            end
3504            if basechar then
3505                local scale   = (parameters.scale or 1) * compactscale
3506                local variant = parameters.variant
3507                if variant then
3508                    for i=1,variant do
3509                        local okay = basechar.next
3510                        if okay then
3511                            basecode = okay
3512                            basechar = characters[basecode]
3513                        else
3514                            break
3515                        end
3516                    end
3517                end
3518                local baseheight = scale * (basechar.height or 0)
3519                local basedepth  = scale * (basechar.depth  or 0)
3520                local basewidth  = scale * (basechar.width  or 0)
3521                local used       = baseheight/2
3522                local total      = baseheight + basedepth
3523                if reverse then
3524                    used = total / 2 -- basedepth / 2
3525                end
3526                characters[movecode] = {
3527                    width    = basewidth,
3528                    height   = used,
3529                    unicode  = 0xFFFD,
3530                    commands = {
3531                        downcommand[used],
3532                        { "rule", used, 0 },
3533                    },
3534                }
3535                local parts = {
3536                    {
3537                        advance  = used,
3538                        ["end"]  = used,
3539                        extender = 1,
3540                        glyph    = movecode, -- bottom
3541                        start    = used,
3542                    },
3543                    {
3544                        advance  = total,
3545                        ["end"]  = 0,
3546                        glyph    = fouriercode, -- top
3547                        start    = total,
3548                    },
3549                }
3550                if reverse then
3551                    parts[1], parts[2] = parts[2], parts[1]
3552                end
3553                characters[fouriercode] = {
3554                    width    = basewidth,
3555                    height   = baseheight, -- somehow no \primed antykwa (unless we double the height)
3556                    depth    = basedepth,
3557                    unicode  = basecode,
3558                    commands = {
3559                        scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale },
3560                    },
3561                    partsorientation = "vertical",
3562                    parts            = parts,
3563--                     keepvirtual      = basechar.commands and true or false,
3564        --             callback = "devirtualize",
3565                }
3566                if trace_tweaking then
3567                    report_tweak("fourier %U added using %U",target,original,basecode,fouriercode)
3568                end
3569            end
3570        end
3571    end
3572
3573end
3574
3575do
3576
3577    -- \im{\left\Uchar"007C \frac{1}{2} \right\Uchar"007C}
3578    -- \im{\left\Uchar"2016 \frac{1}{2} \right\Uchar"2016}
3579    -- \im{\left\Uchar"2980 \frac{1}{2} \right\Uchar"2980}
3580
3581    local single <const> = 0x007C
3582    local double <const> = 0x2016
3583    local triple <const> = 0x2980
3584
3585    local function variantlist(unicode,chardata,total,used)
3586        chardata.varianttemplate = 0x0028
3587     -- chardata.next = nil -- better use wipe variants but for testign we keep them
3588        chardata.parts = {
3589            {
3590                advance  = total,
3591                ["end"]  = used,
3592                glyph    = unicode,
3593                start    = 0,
3594            },
3595            {
3596                advance  = total,
3597             -- ["end"]  = 0,
3598             -- ["end"]  = used/5,   -- prevents small gap with inward curved endpoints
3599                ["end"]  = 4*used/5, -- is this better?
3600                extender = 1,
3601                glyph    = unicode,
3602                start    = used,
3603            },
3604        }
3605        chardata.partsorientation = "vertical"
3606    end
3607
3608    -- This is such an inconsistent mess that we cannot simply copy variants so we
3609    -- just accept some middle ground for the missing ones. See goodie files for when
3610    -- 'check' and/or 'variant' is used.
3611
3612    function mathtweaks.addbars(target,original,parameters)
3613        local characters = target.characters
3614        local template   = single
3615        local basechar   = characters[template]
3616        local tempchar   = basechar
3617        local check      = parameters.check
3618     -- local variant    = false -- alas ... too large: tonumber(parameters.variant)
3619     -- if variant then
3620     --     for i=1,variant do
3621     --         local n = tempchar.next
3622     --         if n then
3623     --             template = n
3624     --             tempchar = characters[template]
3625     --         else
3626     --             break
3627     --         end
3628     --     end
3629     -- end
3630        local width   = tempchar.width
3631        local height  = tempchar.height
3632        local depth   = tempchar.depth
3633        local advance = (parameters.advance or 1/10) * width
3634        local used    = 1.2*height -- large overlap because no smaller pieces
3635        local total   = height + depth
3636        --
3637        if not check or not basechar.parts then
3638            variantlist(template,basechar,total,used)
3639        end
3640        if not check or not characters[double] then
3641            basechar = {
3642                unicode  = double,
3643                width    = 2*width - 1*advance,
3644                height   = height,
3645                depth    = depth,
3646             -- callback = "devirtualize",
3647                commands = {
3648                    charcommand[template],
3649                    leftcommand[advance],
3650                    charcommand[template],
3651                },
3652            }
3653            characters[double] = basechar
3654        else
3655            basechar = characters[double]
3656        end
3657        if not check or not basechar.parts then
3658            variantlist(double,basechar,total,used)
3659        end
3660        if not check or not characters[triple] then
3661            basechar = {
3662                unicode  = triple,
3663                width    = 3*width - 2*advance,
3664                height   = height,
3665                depth    = depth,
3666             -- callback = "devirtualize",
3667                commands = {
3668                    charcommand[template],
3669                    leftcommand[advance],
3670                    charcommand[template],
3671                    leftcommand[advance],
3672                    charcommand[template],
3673                },
3674            }
3675            characters[triple] = basechar
3676        else
3677            basechar = characters[triple]
3678        end
3679        if not check or not basechar.parts then
3680            variantlist(triple,basechar,total,used)
3681        end
3682        --
3683        if trace_tweaking then
3684            report_tweak("single, double and triple bars added",target,original)
3685        end
3686    end
3687
3688end
3689
3690do
3691
3692    -- lucida: \im{\cdot \ldot \cdots \ldots}
3693
3694    local snormal <const> = 0x002E
3695    local sraised <const> = 0x22C5
3696
3697    local tnormal <const> = 0x2026
3698    local traised <const> = 0x22EF
3699
3700    function mathtweaks.fixellipses(target,original,parameters)
3701        local characters = target.characters
3702        local function fix(normal,raised)
3703            local normaldata = characters[normal]
3704            if normaldata then
3705                local raiseddata = copytable(normaldata)
3706                characters[raised] = raiseddata
3707                raiseddata.unicode = raised
3708                local height  = raiseddata.height
3709                local yoffset = (parameters.yoffset or 2) * height
3710                raiseddata.yoffset = yoffset
3711                raiseddata.height  = height + yoffset
3712                if trace_tweaking then
3713                    report_tweak("taking %U from %U",target,original,raised,normal)
3714                end
3715            end
3716        end
3717        fix(snormal,sraised)
3718        fix(tnormal,traised)
3719    end
3720
3721end
3722
3723do
3724
3725    -- For Ton, who needs the high minus and plus for calculator signs in Dutch
3726    -- school math books.
3727
3728    local list = {
3729        { 0x207A, 0x002B, true  },
3730        { 0x207B, 0x2212, true  },
3731        { 0x208A, 0x002B, false },
3732        { 0x208B, 0x2212, false },
3733    }
3734
3735    datasets.addscripts = list
3736
3737    local function add(target,original,characters,unicode,template,super,baseheight,scale)
3738        if not characters[unicode] then
3739            local origdata = characters[template]
3740            if origdata then
3741                local width  = scale * (origdata.width  or 0)
3742                local height = scale * (origdata.height or 0)
3743                local depth  = scale * (origdata.depth  or 0)
3744                local half   = - (height + depth) / 2
3745                local offset = super and baseheight/2 or -baseheight/4
3746                characters[unicode] = {
3747                    width    = width,
3748                    height   = height + offset,
3749                    depth    = depth  - offset,
3750                    unicode  = unicode,
3751                    commands = {
3752                        { "offset", 0, offset, template, scale, scale }
3753                    },
3754                }
3755                if trace_tweaking then
3756                    report_tweak("adding script %U scaled %0.3f",target,original,unicode,scale)
3757                end
3758                -- no need for smaller
3759            end
3760        end
3761    end
3762
3763    function mathtweaks.addscripts(target,original,parameters)
3764        local characters = target.characters
3765        local baseheight = target.mathparameters.AccentBaseHeight
3766        local scaledown  = parameters.scale or target.mathparameters.ScriptScriptPercentScaleDown / 100
3767        for i=1,#list do
3768            local entry = list[i]
3769            if entry then
3770                add(target,original,characters,entry[1],entry[2],entry[3],baseheight,scaledown)
3771            end
3772        end
3773    end
3774
3775end
3776
3777do
3778
3779    function mathtweaks.sortvariants(target,original,parameters)
3780        local list = parameters.list
3781        if list then
3782            local characters = target.characters
3783            local horizontal = parameters.orientation == "horizontal"
3784            for i=1,#list do
3785                local u = list[i]
3786                local c = characters[u]
3787                if c then
3788                    local t = { }
3789                    while true do
3790                        local n = c.next
3791                        if n then
3792                            c = characters[n]
3793                        end
3794                        if c and not c.parts then
3795                            if horizontal then
3796                                t[c.width or 0] = n
3797                            else
3798                                t[(c.height or 0) + (c.depth or 0)] = n
3799                            end
3800                        else
3801                            break
3802                        end
3803                    end
3804                    local c = characters[u]
3805                    for k, v in sortedhash(t) do
3806                        c.next = v
3807                        c = characters[v]
3808                    end
3809                end
3810            end
3811        end
3812    end
3813
3814end
3815
3816do
3817
3818    -- We started with the list that xits has in rtlm but most of them can be derived from
3819    -- the database, and others need to be added.
3820
3821    -- Checked while watching/listening to Dave Matthews Band: The Central Park Concert
3822    -- (with superb solos by Warren Haynes), a DVD I bought around when we started with the
3823    -- LUATEX advanture.
3824
3825    local mirrors = {
3826
3827        [0x0002F] = true, -- slashes
3828        [0x0005C] = true,
3829        [0x000F7] = true,
3830        [0x02044] = true,
3831        [0x02215] = true,
3832
3833        [0x02032] = true, -- primes
3834        [0x02033] = true,
3835        [0x02034] = true,
3836        [0x02057] = true,
3837        [0x02035] = true,
3838        [0x02036] = true,
3839        [0x02037] = true,
3840
3841        [0x0221A] = true, -- radicals
3842        [0x0221B] = true,
3843        [0x0221C] = true,
3844        [0x0221D] = true,
3845
3846        [0x0222B] = true, -- integrals
3847        [0x0222C] = true,
3848        [0x0222D] = true,
3849        [0x0222E] = true,
3850        [0x0222F] = true,
3851        [0x02230] = true,
3852        [0x02231] = true,
3853        [0x02232] = true,
3854        [0x02233] = true,
3855
3856        [0x02A0A] = true, -- seen in xits (to be checked)
3857        [0x02A0B] = true,
3858        [0x02A0C] = true,
3859        [0x02A0D] = true,
3860        [0x02A0E] = true,
3861
3862        [0x02140] = true,
3863        [0x02201] = true,
3864        [0x02202] = true,
3865        [0x02203] = true,
3866        [0x02204] = true,
3867        [0x02211] = true,
3868
3869        [0x02239] = true,
3870        [0x0225F] = true,
3871        [0x0228C] = true,
3872        [0x022A7] = true,
3873        [0x022AA] = true,
3874        [0x022AC] = true,
3875        [0x022AD] = true,
3876        [0x022AE] = true,
3877        [0x022AF] = true,
3878        [0x022F5] = true,
3879        [0x022F8] = true,
3880        [0x022F9] = true,
3881        [0x022FF] = true,
3882        [0x02320] = true,
3883        [0x02321] = true,
3884        [0x027C0] = true,
3885        [0x029DC] = true,
3886        [0x029F4] = true,
3887
3888        [0x02A0F] = true,
3889        [0x02A10] = true,
3890        [0x02A11] = true,
3891        [0x02A12] = true,
3892        [0x02A13] = true,
3893        [0x02A14] = true,
3894        [0x02A15] = true,
3895        [0x02A16] = true,
3896        [0x02A17] = true,
3897        [0x02A18] = true,
3898        [0x02A19] = true,
3899        [0x02A1A] = true,
3900        [0x02A1B] = true,
3901        [0x02A1C] = true,
3902        [0x02A20] = true,
3903
3904        [0x02A74] = true,
3905        [0x02AA3] = true,
3906        [0x02AE2] = true,
3907        [0x02AE6] = true,
3908        [0x1D715] = true,
3909    }
3910
3911    local new = fonts.helpers.newprivateslot
3912
3913    local function add(target,original,characters,unicode,what)
3914        local data = characters[unicode]
3915        if data then
3916            if not data.mirror then
3917                local slot      = new("mirror."..unicode)
3918                local mirror    = copytable(data)
3919                data.mirror     = slot
3920                mirror.rorrim   = unicode -- so we can check later
3921                mirror.commands = {
3922                    { "offset", data.width, 0, unicode, -1, 1 }
3923                }
3924                if trace_tweaking then
3925                    report_tweak("adding mirror %U (%s)",target,original,unicode,what)
3926                end
3927                characters[slot] = mirror
3928            elseif trace_tweaking then
3929                report_tweak("skipping mirror %U (%s)",target,original,unicode,what)
3930            end
3931            local parts = data.parts
3932            if parts then
3933                for i=1,#parts do
3934                    add(target,original,characters,parts[i],"hpart")
3935                end
3936            end
3937            local smaller = data.smaller
3938            if smaller then
3939                add(target,original,characters,"smaller")
3940            end
3941            local next = data.next
3942            if next then
3943                if next == unicode then
3944                    report_tweak("skipping cyclic %U (%s)",target,original,unicode,"next")
3945                else
3946                    add(target,original,characters,next,"next")
3947                end
3948            end
3949        end
3950    end
3951
3952    -- todo: also check the rtlm table if present
3953
3954    function mathtweaks.addmirrors(target,original,parameters)
3955        local characters = target.characters
3956     -- for unicode, detail in sortedhash(characters) do
3957        for unicode, detail in next, characters do
3958            local data = chardata[unicode]
3959            if data and data.mirror then
3960                add(target,original,characters,unicode,"mirror")
3961            end
3962        end
3963        for unicode, detail in sortedhash(mirrors) do
3964            if characters[unicode] then
3965                add(target,original,characters,unicode,"character")
3966            elseif trace_tweaking then
3967                report_tweak("ignoring mirror %U (%s)",target,original,unicode,what)
3968            end
3969        end
3970    end
3971
3972end
3973
3974do
3975
3976    local reported = { }
3977
3978    function mathtweaks.version(target,original,parameters)
3979        local metadata = original.shared.rawdata.metadata
3980        if metadata then
3981            local version = string.strip(metadata.version or "") -- some have trailing spaces
3982            if version then
3983                local expected = parameters.expected
3984                local fontname = metadata.fontname or false
3985                local message  = parameters.message
3986             -- version = tonumber(string.match(version,"%d+.%d+"))
3987                if version ~= expected and not reported[fontname] then
3988                    report_tweak("version %a found, version %a expected",target,original,version,expected)
3989                elseif trace_tweaking then
3990                    report_tweak("version %a found",target,original,version)
3991                end
3992                if message and message ~= "" and not reported[fontname] then
3993                    report_tweak()
3994                    report_tweak("%s",target,original,message)
3995                    report_tweak()
3996                end
3997                reported[fontname] = true
3998                target.tweakversion = version
3999            end
4000        end
4001    end
4002
4003end
4004
4005do
4006
4007    function mathtweaks.parameters(target,original,parameters)
4008        local newparameters = parameters.list
4009        local oldparameters = target.mathparameters
4010        if newparameters and oldparameters then
4011            newparameters = copytable(newparameters)
4012            scaleparameters(newparameters,target.parameters)
4013            for name, newvalue in next, newparameters do
4014                oldparameters[name] = newvalue
4015            end
4016        end
4017    end
4018
4019    function mathtweaks.bigslots(target,original,parameters)
4020        local list = parameters.list
4021        if list then
4022            target.bigslots = list
4023        end
4024    end
4025
4026end
4027
4028-- do
4029--
4030--     function mathtweaks.diagnose(target,original,parameters)
4031--         local characters = target.characters
4032--         for k, v in sortedhash(characters) do
4033--             local italic = v.italic
4034--             if italic then
4035--                 report_tweak("italics: %C %p",target,original,k,italic)
4036--             end
4037--         end
4038--     end
4039--
4040-- end
4041
4042do
4043
4044    function mathtweaks.setoptions(target,original,parameters)
4045        local setlist   = parameters.set or parameters.list
4046        local resetlist = parameters.reset
4047        if setlist or resetlist then
4048            local properties = target.properties
4049            local codes      = tex.mathcontrolcodes
4050            local oldcontrol = texget("mathfontcontrol")
4051            local newcontrol = oldcontrol
4052            -- todo: reset
4053            if resetlist then
4054                for i=1,#resetlist do
4055                    local v = tonumber(codes[resetlist[i]])
4056                    if v then
4057                        newcontrol = newcontrol & (not v)
4058                    end
4059                end
4060            end
4061            if setlist then
4062                for i=1,#setlist do
4063                    local v = tonumber(codes[setlist[i]])
4064                    if v then
4065                        newcontrol = newcontrol | v
4066                    end
4067                end
4068            end
4069            newcontrol = newcontrol | codes.usefontcontrol
4070            properties.mathcontrol = newcontrol
4071            target.mathcontrol = newcontrol
4072            if trace_tweaking then
4073                report_tweak("forcing math font options 0x%08X instead of 0x%08X",target,original,newcontrol,oldcontrol)
4074            end
4075        end
4076    end
4077
4078end
4079
4080do
4081
4082    function mathtweaks.setovershoots(target,original,parameters)
4083        local list = parameters.list
4084        if list then
4085            local characters = target.characters
4086            local emwidth    = target.parameters.quad
4087            local done       = false
4088            for i=1,#list do
4089                local entry  = list[i]
4090                local target = entry.target
4091                local top    = entry.topovershoot
4092                local quad   = entry.quad
4093                if target and top then
4094                    local range = blocks[target]
4095                    if range then
4096                        if quad then
4097                            quad = emwidth
4098                        end
4099                        for r = range.first, range.last do
4100                            local unicode = mathgaps[r] or r
4101                            local data = characters[unicode]
4102                            if data then
4103                                data.topovershoot = top * (quad or data.width or 0)
4104                                done = registerdone(done,r)
4105                            end
4106                        end
4107                    end
4108                end
4109            end
4110            feedback_tweak("setovershoots",target,original,done)
4111        end
4112    end
4113
4114    -- there is no real need for this but let's play nice with memory anyway
4115
4116    local efindex = 0
4117    local effects = setmetatableindex (function (t,k)
4118        efindex = efindex + 1
4119        local v = "tweakreplacealphabets" .. efindex
4120        local e = fonts.specifiers.presetcontext(v,"",k)
4121     -- print(k,v,e)
4122        t[k] = v
4123        return v
4124    end)
4125
4126    function mathtweaks.replacealphabets(target,original,parameters)
4127        local list = parameters.list
4128        if list then
4129            local features        = target.specification.features.normal
4130            local definedfont     = fonts.definers.internal
4131            local copiedglyph     = fonts.handlers.vf.math.copy_glyph
4132            -- does a deep copy, including parts and so
4133            local getsubstitution = fonts.handlers.otf.getsubstitution
4134            local fontdata        = fonts.hashes.identifiers
4135            --
4136            local fonts      = target.fonts
4137            local size       = target.size
4138            local characters = target.characters
4139            if not fonts then
4140                fonts = { }
4141                target.fonts = fonts
4142            end
4143            if #fonts == 0 then
4144                fonts[1] = { id = 0, size = size } -- self, will be resolved later
4145            end
4146            for i=1,#list do
4147                local entry      = list[i]
4148                local filename   = entry.filename or parameters.filename
4149                local feature    = entry.feature
4150                local thesource  = entry.source
4151                local thetarget  = entry.target or thesource
4152                local theunicode = entry.unicode
4153                local unicodes   = entry.unicodes
4154                local keep       = (entry.keep == true) or (parameters.keep == true)
4155                if (thesource and thetarget) or unicodes then
4156                    local function getrange(t)
4157                        return (type(t) == "table" and t) or (type(t) == "string" and blocks[t])  -- .gaps
4158                    end
4159                    local sourcerange  = thesource   and getrange(thesource)
4160                    local targetrange  = thetarget   and getrange(thetarget)
4161                    local unicoderange = theunicode  and getrange(theunicode)
4162                    local firsttarget  = targetrange and targetrange.first
4163                    local firstsource  = sourcerange and sourcerange.first
4164                    local lastsource   = sourcerange and sourcerange.last or firstsource
4165                    if (firstsource and firsttarget) or unicodes then
4166                        local offset = unicodes and 0 or (firsttarget - firstsource)
4167                        if filename then
4168                            local rscale = entry.rscale or 1 -- todo
4169                            size = size * rscale -- maybe use scale in vf command
4170                            -- load font, todo: set language and script, the effect hack is ugly
4171                            local fullname = filename
4172                            local effect = features.effect
4173                            if effect then
4174                                fullname = fullname .. "*" .. effects["effect={"..effect.."}"]
4175                            end
4176                            local id = definedfont {
4177                                name = fullname,
4178                                size = size,
4179                            }
4180                            if id <= 0 then
4181                                report_math("font %a doesn't exist",fullname)
4182                            else
4183                                local chars  = fontchars[id]
4184                                local dropin = fontdata[id]
4185                                local index  = false
4186                                for i=1,#fonts do
4187                                    local f = fonts[i]
4188                                    if f.id == id and f.size == size then
4189                                        index = i
4190                                        break
4191                                    end
4192                                end
4193                                if not index then
4194                                    index = #fonts + 1
4195                                    fonts[index] = { id = id, size = size }
4196                                end
4197                                -- copy characters
4198                                local function use(sourceunicode,targetunicode)
4199                                    if keep and characters[targetunicode] then
4200                                        -- okay
4201                                    else
4202                                        if feature then
4203                                            sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode
4204                                        end
4205                                        characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index)
4206                                    end
4207                                end
4208                                if unicodes then
4209                                    for i=1,#unicodes do
4210                                        local unicode = unicodes[i]
4211                                        if chars[unicode] then
4212                                            use(unicode,unicode)
4213                                        end
4214                                    end
4215                                else
4216                                    for s=firstsource,lastsource do
4217                                        local t = s + offset
4218                                        local sourceunicode = mathgaps[s] or s
4219                                        if chars[sourceunicode] then
4220                                            use(sourceunicode,mathgaps[t] or t)
4221                                        end
4222                                    end
4223                                end
4224                                --
4225                                local inherit = entry.inherit
4226                                if inherit then
4227                                    local mathparameters = target.mathparameters
4228                                    local dropparameters = fontdata[id].mathparameters
4229                                    if dropparameters then
4230                                        for name in sortedhash(inherit) do
4231                                            local value = dropparameters[name]
4232                                            if value then
4233                                                mathparameters[name] = value
4234                                            end
4235                                        end
4236                                    end
4237                                end
4238                            end
4239                        elseif feature then
4240                            local function use(sourceunicode,targetunicode)
4241                                local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt")
4242                                local data    = characters[variant]
4243                                if data then
4244                                    characters[targetunicode] = copytable(data)
4245                                end
4246                            end
4247                            if unicodes then
4248                                for i=1,#unicodes do
4249                                    local unicode = unicodes[i]
4250                                    use(unicode,unicode)
4251                                end
4252                            else
4253                                for s=firstsource,lastsource do
4254                                    local t = s + offset
4255                                    use(mathgaps[s] or s,mathgaps[t] or t)
4256                                end
4257                            end
4258                        else
4259                            -- this is a hack for stix providing alphabets without proper unicodes
4260                            if unicodes then
4261                                -- not supported
4262                            else
4263                                local firstcode    = unicoderange and unicoderange.first
4264                                local descriptions = original.descriptions
4265                                for s=firstsource,lastsource do
4266                                    local t = s + offset
4267                                    local sourceunicode = mathgaps[s] or s
4268                                    local targetunicode = mathgaps[t] or t
4269                                    if sourceunicode ~= targetunicode then
4270                                        local data = characters[sourceunicode]
4271                                        if data then
4272                                            if firstcode then
4273                                                local d = descriptions[sourceunicode]
4274                                                if d then
4275                                                    d.unicode = firstcode -- wins
4276                                                end
4277                                                data.unicode = firstcode
4278                                                firstcode = firstcode + 1
4279                                            end
4280                                            characters[targetunicode] = copytable(data)
4281                                        end
4282                                    end
4283                                end
4284                            end
4285                        end
4286                    end
4287                end
4288            end
4289        end
4290    end
4291
4292    function mathtweaks.fallbacks(target,original,parameters)
4293        local fallbacks = target.specification.fallbacks
4294        if fallbacks then
4295            local definitions = fonts.collections.definitions[fallbacks]
4296            if definitions then
4297                local list = { }
4298                for i=1,#definitions do
4299                    local definition = definitions[i]
4300                 -- local check  = definition.check
4301                 -- local force  = definition.force
4302                    local first  = definition.start
4303                    local last   = definition.stop
4304                    local offset = definition.offset or first
4305                    list[#list+1] = {
4306                        filename = definition.font,
4307                        rscale   = definition.rscale or 1,
4308                        source   = { first = first,  last = last },
4309                        target   = { first = offset, last = offset + (last - first) },
4310                    }
4311                end
4312                mathtweaks.replacealphabets(target,original,{
4313                    tweak = "replacealphabets",
4314                    list  = list,
4315                } )
4316            end
4317        end
4318    end
4319
4320end
4321
4322local apply_tweaks   = true  directives.register("math.applytweaks", function(v) apply_tweaks = v end)
4323local applied_tweaks = 0
4324
4325local function tweaklist(target,original,tweaks)
4326    if type(tweaks) == "table" then
4327        for i=1,#tweaks do
4328            local tweak  = tweaks[i]
4329            if type(tweak) == "table" then
4330                local action = mathtweaks[tweak.tweak or ""]
4331                if action then
4332                    local feature  = tweak.feature
4333                    local features = target.specification.features.normal
4334                    if feature == nil or features[feature] then
4335                        local version = tweak.version
4336                        if version and version ~= target.tweakversion then
4337                            report_math("skipping tweak %a version %a",tweak.tweak,version)
4338                        elseif original then
4339                            action(target,original,tweak)
4340                        else
4341                            action(target,tweak)
4342                        end
4343                    end
4344                end
4345            end
4346        end
4347    end
4348end
4349
4350function mathtweaks.tweaks(target,original,parameters)
4351    tweaklist(target,original,parameters.list)
4352end
4353
4354local function applytweaks(when,target,original)
4355    if apply_tweaks then
4356        local goodies = original.goodies
4357        if goodies then
4358            local tweaked = target.tweaked or { }
4359            if tweaked[when] then
4360                if trace_defining then
4361                    report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done")
4362                end
4363            else
4364                for i=1,#goodies do
4365                    local goodie = goodies[i]
4366                    local mathematics = goodie.mathematics
4367                    local tweaks = mathematics and mathematics.tweaks
4368                    if type(tweaks) == "table" then
4369                        statistics.starttiming(mathtweaks)
4370                        applied_tweaks = applied_tweaks + 1
4371                        tweaks = tweaks[when]
4372                        if trace_defining then
4373                            report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"okay")
4374                        end
4375                        tweaklist(target,original,tweaks)
4376                    end
4377                    statistics.stoptiming(mathtweaks)
4378                end
4379                tweaked[when] = true
4380                target.tweaked = tweaked
4381            end
4382        end
4383    else
4384        report_math("not tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when)
4385    end
4386end
4387
4388local function tweakable(target)
4389    local mathparameters = target.mathparameters
4390--     local features       = target.specification.features
4391--     local mathscript     = features and features.normal and features.normal.script == "math"
4392--     return mathparameters and mathscript -- and target,properties.hasmath
4393    return mathparameters
4394end
4395
4396function mathematics.tweakbeforecopyingfont(target,original)
4397    if use_math_goodies and tweakable(target) then
4398        applytweaks("beforecopying",target,original)
4399    end
4400end
4401
4402function mathematics.tweakaftercopyingfont(target,original)
4403    if use_math_goodies and tweakable(target) then
4404        applytweaks("aftercopying",target,original)
4405        target.properties.hasitalics = false
4406    end
4407end
4408
4409statistics.register("math tweaking time",function()
4410    if applied_tweaks > 0 then
4411        return string.format("%s seconds, %s math goodie tables", statistics.elapsedtime(mathtweaks),applied_tweaks)
4412    end
4413end)
4414
4415do
4416
4417    local defaults = {
4418        {
4419            source = "uppercasescript",
4420            target = "uppercasecalligraphic",
4421        },
4422        {
4423            source = "lowercasescript",
4424            target = "lowercasecalligraphic",
4425        },
4426        {
4427            source = "uppercaseboldscript",
4428            target = "uppercaseboldcalligraphic",
4429        },
4430        {
4431            source = "lowercaseboldscript",
4432            target = "lowercaseboldcalligraphic",
4433        },
4434    }
4435
4436    local reported = table.setmetatableindex("table")
4437
4438    function mathematics.checkaftercopyingfont(target,original)
4439        if tweakable(target) then
4440            local chardata   = characters.data
4441            local characters = target.characters
4442            --
4443            for i=1,#defaults do
4444                -- we assume no ssty here yet .. todo
4445                local default = defaults[i]
4446                local block   = blocks[default.target]
4447                local first   = block.first
4448                local last    = block.last
4449                if not characters[mathgaps[first] or last] then
4450                    mathtweaks.replacealphabets(target,original,{
4451                        tweak   = "replacealphabets",
4452                        list    = { default }
4453                    })
4454                end
4455            end
4456            --
4457            local addvariant = mathematics.addvariant
4458            local function register(old,new)
4459                for i, cold in next, old do
4460                    local cnew = new[i]
4461                    addvariant(target,cold,cold,0xFE00)
4462                    addvariant(target,cnew,cnew,0xFE01)
4463                    addvariant(target,cnew,cold,0xFE00)
4464                    addvariant(target,cold,cnew,0xFE01)
4465                end
4466            end
4467            local sr = mathematics.alphabets.sr.tf
4468            local ca = mathematics.alphabets.ca.tf
4469            register(sr.ucletters,ca.ucletters)
4470            register(sr.lcletters,ca.lcletters)
4471            --
4472            if checkitalics then
4473                local italics  = 0
4474                local metadata = original.shared.rawdata.metadata
4475                local fontname = metadata and metadata.fontname or false
4476             -- for k, v in sortedhash(characters) do
4477                for k, v in next, characters do
4478                    local italic = v.italic
4479                    if italic then
4480                        local unicode = v.unicode
4481                        if unicode and not reported[fontname][unicode] then -- there can be variants
4482                            local data        = chardata[unicode]
4483                            local description = data.description or ""
4484                            local category    = data.category or "--"
4485                            report_tweak("italics: %C %p %s %s",target,original,k,italic,category,description)
4486                            reported[fontname][unicode] = true
4487                        end
4488                        italics = italics + 1
4489                    end
4490                end
4491                if italics > 0 then
4492                    report_tweak("still has %i italics",target,original,italics)
4493                    goto NEXTSTEP
4494                end
4495            end
4496          ::NEXTSTEP::
4497            -- more to come
4498        end
4499    end
4500
4501end
4502
4503function mathematics.beforepassingfonttotex(target)
4504    if tweakable(target) then
4505        applytweaks("beforepassing",target,target)
4506    end
4507end
4508
4509sequencers.appendaction("mathparameters","system","mathematics.overloadparameters")
4510sequencers.appendaction("mathparameters","system","mathematics.scaleparameters")
4511----------.appendaction("mathparameters","system","mathematics.checkaccentbaseheight")  -- should go in lfg instead
4512----------.appendaction("mathparameters","system","mathematics.checkprivateparameters") -- after scaling !
4513
4514sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont")
4515sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont")
4516sequencers.appendaction("aftercopyingcharacters", "system","mathematics.checkaftercopyingfont")
4517sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex")
4518
4519-- no, it's a feature now (see good-mth):
4520--
4521-- sequencers.appendaction("aftercopyingcharacters", "system","mathematics.overloaddimensions")
4522
4523-- we use numbers at the tex end (otherwise we could stick to chars)
4524
4525local e_left       = extensibles.left
4526local e_right      = extensibles.right
4527local e_horizontal = extensibles.horizontal
4528local e_mixed      = extensibles.mixed
4529local e_unknown    = extensibles.unknown
4530
4531local unknown      = { e_unknown, false, false }
4532
4533-- top curly bracket: 23DE
4534
4535local function extensiblecode(font,unicode)
4536    local characters = fontcharacters[font]
4537    local character = characters[unicode]
4538    if not character then
4539        return unknown
4540    end
4541    local first = character.next
4542    local code = unicode
4543    local next = first
4544    while next do
4545        code = next
4546        character = characters[next]
4547        next = character.next
4548    end
4549    local char = chardata[unicode]
4550    if not char then
4551        return unknown
4552    end
4553    if character.parts then
4554        local m = char.mathextensible
4555        local e = m and extensibles[m]
4556        return e and { e, code, character } or unknown
4557    elseif first then
4558        -- assume accent (they seldom stretch .. sizes)
4559        local m = char.mathextensible or char.mathstretch
4560        local e = m and extensibles[m]
4561        return e and { e, code, character } or unknown
4562    else
4563        return unknown
4564    end
4565end
4566
4567setmetatableindex(extensibles,function(extensibles,font)
4568    local codes = { }
4569    setmetatableindex(codes, function(codes,unicode)
4570        local status = extensiblecode(font,unicode)
4571        codes[unicode] = status
4572        return status
4573    end)
4574    extensibles[font] = codes
4575    return codes
4576end)
4577
4578local function extensiblecode(family,unicode)
4579    return extensibles[getfontoffamily(family or 0)][unicode][1]
4580end
4581
4582-- left       : [head] ...
4583-- right      : ... [head]
4584-- horizontal : [head] ... [head]
4585--
4586-- abs(right["start"] - right["end"]) | right.advance | characters[right.glyph].width
4587
4588local function horizontalcode(family,unicode)
4589    local font    = getfontoffamily(family or 0)
4590    local data    = extensibles[font][unicode]
4591    local kind    = data[1]
4592    local loffset = 0
4593    local roffset = 0
4594    if kind == e_left then
4595        local charlist = data[3].parts
4596        if charlist then
4597            local left = charlist[1]
4598            loffset = abs((left["start"] or 0) - (left["end"] or 0))
4599        end
4600    elseif kind == e_right then
4601        local charlist = data[3].parts
4602        if charlist then
4603            local right = charlist[#charlist]
4604            roffset = abs((right["start"] or 0) - (right["end"] or 0))
4605        end
4606     elseif kind == e_horizontal then
4607        local charlist = data[3].parts
4608        if charlist then
4609            local left  = charlist[1]
4610            local right = charlist[#charlist]
4611            loffset = abs((left ["start"] or 0) - (left ["end"] or 0))
4612            roffset = abs((right["start"] or 0) - (right["end"] or 0))
4613        end
4614    end
4615    return kind, loffset, roffset
4616end
4617
4618mathematics.extensiblecode = extensiblecode
4619mathematics.horizontalcode = horizontalcode
4620
4621interfaces.implement { -- can be public with two times "integerargument"
4622    name      = "extensiblecode",
4623    arguments = "2 integers",
4624    actions   = { extensiblecode, context }
4625}
4626
4627interfaces.implement { -- can be public with two times "integerargument"
4628    name      = "horizontalcode",
4629    arguments = "2 integers",
4630    actions   = function(family,unicode)
4631        local kind, loffset, roffset = horizontalcode(family,unicode)
4632        texsetdimen(d_scratchleftoffset, loffset)
4633        texsetdimen(d_scratchrightoffset,roffset)
4634        context(kind)
4635    end
4636}
4637
4638function mathematics.variantcode(unicode,variant)
4639    local data = fontcharacters[getfontoffamily(texget("fam"))]
4640    local char = data and data[unicode]
4641    if char then
4642        for i=1,variant do
4643            local next = char.next
4644            if next then
4645                unicode = next
4646                char = data[next]
4647            else
4648                break
4649            end
4650        end
4651    end
4652    return unicode
4653end
4654
4655function mathematics.variantcount(unicode)
4656    local data  = fontcharacters[getfontoffamily(texget("fam"))]
4657    local char  = data and data[unicode]
4658    local count = 0
4659    if char then
4660        while true do
4661            local next = char.next
4662            if next then
4663                count = count + 1
4664                char = data[next]
4665            else
4666                break
4667            end
4668        end
4669    end
4670    return count
4671end
4672
4673interfaces.implement {
4674    name      = "mathvariantcode",
4675    public    = true,
4676    arguments = "2 integers",
4677    actions   = { mathematics.variantcode, context },
4678}
4679
4680interfaces.implement {
4681    name      = "mathvariantcount",
4682    public    = true,
4683    arguments = "integer",
4684    actions   = { mathematics.variantcount, context },
4685}
4686