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