math-fbk.lmt /size: 29 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['math-fbk'] = {
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-- This will partly be redone and go via definitions in goodies where we can share
10-- some across fonts. That makes most sense for those patches that might need some
11-- finetuning depending on the font.
12
13local next, type = next, type
14
15local trace_fallbacks   = false  trackers.register("math.fallbacks", function(v) trace_fallbacks = v end)
16
17local report_fallbacks  = logs.reporter("math","fallbacks")
18
19local formatters        = string.formatters
20local fastcopy          = table.fastcopy
21local byte              = string.byte
22local sortedhash        = table.sortedhash
23local setmetatableindex = table.setmetatableindex
24
25local fallbacks         = { }
26mathematics.fallbacks   = fallbacks
27
28----- helpers           = fonts.helpers
29----- charcommand       = helpers.commands.char
30----- slotcommand       = helpers.commands.slot
31----- leftcommand       = helpers.commands.left
32----- rightcommand      = helpers.commands.right
33----- upcommand         = helpers.commands.up
34----- downcommand       = helpers.commands.down
35----- popcommand        = helpers.commands.pop
36----- pushcommand       = helpers.commands.push
37
38local virtualcharacters = { }
39local virtualforced     = { }
40
41local hashes            = fonts.hashes
42local identifiers       = hashes.identifiers
43local lastmathids       = hashes.lastmathids
44
45-- we need a trick (todo): if we define scriptscript, script and text in
46-- that order we could use their id's .. i.e. we could always add a font
47-- table with those id's .. in fact, we could also add a whole lot more
48-- as it doesn't hurt
49
50function fallbacks.apply(target,original)
51    local mathparameters = target.mathparameters
52    if not mathparameters or not next(mathparameters) then
53        return
54    end
55    -- we also have forcedsize ... at this moment we already passed through
56    -- constructors.scale so we have this set
57    local parameters = target.parameters
58    local properties = target.properties
59    local mathsize   = parameters.mathsize
60    if mathsize < 1 or mathsize > 3 then
61        return
62    end
63    local characters  = target.characters
64    local size        = parameters.size
65    local usedfonts   = target.fonts
66    local compactmath = properties.compactmath
67    if not usedfonts then
68        usedfonts    = { { id = 0 } } -- we need at least one entry (automatically done anyway)
69        target.fonts = usedfonts
70    end
71    -- not used
72    local textid, scriptid, scriptscriptid
73    local textindex, scriptindex, scriptscriptindex
74    local textdata, scriptdata, scriptscriptdata
75    if mathsize == 3 then
76        -- scriptscriptsize
77        textid         = 0
78        scriptid       = 0
79        scriptscriptid = 0
80    elseif mathsize == 2 then
81        -- scriptsize
82        textid         = 0
83        scriptid       = lastmathids[3] or 0
84        scriptscriptid = lastmathids[3] or 0
85    else
86        -- textsize
87        textid         = 0
88        scriptid       = lastmathids[2] or 0
89        scriptscriptid = lastmathids[3] or 0
90    end
91    if textid and textid ~= 0 then
92        textindex = #usedfonts + 1
93        textdata  = target
94        usedfonts[textindex] = { id = textid }
95    else
96        textdata = target
97    end
98    if compactmath then
99        scriptid       = textid
100        scriptscriptid = textid
101    end
102    if scriptid and scriptid ~= 0 then
103        scriptindex = #usedfonts  + 1
104        scriptdata  = identifiers[scriptid]
105        usedfonts[scriptindex] = { id = scriptid }
106    else
107        scriptindex = textindex
108        scriptdata  = textdata
109    end
110    if scriptscriptid and scriptscriptid ~= 0 then
111        scriptscriptindex = #usedfonts  + 1
112        scriptscriptdata  = identifiers[scriptscriptid]
113        usedfonts[scriptscriptindex] = { id = scriptscriptid }
114    else
115        scriptscriptindex = scriptindex
116        scriptscriptdata  = scriptdata
117    end
118    -- report_fallbacks("used textid: %S, used script id: %S, used scriptscript id: %S",textid,scriptid,scriptscriptid)
119    local data = {
120        textdata          = textdata,
121        scriptdata        = scriptdata,
122        scriptscriptdata  = scriptscriptdata,
123        textindex         = textindex,
124        scriptindex       = scriptindex,
125        scriptscriptindex = scriptscriptindex,
126        textid            = textid,
127        scriptid          = scriptid,
128        scriptscriptid    = scriptscriptid,
129        characters        = characters,
130        unicode           = k,
131        target            = target,
132        original          = original,
133        size              = size,
134        mathsize          = mathsize,
135    }
136    target.mathrelation = data
137    --
138    local fullname = trace_fallbacks and target.properties.fullname
139    --
140    for k, v in sortedhash(virtualcharacters) do
141        if not characters[k] or virtualforced[k] then
142            local tv = type(v)
143            local cd = nil
144            if tv == "table" then
145                cd = v
146            elseif tv == "number" then
147                cd = characters[v]
148            elseif tv == "function" then
149                cd = v(data) -- ,k
150            end
151            if cd then
152                characters[k] = cd
153            else
154                -- something else
155            end
156            if trace_fallbacks and characters[k] then
157                report_fallbacks("extending math font %a with %U",fullname,k)
158            end
159        end
160    end
161    data.unicode = nil
162end
163
164utilities.sequencers.appendaction("aftercopyingcharacters","system","mathematics.fallbacks.apply")
165
166function fallbacks.install(unicode,value)
167    virtualcharacters[unicode] = value
168end
169
170-- a few examples:
171
172-- local addextra = mathematics.extras.add
173
174-- addextra(0xFE350) -- MATHEMATICAL DOUBLE ARROW LEFT END
175-- addextra(0xFE351) -- MATHEMATICAL DOUBLE ARROW MIDDLE PART
176-- addextra(0xFE352) -- MATHEMATICAL DOUBLE ARROW RIGHT END
177--
178-- local leftarrow  = charcommand[0x2190]
179-- local relbar     = charcommand[0x2212]
180-- local rightarrow = charcommand[0x2192]
181--
182-- virtualcharacters[0xFE350] = function(data)
183--  -- return combined(data,0x2190,0x2212) -- leftarrow relbar
184--     local charone = data.characters[0x2190]
185--     local chartwo = data.characters[0x2212]
186--     if charone and chartwo then
187--         local size = data.size/2
188--         return {
189--             width    = chartwo.width,
190--             height   = size,
191--             depth    = size,
192--             commands = {
193--                 pushcommand,
194--                 downcommand[size/2],
195--                 leftarrow,
196--                 popcommand,
197--                 upcommand[size/2],
198--                 relbar,
199--             }
200--         }
201--     end
202-- end
203--
204-- virtualcharacters[0xFE351] = function(data)
205--  -- return combined(data,0x2212,0x2212) -- relbar, relbar  (isn't that just equal)
206--     local char = data.characters[0x2212]
207--     if char then
208--         local size = data.size/2
209--         return {
210--             width    = char.width,
211--             height   = size,
212--             depth    = size,
213--             commands = {
214--                 pushcommand,
215--                 downcommand[size/2],
216--                 relbar,
217--                 popcommand,
218--                 upcommand[size/2],
219--                 relbar,
220--             }
221--         }
222--     end
223-- end
224--
225-- virtualcharacters[0xFE352] = function(data)
226--  -- return combined(data,0x2192,0x2212) -- rightarrow relbar
227--     local charone = data.characters[0x2192]
228--     local chartwo = data.characters[0x2212]
229--     if charone and chartwo then
230--         local size = data.size/2
231--         return {
232--             width    = chartwo.width,
233--             height   = size,
234--             depth    = size,
235--             commands = {
236--                 pushcommand,
237--                 downcommand[size/2],
238--                 relbar,
239--                 popcommand,
240--                 rightcommand[chartwo.width - charone.width],
241--                 upcommand[size/2],
242--                 rightarrow,
243--             }
244--         }
245--     end
246-- end
247
248---------------------------------------------------------------------------------
249-- these are moved to math-act.lmt (keep this code)
250---------------------------------------------------------------------------------
251
252-- local function accent_to_extensible(target,newchr,original,oldchr,height,depth,swap,offset,unicode)
253--     local characters = target.characters
254--     local olddata = characters[oldchr]
255--     -- brrr ... pagella has only next
256--     if olddata and not olddata.commands then -- not: and olddata.width > 0
257--         local addprivate = fonts.helpers.addprivate
258--         if swap then
259--             swap   = characters[swap]
260--             height = swap.depth or 0
261--             depth  = 0
262--         else
263--             height = height or 0
264--             depth  = depth  or 0
265--         end
266--         local oldheight  = olddata.height or 0
267--         local correction = swap and
268--             downcommand[oldheight - height]
269--          or downcommand[oldheight + (offset or 0)]
270--         local newdata = {
271--             commands = { correction, charcommand[oldchr] },
272--             width    = olddata.width,
273--             height   = height,
274--             depth    = depth,
275--             unicode  = unicode,
276--         }
277--         local glyphdata = newdata
278--         local nextglyph = olddata.next
279--         while nextglyph do
280--             local oldnextdata = characters[nextglyph]
281--             if oldnextdata then
282--                 local newnextdata = {
283--                     commands = { correction, charcommand[nextglyph] },
284--                     width    = oldnextdata.width,
285--                     height   = height,
286--                     depth    = depth,
287--                 }
288--                 local newnextglyph = addprivate(target,formatters["M-N-%H"](nextglyph),newnextdata)
289--                 newdata.next = newnextglyph
290--                 local nextnextglyph = oldnextdata.next
291--                 if nextnextglyph == nextglyph then
292--                     break
293--                 else
294--                     olddata   = oldnextdata
295--                     newdata   = newnextdata
296--                     nextglyph = nextnextglyph
297--                 end
298--             else
299--                 report_fallbacks("error in fallback: no valid next, slot %X",nextglyph)
300--                 break
301--             end
302--         end
303--         local hv = olddata.hparts
304--         if hv then
305--             hv = fastcopy(hv)
306--             newdata.hparts = hv
307--             for i=1,#hv do
308--                 local hvi = hv[i]
309--                 local oldglyph = hvi.glyph
310--                 local olddata = characters[oldglyph]
311--                 if olddata then
312--                     local newdata = {
313--                         commands = { correction, charcommand[oldglyph] },
314--                         width    = olddata.width,
315--                         height   = height,
316--                         depth    = depth,
317--                     }
318--                     hvi.glyph = addprivate(target,formatters["M-H-%H"](oldglyph),newdata)
319--                 else
320--                     report_fallbacks("error in fallback: no valid hparts, slot %X, index %i",oldglyph,i)
321--                 end
322--             end
323--         end
324--         return glyphdata, true
325--     else
326--         return olddata, false
327--     end
328-- end
329--
330-- virtualcharacters[0x203E] = function(data) -- overbar
331--     local target = data.target
332--     local height = 0
333--     local depth  = 0
334--  -- local mathparameters = target.mathparameters
335--  -- if mathparameters then
336--  --     height = mathparameters.OverbarVerticalGap
337--  --     depth  = mathparameters.UnderbarVerticalGap
338--  -- else
339--         height = target.parameters.xheight/4
340--         depth  = height
341--  -- end
342--     return accent_to_extensible(target,0x203E,data.original,0x0305,height,depth,nil,nil,0x203E)
343-- end
344--
345-- -- virtualcharacters[0xFE33E] = virtualcharacters[0x203E] -- convenient
346-- -- virtualcharacters[0xFE33F] = virtualcharacters[0x203E] -- convenient
347--
348-- virtualcharacters[0xFE33E] = function(data)
349--     local target = data.target
350--     local height = 0
351--     local depth  = target.parameters.xheight/4
352--     return accent_to_extensible(target,0xFE33E,data.original,0x0305,height,depth,nil,nil,0x203E)
353-- end
354--
355-- virtualcharacters[0xFE33F] = function(data)
356--     local target = data.target
357--     local height = target.parameters.xheight/8
358--     local depth  = height
359--     return accent_to_extensible(target,0xFE33F,data.original,0x0305,height,depth,nil,nil,0x203E)
360-- end
361--
362-- local function smashed(data,unicode,swap,private)
363--     local target   = data.target
364--     local original = data.original
365--     local chardata = target.characters[unicode]
366--     if chardata and chardata.height > target.parameters.xheight then
367--         return accent_to_extensible(target,private,original,unicode,0,0,swap,nil,unicode)
368--     else
369--         return original.characters[unicode]
370--     end
371-- end
372--
373-- addextra(0xFE3DE) -- EXTENSIBLE OF 0x03DE : overbrace
374-- addextra(0xFE3DC) -- EXTENSIBLE OF 0x03DC : overparent
375-- addextra(0xFE3B4) -- EXTENSIBLE OF 0x03B4 : overbracket
376--
377-- virtualcharacters[0xFE3DE] = function(data) return smashed(data,0x23DE,0x23DF,0xFE3DE) end
378-- virtualcharacters[0xFE3DC] = function(data) return smashed(data,0x23DC,0x23DD,0xFE3DC) end
379-- virtualcharacters[0xFE3B4] = function(data) return smashed(data,0x23B4,0x23B5,0xFE3B4) end
380--
381-- addextra(0xFE3DF) -- EXTENSIBLE OF 0x03DF
382-- addextra(0xFE3DD) -- EXTENSIBLE OF 0x03DD
383-- addextra(0xFE3B5) -- EXTENSIBLE OF 0x03B5
384--
385-- virtualcharacters[0xFE3DF] = function(data) local c = data.target.characters[0x23DF] if c then c.unicode = 0x23DF return c end end
386-- virtualcharacters[0xFE3DD] = function(data) local c = data.target.characters[0x23DD] if c then c.unicode = 0x23DD return c end end
387-- virtualcharacters[0xFE3B5] = function(data) local c = data.target.characters[0x23B5] if c then c.unicode = 0x23B5 return c end end
388--
389-- -- todo: add some more .. numbers might change
390--
391-- addextra(0xFE302) -- EXTENSIBLE OF 0x0302
392-- addextra(0xFE303) -- EXTENSIBLE OF 0x0303
393--
394-- local function smashed(data,unicode,private)
395--     local target = data.target
396--     local height = target.parameters.xheight / 2
397--     local c, done = accent_to_extensible(target,private,data.original,unicode,height,0,nil,-height,unicode)
398--     if done then
399--         c.topanchor = nil -- or maybe also all the others
400--     end
401--     return c
402-- end
403--
404-- virtualcharacters[0xFE302] = function(data) return smashed(data,0x0302,0xFE302) end
405-- virtualcharacters[0xFE303] = function(data) return smashed(data,0x0303,0xFE303) end
406
407---------------------------------------------------------------------------------
408-- these are moved to math-act.lmt
409---------------------------------------------------------------------------------
410
411-- local function reference(index,char)
412--     if index then
413--         return slotcommand[index][char]
414--     else
415--         return charcommand[char]
416--     end
417-- end
418--
419-- local function raised(data,replacement,down)
420--     local character = data.scriptdata.characters[replacement]
421--     if character then
422--         local size = data.size
423--         return {
424--             width    = character.width,
425--             height   = character.height,
426--             depth    = character.depth,
427--             commands = {
428--                 down and downcommand[size/4] or upcommand[size/2],
429--                 reference(data.scriptindex,replacement)
430--             }
431--         }
432--     end
433-- end
434--
435-- virtualcharacters[0x207A] = function(data) return raised(data,0x002B)      end
436-- virtualcharacters[0x207B] = function(data) return raised(data,0x2212)      end
437-- virtualcharacters[0x208A] = function(data) return raised(data,0x002B,true) end
438-- virtualcharacters[0x208B] = function(data) return raised(data,0x2212,true) end
439
440---------------------------------------------------------------------------------
441-- these are moved to math-act.lmt
442---------------------------------------------------------------------------------
443
444-- local function repeated(data,char,n,fraction)
445--     local character = data.characters[char]
446--     if character then
447--         local width = character.width
448--         local delta = width - character.italic -- width * fraction
449--         local c = charcommand[char]
450--         local r = rightcommand[right]
451--         local commands = { }
452--         for i=1,n-1 do
453--             width = width + delta
454--             commands[#commands+1] = c
455--             commands[#commands+1] = -delta
456--         end
457--         commands[#commands+1] = c
458--         return {
459--             width    = width,
460--             height   = character.height,
461--             depth    = character.depth,
462--             commands = commands,
463--         }
464--     end
465-- end
466--
467-- virtualcharacters[0x222C] = function(data)
468--     return repeated(data,0x222B,2,1/8)
469-- end
470--
471-- virtualcharacters[0x222D] = function(data)
472--     return repeated(data,0x222B,3,1/8)
473-- end
474
475---------------------------------------------------------------------------------
476-- these are moved to math-act.lmt
477---------------------------------------------------------------------------------
478
479-- -- spacing (no need for a cache of widths)
480--
481-- local c_zero   = byte('0')
482-- local c_period = byte('.')
483--
484-- local function spacefraction(data,fraction)
485--     local width = fraction * data.target.parameters.space
486--     return {
487--         width    = width,
488--      -- commands = { rightcommand[width] }
489--     }
490-- end
491--
492-- local function charfraction(data,char)
493--     local width = data.target.characters[char].width
494--     return {
495--         width    = width,
496--      -- commands = { rightcommand[width] }
497--     }
498-- end
499--
500-- local function quadfraction(data,fraction)
501--     local width = fraction * data.target.parameters.quad
502--     return {
503--         width  = width,
504--      -- commands = { rightcommand[width] }
505--     }
506-- end
507--
508-- virtualcharacters[0x00A0] = function(data) return spacefraction(data,1)        end -- nbsp
509-- virtualcharacters[0x2000] = function(data) return quadfraction (data,1/2)      end -- enquad
510-- virtualcharacters[0x2001] = function(data) return quadfraction (data,1)        end -- emquad
511-- virtualcharacters[0x2002] = function(data) return quadfraction (data,1/2)      end -- enspace
512-- virtualcharacters[0x2003] = function(data) return quadfraction (data,1)        end -- emspace
513-- virtualcharacters[0x2004] = function(data) return quadfraction (data,1/3)      end -- threeperemspace
514-- virtualcharacters[0x2005] = function(data) return quadfraction (data,1/4)      end -- fourperemspace
515-- virtualcharacters[0x2006] = function(data) return quadfraction (data,1/6)      end -- sixperemspace
516-- virtualcharacters[0x2007] = function(data) return charfraction (data,c_zero)   end -- figurespace
517-- virtualcharacters[0x2008] = function(data) return charfraction (data,c_period) end -- punctuationspace
518-- virtualcharacters[0x2009] = function(data) return quadfraction (data,1/8)      end -- breakablethinspace
519-- virtualcharacters[0x200A] = function(data) return quadfraction (data,1/8)      end -- hairspace
520-- virtualcharacters[0x200B] = function(data) return quadfraction (data,0)        end -- zerowidthspace
521-- virtualcharacters[0x202F] = function(data) return quadfraction (data,1/8)      end -- narrownobreakspace
522-- virtualcharacters[0x205F] = function(data) return spacefraction(data,1/2)      end -- math thinspace
523
524-- -- another crazy hack .. doesn't work as we define scrscr first .. we now have smaller
525-- -- primes so we have smaller primes for the moment, big ones will become an option ..
526-- -- these primes in fonts are a real mess .. kind of a dead end, so don't wonder about
527-- -- the values below
528--
529-- local function smashed(data,unicode,optional)
530--     local oldchar = data.characters[unicode]
531--     if oldchar then
532--         local height  = 0.85 * data.target.mathparameters.AccentBaseHeight
533--         local newchar = table.copy(oldchar)
534--         newchar.yoffset = height - oldchar.height
535--         newchar.height  = height
536--         return newchar
537--     elseif not optional then
538--         report_fallbacks("missing %U prime in font %a",unicode,data.target.properties.fullname)
539--     end
540-- end
541--
542-- addextra(0xFE932) -- SMASHED PRIME 0x02032
543-- addextra(0xFE933) -- SMASHED PRIME 0x02033
544-- addextra(0xFE934) -- SMASHED PRIME 0x02034
545-- addextra(0xFE957) -- SMASHED PRIME 0x02057
546--
547-- addextra(0xFE935) -- SMASHED BACKWARD PRIME 0x02035
548-- addextra(0xFE936) -- SMASHED BACKWARD PRIME 0x02036
549-- addextra(0xFE937) -- SMASHED BACKWARD PRIME 0x02037
550--
551-- virtualcharacters[0xFE932] = function(data) return smashed(data,0x02032) end
552-- virtualcharacters[0xFE933] = function(data) return smashed(data,0x02033) end
553-- virtualcharacters[0xFE934] = function(data) return smashed(data,0x02034) end
554-- virtualcharacters[0xFE957] = function(data) return smashed(data,0x02057) end
555--
556-- virtualcharacters[0xFE935] = function(data) return smashed(data,0x02035,true) end
557-- virtualcharacters[0xFE936] = function(data) return smashed(data,0x02036,true) end
558-- virtualcharacters[0xFE937] = function(data) return smashed(data,0x02037,true) end
559--
560-- local hack = nil
561--
562-- function mathematics.getridofprime(target,original)
563--     local mathparameters = original.mathparameters
564--     if mathparameters and next(mathparameters) then
565--         local changed = original.changed
566--         if changed then
567--             hack = changed[0x02032]
568--             changed[0x02032] = nil
569--             changed[0x02033] = nil
570--             changed[0x02034] = nil
571--             changed[0x02057] = nil
572--             changed[0x02035] = nil
573--             changed[0x02036] = nil
574--             changed[0x02037] = nil
575--         end
576--     end
577-- end
578--
579-- function mathematics.setridofprime(target,original)
580--     local mathparameters = original.mathparameters
581--     if mathparameters and next(mathparameters) and original.changed then
582--         target.characters[0xFE931] = target.characters[hack or 0x2032]
583--         hack = nil
584--     end
585-- end
586--
587-- utilities.sequencers.appendaction("beforecopyingcharacters","system","mathematics.getridofprime")
588-- utilities.sequencers.appendaction("aftercopyingcharacters", "system","mathematics.setridofprime")
589
590-- local list = {
591--     { 0x02032, true  },
592--     { 0x02033, true  },
593--     { 0x02034, true  },
594--     { 0x02057, true  },
595--     { 0x02035, false },
596--     { 0x02036, false },
597--     { 0x02037, false },
598-- }
599--
600-- function mathematics.fixprimes(target,original)
601--     local mathparameters = original.mathparameters
602--     if mathparameters and next(mathparameters) then
603--         for i=1,#list do
604--             local entry = list[i]
605--             local char  = original.characters[entry[1]]
606--             if char then
607--                 local height  = 0.85 * orginal.mathparameters.AccentBaseHeight
608--                 char.yoffset = height - char.height
609--                 char.height  = height
610--                 return char
611--             elseif entry[1] then
612--                 report_fallbacks("missing %U prime in font %a",unicode,original.properties.fullname)
613--             end
614--         end
615--     end
616-- end
617--
618-- utilities.sequencers.appendaction("beforecopyingcharacters","system","mathematics.fixprimes")
619
620---------------------------------------------------------------------------------
621-- this one is not used
622---------------------------------------------------------------------------------
623
624-- addextra(0xFE941) -- EXTREMELY IDENTICAL TO
625--
626-- virtualcharacters[0xFE941] = function(data) -- this character is only needed for mathpairs
627--     local characters = data.target.characters
628--     local parameters = data.target.parameters
629--     local basechar   = characters[0x003D]
630--     local width      = basechar.width or 0
631--     local height     = basechar.height or 0
632--     local depth      = basechar.depth or 0
633--     return {
634--         unicode   = 0xFE941,
635--         width     = width,
636--         height    = height,         -- we cheat (no time now)
637--         depth     = depth,          -- we cheat (no time now)
638--         commands  = {
639--             upcommand[height/2], -- sort of works
640--             charcommand[0x003D],
641--             leftcommand[width],
642--             downcommand[height],     -- sort of works
643--             charcommand[0x003D],
644--         },
645--     }
646-- end
647
648---------------------------------------------------------------------------------
649-- these might move to math-act.lmt
650---------------------------------------------------------------------------------
651--
652-- actuarian (beware: xits has an ugly one)
653
654-- addextra(0xFE940) -- SMALL ANNUITY SYMBOL
655--
656-- local function actuarian(data)
657--     local characters = data.target.characters
658--     local parameters = data.target.parameters
659--     local basechar   = characters[0x0078] -- x (0x0058 X) or 0x1D431
660--     local linewidth  = parameters.xheight / 10
661--     local basewidth  = basechar.width
662--     local baseheight = basechar.height
663--     return {
664--         -- todo: add alttext
665--         -- compromise: lm has large hooks e.g. \actuarial{a}
666--         width    = basewidth + 4 * linewidth,
667--         height   = basechar.height,
668--         depth    = basechar.depth,
669--         unicode  = 0x20E7,
670--         commands = {
671--             rightcommand[2 * linewidth],
672--             downcommand[- baseheight - 3 * linewidth],
673--             { "rule", linewidth, basewidth + 4 * linewidth },
674--             leftcommand[linewidth],
675--             downcommand[baseheight + 4 * linewidth],
676--             { "rule", baseheight + 5 * linewidth, linewidth },
677--         },
678--     }
679-- end
680--
681-- virtualcharacters[0x020E7] = actuarian -- checked
682-- -----------------[0xFE940] = actuarian -- unchecked
683
684-- local function equals(data,unicode,snippet,advance,n) -- mathpair needs them
685--     local characters = data.target.characters
686--     local parameters = data.target.parameters
687--     local basechar   = characters[snippet]
688--     local width      = n*basechar.width
689--     local advance    = advance * parameters.quad -- for equals
690--     return {
691--         unicode  = unicode,
692--         width    = width - (n-1)*advance,
693--         height   = basechar.height,
694--         depth    = basechar.depth,
695--         commands = {
696--             charcommand[snippet],
697--             leftcommand[advance],
698--             charcommand[snippet],
699--             n > 2 and leftcommand[advance] or nil,
700--             n > 2 and charcommand[snippet] or nil,
701--         },
702--     }
703-- end
704--
705-- virtualcharacters[0x2A75] = function(data) return equals(data,0x2A75,0x003D, 1/5,2) end -- ==
706-- virtualcharacters[0x2A76] = function(data) return equals(data,0x2A76,0x003D, 1/5,3) end -- ===
707--
708-- local function bars(data,unicode,snippet,advance,n) -- mathpair needs them
709--     local characters = data.target.characters
710--     local parameters = data.target.parameters
711--     local basechar   = characters[snippet]
712--     local width      = n*basechar.width
713--     advance = advance * width
714--     return {
715--         unicode  = unicode,
716--         width    = width - (n-1)*advance,
717--         height   = basechar.height,
718--         depth    = basechar.depth,
719--         callback = "devirtualize",
720--         commands = {
721--             charcommand[snippet],
722--             leftcommand[advance],
723--             charcommand[snippet],
724--             n > 2 and leftcommand[advance] or nil,
725--             n > 2 and charcommand[snippet] or nil,
726--         },
727--     }
728-- end
729--
730-- virtualcharacters[0x2980] = function(data) return bars(data,0x2980,0x007C,1/10,3) end -- |||
731
732---------------------------------------------------------------------------------
733-- these might move to math-act.lmt -- lucida needs this -- no longer used
734---------------------------------------------------------------------------------
735
736-- virtualcharacters[0x305] = function(data)
737--     local target = data.target
738--     local height = target.parameters.xheight/8
739--     local width  = target.parameters.emwidth/2
740--     local depth  = height
741--     local used   = 0.8 * width
742--     return {
743--         width    = width,
744--         height   = height,
745--         depth    = depth,
746--         commands = { { "rule", height, width } },
747--         hparts   = {
748--             {
749--               advance   = width,
750--               ["end"]   = used,
751--               glyph     = 0x305,
752--               start     = 0,
753--             },
754--             {
755--               advance   = width,
756--               ["end"]   = 0,
757--               extender  = 1,
758--               glyph     = 0x305,
759--               start     = used,
760--             },
761--         }
762--     }
763-- end
764
765-- local function threedots(data,shift)
766--     local characters = data.target.characters
767--     local parameters = data.target.parameters
768--     local periodchar = characters[0x002E]
769--     local pluschar   = characters[0x002B]
770--     local period     = charcommand[0x002E]
771--     local periodwd   = periodchar.width  or 0
772--     local periodht   = periodchar.height or 0
773--     local perioddp   = periodchar.depth or 0
774--     local offset     = 0
775--     if shift then
776--         local plusht = pluschar.height or 0
777--         local plusdp = pluschar.depth or 0
778--         local axis   = (plusdp + plusht)//2 - plusdp
779--         offset   = axis - periodht//2
780--         periodht = axis + periodht//2
781--     end
782--     return {
783--         width    = 3*periodwd,
784--         height   = periodht,
785--         depth    = 0,
786--         commands = { upcommand[offset], period, period, period }
787--     }
788-- end
789--
790-- virtualcharacters[0x2026] = function(data) return threedots(data,false) end -- virtualforced[0x2026] = true
791-- virtualcharacters[0x22EF] = function(data) return threedots(data, true) end -- virtualforced[0x22EF] = true
792