math-vfu.lmt /size: 57 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['math-vfu'] = {
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-- somehow an italic gets passed for 'next'
10
11-- All these math vectors .. thanks to Aditya and Mojca they become better and
12-- better. If you have problems with math fonts or miss characters report it to the
13-- ConTeXt mailing list. Also thanks to Boguslaw for finding a couple of errors.
14
15-- Although this mechanism was a candidate for obsolence, the fact that Iwona and
16-- Antykwa are nice fonts (especially for display) I decided to keep it around but
17-- in a bit upgraded way. It is also a test for virtual tfm/pfb fonts that we keep
18-- around but hardly gets tested. However, much is still pretty old code, dating
19-- from when we emulated \UNICODE\ math and \OPENTYPE\ math fonts using traditional
20-- fonts.
21
22-- Musical timestamp: Januari 2023 Riverside ID.Entity release date, after all we
23-- only use the following code for ther Polish Iwona, Kurier and Antykwa fonts.
24
25local type, next, tonumber = type, next, tonumber
26local max = math.max
27local fastcopy, sortedhash = table.copy, table.sortedhash
28
29local fonts, mathematics = fonts, mathematics
30
31local trace_virtual = false  trackers.register("math.virtual", function(v) trace_virtual = v end)
32local trace_timings = false  trackers.register("math.timings", function(v) trace_timings = v end)
33
34local add_optional  = false  directives.register("math.virtual.optional",function(v) add_optional = v end)
35
36local report_virtual    = logs.reporter("fonts","virtual math")
37
38local allocate          = utilities.storage.allocate
39local setmetatableindex = table.setmetatableindex
40local formatters        = string.formatters
41
42local chardata          = characters.data
43
44local mathencodings     = allocate()
45fonts.encodings.math    = mathencodings -- better is then: fonts.encodings.vectors
46local vfmath            = allocate()
47fonts.handlers.vf.math  = vfmath
48
49local helpers           = fonts.helpers
50
51local addprivate        = helpers.addprivate
52local hasprivate        = helpers.hasprivate
53local vfcommands        = helpers.commands
54
55local rightcommand      = vfcommands.right
56local leftcommand       = vfcommands.left
57local downcommand       = vfcommands.down
58local upcommand         = vfcommands.up
59local push              = vfcommands.push
60local pop               = vfcommands.pop
61local slotcommand       = vfcommands.slot
62local staycommand       = vfcommands.stay
63
64local nps = fonts.helpers.newprivateslot
65local ps  = fonts.helpers.privateslot
66
67nps("rule middle piece")
68nps("rule right piece")
69nps("rule left piece")
70nps("double rule middle piece")
71nps("double rule right piece")
72nps("double rule left piece")
73nps("arrow left piece")
74nps("arrow right piece")
75nps("double arrow left piece")
76nps("double arrow right piece")
77
78nps("flat rule left piece")
79nps("flat rule middle piece")
80nps("flat rule right piece")
81
82nps("flat double rule left piece")
83nps("flat double rule middle piece")
84nps("flat double rule right piece")
85
86nps("minus rule left piece")
87nps("minus rule middle piece")
88nps("minus rule right piece")
89
90do
91
92    -- this overlaps with math-act
93
94    local function horibar(main,unicode,rule,left,right,normal,force,m,l,r)
95        local characters = main.characters
96        local data       = characters[unicode]
97        if force or not data then
98            local height = main.mathparameters.defaultrulethickness or 4*65536/10
99            local f_rule = rule and formatters["M-HORIBAR-M-%H"](rule)
100            local p_rule = rule and hasprivate(main,f_rule)
101            local ndata  = normal and characters[normal]
102            if rule and left and right and normal then
103                local ldata  = characters[l or left]
104                local mdata  = characters[m or rule]
105                local rdata  = characters[r or right]
106                local lwidth = ldata.width or 0
107                local mwidth = mdata.width or 0
108                local rwidth = rdata.width or 0
109                local nwidth = ndata.width or 0
110                local down   = (mdata.height / 2) - height
111if unicode == normal then
112    height = ndata.height
113    down   = 0
114end                --
115                local f_left  = left  and formatters["M-HORIBAR-L-%H"](left)
116                local f_right = right and formatters["M-HORIBAR-R-%H"](right)
117                local p_left  = left  and hasprivate(main,f_left)
118                local p_right = right and hasprivate(main,f_right)
119                --
120                if not characters[p_rule] then
121                    p_rule = addprivate(main,f_rule,{
122                        height   = height,
123                        width    = .95*mwidth,
124-- keepvirtual      = true,
125                        commands = {
126                            push,
127                            leftcommand[.025*mwidth],
128                            downcommand[down],
129                            slotcommand[0][m or rule],
130                            pop,
131                        },
132                    })
133                end
134                if not characters[p_left] then
135                    p_left = addprivate(main,f_left,{
136                        height   = height,
137                        width    = .95*lwidth,
138-- keepvirtual      = true,
139                        commands = {
140                            push,
141                            leftcommand[.025*lwidth],
142                            downcommand[down],
143                            slotcommand[0][l or left],
144                            pop,
145                        },
146                    })
147                end
148                if not characters[p_right] then
149                    p_right = addprivate(main,f_right,{
150                        height   = height,
151                        width    = .95*rwidth,
152-- keepvirtual      = true,
153                        commands = {
154                            push,
155                            leftcommand[.025*rwidth],
156                            downcommand[down],
157                            slotcommand[0][r or right],
158                            pop,
159                        },
160                    })
161                end
162if unicode ~= normal then
163                data = {
164                    unicode  = unicode,
165                    height   = height,
166                    width    = nwidth,
167                    commands = {
168                        downcommand[down],
169                        slotcommand[0][normal]
170                    },
171                }
172                characters[unicode] = data
173end
174                data.parts = {
175                    { glyph = p_left, ["end"] = 0.4*lwidth },
176                    { glyph = p_rule, extender = 1, ["start"] = mwidth, ["end"] = mwidth },
177                    { glyph = p_right, ["start"] = 0.6*rwidth },
178                }
179            else
180                local width = main.parameters.quad/2 or 4*65536 -- 3
181                if not characters[p_rule] then
182                    if unicode == normal then
183                        p_rule = addprivate(main,f_rule,{
184                            height   = ndata.height,
185                            width    = width,
186                            commands = {
187                                push,
188                                upcommand[(ndata.height - height)/2],
189                                { "rule", height, width },
190                                pop
191                            },
192                        })
193                    else
194                        p_rule = addprivate(main,f_rule,{
195                            height   = height,
196                            width    = width,
197                            commands = {
198                                push,
199                                { "rule", height, width },
200                                pop
201                            },
202                        })
203                    end
204                end
205if unicode ~= normal then
206                data = {
207                    unicode  = unicode,
208                    height   = height,
209                    width    = width,
210                    commands = {
211                        slotcommand[0][p_rule]
212                    }
213                }
214                characters[unicode] = data
215end
216                data.parts = {
217                    { glyph = p_rule, ["start"] = width/2, ["end"] = width/2 },
218                    { glyph = p_rule, extender = 1, ["start"] = width/2, ["end"] = width/2 },
219                }
220            end
221            data.keepvirtual = true -- i need to figure this out
222            data.partsorientation = "horizontal"
223        end
224    end
225
226    -- local rootbarmiddle -- false = addprivate(main,formatters["M-R-%H"](next))
227    -- local rootbarright  -- false = addprivate(main,formatters["M-R-%H"](next))
228
229    local function rootbar(main,unicode,rule,right,normal)
230        local characters = main.characters
231        if not characters[unicode] then
232            local height = main.mathparameters.defaultrulethickness or 4*65536/10
233            if rule and right and normal then
234                local mdata  = characters[rule]
235                local rdata  = characters[right]
236                local ndata  = characters[normal]
237                local mwidth = mdata.width or 0
238                local rwidth = rdata.width or 0
239                local nwidth = ndata.width or 0
240                local down   = (mdata.height / 2) - height
241                --
242                local f_rule  = rule  and formatters["M-ROOTBAR-M-%H"](rule)
243                local f_right = right and formatters["M-ROOTBAR-R-%H"](right)
244                local p_rule  = rule  and hasprivate(main,f_rule)
245                local p_right = right and hasprivate(main,f_right)
246                --
247                if not p_rule then
248                    p_rule = addprivate(main,f_rule,{
249                        height   = height,
250                        width    = .95*mwidth,
251                        commands = {
252                            push,
253                            leftcommand[.05*mwidth],
254                            downcommand[down],
255                            slotcommand[0][rule],
256                            pop,
257                        },
258                    })
259                end
260                if right and not p_right then
261                    p_right = addprivate(main,p_right,{
262                        height   = height,
263                        width    = .95*rwidth,
264                        commands = {
265                            push,
266                            leftcommand[.05*rwidth],
267                            downcommand[down],
268                            slotcommand[0][right],
269                            pop,
270                        },
271                    })
272                end
273                characters[unicode] = {
274                    keepvirtual      = true,
275                    partsorientation = "horizontal",
276                    height           = height,
277                    width            = rwidth,
278                    commands         = {
279                        slotcommand[0][p_right],
280                    },
281                    parts            = {
282                        { glyph = p_rule, extender = 1, ["start"] = mwidth, ["end"] = 0.9*mwidth },
283                        { glyph = p_right, ["start"] = 0.6*rwidth },
284                    }
285                }
286            end
287        end
288    end
289
290    local function parent(main,unicode,first,rule,last,where)
291        local characters = main.characters
292        local chardata   = characters[unicode]
293        if characters[unicode] then
294            local template = characters[first]
295            if template then
296                local crule   = characters[rule]
297                local xheight = main.mathparameters.xheight
298                local rheight = 0
299                local rdepth  = 0
300--                 if crule then
301--                     rheight = crule.height
302--                     rdepth  = crule.depth
303--                 else
304                    local width   = template.width / 4
305                    local height  = template.height
306                    local depth   = template.depth
307                    rheight = where == "top" and   height or 3*height
308                    rdepth  = where == "top" and 2*height or 0
309                    characters[rule] = {
310                        height   = rheight,
311                        depth    = rdepth,
312                        width    = width,
313                        commands = { push, { "rule", height, width }, pop },
314                    }
315--                 end
316                characters[first].depth = rdepth
317                characters[last] .depth = rdepth
318                if where == "top" then
319                    while true do
320                        chardata.height  = chardata.height - xheight
321                        chardata.depth   = 0
322                        chardata.yoffset = -xheight
323                        local next = chardata.next
324                        if next then
325                            unicode  = next
326                            chardata = characters[unicode]
327                        else
328                            break
329                        end
330                    end
331                else
332                    while true do
333                        chardata.height  = 0
334                        local next = chardata.next
335                        if next then
336                            unicode  = next
337                            chardata = characters[unicode]
338                        else
339                            break
340                        end
341                    end
342                end
343                chardata.keepvirtual      = true
344                chardata.partsorientation = "horizontal"
345                chardata.parts            = {
346                    { glyph = first },
347                    { glyph = rule, extender = 1, start = width/2, ["end"] = width/2 },
348                    { glyph = last  },
349                }
350            end
351        end
352    end
353
354    local function brace(main,unicode,first,rule,left,right,rule,last,where)
355        local characters = main.characters
356        local chardata   = characters[unicode]
357        if chardata then
358            local template = characters[first]
359            if template then
360                local xheight = main.mathparameters.xheight
361                local width   = template.width / 4
362                local height  = template.height
363                local depth   = template.depth
364                local rheight = 3*height
365                local rdepth  = 2*height
366                characters[rule] = {
367                    height   = rheight,
368                    depth    = rdepth,
369                    width    = width,
370                    commands = { push, { "rule", height, width }, pop },
371                }
372                -- don't change snippets, they serve dual purposes
373                if where == "top" then
374                    while true do
375                        chardata.height  = chardata.height - xheight
376                        chardata.depth   = 0
377                        chardata.yoffset = -xheight
378                        local next = chardata.next
379                        if next then
380                            unicode  = next
381                            chardata = characters[unicode]
382                        else
383                            break
384                        end
385                    end
386                else
387                    while true do
388                        chardata.height = 0
389                        local next = chardata.next
390                        if next then
391                            unicode  = next
392                            chardata = characters[unicode]
393                        else
394                            break
395                        end
396                    end
397                end
398                chardata.keepvirtual = true
399                chardata.partsorientation = "horizontal"
400                chardata.parts            = {
401                    { glyph = first },
402                    { glyph = rule, extender = 1, start = width/2, ["end"] = width/2 },
403                    { glyph = left  },
404                    { glyph = right },
405                    { glyph = rule, extender = 1, start = width/2, ["end"] = width/2 },
406                    { glyph = last  },
407                }
408            end
409        end
410    end
411
412    local function dots(main,unicode)
413        local characters = main.characters
414        local c = characters[0x002E]
415        if c then
416            local w         = c.width
417            local h         = c.height
418            local d         = c.depth
419            local size      = main.parameters.size
420            local mu        = size/18
421            local right3mu  = rightcommand[3*mu]
422            local right1mu  = rightcommand[1*mu]
423            local up1size   = upcommand[.1*size]
424            local up4size   = upcommand[.4*size]
425            local up7size   = upcommand[.7*size]
426            local right2muw = rightcommand[2*mu + w]
427            local slot      = slotcommand[0][0x002E]
428            if unicode == 0x22EF then
429                local c = characters[0x022C5]
430                if c then
431                    local width  = c.width
432                    local height = c.height
433                    local depth  = c.depth
434                    local slot   = slotcommand[0][0x022C5]
435                 -- local stay   = staycommand[0x022C5]
436                 -- local right3mu  = rightcommand[width+3*mu]
437                    characters[unicode] = {
438                        width    = 3*width + 2*3*mu,
439                        height   = height,
440                        depth    = depth,
441                        commands = {
442                            slot, right3mu, slot, right3mu, slot,
443                         -- push, slot, right3mu, slot, right3mu, slot, pop,
444                         -- stay, right3mu, stay, right3mu, stay,
445                        }
446                    }
447                end
448            elseif unicode == 0x22EE then
449                characters[unicode] = {
450                    width    = w,
451                    height   = h+0.8*size,
452                    depth    = 0,
453                    commands = {
454                     -- push, push, slot, pop, up4size, push, slot, pop, up4size, slot, pop,
455                        push, slot, pop, up4size, push, slot, pop, up4size, slot,
456                    }
457                }
458            elseif unicode == 0x22F1 then
459                characters[unicode] = {
460                    width    = 3*w + 6*size/18,
461                    height   = h+0.7*size,
462                    depth    = 0,
463                    commands = {
464                     -- push,
465                        right1mu,
466                        push, up7size, slot, pop,
467                        right2muw,
468                        push, up4size, slot, pop,
469                        right2muw,
470                        push, up1size, slot, pop,
471                        right1mu,
472                     -- pop
473                    }
474                }
475            elseif unicode == 0x22F0 then
476                characters[unicode] = {
477                    width    = 3*w + 6*size/18,
478                    height   = h+0.7*size,
479                    depth    = 0,
480                    commands = {
481                     -- push,
482                        right1mu,
483                        push, up1size, slot, pop,
484                        right2muw,
485                        push, up4size, slot, pop,
486                        right2muw,
487                        push, up7size, slot, pop,
488                        right1mu,
489                     -- pop
490                    }
491                }
492            else
493                characters[unicode] = {
494                    width    = 3*w + 2*3*mu,
495                    height   = h,
496                    depth    = d,
497                    commands = {
498                     -- push, slot, right3mu, slot, right3mu, slot, pop,
499                        slot, right3mu, slot, right3mu, slot,
500                    }
501                }
502            end
503        end
504    end
505
506    local function jointwo(main,unicode,u1,d12,u2)
507        local characters = main.characters
508        local c1 = characters[u1]
509        local c2 = characters[u2]
510        if c1 and c2 then
511            local w1 = c1.width
512            local w2 = c2.width
513            local width
514            if d12 == false then
515                d12   = 0
516                width = w2
517            elseif d12 < 0 then
518                d12   = d12 * w2
519                width = w2
520            else
521                d12   = d12 * main.parameters.size/18 -- mu
522                width = w1 + w2 - d12
523            end
524            characters[unicode] = {
525                unicode  = unicode,
526                width    = width,
527                height   = max(c1.height or 0, c2.height or 0),
528                depth    = max(c1.depth  or 0, c2.depth  or 0),
529keepvirtual = true,
530                commands = {
531                 -- { "inspect" },
532                 -- { "trace" },
533                    slotcommand[0][u1],
534                 -- { "trace" },
535                    d12 ~= 0 and leftcommand[d12] or false,
536                    slotcommand[0][u2],
537                 -- { "trace" },
538                },
539            }
540        end
541    end
542
543    local function overlaytwo(main,unicode,u1,factor,u2) -- not ...
544        local characters = main.characters
545        local c1 = characters[u1]
546        local c2 = characters[u2]
547        if c1 and c2 then
548            local width = c2.width
549            characters[unicode] = {
550                width    = width,
551                height   = max(c1.height or 0, c2.height or 0),
552                depth    = max(c1.depth  or 0, c2.depth  or 0),
553                commands = {
554                    push,
555                    slotcommand[0][u2], -- =
556                    pop,
557                    factor ~= 0 and rightcommand[factor*width] or false,
558                    slotcommand[0][u1], -- /
559                },
560            }
561        end
562    end
563
564    local function jointhree(main,unicode,u1,d12,u2,d23,u3)
565        local characters = main.characters
566        local c1 = characters[u1]
567        local c2 = characters[u2]
568        local c3 = characters[u3]
569        if c1 and c2 and c3 then
570            local w1 = c1.width
571            local w2 = c2.width
572            local w3 = c3.width
573            local ds = main.parameters.size/18
574            d12 = d12 * ds
575            d23 = d23 * ds
576            characters[unicode] = {
577                unicode  = unicode,
578                width    = w1 + w2 + w3 - d12 - d23,
579                height   = max(c1.height or 0, c2.height or 0, c3.height or 0),
580                depth    = max(c1.depth  or 0, c2.depth  or 0, c3.depth  or 0),
581                commands = {
582                 -- push,
583                    slotcommand[0][u1],
584                 -- pop,
585                    d12 ~= 0 and leftcommand[d12] or false,
586                 -- push,
587                    slotcommand[0][u2],
588                 -- pop,
589                    d23 ~= 0 and leftcommand[d23] or false,
590                 -- push,
591                    slotcommand[0][u3],
592                 -- pop,
593                }
594            }
595        end
596    end
597
598    local function stack(main,unicode,u1,d12,u2)
599        local characters = main.characters
600        local c1 = characters[u1]
601        if not c1 then
602            return
603        end
604        local c2 = characters[u2]
605        if not c2 then
606            return
607        end
608        local w1 = c1.width  or 0
609        local h1 = c1.height or 0
610        local d1 = c1.depth  or 0
611        local w2 = c2.width  or 0
612        local h2 = c2.height or 0
613        local d2 = c2.depth  or 0
614        local mu = main.parameters.size/18
615        characters[unicode] = {
616            width    = w1,
617            height   = h1 + h2 + d12*mu,
618            depth    = d1,
619            commands = {
620                slotcommand[0][u1],
621                leftcommand[w1/2 + w2/2],
622                downcommand[-h1 + d2 -d12*mu],
623                slotcommand[0][u2],
624            }
625        }
626    end
627
628    local function repeated(main,unicode,u,n,fraction)
629        local characters = main.characters
630        local c = characters[u]
631        if c then
632            if n == 1 then
633               -- skip this one
634            else
635                local width  = c.width
636                local italic = fraction*width -- c.italic or 0 -- larger ones have funny italics
637                local tc     = slotcommand[0][u]
638                local tr     = leftcommand[italic] -- see hack elsewhere
639                local commands = { }
640                for i=1,n-1 do
641                    commands[#commands+1] = tc
642                    commands[#commands+1] = tr
643                end
644                commands[#commands+1] = tc
645                local next = c.next
646                if next then
647                    local p = addprivate(main,formatters["M-R-%H"](next))
648                    repeated(main,p,next,n,fraction)
649                    next = p
650                end
651                characters[unicode] = {
652                    width       = width + (n-1)*(width-italic),
653                    height      = c.height,
654                    depth       = c.depth,
655                    italic      = italic,
656                    commands    = commands,
657                    keepvirtual = true,
658                    next        = next,
659                }
660            end
661        end
662    end
663
664    local function extension(main,unicode,first,middle,last,ffactor,mfactor,lfactor)
665        local characters = main.characters
666        local chardata   = characters[unicode]
667        if chardata then
668            local fw = first  and characters[first]
669            local mw = middle and characters[middle]
670            local lw = last   and characters[last]
671            if fw and lw then
672                fw = fw.width ; if fw == 0 then fw = 1 end
673                lw = lw.width ; if lw == 0 then lw = 1 end
674                if middle == "left" then
675                    chardata.parts = {
676                        { extender = 0, glyph = first,  ["end"] = fw/2, start = 0,    advance = fw },
677                        { extender = 1, glyph = last,   ["end"] = lw/2, start = lw/2, advance = lw },
678                    }
679                elseif middle == "right" then
680                    chardata.parts = {
681                        { extender = 1, glyph = first,  ["end"] = fw/2, start = fw/2, advance = fw },
682                        { extender = 0, glyph = last,   ["end"] = lw/2, start = 0,    advance = lw },
683                    }
684                elseif mw then
685                    mw = mw.width ; if mw == 0 then mw = 1 end
686                    chardata.parts = {
687                        { extender = 0, glyph = first,  ["end"] = fw/2, start = 0,    advance = (ffactor or 1)*fw },
688                        { extender = 1, glyph = middle, ["end"] = mw/2, start = mw/2, advance = (mfactor or 1)*mw },
689                        { extender = 0, glyph = last,   ["end"] = 0,    start = lw/2, advance = (lfactor or 1)*lw },
690                    }
691                end
692                chardata.keepvirtual      = true -- why this issue with nested virtuals
693                chardata.partsorientation = "horizontal"
694            end
695        end
696    end
697
698    vfmath.builders = {
699        horibar    = horibar,
700        rootbar    = rootbar,
701        parent     = parent,
702        brace      = brace,
703        dots       = dots,
704        jointwo    = jointwo,
705        overlaytwo = overlaytwo,
706        jointhree  = jointhree,
707        stack      = stack,
708        repeated   = repeated,
709        extension  = extension,
710    }
711
712    -- todo: move this to the lfg files
713
714end
715
716local unique = 0 -- testcase: \startTEXpage \math{!\text{-}\text{-}\text{-}} \stopTEXpage
717
718local reported = { }
719local reverse  = { } -- index -> unicode
720
721setmetatableindex(reverse, function(t,name)
722    if trace_virtual then
723        report_virtual("initializing math vector %a",name)
724    end
725    local m = mathencodings[name]
726    local r = { }
727    for u, i in next, m do
728        r[i] = u
729    end
730    reverse[name] = r
731    return r
732end)
733
734-- Used in fallbacks (might move):
735
736local function copy_glyph(main,target,original,unicode,slot)
737    local olddata = original[unicode]
738    if olddata then
739        local newdata = {
740            width     = olddata.width,
741            height    = olddata.height,
742            depth     = olddata.depth,
743            italic    = olddata.italic,
744            topanchor = olddata.topanchor,
745            kerns     = olddata.kerns,
746            mathkerns = olddata.mathkerns,
747            tounicode = olddata.tounicode,
748         -- smaller   = olddata.smaller,
749            commands  = { slotcommand[slot][unicode] },
750        }
751        local glyphdata = newdata
752        local nextglyph = olddata.next
753        while nextglyph do
754            local oldnextdata = original[nextglyph]
755            if oldnextdata then
756                local newnextdata = {
757                    width     = oldnextdata.width,
758                    height    = oldnextdata.height,
759                    depth     = oldnextdata.depth,
760                    italic    = oldnextdata.italic,
761                    topanchor = oldnextdata.topanchor,
762                    kerns     = olddata.kerns,
763                    mathkerns = olddata.mathkerns,
764                    tounicode = olddata.tounicode,
765                    smaller   = olddata.smaller,
766                    commands  = { slotcommand[slot][nextglyph] },
767                }
768                local newnextglyph = addprivate(main,formatters["M-N-%H"](nextglyph),newnextdata)
769                newdata.next = newnextglyph
770                local nextnextglyph = oldnextdata.next
771                if nextnextglyph == nextglyph then
772                    break
773                else
774                    olddata   = oldnextdata
775                    newdata   = newnextdata
776                    nextglyph = nextnextglyph
777                end
778            else
779                break -- safeguard (when testing stuff)
780            end
781        end
782        local oldparts = olddata.parts
783        if oldparts then
784            newparts = fastcopy(oldparts)
785            newdata.parts            = newparts
786            newdata.partsorientation = olddata.partsorientation
787            newdata.partsitalic      = olddata.partsitalic
788            for i=1,#newparts do
789                local newpart  = newparts[i]
790                local oldglyph = newpart.glyph
791                local olddata  = original[oldglyph]
792                local newdata  = {
793                    width     = olddata.width,
794                    height    = olddata.height,
795                    depth     = olddata.depth,
796                    tounicode = olddata.tounicode,
797                    commands  = { slotcommand[slot][oldglyph] },
798                }
799                newpart.glyph = addprivate(main,formatters["M-P-%H"](oldglyph),newdata)
800            end
801        end
802        local smaller = olddata.smaller
803        if smaller then
804            local smallerdata = copy_glyph(main,target,original,smaller,slot)
805            glyphdata.smaller = addprivate(main,formatters["M-S-%H"](smaller),smallerdata)
806        end
807        return glyphdata
808    end
809end
810
811vfmath.copy_glyph = copy_glyph
812
813-- It's time to get rid of this type 1 mess ... take iwona: uppercase /A .. Z but
814-- lowercase /a.math ... /z.math ... anyway, we now follow a slightly different
815-- route: use the "order" field. I can probably make it a bit leaner but it's not
816-- worth spending much time on now.
817
818local noitalics = true -- false can be used to test the engine
819
820-- revision timestamp 2023: after watching ten times "Over The Mountain (feat.
821-- Sierra Hull)" - Cory Wong (Live @ Brooklyn Steel FEB 2022):
822--
823-- https://www.youtube.com/watch?v=lT-W-UEcsns
824-- https://www.youtube.com/watch?v=TPGbj1gFalA
825
826-- The following code is now only used for iwona and antykwa, so I simplified it a
827-- bit to suit that purpose. It might get even simpler.
828
829local function virtualize(s,uni,fci,skewchar,move,mathparameters,unicode,parameters)
830    if fci then
831        local kerns   = fci.kerns
832        local width   = fci.width
833        local height  = fci.height
834        local depth   = fci.depth
835        local italic  = fci.italic
836        local advance = width
837        local bottomright
838        local topanchor
839        local yoffset
840        if kerns and skewchar then
841            local k = kerns[skewchar]
842            if k then
843                topanchor = width/2 + k
844            end
845        end
846        if italic and noitalics then
847            width       = width + italic
848            bottomright = - italic
849            italic      = nil
850        end
851        if move then  -- 0x222B
852            local axis = move * mathparameters.axisheight
853            local half = (height + depth ) / 2
854            yoffset = depth - (half - axis)
855            height = half + axis
856            depth  = half - axis
857        end
858        --
859        local next = fci.next
860        return {
861            advance          = advance,
862            width            = width,
863            height           = height,
864            depth            = depth,
865            italic           = italic,
866            bottomright      = bottomright,
867            topanchor        = topanchor,
868            yoffset          = yoffset,
869            commands         = { slotcommand[s][uni] },
870         -- keepvirtual      = true,
871            next             = fci.next,
872            parts            = fci.parts,
873            partsorientation = fci.partsorientation,
874            partsitalic      = fci.partsitalic,
875            unicode          = unicode,
876            name             = fci.name,
877        }
878    else
879        -- error
880    end
881end
882
883function vfmath.define(specification,virtual,goodies)
884    local name     = specification.name -- symbolic name
885    local size     = specification.size -- given size
886    local loaded   = { }
887    local fontlist = { }
888    local names    = { }
889    local main     = nil
890    local start    = (trace_virtual or trace_timings) and os.clock()
891    local okset    = { }
892    local n        = 0
893    local f_extra  = formatters["virtual.extra.%05X"]
894    local setlist  = virtual.recipe or virtual
895    for s=1,#setlist do
896        local ss     = setlist[s]
897        local ssname = ss.name
898        if add_optional and ss.optional then
899            if trace_virtual then
900                report_virtual("loading font %a subfont %s with name %a at %p is skipped",name,s,ssname,size)
901            end
902        else
903            if ss.features then
904                ssname = ssname .. "*" .. ss.features
905            end
906            if ss.main then
907                main = s
908            end
909            local alreadyloaded = names[ssname] -- for px we load one twice (saves .04 sec)
910-- local sshash = name .. ":" .. ssname
911-- local alreadyloaded = names[sshash] -- for px we load one twice (saves .04 sec)
912            local f, id
913            if alreadyloaded then
914                f, id = alreadyloaded.f, alreadyloaded.id
915                if trace_virtual then
916                    report_virtual("loading font %a subfont %s with name %a is reused",name,s,ssname)
917                end
918            else
919                f, id = fonts.constructors.readanddefine(ssname,size)
920                names[ssname] = {
921-- names[sshash] = {
922                    f        = f,
923                    id       = id,
924                    fontname = ssname, -- diagnostics
925                }
926            end
927            if not f or id == 0 then
928                report_virtual("loading font %a subfont %s with name %a at %p is skipped, not found",name,s,ssname,size)
929            else
930                n = n + 1
931                okset[n] = ss
932                loaded[n] = f
933                fontlist[n] = {
934                    id       = id,
935                    size     = size,
936                    fontname = ssname, -- diagnostics
937                }
938                if trace_virtual then
939                    report_virtual("loading font %a subfont %s with name %a at %p as id %s using encoding %a",name,s,ssname,size,id,ss.vector)
940                end
941            end
942        end
943    end
944    -- beware, loaded[1] is already passed to tex (we need to make a simple copy then .. todo)
945    local parent         = loaded[1] or { } -- a text font
946    local characters     = { }
947    local parameters     = { }
948    local mathparameters = { }
949    local descriptions   = { }
950    local metadata       = { }
951    local properties     = {
952        hasitalics = true,
953        hasmath    = true,
954    }
955    if not goodies then
956        goodies = { }
957    end
958    local main           = {
959        metadata         = metadata,
960        properties       = properties,
961        characters       = characters,
962        descriptions     = descriptions,
963        parameters       = parameters,
964        mathparameters   = mathparameters,
965        fonts            = fontlist,
966        goodies          = goodies,
967        --
968        fullname         = properties.fullname,
969        nomath           = false,
970    }
971    --
972    for key, value in next, parent do
973        if type(value) ~= "table" then
974            main[key] = value
975        end
976    end
977    --
978    if parent.characters then
979        for unicode, character in next, parent.characters do
980            characters[unicode] = character
981        end
982    else
983        report_virtual("font %a has no characters",name)
984    end
985    --
986    if parent.parameters then
987        for key, value in next, parent.parameters do
988            parameters[key] = value
989        end
990    else
991        report_virtual("font %a has no parameters",name)
992    end
993    --
994    local description = { name = "<unset>" }
995    setmetatableindex(descriptions,function() return description end)
996    --
997    if parent.properties then
998        setmetatableindex(properties,parent.properties)
999    end
1000    --
1001    if parent.goodies then
1002        setmetatableindex(goodies,parent.goodies)
1003    end
1004    --
1005    local fullname = properties.fullname -- parent via mt
1006    if fullname then
1007        unique = unique + 1
1008        properties.fullname = fullname .. "-" .. unique
1009    end
1010    --
1011    if not parameters.xheight then
1012        parameters.xheight = 0
1013    end
1014    --
1015    local already_reported = false
1016    local parameters_done  = false
1017    local offset           = 0 -- 0xFF000 -- todo: -- private
1018    --
1019    for s=1,n do
1020        local ss = okset[s]
1021        local fs = loaded[s]
1022        if not fs then
1023            -- skip, error
1024        elseif add_optional and ss.optional then
1025            -- skip, redundant
1026        else
1027            local newparameters     = fs.parameters
1028            local newmathparameters = fs.mathparameters and ss.parameters ~= false
1029            if newmathparameters then
1030                if not parameters_done or ss.parameters then
1031                    mathparameters  = newmathparameters
1032                    parameters_done = true
1033                end
1034            elseif not newparameters then
1035                report_virtual("no parameters set in font %a",name)
1036            elseif ss.extension then
1037                mathparameters.xheight              = newparameters.xheight or 0 -- mathxheight          : height of x
1038                mathparameters.defaultrulethickness = newparameters[ 8]     or 0 -- defaultrulethickness : thickness of \over bars
1039                mathparameters.bigopspacing1        = newparameters[ 9]     or 0 -- bigopspacing1        : minimum clearance above a displayed op
1040                mathparameters.bigopspacing2        = newparameters[10]     or 0 -- bigopspacing2        : minimum clearance below a displayed op
1041                mathparameters.bigopspacing3        = newparameters[11]     or 0 -- bigopspacing3        : minimum baselineskip above displayed op
1042                mathparameters.bigopspacing4        = newparameters[12]     or 0 -- bigopspacing4        : minimum baselineskip below displayed op
1043                mathparameters.bigopspacing5        = newparameters[13]     or 0 -- bigopspacing5        : padding above and below displayed limits
1044            --  report_virtual("loading and virtualizing font %a at size %p, setting ex parameters",name,size)
1045            elseif ss.parameters then
1046                mathparameters.xheight    = newparameters.xheight
1047                                         or mathparameters.xheight
1048                                         or fs.xheight        or 0 -- xheight    : height of x
1049                mathparameters.num1       = newparameters[ 8] or 0 -- num1       : numerator shift-up in display styles
1050                mathparameters.num2       = newparameters[ 9] or 0 -- num2       : numerator shift-up in non-display, non-\atop
1051                mathparameters.num3       = newparameters[10] or 0 -- num3       : numerator shift-up in non-display \atop
1052                mathparameters.denom1     = newparameters[11] or 0 -- denom1     : denominator shift-down in display styles
1053                mathparameters.denom2     = newparameters[12] or 0 -- denom2     : denominator shift-down in non-display styles
1054                mathparameters.sup1       = newparameters[13] or 0 -- sup1       : superscript shift-up in uncramped display style
1055                mathparameters.sup2       = newparameters[14] or 0 -- sup2       : superscript shift-up in uncramped non-display
1056                mathparameters.sup3       = newparameters[15] or 0 -- sup3       : superscript shift-up in cramped styles
1057                mathparameters.sub1       = newparameters[16] or 0 -- sub1       : subscript shift-down if superscript is absent
1058                mathparameters.sub2       = newparameters[17] or 0 -- sub2       : subscript shift-down if superscript is present
1059                mathparameters.supdrop    = newparameters[18] or 0 -- supdrop    : superscript baseline below top of large box
1060                mathparameters.subdrop    = newparameters[19] or 0 -- subdrop    : subscript baseline below bottom of large box
1061                mathparameters.delim1     = newparameters[20] or 0 -- delim1     : size of \atopwithdelims delimiters in display styles
1062                mathparameters.delim2     = newparameters[21] or 0 -- delim2     : size of \atopwithdelims delimiters in non-displays
1063                mathparameters.axisheight = newparameters[22] or 0 -- axisheight : height of fraction lines above the baseline
1064            --  report_virtual("loading and virtualizing font %a at size %p, setting sy parameters",name,size)
1065            end
1066            -- We no longer care about kerns and ligatures here. We use backmap because we need to know
1067            -- the original order and the loader has made a unicode font of it and weird glyph names have
1068            -- spoiled that a bit too.
1069            if ss.overlay then
1070                -- This branch / option will go away.
1071                local fc    = fs.characters
1072                local first = ss.first
1073                if first then
1074                    local last = ss.last or first
1075                    for unicode = first, last do
1076                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
1077                    end
1078                else
1079                    for unicode, data in next, fc do
1080                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
1081                    end
1082                end
1083            else
1084                local vectorname = ss.vector
1085                if vectorname then
1086                    local vector      = mathencodings[vectorname]
1087                    local isextension = ss.extension
1088                    if vector then
1089                        local fc       = fs.characters
1090                        local fd       = fs.descriptions
1091                        local fp       = fs.parameters
1092                        local fontname = fs.properties.name or "unknown"
1093                        local skewchar = ss.skewchar
1094                        local backmap  = ss.backmap
1095                        local badones  = ss.badones
1096                        local ignore   = ss.ignore
1097                        local done     = { }
1098                        local extras   = { }
1099                        if backmap == false then
1100                         -- backmap = { }
1101                        elseif not backmap then
1102                            backmap = { }
1103                            for unicode, character in next, fc do
1104                                backmap[character.order or character.index or unicode] = unicode
1105                            end
1106                            ss.backmap  = backmap
1107                        end
1108                        for unicode, index in sortedhash(vector) do
1109                            local uni = backmap and backmap[index] or index
1110                            local fci = fc[uni]
1111                            if not fci then
1112                                local rf = reported[fontname]
1113                                if not rf then rf = { } reported[fontname] = rf end
1114                                local rv = rf[vectorname]
1115                                if not rv then rv = { } rf[vectorname] = rv end
1116                                local ru = rv[unicode]
1117                                if not ru then
1118                                    if trace_virtual then
1119                                        local d = chardata[unicode].description
1120                                        if index then
1121                                            report_virtual("character %C has no index %H in vector %a for font %a (%S)",unicode,index,vectorname,fontname,d)
1122                                        else
1123                                            report_virtual("character %C has no entry in vector %a for font %a (%S)",unicode,vectorname,fontname,d)
1124                                        end
1125                                    elseif not already_reported then
1126                                        report_virtual("the mapping is incomplete for %a at %p",name,size)
1127                                        already_reported = true
1128                                    end
1129                                    rv[unicode] = true
1130                                end
1131                            else
1132                                local name = fci.name or ""
1133                                if ignore and ignore[name] then
1134                                -- get rid of ugly slanted antykwa { }
1135                                else
1136                                    local u = mathematics.gaps[unicode] or unicode
1137                                    local t = virtualize(s,uni,fci,skewchar,tonumber(badones and badones[name]),mathparameters,u,fp)
1138                                    done[uni] = t
1139                                    characters[unicode] = t
1140                                    fci.unicode = u
1141                                end
1142                            end
1143                        end
1144--                         if ss.jmn then
1145--                             local extension = mathencodings["extensible-jmn-private"]
1146--                             for unicode, index in sortedhash(extension) do
1147--                                 if not characters[unicode] then
1148--                                     local uni = backmap and backmap[index] or index
1149--                                     local fci = fc[uni]
1150--                                     characters[unicode] = virtualize(s,uni,fci,skewchar,false,mathparameters,unicode,fp)
1151--                                 end
1152--                             end
1153--                         end
1154                        if isextension then
1155                            local extension = mathencodings["large-to-small"]
1156                            for uni, fci in sortedhash(fc) do
1157                                local name = fci.name or ""
1158                                if ignore and ignore[name] then
1159                                    -- get rid of ugly antykwa bar
1160                                elseif not done[uni] then
1161                                    local t = virtualize(s,uni,fci,skewchar,tonumber(badones and badones[name]),mathparameters,nil,fp)
1162                                    local o = addprivate(main,f_extra(offset))
1163                                    extras[uni] = o
1164                                    characters[o] = t
1165                                    done[uni] = t
1166                                    offset = offset + 1
1167                                end
1168                            end
1169                            for uni, fci in sortedhash(done) do
1170                                local next = fci.next
1171                                if next then
1172                                    fci.next = extras[backmap and backmap[next] or next]
1173                                end
1174                                local parts = fci.parts
1175                                if parts then
1176                                    local p = table.copy(parts)
1177                                    for i=1,#p do
1178                                        local part  = p[i]
1179                                        local glyph = part.glyph
1180                                        part.glyph  = extras[backmap and backmap[glyph] or glyph] or glyph
1181                                    end
1182                                    fci.keepvirtual      = true
1183                                    fci.parts            = p
1184                                    fci.partsorientation = "vertical" -- nasty as some are horizontal
1185                                    fci.partsitalic      = fci.partsitalic or fci.italic
1186                                end
1187                            end
1188                            for unicode, index in sortedhash(extension) do
1189                                local fci = characters[unicode]
1190                                if fci then
1191                                    fci.next = extras[backmap[index] or index]
1192                                end
1193                            end
1194                            local extension = mathencodings["large-to-small-private"]
1195                            for unicode, index in sortedhash(extension) do
1196                                if not characters[unicode] then
1197                                    local uni = backmap and backmap[index] or index
1198                                    local fci = fc[uni]
1199                                    characters[unicode] = virtualize(s,uni,fci,skewchar,false,mathparameters,unicode,fp)
1200                                end
1201                            end
1202                        end
1203                    else
1204                        report_virtual("error in loading %a, problematic vector %a",name,vectorname)
1205                    end
1206                end
1207            end
1208         -- mathematics.extras.copy(main) -- Not needed here (yet) ... might go.
1209        end
1210    end
1211    --
1212    main.mathparameters = mathparameters -- still traditional ones
1213    fontlist[#fontlist+1] = {
1214        id       = 0,
1215        size     = size,
1216        fontname = name, -- diagnostics
1217    }
1218    --
1219    local virtualtweaks = virtual.tweaks
1220    if virtualtweaks then
1221        --
1222        local mathtweaks  = mathematics.tweaks
1223        local knowntweaks = {
1224            addmissing = function(main,specification)
1225                local action = specification.action
1226                if action then
1227                    action(main,specification) -- source == target
1228                end
1229            end
1230        }
1231        --
1232        for i=1,#virtualtweaks do
1233            local specification = virtualtweaks[i]
1234            local tweak         = specification.tweak
1235            local action        = knowntweaks[tweak]
1236            if action then
1237                action(main,specification)
1238            else
1239                action = mathtweaks[tweak]
1240                if action then
1241                    action(main,main,specification)
1242                end
1243            end
1244        end
1245    end
1246    --
1247    mathematics.addfallbacks(main)
1248    --
1249    main.properties.math_is_scaled = true -- signal
1250    fonts.constructors.assignmathparameters(main,main)
1251    --
1252    mathematics.initializeparameters(main,main,"noscale")
1253    main.mathconstants = main.mathparameters -- we directly pass it to TeX (bypasses the scaler) so this is needed
1254    main.MathConstants = main.mathconstants
1255    main.nomath        = false
1256    --
1257    if trace_virtual or trace_timings then
1258        report_virtual("loading and virtualizing font %a at size %p took %0.3f seconds",name,size,os.clock()-start)
1259    end
1260    --
1261    return main
1262end
1263
1264function mathematics.makefont(name,set,goodies)
1265    fonts.definers.methods.variants[name] = function(specification)
1266        return vfmath.define(specification,set,goodies)
1267    end
1268end
1269
1270-- helpers (todo: gaps)
1271
1272function vfmath.setletters(font_encoding, name, uppercase, lowercase)
1273    local enc = font_encoding[name]
1274    for i = 0,25 do
1275        enc[uppercase+i] = i + 0x41
1276        enc[lowercase+i] = i + 0x61
1277    end
1278end
1279
1280function vfmath.setdigits(font_encoding, name, digits)
1281    local enc = font_encoding[name]
1282    for i = 0,9 do
1283        enc[digits+i] = i + 0x30
1284    end
1285end
1286
1287-- local step = 0.2 -- 0.1 is nicer but gives larger files
1288
1289-- local function clipped(main,characters,id,size,unicode,original) -- push/pop needed?
1290--     local minus = characters[original]
1291--     if minus then
1292--         local mu    = size/18
1293--         local step  = 3*mu
1294--         local width = minus.width
1295--         if width > step then
1296--             width = width - step
1297--             step  = step / 2
1298--         else
1299--             width = width / 2
1300--             step  = width
1301--         end
1302--         characters[unicode] = {
1303--             width    = width,
1304--             height   = minus.height,
1305--             depth    = minus.depth,
1306--             commands = {
1307--                 push,
1308--                 leftcommand[step],
1309--                 slotcommand[0][original],
1310--                 pop,
1311--             }
1312--         }
1313--     end
1314-- end
1315
1316-- local function vertbar(main,characters,id,size,parent,scale,unicode)
1317--     local cp = characters[parent]
1318--     if cp then
1319--         local sc = scale * size
1320--         local pc = slotcommand[0][parent]
1321--         characters[unicode] = {
1322--             width    = cp.width,
1323--             height   = cp.height + sc,
1324--             depth    = cp.depth + sc,
1325--             next     = cp.next, -- can be extensible
1326--             commands = {
1327--                 push, upcommand  [sc], pc, pop,
1328--                 push, downcommand[sc], pc, pop,
1329--                                        pc,
1330--             },
1331--         }
1332--         cp.next = unicode
1333--     end
1334-- end
1335
1336-- vertbar  (main,characters,id,size,0x0007C,0.10,0xFF601) -- big  : 0.85 bodyfontsize
1337-- vertbar  (main,characters,id,size,0xFF601,0.30,0xFF602) -- Big  : 1.15 bodyfontsize
1338-- vertbar  (main,characters,id,size,0xFF602,0.30,0xFF603) -- bigg : 1.45 bodyfontsize
1339-- vertbar  (main,characters,id,size,0xFF603,0.30,0xFF604) -- Bigg : 1.75 bodyfontsize
1340-- vertbar  (main,characters,id,size,0x02016,0.10,0xFF605)
1341-- vertbar  (main,characters,id,size,0xFF605,0.30,0xFF606)
1342-- vertbar  (main,characters,id,size,0xFF606,0.30,0xFF607)
1343-- vertbar  (main,characters,id,size,0xFF607,0.30,0xFF608)
1344
1345-- clipped  (main,characters,id,size,0xFF501,0x0002D) -- minus
1346-- clipped  (main,characters,id,size,0xFF502,0x02190) -- lefthead
1347-- clipped  (main,characters,id,size,0xFF503,0x02192) -- righthead
1348-- clipped  (main,characters,id,size,0xFF504,ps("maps to piece") -- mapsto
1349-- clipped  (main,characters,id,size,0xFF505,0xFE322) -- lhook
1350-- clipped  (main,characters,id,size,0xFF506,0xFE323) -- rhook
1351-- clipped  (main,characters,id,size,0xFF507,0xFE324) -- mapsfrom
1352-- clipped  (main,characters,id,size,0xFF508,0x021D0) -- double lefthead
1353-- clipped  (main,characters,id,size,0xFF509,0x021D2) -- double righthead
1354-- clipped  (main,characters,id,size,0xFF50A,0x0003D) -- equal
1355-- clipped  (main,characters,id,size,0xFF50B,0x0219E) -- lefttwohead
1356-- clipped  (main,characters,id,size,0xFF50C,0x021A0) -- righttwohead
1357-- clipped  (main,characters,id,size,0xFF50D,0xFF350) -- lr arrow combi snippet
1358-- clipped  (main,characters,id,size,0xFF50E,0xFF351) -- lr arrow combi snippet
1359-- clipped  (main,characters,id,size,0xFF50F,0xFF352) -- lr arrow combi snippet
1360-- clipped  (main,characters,id,size,0xFF510,0x02261) -- equiv
1361
1362-- extension(main,characters,id,size,0x2190,0xFF502,0xFF501,0xFF501) -- \leftarrow
1363-- extension(main,characters,id,size,0x2192,0xFF501,0xFF501,0xFF503) -- \rightarrow
1364
1365-- extension(main,characters,id,size,0x002D,0xFF501,0xFF501,0xFF501)                 -- \rel
1366-- extension(main,characters,id,size,0x003D,0xFF50A,0xFF50A,0xFF50A)                 -- \equal
1367-- extension(main,characters,id,size,0x2261,0xFF510,0xFF510,0xFF510)                 -- \equiv
1368
1369-- local lh = ps("left hook piece")]  -- was FE322
1370-- local rh = ps("right hook piece")] -- was FE323
1371
1372-- jointwo  (main,characters,id,size,0x21A6,ps("maps to piece"),0,0x02192)                       -- \mapstochar\rightarrow
1373-- jointwo  (main,characters,id,size,0x21A9,0x02190,joinrelfactor,0xFE323)           -- \leftarrow\joinrel\rhook
1374-- jointwo  (main,characters,id,size,0x21AA,0xFE322,joinrelfactor,0x02192)           -- \lhook\joinrel\rightarrow
1375-- jointhree(main,characters,id,size,0x27FB,0x02190,joinrelfactor,0x0002D,0,0xFE324) -- \leftarrow\joinrel\relbar\mapsfromchar
1376
1377-- jointhree(main,characters,id,size,0x27FC,ps("maps to piece"),0,0x0002D,joinrelfactor,0x02192) -- \mapstochar\relbar\joinrel\rightarrow
1378
1379-- extension(main,characters,id,size,0x21A6,0xFF504,0xFF501,0xFF503) -- \mapstochar\rightarrow
1380-- extension(main,characters,id,size,0x21A9,0xFF502,0xFF501,0xFF506) -- \leftarrow\joinrel\rhook
1381-- extension(main,characters,id,size,0x21AA,0xFF505,0xFF501,0xFF503) -- \lhook\joinrel\rightarrow
1382-- extension(main,characters,id,size,0x27F5,0xFF502,0xFF501,0xFF501) -- \leftarrow\joinrel\relbar
1383-- extension(main,characters,id,size,0x27F6,0xFF501,0xFF501,0xFF503) -- \relbar\joinrel\rightarrow
1384-- extension(main,characters,id,size,0x27F7,0xFF502,0xFF501,0xFF503) -- \leftarrow\joinrel\rightarrow
1385-- extension(main,characters,id,size,0x27F8,0xFF508,0xFF50A,0xFF50A) -- \Leftarrow\joinrel\Relbar
1386-- extension(main,characters,id,size,0x27F9,0xFF50A,0xFF50A,0xFF509) -- \Relbar\joinrel\Rightarrow
1387-- extension(main,characters,id,size,0x27FA,0xFF508,0xFF50A,0xFF509) -- \Leftarrow\joinrel\Rightarrow
1388-- extension(main,characters,id,size,0x27FB,0xFF502,0xFF501,0xFF507) -- \leftarrow\joinrel\relbar\mapsfromchar
1389-- extension(main,characters,id,size,0x27FC,0xFF504,0xFF501,0xFF503) -- \mapstochar\relbar\joinrel\rightarrow
1390
1391-- extension(main,characters,id,size,0x219E,0xFF50B,0xFF501,0xFF501) -- \twoheadleftarrow\joinrel\relbar
1392-- extension(main,characters,id,size,0x21A0,0xFF501,0xFF501,0xFF50C) -- \relbar\joinrel\twoheadrightarrow
1393-- extension(main,characters,id,size,0x21C4,0xFF50D,0xFF50E,0xFF50F) -- leftoverright
1394