math-vfu.lmt /size: 60 Kb    last modification: 2025-02-21 11:03
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, tohash = table.copy, table.sortedhash, table.tohash
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            bottomanchor = olddata.bottomanchor,
746            kerns        = olddata.kerns,
747            mathkerns    = olddata.mathkerns,
748            tounicode    = olddata.tounicode,
749            unicode      = olddata.unicode,
750         -- smaller      = olddata.smaller,
751            commands     = { slotcommand[slot][unicode] },
752        }
753        local glyphdata = newdata
754        local nextglyph = olddata.next
755        while nextglyph do
756            local oldnextdata = original[nextglyph]
757            if oldnextdata then
758                local newnextdata = {
759                    width        = oldnextdata.width,
760                    height       = oldnextdata.height,
761                    depth        = oldnextdata.depth,
762                    italic       = oldnextdata.italic,
763                    topanchor    = oldnextdata.topanchor,
764                    bottomanchor = oldnextdata.bottomanchor,
765                    kerns        = olddata.kerns,
766                    mathkerns    = olddata.mathkerns,
767                    tounicode    = olddata.tounicode,
768                    unicode      = olddata.unicode,
769                    smaller      = olddata.smaller,
770                    commands     = { slotcommand[slot][nextglyph] },
771                }
772                local newnextglyph = addprivate(main,formatters["M-N-%H"](nextglyph),newnextdata)
773                newdata.next = newnextglyph
774                local nextnextglyph = oldnextdata.next
775                if nextnextglyph == nextglyph then
776                    break
777                else
778                    olddata   = oldnextdata
779                    newdata   = newnextdata
780                    nextglyph = nextnextglyph
781                end
782            else
783                break -- safeguard (when testing stuff)
784            end
785        end
786        local oldparts = olddata.parts
787        if oldparts then
788            newparts = fastcopy(oldparts)
789            newdata.parts            = newparts
790            newdata.partsorientation = olddata.partsorientation
791            newdata.partsitalic      = olddata.partsitalic
792            for i=1,#newparts do
793                local newpart  = newparts[i]
794                local oldglyph = newpart.glyph
795                local olddata  = original[oldglyph]
796                local newdata  = {
797                    width     = olddata.width,
798                    height    = olddata.height,
799                    depth     = olddata.depth,
800                    tounicode = olddata.tounicode,
801                    unicode   = olddata.unicode,
802                    commands  = { slotcommand[slot][oldglyph] },
803                }
804                newpart.glyph = addprivate(main,formatters["M-P-%H"](oldglyph),newdata)
805            end
806        end
807        local smaller = olddata.smaller
808        if smaller then
809            local smallerdata = copy_glyph(main,target,original,smaller,slot)
810            glyphdata.smaller = addprivate(main,formatters["M-S-%H"](smaller),smallerdata)
811        end
812        return glyphdata
813    end
814end
815
816vfmath.copy_glyph = copy_glyph
817
818-- It's time to get rid of this type 1 mess ... take iwona: uppercase /A .. Z but
819-- lowercase /a.math ... /z.math ... anyway, we now follow a slightly different
820-- route: use the "order" field. I can probably make it a bit leaner but it's not
821-- worth spending much time on now.
822
823local noitalics = true -- false can be used to test the engine
824
825-- revision timestamp 2023: after watching ten times "Over The Mountain (feat.
826-- Sierra Hull)" - Cory Wong (Live @ Brooklyn Steel FEB 2022):
827--
828-- https://www.youtube.com/watch?v=lT-W-UEcsns
829-- https://www.youtube.com/watch?v=TPGbj1gFalA
830
831-- The following code is now only used for iwona and antykwa, so I simplified it a
832-- bit to suit that purpose. It might get even simpler.
833
834local integrals = setmetatableindex(function(t,k)
835    integrals = tohash(mathematics.tweaks.subsets.integrals)
836    return integrals[k]
837end)
838
839local function virtualize(s,uni,fci,skewchar,move,mathparameters,unicode,parameters,characters)
840    if fci then
841        local kerns        = fci.kerns
842        local width        = fci.width
843        local height       = fci.height
844        local depth        = fci.depth
845        local italic       = fci.italic
846        local topanchor    = fci.topanchor
847        local bottomanchor = fci.bottomanchor
848        local bottomright  = fci.bottomright
849        local advance      = width
850        local nextone      = fci.next
851        local yoffset
852        if kerns and skewchar then
853            local k = kerns[skewchar]
854            if k then
855                topanchor = width/2 + k
856            end
857        end
858--         if italic and noitalics then
859--             width = width + italic
860--             bottomright = - italic
861--             if not fci.anchored and integrals[unicode] then
862--                 topanchor    = (width + italic) / 2
863--                 bottomanchor = (width - italic) / 2
864--                         inspect(fci)
865--                 if nextone then
866--                     local n = nextone
867--                     while n do
868--                         local c = characters[n]
869--                         if c and not c.anchored and integrals[c.unicode] then
870--                             local italic = c.italic
871--                             local width  = c.width + italic
872--                             c.bottomright  = - italic
873--                             c.topanchor    = (width + italic) / 2
874--                             c.bottomanchor = (width - italic) / 2
875--                             c.anchored     = true
876--                             c.italic       = nil
877--                         end
878--                         n = c.next
879--                     end
880--                 end
881--             end
882--             italic = nil
883--         end
884        if move then  -- 0x222B
885            local axis = move * mathparameters.axisheight
886            local half = (height + depth ) / 2
887            yoffset = depth - (half - axis)
888            height = half + axis
889            depth  = half - axis
890        end
891        --
892        return {
893            advance          = advance,
894            width            = width,
895            height           = height,
896            depth            = depth,
897            italic           = italic,
898            bottomright      = bottomright,
899            topanchor        = topanchor,
900            bottomanchor     = bottomanchor,
901            yoffset          = yoffset,
902            commands         = { slotcommand[s][uni] },
903         -- keepvirtual      = true,
904            next             = nextone,
905            parts            = fci.parts,
906            partsorientation = fci.partsorientation,
907            partsitalic      = fci.partsitalic,
908            unicode          = unicode,
909            name             = fci.name,
910--             anchored         = true,
911        }
912    else
913        -- error
914    end
915end
916
917function vfmath.define(specification,virtual,goodies)
918    local name     = specification.name -- symbolic name
919    local size     = specification.size -- given size
920    local loaded   = { }
921    local fontlist = { }
922    local names    = { }
923    local main     = nil
924    local start    = (trace_virtual or trace_timings) and os.clock()
925    local okset    = { }
926    local n        = 0
927    local f_extra  = formatters["virtual.extra.%05X"]
928    local setlist  = virtual.recipe or virtual
929    for s=1,#setlist do
930        local ss     = setlist[s]
931        local ssname = ss.name
932        if add_optional and ss.optional then
933            if trace_virtual then
934                report_virtual("loading font %a subfont %s with name %a at %p is skipped",name,s,ssname,size)
935            end
936        else
937            if ss.features then
938                ssname = ssname .. "*" .. ss.features
939            end
940            if ss.main then
941                main = s
942            end
943            local alreadyloaded = names[ssname] -- for px we load one twice (saves .04 sec)
944-- local sshash = name .. ":" .. ssname
945-- local alreadyloaded = names[sshash] -- for px we load one twice (saves .04 sec)
946            local f, id
947            if alreadyloaded then
948                f, id = alreadyloaded.f, alreadyloaded.id
949                if trace_virtual then
950                    report_virtual("loading font %a subfont %s with name %a is reused",name,s,ssname)
951                end
952            else
953                f, id = fonts.constructors.readanddefine(ssname,size)
954                names[ssname] = {
955-- names[sshash] = {
956                    f        = f,
957                    id       = id,
958                    fontname = ssname, -- diagnostics
959                }
960            end
961            if not f or id == 0 then
962                report_virtual("loading font %a subfont %s with name %a at %p is skipped, not found",name,s,ssname,size)
963            else
964                n = n + 1
965                okset[n] = ss
966                loaded[n] = f
967                fontlist[n] = {
968                    id       = id,
969                    size     = size,
970                    fontname = ssname, -- diagnostics
971                }
972                if trace_virtual then
973                    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)
974                end
975            end
976        end
977    end
978    -- beware, loaded[1] is already passed to tex (we need to make a simple copy then .. todo)
979    local parent         = loaded[1] or { } -- a text font
980    local characters     = { }
981    local parameters     = { }
982    local mathparameters = { }
983    local descriptions   = { }
984    local metadata       = { }
985    local properties     = {
986        hasitalics = true,
987        hasmath    = true,
988    }
989    if not goodies then
990        goodies = { }
991    end
992    local main           = {
993        metadata         = metadata,
994        properties       = properties,
995        characters       = characters,
996        descriptions     = descriptions,
997        parameters       = parameters,
998        mathparameters   = mathparameters,
999        fonts            = fontlist,
1000        goodies          = goodies,
1001        --
1002        fullname         = properties.fullname,
1003        nomath           = false,
1004    }
1005    --
1006    for key, value in next, parent do
1007        if type(value) ~= "table" then
1008            main[key] = value
1009        end
1010    end
1011    --
1012    if parent.characters then
1013        for unicode, character in next, parent.characters do
1014            characters[unicode] = character
1015        end
1016    else
1017        report_virtual("font %a has no characters",name)
1018    end
1019    --
1020    if parent.parameters then
1021        for key, value in next, parent.parameters do
1022            parameters[key] = value
1023        end
1024    else
1025        report_virtual("font %a has no parameters",name)
1026    end
1027    --
1028    local description = { name = "<unset>" }
1029    setmetatableindex(descriptions,function() return description end)
1030    --
1031    if parent.properties then
1032        setmetatableindex(properties,parent.properties)
1033    end
1034    --
1035    if parent.goodies then
1036        setmetatableindex(goodies,parent.goodies)
1037    end
1038    --
1039    local fullname = properties.fullname -- parent via mt
1040    if fullname then
1041        unique = unique + 1
1042        properties.fullname = fullname .. "-" .. unique
1043    end
1044    --
1045    if not parameters.xheight then
1046        parameters.xheight = 0
1047    end
1048    --
1049    local already_reported = false
1050    local parameters_done  = false
1051    local offset           = 0 -- 0xFF000 -- todo: -- private
1052    --
1053    for s=1,n do
1054        local ss = okset[s]
1055        local fs = loaded[s]
1056        if not fs then
1057            -- skip, error
1058        elseif add_optional and ss.optional then
1059            -- skip, redundant
1060        else
1061            local newparameters     = fs.parameters
1062            local newmathparameters = fs.mathparameters and ss.parameters ~= false
1063            if newmathparameters then
1064                if not parameters_done or ss.parameters then
1065                    mathparameters  = newmathparameters
1066                    parameters_done = true
1067                end
1068            elseif not newparameters then
1069                report_virtual("no parameters set in font %a",name)
1070            elseif ss.extension then
1071                mathparameters.xheight              = newparameters.xheight or 0 -- mathxheight          : height of x
1072                mathparameters.defaultrulethickness = newparameters[ 8]     or 0 -- defaultrulethickness : thickness of \over bars
1073                mathparameters.bigopspacing1        = newparameters[ 9]     or 0 -- bigopspacing1        : minimum clearance above a displayed op
1074                mathparameters.bigopspacing2        = newparameters[10]     or 0 -- bigopspacing2        : minimum clearance below a displayed op
1075                mathparameters.bigopspacing3        = newparameters[11]     or 0 -- bigopspacing3        : minimum baselineskip above displayed op
1076                mathparameters.bigopspacing4        = newparameters[12]     or 0 -- bigopspacing4        : minimum baselineskip below displayed op
1077                mathparameters.bigopspacing5        = newparameters[13]     or 0 -- bigopspacing5        : padding above and below displayed limits
1078            --  report_virtual("loading and virtualizing font %a at size %p, setting ex parameters",name,size)
1079            elseif ss.parameters then
1080                mathparameters.xheight    = newparameters.xheight
1081                                         or mathparameters.xheight
1082                                         or fs.xheight        or 0 -- xheight    : height of x
1083                mathparameters.num1       = newparameters[ 8] or 0 -- num1       : numerator shift-up in display styles
1084                mathparameters.num2       = newparameters[ 9] or 0 -- num2       : numerator shift-up in non-display, non-\atop
1085                mathparameters.num3       = newparameters[10] or 0 -- num3       : numerator shift-up in non-display \atop
1086                mathparameters.denom1     = newparameters[11] or 0 -- denom1     : denominator shift-down in display styles
1087                mathparameters.denom2     = newparameters[12] or 0 -- denom2     : denominator shift-down in non-display styles
1088                mathparameters.sup1       = newparameters[13] or 0 -- sup1       : superscript shift-up in uncramped display style
1089                mathparameters.sup2       = newparameters[14] or 0 -- sup2       : superscript shift-up in uncramped non-display
1090                mathparameters.sup3       = newparameters[15] or 0 -- sup3       : superscript shift-up in cramped styles
1091                mathparameters.sub1       = newparameters[16] or 0 -- sub1       : subscript shift-down if superscript is absent
1092                mathparameters.sub2       = newparameters[17] or 0 -- sub2       : subscript shift-down if superscript is present
1093                mathparameters.supdrop    = newparameters[18] or 0 -- supdrop    : superscript baseline below top of large box
1094                mathparameters.subdrop    = newparameters[19] or 0 -- subdrop    : subscript baseline below bottom of large box
1095                mathparameters.delim1     = newparameters[20] or 0 -- delim1     : size of \atopwithdelims delimiters in display styles
1096                mathparameters.delim2     = newparameters[21] or 0 -- delim2     : size of \atopwithdelims delimiters in non-displays
1097                mathparameters.axisheight = newparameters[22] or 0 -- axisheight : height of fraction lines above the baseline
1098            --  report_virtual("loading and virtualizing font %a at size %p, setting sy parameters",name,size)
1099            end
1100            -- We no longer care about kerns and ligatures here. We use backmap because we need to know
1101            -- the original order and the loader has made a unicode font of it and weird glyph names have
1102            -- spoiled that a bit too.
1103            if ss.overlay then
1104                -- This branch / option will go away.
1105                local fc    = fs.characters
1106                local first = ss.first
1107                if first then
1108                    local last = ss.last or first
1109                    for unicode = first, last do
1110                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
1111                    end
1112                else
1113                    for unicode, data in next, fc do
1114                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
1115                    end
1116                end
1117            else
1118                local vectorname = ss.vector
1119                if vectorname then
1120                    local vector      = mathencodings[vectorname]
1121                    local isextension = ss.extension
1122                    if vector then
1123                        local fc       = fs.characters
1124                        local fd       = fs.descriptions
1125                        local fp       = fs.parameters
1126                        local fontname = fs.properties.name or "unknown"
1127                        local skewchar = ss.skewchar
1128                        local backmap  = ss.backmap
1129                        local badones  = ss.badones
1130                        local ignore   = ss.ignore
1131                        local done     = { }
1132                        local extras   = { }
1133                        if backmap == false then
1134                         -- backmap = { }
1135                        elseif not backmap then
1136                            backmap = { }
1137                            for unicode, character in next, fc do
1138                                backmap[character.order or character.index or unicode] = unicode
1139                            end
1140                            ss.backmap  = backmap
1141                        end
1142                        for unicode, index in sortedhash(vector) do
1143                            local uni = backmap and backmap[index] or index
1144                            local fci = fc[uni]
1145                            if not fci then
1146                                local rf = reported[fontname]
1147                                if not rf then rf = { } reported[fontname] = rf end
1148                                local rv = rf[vectorname]
1149                                if not rv then rv = { } rf[vectorname] = rv end
1150                                local ru = rv[unicode]
1151                                if not ru then
1152                                    if trace_virtual then
1153                                        local d = chardata[unicode].description
1154                                        if index then
1155                                            report_virtual("character %C has no index %H in vector %a for font %a (%S)",unicode,index,vectorname,fontname,d)
1156                                        else
1157                                            report_virtual("character %C has no entry in vector %a for font %a (%S)",unicode,vectorname,fontname,d)
1158                                        end
1159                                    elseif not already_reported then
1160                                        report_virtual("the mapping is incomplete for %a at %p",name,size)
1161                                        already_reported = true
1162                                    end
1163                                    rv[unicode] = true
1164                                end
1165                            else
1166                                local name = fci.name or ""
1167                                if ignore and ignore[name] then
1168                                -- get rid of ugly slanted antykwa { }
1169                                else
1170                                    local u = mathematics.gaps[unicode] or unicode
1171                                    local t = virtualize(s,uni,fci,skewchar,tonumber(badones and badones[name]),mathparameters,u,fp,characters)
1172                                    done[uni] = t
1173                                    characters[unicode] = t
1174                                    fci.unicode = u
1175                                end
1176                            end
1177                        end
1178                     -- if ss.jmn then
1179                     --     local extension = mathencodings["extensible-jmn-private"]
1180                     --     for unicode, index in sortedhash(extension) do
1181                     --         if not characters[unicode] then
1182                     --             local uni = backmap and backmap[index] or index
1183                     --             local fci = fc[uni]
1184                     --             characters[unicode] = virtualize(s,uni,fci,skewchar,false,mathparameters,unicode,fp,characters)
1185                     --         end
1186                     --     end
1187                     -- end
1188                        if isextension then
1189                            local extension = mathencodings["large-to-small"]
1190                            for uni, fci in sortedhash(fc) do
1191                                local name = fci.name or ""
1192                                if ignore and ignore[name] then
1193                                    -- get rid of ugly antykwa bar
1194                                elseif not done[uni] then
1195                                    local t = virtualize(s,uni,fci,skewchar,tonumber(badones and badones[name]),mathparameters,nil,fp,characters)
1196                                    local o = addprivate(main,f_extra(offset))
1197                                    extras[uni] = o
1198                                    characters[o] = t
1199                                    done[uni] = t
1200                                    offset = offset + 1
1201                                end
1202                            end
1203                            for uni, fci in sortedhash(done) do
1204                                local next = fci.next
1205                                if next then
1206                                    fci.next = extras[backmap and backmap[next] or next]
1207                                end
1208                                local parts = fci.parts
1209                                if parts then
1210                                    local p = table.copy(parts)
1211                                    for i=1,#p do
1212                                        local part  = p[i]
1213                                        local glyph = part.glyph
1214                                        part.glyph  = extras[backmap and backmap[glyph] or glyph] or glyph
1215                                    end
1216                                    fci.keepvirtual      = true
1217                                    fci.parts            = p
1218                                    fci.partsorientation = "vertical" -- nasty as some are horizontal
1219                                    fci.partsitalic      = fci.partsitalic or fci.italic
1220                                end
1221                            end
1222                            for unicode, index in sortedhash(extension) do
1223                                local fci = characters[unicode]
1224                                if fci then
1225                                    fci.next = extras[backmap[index] or index]
1226                                end
1227                            end
1228                            local extension = mathencodings["large-to-small-private"]
1229                            for unicode, index in sortedhash(extension) do
1230                                if not characters[unicode] then
1231                                    local uni = backmap and backmap[index] or index
1232                                    local fci = fc[uni]
1233                                    characters[unicode] = virtualize(s,uni,fci,skewchar,false,mathparameters,unicode,fp,characters)
1234                                end
1235                            end
1236                        end
1237                    else
1238                        report_virtual("error in loading %a, problematic vector %a",name,vectorname)
1239                    end
1240                end
1241            end
1242         -- mathematics.extras.copy(main) -- Not needed here (yet) ... might go.
1243        end
1244    end
1245    for unicode, data in next, characters do
1246        local n = data.next
1247        while n do
1248            local c = characters[n]
1249            if c then
1250                c.unicode = unicode
1251                n = c.next
1252            else
1253                break
1254            end
1255        end
1256    end
1257    if noitalics then
1258        for unicode, data in next, characters do
1259            local italic = data.italic
1260            if italic and italic ~= 0 then
1261                local width = data.width + italic
1262                if integrals[data.unicode] then -- and not data.anchored then
1263                    data.topanchor    = (width + italic) / 2
1264                    data.bottomanchor = (width - italic) / 2
1265                 -- data.anchored     = true
1266                end
1267                data.width       = width
1268                data.italic      = nil
1269                data.bottomright = - italic
1270            end
1271        end
1272    end
1273    --
1274    main.mathparameters = mathparameters -- still traditional ones
1275    fontlist[#fontlist+1] = {
1276        id       = 0,
1277        size     = size,
1278        fontname = name, -- diagnostics
1279    }
1280    --
1281    local virtualtweaks = virtual.tweaks
1282    if virtualtweaks then
1283        --
1284        local mathtweaks  = mathematics.tweaks
1285        local knowntweaks = {
1286            addmissing = function(main,specification)
1287                local action = specification.action
1288                if action then
1289                    action(main,specification) -- source == target
1290                end
1291            end
1292        }
1293        --
1294        for i=1,#virtualtweaks do
1295            local specification = virtualtweaks[i]
1296            local tweak         = specification.tweak
1297            local action        = knowntweaks[tweak]
1298            if action then
1299                action(main,specification)
1300            else
1301                action = mathtweaks[tweak]
1302                if action then
1303                    action(main,main,specification)
1304                end
1305            end
1306        end
1307    end
1308    --
1309    mathematics.addfallbacks(main)
1310    --
1311    main.properties.math_is_scaled = true -- signal
1312    fonts.constructors.assignmathparameters(main,main)
1313    --
1314    mathematics.initializeparameters(main,main,"noscale")
1315    main.mathconstants = main.mathparameters -- we directly pass it to TeX (bypasses the scaler) so this is needed
1316    main.MathConstants = main.mathconstants
1317    main.nomath        = false
1318    --
1319    if trace_virtual or trace_timings then
1320        report_virtual("loading and virtualizing font %a at size %p took %0.3f seconds",name,size,os.clock()-start)
1321    end
1322    --
1323    return main
1324end
1325
1326function mathematics.makefont(name,set,goodies)
1327    fonts.definers.methods.variants[name] = function(specification)
1328        return vfmath.define(specification,set,goodies)
1329    end
1330end
1331
1332-- helpers (todo: gaps)
1333
1334function vfmath.setletters(font_encoding, name, uppercase, lowercase)
1335    local enc = font_encoding[name]
1336    for i = 0,25 do
1337        enc[uppercase+i] = i + 0x41
1338        enc[lowercase+i] = i + 0x61
1339    end
1340end
1341
1342function vfmath.setdigits(font_encoding, name, digits)
1343    local enc = font_encoding[name]
1344    for i = 0,9 do
1345        enc[digits+i] = i + 0x30
1346    end
1347end
1348
1349-- local step = 0.2 -- 0.1 is nicer but gives larger files
1350
1351-- local function clipped(main,characters,id,size,unicode,original) -- push/pop needed?
1352--     local minus = characters[original]
1353--     if minus then
1354--         local mu    = size/18
1355--         local step  = 3*mu
1356--         local width = minus.width
1357--         if width > step then
1358--             width = width - step
1359--             step  = step / 2
1360--         else
1361--             width = width / 2
1362--             step  = width
1363--         end
1364--         characters[unicode] = {
1365--             width    = width,
1366--             height   = minus.height,
1367--             depth    = minus.depth,
1368--             commands = {
1369--                 push,
1370--                 leftcommand[step],
1371--                 slotcommand[0][original],
1372--                 pop,
1373--             }
1374--         }
1375--     end
1376-- end
1377
1378-- local function vertbar(main,characters,id,size,parent,scale,unicode)
1379--     local cp = characters[parent]
1380--     if cp then
1381--         local sc = scale * size
1382--         local pc = slotcommand[0][parent]
1383--         characters[unicode] = {
1384--             width    = cp.width,
1385--             height   = cp.height + sc,
1386--             depth    = cp.depth + sc,
1387--             next     = cp.next, -- can be extensible
1388--             commands = {
1389--                 push, upcommand  [sc], pc, pop,
1390--                 push, downcommand[sc], pc, pop,
1391--                                        pc,
1392--             },
1393--         }
1394--         cp.next = unicode
1395--     end
1396-- end
1397
1398-- vertbar  (main,characters,id,size,0x0007C,0.10,0xFF601) -- big  : 0.85 bodyfontsize
1399-- vertbar  (main,characters,id,size,0xFF601,0.30,0xFF602) -- Big  : 1.15 bodyfontsize
1400-- vertbar  (main,characters,id,size,0xFF602,0.30,0xFF603) -- bigg : 1.45 bodyfontsize
1401-- vertbar  (main,characters,id,size,0xFF603,0.30,0xFF604) -- Bigg : 1.75 bodyfontsize
1402-- vertbar  (main,characters,id,size,0x02016,0.10,0xFF605)
1403-- vertbar  (main,characters,id,size,0xFF605,0.30,0xFF606)
1404-- vertbar  (main,characters,id,size,0xFF606,0.30,0xFF607)
1405-- vertbar  (main,characters,id,size,0xFF607,0.30,0xFF608)
1406
1407-- clipped  (main,characters,id,size,0xFF501,0x0002D) -- minus
1408-- clipped  (main,characters,id,size,0xFF502,0x02190) -- lefthead
1409-- clipped  (main,characters,id,size,0xFF503,0x02192) -- righthead
1410-- clipped  (main,characters,id,size,0xFF504,ps("maps to piece") -- mapsto
1411-- clipped  (main,characters,id,size,0xFF505,0xFE322) -- lhook
1412-- clipped  (main,characters,id,size,0xFF506,0xFE323) -- rhook
1413-- clipped  (main,characters,id,size,0xFF507,0xFE324) -- mapsfrom
1414-- clipped  (main,characters,id,size,0xFF508,0x021D0) -- double lefthead
1415-- clipped  (main,characters,id,size,0xFF509,0x021D2) -- double righthead
1416-- clipped  (main,characters,id,size,0xFF50A,0x0003D) -- equal
1417-- clipped  (main,characters,id,size,0xFF50B,0x0219E) -- lefttwohead
1418-- clipped  (main,characters,id,size,0xFF50C,0x021A0) -- righttwohead
1419-- clipped  (main,characters,id,size,0xFF50D,0xFF350) -- lr arrow combi snippet
1420-- clipped  (main,characters,id,size,0xFF50E,0xFF351) -- lr arrow combi snippet
1421-- clipped  (main,characters,id,size,0xFF50F,0xFF352) -- lr arrow combi snippet
1422-- clipped  (main,characters,id,size,0xFF510,0x02261) -- equiv
1423
1424-- extension(main,characters,id,size,0x2190,0xFF502,0xFF501,0xFF501) -- \leftarrow
1425-- extension(main,characters,id,size,0x2192,0xFF501,0xFF501,0xFF503) -- \rightarrow
1426
1427-- extension(main,characters,id,size,0x002D,0xFF501,0xFF501,0xFF501)                 -- \rel
1428-- extension(main,characters,id,size,0x003D,0xFF50A,0xFF50A,0xFF50A)                 -- \equal
1429-- extension(main,characters,id,size,0x2261,0xFF510,0xFF510,0xFF510)                 -- \equiv
1430
1431-- local lh = ps("left hook piece")]  -- was FE322
1432-- local rh = ps("right hook piece")] -- was FE323
1433
1434-- jointwo  (main,characters,id,size,0x21A6,ps("maps to piece"),0,0x02192)                       -- \mapstochar\rightarrow
1435-- jointwo  (main,characters,id,size,0x21A9,0x02190,joinrelfactor,0xFE323)           -- \leftarrow\joinrel\rhook
1436-- jointwo  (main,characters,id,size,0x21AA,0xFE322,joinrelfactor,0x02192)           -- \lhook\joinrel\rightarrow
1437-- jointhree(main,characters,id,size,0x27FB,0x02190,joinrelfactor,0x0002D,0,0xFE324) -- \leftarrow\joinrel\relbar\mapsfromchar
1438
1439-- jointhree(main,characters,id,size,0x27FC,ps("maps to piece"),0,0x0002D,joinrelfactor,0x02192) -- \mapstochar\relbar\joinrel\rightarrow
1440
1441-- extension(main,characters,id,size,0x21A6,0xFF504,0xFF501,0xFF503) -- \mapstochar\rightarrow
1442-- extension(main,characters,id,size,0x21A9,0xFF502,0xFF501,0xFF506) -- \leftarrow\joinrel\rhook
1443-- extension(main,characters,id,size,0x21AA,0xFF505,0xFF501,0xFF503) -- \lhook\joinrel\rightarrow
1444-- extension(main,characters,id,size,0x27F5,0xFF502,0xFF501,0xFF501) -- \leftarrow\joinrel\relbar
1445-- extension(main,characters,id,size,0x27F6,0xFF501,0xFF501,0xFF503) -- \relbar\joinrel\rightarrow
1446-- extension(main,characters,id,size,0x27F7,0xFF502,0xFF501,0xFF503) -- \leftarrow\joinrel\rightarrow
1447-- extension(main,characters,id,size,0x27F8,0xFF508,0xFF50A,0xFF50A) -- \Leftarrow\joinrel\Relbar
1448-- extension(main,characters,id,size,0x27F9,0xFF50A,0xFF50A,0xFF509) -- \Relbar\joinrel\Rightarrow
1449-- extension(main,characters,id,size,0x27FA,0xFF508,0xFF50A,0xFF509) -- \Leftarrow\joinrel\Rightarrow
1450-- extension(main,characters,id,size,0x27FB,0xFF502,0xFF501,0xFF507) -- \leftarrow\joinrel\relbar\mapsfromchar
1451-- extension(main,characters,id,size,0x27FC,0xFF504,0xFF501,0xFF503) -- \mapstochar\relbar\joinrel\rightarrow
1452
1453-- extension(main,characters,id,size,0x219E,0xFF50B,0xFF501,0xFF501) -- \twoheadleftarrow\joinrel\relbar
1454-- extension(main,characters,id,size,0x21A0,0xFF501,0xFF501,0xFF50C) -- \relbar\joinrel\twoheadrightarrow
1455-- extension(main,characters,id,size,0x21C4,0xFF50D,0xFF50E,0xFF50F) -- leftoverright
1456