math-vfu.lmt /size: 48 Kb    last modification: 2021-10-28 13:51
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-- All these math vectors .. thanks to Aditya and Mojca they become better and
10-- better. If you have problems with math fonts or miss characters report it to the
11-- ConTeXt mailing list. Also thanks to Boguslaw for finding a couple of errors.
12-- This mechanism will eventually disappear.
13
14-- 20D6 -> 2190
15-- 20D7 -> 2192
16
17local type, next, tonumber = type, next, tonumber
18local max = math.max
19local fastcopy = table.copy
20
21local fonts, nodes, mathematics = fonts, nodes, mathematics
22
23local trace_virtual = false  trackers.register("math.virtual", function(v) trace_virtual = v end)
24local trace_timings = false  trackers.register("math.timings", function(v) trace_timings = v end)
25
26local add_optional  = false  directives.register("math.virtual.optional",function(v) add_optional = v end)
27
28local report_virtual    = logs.reporter("fonts","virtual math")
29
30local allocate          = utilities.storage.allocate
31local setmetatableindex = table.setmetatableindex
32local formatters        = string.formatters
33
34local chardata          = characters.data
35
36local mathencodings     = allocate()
37fonts.encodings.math    = mathencodings -- better is then: fonts.encodings.vectors
38local vfmath            = allocate()
39fonts.handlers.vf.math  = vfmath
40
41local helpers           = fonts.helpers
42local vfcommands        = helpers.commands
43local rightcommand      = vfcommands.right
44local leftcommand       = vfcommands.left
45local downcommand       = vfcommands.down
46local upcommand         = vfcommands.up
47local push              = vfcommands.push
48local pop               = vfcommands.pop
49
50local shared            = { }
51
52-- local back = { "slot", 1, 0x2215 }
53--
54-- local function negate(main,characters,id,size,unicode,basecode)
55--     if not characters[unicode] then
56--         local basechar = characters[basecode]
57--         if basechar then
58--             local ht, wd = basechar.height, basechar.width
59--             characters[unicode] = {
60--                 width    = wd,
61--                 height   = ht,
62--                 depth    = basechar.depth,
63--                 italic   = basechar.italic,
64--                 kerns    = basechar.kerns,
65--                 commands = {
66--                     { "slot", 1, basecode },
67--                     push,
68--                     downcommand[ht/5],
69--                     leftcommand[wd/2],
70--                     back,
71--                     push,
72--                 }
73--             }
74--         end
75--     end
76-- end
77--
78-- \Umathchardef\braceld="0 "1 "FF07A
79-- \Umathchardef\bracerd="0 "1 "FF07B
80-- \Umathchardef\bracelu="0 "1 "FF07C
81-- \Umathchardef\braceru="0 "1 "FF07D
82
83local function brace(main,characters,id,size,unicode,first,rule,left,right,rule,last)
84    if not characters[unicode] then
85        characters[unicode] = {
86            horiz_variants = {
87                { extender = 0, glyph = first },
88                { extender = 1, glyph = rule  },
89                { extender = 0, glyph = left  },
90                { extender = 0, glyph = right },
91                { extender = 1, glyph = rule  },
92                { extender = 0, glyph = last  },
93            }
94        }
95    end
96end
97
98local function extension(main,characters,id,size,unicode,first,middle,last)
99    local chr = characters[unicode]
100    if not chr then
101        return -- skip
102    end
103    local fw = characters[first]
104    if not fw then
105        return
106    end
107    local mw = characters[middle]
108    if not mw then
109        return
110    end
111    local lw = characters[last]
112    if not lw then
113        return
114    end
115    fw = fw.width
116    mw = mw.width
117    lw = lw.width
118    if fw == 0 then
119        fw = 1
120    end
121    if lw == 0 then
122        lw = 1
123    end
124    chr.horiz_variants = {
125        { extender = 0, glyph = first,  ["end"] = fw/2, start = 0,    advance = fw },
126        { extender = 1, glyph = middle, ["end"] = mw/2, start = mw/2, advance = mw },
127        { extender = 0, glyph = last,   ["end"] = 0,    start = lw/2, advance = lw },
128    }
129end
130
131local function parent(main,characters,id,size,unicode,first,rule,last)
132    if not characters[unicode] then
133        characters[unicode] = {
134            horiz_variants = {
135                { extender = 0, glyph = first },
136                { extender = 1, glyph = rule  },
137                { extender = 0, glyph = last  },
138            }
139        }
140    end
141end
142
143local step = 0.2 -- 0.1 is nicer but gives larger files
144
145local function make(main,characters,id,size,n,m)
146    local old = 0xFF000 + n
147    local c   = characters[old]
148    if c then
149        local upslot    = 0xFF100 + n
150        local dnslot    = 0xFF200 + n
151        local uprule    = 0xFF300 + m
152        local dnrule    = 0xFF400 + m
153        local xu        = main.parameters.x_height + 0.3*size
154        local xd        = 0.3*size
155        local w         = c.width or 0
156        local h         = c.height or 0
157        local d         = c.depth or 0
158        local thickness = h - d
159        local rulewidth = step*size -- we could use an overlap
160        local slot      = { "slot", id, old }
161        local rule      = { "rule", thickness, rulewidth  }
162        local up        = upcommand[xu]
163        local dn        = downcommand[xd]
164        local ht        = xu + 3*thickness
165        local dp        = 0
166        if not characters[uprule] then
167            characters[uprule] = {
168                width    = rulewidth,
169                height   = ht,
170                depth    = dp,
171                commands = { push, up, rule, pop },
172            }
173        end
174        characters[upslot] = {
175            width    = w,
176            height   = ht,
177            depth    = dp,
178            commands = { push, up, slot, pop },
179        }
180        local ht = 0
181        local dp = xd + 3*thickness
182        if not characters[dnrule] then
183            characters[dnrule] = {
184                width    = rulewidth,
185                height   = ht,
186                depth    = dp,
187                commands = { push, dn, rule, pop }
188            }
189        end
190        characters[dnslot] = {
191            width    = w,
192            height   = ht,
193            depth    = dp,
194            commands = { push, dn, slot, pop },
195        }
196    end
197end
198
199local function clipped(main,characters,id,size,unicode,original) -- push/pop needed?
200    local minus = characters[original]
201    if minus then
202        local mu    = size/18
203        local step  = 3*mu
204        local width = minus.width
205        if width > step then
206            width = width - step
207            step  = step / 2
208        else
209            width = width / 2
210            step  = width
211        end
212        characters[unicode] = {
213            width    = width,
214            height   = minus.height,
215            depth    = minus.depth,
216            commands = {
217                push,
218                leftcommand[step],
219                { "slot", id, original },
220                pop,
221            }
222        }
223    end
224end
225
226local function raise(main,characters,id,size,unicode,private,n,id_of_smaller) -- this is a real fake mess
227    local raised = fonts.hashes.characters[main.fonts[id_of_smaller].id][private]  -- characters[private]
228    if raised then
229        local up   = 0.85 * main.parameters.x_height
230        local slot = { "slot", id_of_smaller, private }
231        local commands = {
232            push, upcommand[up], slot,
233        }
234        for i=2,n do
235            commands[#commands+1] = slot
236        end
237        commands[#commands+1] = pop
238        characters[unicode] = {
239            width    = n * raised.width,
240            height   = (raised.height or 0) + up,
241            depth    = (raised.depth or 0) - up,
242            italic   = raised.italic,
243            commands = commands,
244        }
245    end
246end
247
248local function dots(main,characters,id,size,unicode)
249    local c = characters[0x002E]
250    if c then
251        local w         = c.width
252        local h         = c.height
253        local d         = c.depth
254        local mu        = size/18
255        local right3mu  = rightcommand[3*mu]
256        local right1mu  = rightcommand[1*mu]
257        local up1size   = upcommand[.1*size]
258        local up4size   = upcommand[.4*size]
259        local up7size   = upcommand[.7*size]
260        local right2muw = rightcommand[2*mu + w]
261        local slot      = { "slot", id, 0x002E }
262        if unicode == 0x22EF then
263            local c = characters[0x022C5]
264            if c then
265                local width  = c.width
266                local height = c.height
267                local depth  = c.depth
268                local slot   = { "slot", id, 0x022C5 }
269                characters[unicode] = {
270                    width    = 3*width + 2*3*mu,
271                    height   = height,
272                    depth    = depth,
273                    commands = {
274                        push, slot, right3mu, slot, right3mu, slot, pop,
275                    }
276                }
277            end
278        elseif unicode == 0x22EE then
279            -- weird height !
280            characters[unicode] = {
281                width    = w,
282                height   = h+(1.4)*size,
283                depth    = 0,
284                commands = {
285                    push, push, slot, pop, up4size, push, slot, pop, up4size, slot, pop,
286                }
287            }
288        elseif unicode == 0x22F1 then
289            characters[unicode] = {
290                width    = 3*w + 6*size/18,
291                height   = 1.5*size,
292                depth    = 0,
293                commands = {
294                    push,
295                    right1mu,
296                    push, up7size, slot, pop,
297                    right2muw,
298                    push, up4size, slot, pop,
299                    right2muw,
300                    push, up1size, slot, pop,
301                    right1mu,
302                    pop
303                }
304            }
305        elseif unicode == 0x22F0 then
306            characters[unicode] = {
307                width    = 3*w + 6*size/18,
308                height   = 1.5*size,
309                depth    = 0,
310                commands = {
311                    push,
312                    right1mu,
313                    push, up1size, slot, pop,
314                    right2muw,
315                    push, up4size, slot, pop,
316                    right2muw,
317                    push, up7size, slot, pop,
318                    right1mu,
319                    pop
320                }
321            }
322        else
323            characters[unicode] = {
324                width    = 3*w + 2*3*mu,
325                height   = h,
326                depth    = d,
327                commands = {
328                    push, slot, right3mu, slot, right3mu, slot, pop,
329                }
330            }
331        end
332    end
333end
334
335local function vertbar(main,characters,id,size,parent,scale,unicode)
336    local cp = characters[parent]
337    if cp then
338        local sc = scale * size
339        local pc = { "slot", id, parent }
340        characters[unicode] = {
341            width    = cp.width,
342            height   = cp.height + sc,
343            depth    = cp.depth + sc,
344            next     = cp.next, -- can be extensible
345            commands = {
346                push, upcommand  [sc], pc, pop,
347                push, downcommand[sc], pc, pop,
348                                       pc,
349            },
350        }
351        cp.next = unicode
352    end
353end
354
355local function jointwo(main,characters,id,size,unicode,u1,d12,u2,what)
356    local c1 = characters[u1]
357    local c2 = characters[u2]
358    if c1 and c2 then
359        local w1 = c1.width
360        local w2 = c2.width
361        local mu = size/18
362        characters[unicode] = {
363            width    = w1 + w2 - d12 * mu,
364            height   = max(c1.height or 0, c2.height or 0),
365            depth    = max(c1.depth  or 0, c2.depth  or 0),
366            commands = {
367                { "slot", id, u1 },
368                leftcommand[d12*mu],
369                { "slot", id, u2 },
370            },
371        }
372    end
373end
374
375local function jointhree(main,characters,id,size,unicode,u1,d12,u2,d23,u3)
376    local c1 = characters[u1]
377    local c2 = characters[u2]
378    local c3 = characters[u3]
379    if c1 and c2 and c3 then
380        local w1 = c1.width
381        local w2 = c2.width
382        local w3 = c3.width
383        local mu = size/18
384        characters[unicode] = {
385            width    = w1 + w2 + w3 - d12*mu - d23*mu,
386            height   = max(c1.height or 0, c2.height or 0, c3.height or 0),
387            depth    = max(c1.depth  or 0, c2.depth  or 0, c3.depth  or 0),
388            commands = {
389                { "slot", id, u1 },
390                leftcommand[d12*mu],
391                { "slot", id, u2 },
392                leftcommand[d23*mu],
393                { "slot", id, u3 },
394            }
395        }
396    end
397end
398
399local function stack(main,characters,id,size,unicode,u1,d12,u2)
400    local c1 = characters[u1]
401    if not c1 then
402        return
403    end
404    local c2 = characters[u2]
405    if not c2 then
406        return
407    end
408    local w1 = c1.width  or 0
409    local h1 = c1.height or 0
410    local d1 = c1.depth  or 0
411    local w2 = c2.width  or 0
412    local h2 = c2.height or 0
413    local d2 = c2.depth  or 0
414    local mu = size/18
415    characters[unicode] = {
416        width    = w1,
417        height   = h1 + h2 + d12,
418        depth    = d1,
419        commands = {
420            { "slot", id, u1 },
421            leftcommand[w1/2 + w2/2],
422            downcommand[-h1 + d2 -d12*mu],
423            { "slot", id, u2 },
424        }
425    }
426end
427
428local function repeated(main,characters,id,size,unicode,u,n,private,fraction) -- math-fbk.lua
429    local c = characters[u]
430    if c then
431        local width  = c.width
432        local italic = fraction*width -- c.italic or 0 -- larger ones have funny italics
433        local tc     = { "slot", id, u }
434        local tr     = leftcommand[italic] -- see hack elsewhere
435        local commands = { }
436        for i=1,n-1 do
437            commands[#commands+1] = tc
438            commands[#commands+1] = tr
439        end
440        commands[#commands+1] = tc
441        local next = c.next
442        if next then
443            repeated(main,characters,id,size,private,next,n,private+1,fraction)
444            next = private
445        end
446        characters[unicode] = {
447            width    = width + (n-1)*(width-italic),
448            height   = c.height,
449            depth    = c.depth,
450            italic   = italic,
451            commands = commands,
452            next     = next,
453        }
454    end
455end
456
457local function cloned(main,characters,id,size,source,target)
458    local data = characters[source]
459    if data then
460        characters[target] = data
461        return true
462    end
463end
464
465-- we use the fact that context defines the smallest sizes first .. a real dirty and ugly hack
466
467local data_of_smaller = nil
468local size_of_smaller = 0
469
470function vfmath.addmissing(main,id,size)
471
472    local id_of_smaller = nil
473
474    if size < size_of_smaller or size_of_smaller == 0 then
475        data_of_smaller = main.fonts[id]
476        id_of_smaller = id
477    else
478        id_of_smaller = #main.fonts + 1
479        main.fonts[id_of_smaller] = data_of_smaller
480    end
481
482    -- here id is the index in fonts (normally 14 or so) and that slot points to self
483
484    local characters    = main.characters
485    local shared        = main.shared
486    local variables     = main.goodies.mathematics and main.goodies.mathematics.variables or { }
487    local joinrelfactor = variables.joinrelfactor or 3
488
489    for i=0x7A,0x7D do
490        make(main,characters,id,size,i,1)
491    end
492
493    brace    (main,characters,id,size,0x23DE,0xFF17A,0xFF301,0xFF17D,0xFF17C,0xFF301,0xFF17B)
494    brace    (main,characters,id,size,0x23DF,0xFF27C,0xFF401,0xFF27B,0xFF27A,0xFF401,0xFF27D)
495
496    parent   (main,characters,id,size,0x23DC,0xFF17A,0xFF301,0xFF17B)
497    parent   (main,characters,id,size,0x23DD,0xFF27C,0xFF401,0xFF27D)
498
499 -- negate   (main,characters,id,size,0x2260,0x003D)
500    dots     (main,characters,id,size,0x2026) -- ldots
501    dots     (main,characters,id,size,0x22EE) -- vdots
502    dots     (main,characters,id,size,0x22EF) -- cdots
503    dots     (main,characters,id,size,0x22F1) -- ddots
504    dots     (main,characters,id,size,0x22F0) -- udots
505
506    vertbar  (main,characters,id,size,0x0007C,0.10,0xFF601) -- big  : 0.85 bodyfontsize
507    vertbar  (main,characters,id,size,0xFF601,0.30,0xFF602) -- Big  : 1.15 bodyfontsize
508    vertbar  (main,characters,id,size,0xFF602,0.30,0xFF603) -- bigg : 1.45 bodyfontsize
509    vertbar  (main,characters,id,size,0xFF603,0.30,0xFF604) -- Bigg : 1.75 bodyfontsize
510    vertbar  (main,characters,id,size,0x02016,0.10,0xFF605)
511    vertbar  (main,characters,id,size,0xFF605,0.30,0xFF606)
512    vertbar  (main,characters,id,size,0xFF606,0.30,0xFF607)
513    vertbar  (main,characters,id,size,0xFF607,0.30,0xFF608)
514
515    clipped  (main,characters,id,size,0xFF501,0x0002D) -- minus
516    clipped  (main,characters,id,size,0xFF502,0x02190) -- lefthead
517    clipped  (main,characters,id,size,0xFF503,0x02192) -- righthead
518    clipped  (main,characters,id,size,0xFF504,0xFE321) -- mapsto
519    clipped  (main,characters,id,size,0xFF505,0xFE322) -- lhook
520    clipped  (main,characters,id,size,0xFF506,0xFE323) -- rhook
521    clipped  (main,characters,id,size,0xFF507,0xFE324) -- mapsfrom
522    clipped  (main,characters,id,size,0xFF508,0x021D0) -- double lefthead
523    clipped  (main,characters,id,size,0xFF509,0x021D2) -- double righthead
524    clipped  (main,characters,id,size,0xFF50A,0x0003D) -- equal
525    clipped  (main,characters,id,size,0xFF50B,0x0219E) -- lefttwohead
526    clipped  (main,characters,id,size,0xFF50C,0x021A0) -- righttwohead
527    clipped  (main,characters,id,size,0xFF50D,0xFF350) -- lr arrow combi snippet
528    clipped  (main,characters,id,size,0xFF50E,0xFF351) -- lr arrow combi snippet
529    clipped  (main,characters,id,size,0xFF50F,0xFF352) -- lr arrow combi snippet
530    clipped  (main,characters,id,size,0xFF510,0x02261) -- equiv
531
532    extension(main,characters,id,size,0x2190,0xFF502,0xFF501,0xFF501)                 -- \leftarrow
533    extension(main,characters,id,size,0x2192,0xFF501,0xFF501,0xFF503)                 -- \rightarrow
534
535    extension(main,characters,id,size,0x002D,0xFF501,0xFF501,0xFF501)                 -- \rel
536    extension(main,characters,id,size,0x003D,0xFF50A,0xFF50A,0xFF50A)                 -- \equal
537    extension(main,characters,id,size,0x2261,0xFF510,0xFF510,0xFF510)                 -- \equiv
538
539    jointwo  (main,characters,id,size,0x21A6,0xFE321,0,0x02192)                       -- \mapstochar\rightarrow
540    jointwo  (main,characters,id,size,0x21A9,0x02190,joinrelfactor,0xFE323)           -- \leftarrow\joinrel\rhook
541    jointwo  (main,characters,id,size,0x21AA,0xFE322,joinrelfactor,0x02192)           -- \lhook\joinrel\rightarrow
542    jointwo  (main,characters,id,size,0x27F5,0x02190,joinrelfactor,0x0002D)           -- \leftarrow\joinrel\relbar
543    jointwo  (main,characters,id,size,0x27F6,0x0002D,joinrelfactor,0x02192,2)         -- \relbar\joinrel\rightarrow
544    jointwo  (main,characters,id,size,0x27F7,0x02190,joinrelfactor,0x02192)           -- \leftarrow\joinrel\rightarrow
545    jointwo  (main,characters,id,size,0x27F8,0x021D0,joinrelfactor,0x0003D)           -- \Leftarrow\joinrel\Relbar
546    jointwo  (main,characters,id,size,0x27F9,0x0003D,joinrelfactor,0x021D2)           -- \Relbar\joinrel\Rightarrow
547    jointwo  (main,characters,id,size,0x27FA,0x021D0,joinrelfactor,0x021D2)           -- \Leftarrow\joinrel\Rightarrow
548    jointhree(main,characters,id,size,0x27FB,0x02190,joinrelfactor,0x0002D,0,0xFE324) -- \leftarrow\joinrel\relbar\mapsfromchar
549    jointhree(main,characters,id,size,0x27FC,0xFE321,0,0x0002D,joinrelfactor,0x02192) -- \mapstochar\relbar\joinrel\rightarrow
550
551    extension(main,characters,id,size,0x21A6,0xFF504,0xFF501,0xFF503)                 -- \mapstochar\rightarrow
552    extension(main,characters,id,size,0x21A9,0xFF502,0xFF501,0xFF506)                 -- \leftarrow\joinrel\rhook
553    extension(main,characters,id,size,0x21AA,0xFF505,0xFF501,0xFF503)                 -- \lhook\joinrel\rightarrow
554    extension(main,characters,id,size,0x27F5,0xFF502,0xFF501,0xFF501)                 -- \leftarrow\joinrel\relbar
555    extension(main,characters,id,size,0x27F6,0xFF501,0xFF501,0xFF503)                 -- \relbar\joinrel\rightarrow
556    extension(main,characters,id,size,0x27F7,0xFF502,0xFF501,0xFF503)                 -- \leftarrow\joinrel\rightarrow
557    extension(main,characters,id,size,0x27F8,0xFF508,0xFF50A,0xFF50A)                 -- \Leftarrow\joinrel\Relbar
558    extension(main,characters,id,size,0x27F9,0xFF50A,0xFF50A,0xFF509)                 -- \Relbar\joinrel\Rightarrow
559    extension(main,characters,id,size,0x27FA,0xFF508,0xFF50A,0xFF509)                 -- \Leftarrow\joinrel\Rightarrow
560    extension(main,characters,id,size,0x27FB,0xFF502,0xFF501,0xFF507)                 -- \leftarrow\joinrel\relbar\mapsfromchar
561    extension(main,characters,id,size,0x27FC,0xFF504,0xFF501,0xFF503)                 -- \mapstochar\relbar\joinrel\rightarrow
562
563    extension(main,characters,id,size,0x219E,0xFF50B,0xFF501,0xFF501)                 -- \twoheadleftarrow\joinrel\relbar
564    extension(main,characters,id,size,0x21A0,0xFF501,0xFF501,0xFF50C)                 -- \relbar\joinrel\twoheadrightarrow
565    extension(main,characters,id,size,0x21C4,0xFF50D,0xFF50E,0xFF50F)                 -- leftoverright
566
567    -- 21CB leftrightharpoon
568    -- 21CC rightleftharpoon
569
570    stack(main,characters,id,size,0x2259,0x0003D,3,0x02227)                       -- \buildrel\wedge\over=
571
572    jointwo(main,characters,id,size,0x22C8,0x022B3,joinrelfactor,0x022B2)           -- \mathrel\triangleright\joinrel\mathrel\triangleleft (4 looks better than 3)
573    jointwo(main,characters,id,size,0x22A7,0x0007C,joinrelfactor,0x0003D)           -- \mathrel|\joinrel=
574    jointwo(main,characters,id,size,0x2260,0x00338,0,0x0003D)                       -- \not\equal
575    jointwo(main,characters,id,size,0x2284,0x00338,0,0x02282)                       -- \not\subset
576    jointwo(main,characters,id,size,0x2285,0x00338,0,0x02283)                       -- \not\supset
577    jointwo(main,characters,id,size,0x2209,0x00338,0,0x02208)                       -- \not\in
578    jointwo(main,characters,id,size,0x2254,0x03A,0,0x03D)                           -- := (≔)
579
580    repeated(main,characters,id,size,0x222C,0x222B,2,0xFF800,1/3)
581    repeated(main,characters,id,size,0x222D,0x222B,3,0xFF810,1/3)
582
583    if cloned(main,characters,id,size,0x2032,0xFE325) then
584        raise(main,characters,id,size,0x2032,0xFE325,1,id_of_smaller) -- prime
585        raise(main,characters,id,size,0x2033,0xFE325,2,id_of_smaller) -- double prime
586        raise(main,characters,id,size,0x2034,0xFE325,3,id_of_smaller) -- triple prime
587        -- to satisfy the prime resolver
588        characters[0xFE932] = characters[0x2032]
589        characters[0xFE933] = characters[0x2033]
590        characters[0xFE934] = characters[0x2034]
591    end
592
593    -- there are more (needs discussion first):
594
595 -- characters[0x20D6] = characters[0x2190]
596 -- characters[0x20D7] = characters[0x2192]
597
598    characters[0x02B9] = characters[0x2032] -- we're nice
599
600    data_of_smaller = main.fonts[id]
601    size_of_smaller = size
602
603end
604
605local unique = 0 -- testcase: \startTEXpage \math{!\text{-}\text{-}\text{-}} \stopTEXpage
606
607local reported = { }
608local reverse  = { } -- index -> unicode
609
610setmetatableindex(reverse, function(t,name)
611    if trace_virtual then
612        report_virtual("initializing math vector %a",name)
613    end
614    local m = mathencodings[name]
615    local r = { }
616    for u, i in next, m do
617        r[i] = u
618    end
619    reverse[name] = r
620    return r
621end)
622
623local function copy_glyph(main,target,original,unicode,slot)
624    local addprivate = fonts.helpers.addprivate
625    local olddata    = original[unicode]
626    if olddata then
627        local newdata = {
628            width     = olddata.width,
629            height    = olddata.height,
630            depth     = olddata.depth,
631            italic    = olddata.italic,
632            kerns     = olddata.kerns,
633            tounicode = olddata.tounicode,
634            commands  = { { "slot", slot, unicode } },
635        }
636        local glyphdata = newdata
637        local nextglyph = olddata.next
638        while nextglyph do
639            local oldnextdata = original[nextglyph]
640            if oldnextdata then
641                local newnextdata = {
642                    width     = oldnextdata.width,
643                    height    = oldnextdata.height,
644                    depth     = oldnextdata.depth,
645                    tounicode = olddata.tounicode,
646                    commands  = { { "slot", slot, nextglyph } },
647                }
648                local newnextglyph = addprivate(main,formatters["M-N-%H"](nextglyph),newnextdata)
649                newdata.next = newnextglyph
650                local nextnextglyph = oldnextdata.next
651                if nextnextglyph == nextglyph then
652                    break
653                else
654                    olddata   = oldnextdata
655                    newdata   = newnextdata
656                    nextglyph = nextnextglyph
657                end
658            else
659                break -- safeguard (when testing stuff)
660            end
661        end
662        local hv = olddata.horiz_variants
663        if hv then
664            hv = fastcopy(hv)
665            newdata.horiz_variants = hv
666            for i=1,#hv do
667                local hvi = hv[i]
668                local oldglyph = hvi.glyph
669                local olddata = original[oldglyph]
670                local newdata = {
671                    width     = olddata.width,
672                    height    = olddata.height,
673                    depth     = olddata.depth,
674                    tounicode = olddata.tounicode,
675                    commands  = { { "slot", slot, oldglyph } },
676                }
677                hvi.glyph = addprivate(main,formatters["M-H-%H"](oldglyph),newdata)
678            end
679        end
680        local vv = olddata.vert_variants
681        if vv then
682            vv = fastcopy(vv)
683            newdata.vert_variants = vv
684            for i=1,#vv do
685                local vvi = vv[i]
686                local oldglyph = vvi.glyph
687                local olddata = original[oldglyph]
688                local newdata = {
689                    width     = olddata.width,
690                    height    = olddata.height,
691                    depth     = olddata.depth,
692                    tounicode = olddata.tounicode,
693                    commands  = { { "slot", slot, oldglyph } },
694                }
695                vvi.glyph = addprivate(main,formatters["M-V-%H"](oldglyph),newdata)
696            end
697        end
698        return newdata
699    end
700end
701
702vfmath.copy_glyph = copy_glyph
703
704-- It's time to get rid of this type 1 mess ... take iwona: uppercase /A .. Z but
705-- lowercase /a.math ... /z.math ... anyway, we now follow a slightly different
706-- route: use the "order" field. I can probably make it a bit leaner but it's not
707-- worth spending much time on now.
708
709function vfmath.define(specification,set,goodies)
710    local name     = specification.name -- symbolic name
711    local size     = specification.size -- given size
712    local loaded   = { }
713    local fontlist = { }
714    local names    = { }
715    local main     = nil
716    local start    = (trace_virtual or trace_timings) and os.clock()
717    local okset    = { }
718    local n        = 0
719    for s=1,#set do
720        local ss     = set[s]
721        local ssname = ss.name
722        if add_optional and ss.optional then
723            if trace_virtual then
724                report_virtual("loading font %a subfont %s with name %a at %p is skipped",name,s,ssname,size)
725            end
726        else
727            if ss.features then
728                ssname = ssname .. "*" .. ss.features
729            end
730            if ss.main then
731                main = s
732            end
733            local alreadyloaded = names[ssname] -- for px we load one twice (saves .04 sec)
734            local f, id
735            if alreadyloaded then
736                f, id = alreadyloaded.f, alreadyloaded.id
737                if trace_virtual then
738                    report_virtual("loading font %a subfont %s with name %a is reused",name,s,ssname)
739                end
740            else
741                f, id = fonts.constructors.readanddefine(ssname,size)
742                names[ssname] = { f = f, id = id }
743            end
744            if not f or id == 0 then
745                report_virtual("loading font %a subfont %s with name %a at %p is skipped, not found",name,s,ssname,size)
746            else
747                n = n + 1
748                okset[n] = ss
749                loaded[n] = f
750                fontlist[n] = { id = id, size = size }
751                if not shared[s] then
752                    shared[n] = { }
753                end
754                if trace_virtual then
755                    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)
756                end
757            end
758        end
759    end
760    -- beware, loaded[1] is already passed to tex (we need to make a simple copy then .. todo)
761    local parent         = loaded[1] or { } -- a text font
762    local characters     = { }
763    local parameters     = { }
764    local mathparameters = { }
765    local descriptions   = { }
766    local metadata       = { }
767    local properties     = {
768        hasitalics = true,
769        hasmath    = true,
770    }
771    local goodies        = { }
772    local main           = {
773        metadata         = metadata,
774        properties       = properties,
775        characters       = characters,
776        descriptions     = descriptions,
777        parameters       = parameters,
778        mathparameters   = mathparameters,
779        fonts            = fontlist,
780        goodies          = goodies,
781        --
782        fullname         = properties.fullname,
783        nomath           = false,
784    }
785    --
786    for key, value in next, parent do
787        if type(value) ~= "table" then
788            main[key] = value
789        end
790    end
791    --
792    if parent.characters then
793        for unicode, character in next, parent.characters do
794            characters[unicode] = character
795        end
796    else
797        report_virtual("font %a has no characters",name)
798    end
799    --
800    if parent.parameters then
801        for key, value in next, parent.parameters do
802            parameters[key] = value
803        end
804    else
805        report_virtual("font %a has no parameters",name)
806    end
807    --
808    local description = { name = "<unset>" }
809    setmetatableindex(descriptions,function() return description end)
810    --
811    if parent.properties then
812        setmetatableindex(properties,parent.properties)
813    end
814    --
815    if parent.goodies then
816        setmetatableindex(goodies,parent.goodies)
817    end
818    --
819    local fullname = properties.fullname -- parent via mt
820    if fullname then
821        unique = unique + 1
822        properties.fullname = fullname .. "-" .. unique
823    end
824    --
825    if not parameters.x_height then
826        parameters.x_height = 0
827    end
828    --
829    local already_reported = false
830    local parameters_done = false
831    for s=1,n do
832        local ss, fs = okset[s], loaded[s]
833        if not fs then
834            -- skip, error
835        elseif add_optional and ss.optional then
836            -- skip, redundant
837        else
838            local newparameters     = fs.parameters
839            local newmathparameters = fs.mathparameters
840            if newmathparameters then
841                if not parameters_done or ss.parameters then
842                    mathparameters  = newmathparameters
843                    parameters_done = true
844                end
845            elseif not newparameters then
846                report_virtual("no parameters set in font %a",name)
847            elseif ss.extension then
848                mathparameters.math_x_height          = newparameters.x_height or 0        -- math_x_height          : height of x
849                mathparameters.default_rule_thickness = newparameters[ 8]      or 0        -- default_rule_thickness : thickness of \over bars
850                mathparameters.big_op_spacing1        = newparameters[ 9]      or 0        -- big_op_spacing1        : minimum clearance above a displayed op
851                mathparameters.big_op_spacing2        = newparameters[10]      or 0        -- big_op_spacing2        : minimum clearance below a displayed op
852                mathparameters.big_op_spacing3        = newparameters[11]      or 0        -- big_op_spacing3        : minimum baselineskip above displayed op
853                mathparameters.big_op_spacing4        = newparameters[12]      or 0        -- big_op_spacing4        : minimum baselineskip below displayed op
854                mathparameters.big_op_spacing5        = newparameters[13]      or 0        -- big_op_spacing5        : padding above and below displayed limits
855            --  report_virtual("loading and virtualizing font %a at size %p, setting ex parameters",name,size)
856            elseif ss.parameters then
857                mathparameters.x_height      = newparameters.x_height or mathparameters.x_height
858                mathparameters.x_height      = mathparameters.x_height or fp.x_height or 0 -- x_height               : height of x
859                mathparameters.num1          = newparameters[ 8] or 0                      -- num1                   : numerator shift-up in display styles
860                mathparameters.num2          = newparameters[ 9] or 0                      -- num2                   : numerator shift-up in non-display, non-\atop
861                mathparameters.num3          = newparameters[10] or 0                      -- num3                   : numerator shift-up in non-display \atop
862                mathparameters.denom1        = newparameters[11] or 0                      -- denom1                 : denominator shift-down in display styles
863                mathparameters.denom2        = newparameters[12] or 0                      -- denom2                 : denominator shift-down in non-display styles
864                mathparameters.sup1          = newparameters[13] or 0                      -- sup1                   : superscript shift-up in uncramped display style
865                mathparameters.sup2          = newparameters[14] or 0                      -- sup2                   : superscript shift-up in uncramped non-display
866                mathparameters.sup3          = newparameters[15] or 0                      -- sup3                   : superscript shift-up in cramped styles
867                mathparameters.sub1          = newparameters[16] or 0                      -- sub1                   : subscript shift-down if superscript is absent
868                mathparameters.sub2          = newparameters[17] or 0                      -- sub2                   : subscript shift-down if superscript is present
869                mathparameters.sup_drop      = newparameters[18] or 0                      -- sup_drop               : superscript baseline below top of large box
870                mathparameters.sub_drop      = newparameters[19] or 0                      -- sub_drop               : subscript baseline below bottom of large box
871                mathparameters.delim1        = newparameters[20] or 0                      -- delim1                 : size of \atopwithdelims delimiters in display styles
872                mathparameters.delim2        = newparameters[21] or 0                      -- delim2                 : size of \atopwithdelims delimiters in non-displays
873                mathparameters.axis_height   = newparameters[22] or 0                      -- axis_height            : height of fraction lines above the baseline
874            --  report_virtual("loading and virtualizing font %a at size %p, setting sy parameters",name,size)
875            end
876            if ss.overlay then
877                local fc    = fs.characters
878                local first = ss.first
879                if first then
880                    local last = ss.last or first
881                    for unicode = first, last do
882                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
883                    end
884                else
885                    for unicode, data in next, fc do
886                        characters[unicode] = copy_glyph(main,characters,fc,unicode,s)
887                    end
888                end
889            else
890                local vectorname = ss.vector
891                if vectorname then
892                    local offset      = 0xFF000 -- todo: -- private
893                    local vector      = mathencodings[vectorname]
894                    local rotcev      = reverse[vectorname]
895                    local isextension = ss.extension
896                    if vector and rotcev then
897                        local fc       = fs.characters
898                        local fd       = fs.descriptions
899                        local si       = shared[s]
900                        local fontname = fs.properties.name or "unknown"
901                        local skewchar = ss.skewchar
902                        local backmap  = ss.backmap
903                        -- we need to know the original order because the loader has made a
904                        -- unicode font of it and weird glyphnames have spoiled that a bit
905                        if backmap then
906                            backmap = { }
907                            for unicode, character in next, fc do
908                                backmap[character.order or character.index or unicode] = unicode
909                            end
910                        end
911                        for unicode, i in next, vector do
912                            -- So, here we can have an extra remapping (compared to mkiv).
913                            local index = backmap and backmap[i] or i
914                            local fci = fc[index]
915                            if not fci then
916                                local rf = reported[fontname]
917                                if not rf then rf = { } reported[fontname] = rf end
918                                local rv = rf[vectorname]
919                                if not rv then rv = { } rf[vectorname] = rv end
920                                local ru = rv[unicode]
921                                if not ru then
922                                    if trace_virtual then
923                                        local d = chardata[unicode].description
924                                        if index then
925                                            report_virtual("character %C has no index %H in vector %a for font %a (%S)",unicode,index,vectorname,fontname,d)
926                                        else
927                                            report_virtual("character %C has no entry in vector %a for font %a (%S)",unicode,vectorname,fontname,d)
928                                        end
929                                    elseif not already_reported then
930                                        report_virtual("the mapping is incomplete for %a at %p",name,size)
931                                        already_reported = true
932                                    end
933                                    rv[unicode] = true
934                                end
935                            else
936                                local ref = si[index]
937                                if not ref then
938                                    ref = { { 'slot', s, index } }
939                                    si[index] = ref
940                                end
941                                local kerns  = fci.kerns
942                                local width  = fci.width
943                                local italic = fci.italic
944-- if trace_virtual then
945--     report_virtual("character %C uses index %H in vector %a for font %a, %s, %s",
946--         unicode,index,vectorname,fontname,
947--         kerns and "adding kerns" or "no kerns",
948--         kerns and "adding italic" or "no italic"
949--     )
950-- end
951                                if italic and italic > 0 then
952                                    -- int_a^b
953                                    if isextension then
954                                        width = width + italic -- for obscure reasons the integral as a width + italic correction
955                                    end
956                                end
957                                if kerns then
958                                    local krn = { }
959                                    for k, v in next, kerns do -- kerns is sparse
960                                        local rk = rotcev[k]
961                                        if rk then
962                                            krn[rk] = v -- kerns[k]
963                                        end
964                                    end
965                                    if not next(krn) then
966                                        krn = nil
967                                    end
968                                    local t = {
969                                        width    = width,
970                                        height   = fci.height,
971                                        depth    = fci.depth,
972                                        italic   = italic,
973                                        kerns    = krn,
974                                        commands = ref,
975                                    }
976                                    if skewchar then
977                                        local k = kerns[skewchar]
978                                        if k then
979                                            t.top_accent = width/2 + k
980                                        end
981                                    end
982                                    characters[unicode] = t
983                                else
984                                    characters[unicode] = {
985                                        width    = width,
986                                        height   = fci.height,
987                                        depth    = fci.depth,
988                                        italic   = italic,
989                                        commands = ref,
990                                    }
991                                end
992                            end
993                        end
994                        if isextension then
995                            -- todo: if multiple ex, then 256 offsets per instance
996                            local extension = mathencodings["large-to-small"]
997                            local variants_done = fs.variants_done
998                            for index, fci in next, fc do -- the raw ex file
999                                if type(index) == "number" then
1000                                    local ref = si[index]
1001                                    if not ref then
1002                                        ref = { { 'slot', s, index } }
1003                                        si[index] = ref
1004                                    end
1005                                    local italic = fci.italic
1006                                    local t = {
1007                                        width    = fci.width,
1008                                        height   = fci.height,
1009                                        depth    = fci.depth,
1010                                        italic   = italic,
1011                                        commands = ref,
1012                                    }
1013                                    local n = fci.next
1014                                    if n then
1015                                        t.next = offset + n
1016                                    elseif variants_done then
1017                                        local vv = fci.vert_variants
1018                                        if vv then
1019                                            t.vert_variants = vv
1020                                        end
1021                                        local hv = fci.horiz_variants
1022                                        if hv then
1023                                            t.horiz_variants = hv
1024                                        end
1025                                    else
1026                                        local vv = fci.vert_variants
1027                                        if vv then
1028                                            for i=1,#vv do
1029                                                local vvi = vv[i]
1030                                                vvi.glyph = vvi.glyph + offset
1031                                            end
1032                                            t.vert_variants = vv
1033                                        end
1034                                        local hv = fci.horiz_variants
1035                                        if hv then
1036                                            for i=1,#hv do
1037                                                local hvi = hv[i]
1038                                                hvi.glyph = hvi.glyph + offset
1039                                            end
1040                                            t.horiz_variants = hv
1041                                        end
1042                                    end
1043                                    characters[offset + index] = t
1044                                end
1045                            end
1046                            fs.variants_done = true
1047                            for unicode, index in next, extension do
1048                                local cu = characters[unicode]
1049                                if cu then
1050                                    cu.next = offset + index
1051                                else
1052                                    local fci = fc[index]
1053                                    if not fci then
1054                                        -- do nothing
1055                                    else
1056                                        -- probably never entered
1057                                        local ref = si[index]
1058                                        if not ref then
1059                                            ref = { { 'slot', s, index } }
1060                                            si[index] = ref
1061                                        end
1062                                        local kerns = fci.kerns
1063                                        if kerns then
1064                                            local krn = { }
1065                                         -- for k=1,#kerns do
1066                                         --     krn[offset + k] = kerns[k]
1067                                         -- end
1068                                            for k, v in next, kerns do -- is kerns sparse?
1069                                                krn[offset + k] = v
1070                                            end
1071                                            characters[unicode] = {
1072                                                width    = fci.width,
1073                                                height   = fci.height,
1074                                                depth    = fci.depth,
1075                                                italic   = fci.italic,
1076                                                commands = ref,
1077                                                kerns    = krn,
1078                                                next     = offset + index,
1079                                            }
1080                                        else
1081                                            characters[unicode] = {
1082                                                width    = fci.width,
1083                                                height   = fci.height,
1084                                                depth    = fci.depth,
1085                                                italic   = fci.italic,
1086                                                commands = ref,
1087                                                next     = offset + index,
1088                                            }
1089                                        end
1090                                    end
1091                                end
1092                            end
1093                        end
1094                    else
1095                        report_virtual("error in loading %a, problematic vector %a",name,vectorname)
1096                    end
1097                end
1098            end
1099            mathematics.extras.copy(main) --not needed here (yet)
1100        end
1101    end
1102-- inspect(characters[0x1D465])
1103-- inspect(fonts.encodings.math["tex-it"])
1104-- inspect(fontlist)
1105    --
1106    main.mathparameters = mathparameters -- still traditional ones
1107    -- This should change (some day) as it's the only place where we look forward,
1108    -- so better is to also reserve the id already which then involves some more
1109    -- management (so not now).
1110    fontlist[#fontlist+1] = {
1111     -- id   = font.nextid(),
1112        id   = 0, -- self
1113        size = size,
1114    }
1115    vfmath.addmissing(main,#fontlist,size)
1116    --
1117    mathematics.addfallbacks(main)
1118    --
1119    main.properties.math_is_scaled = true -- signal
1120    fonts.constructors.assignmathparameters(main,main)
1121    --
1122    main.MathConstants = main.mathparameters -- we directly pass it to TeX (bypasses the scaler) so this is needed
1123    --
1124    if trace_virtual or trace_timings then
1125        report_virtual("loading and virtualizing font %a at size %p took %0.3f seconds",name,size,os.clock()-start)
1126    end
1127    --
1128    main.oldmath = true
1129    return main
1130end
1131
1132function mathematics.makefont(name,set,goodies)
1133    fonts.definers.methods.variants[name] = function(specification)
1134        return vfmath.define(specification,set,goodies)
1135    end
1136end
1137
1138-- helpers
1139
1140function vfmath.setletters(font_encoding, name, uppercase, lowercase)
1141    local enc = font_encoding[name]
1142    for i = 0,25 do
1143        enc[uppercase+i] = i + 0x41
1144        enc[lowercase+i] = i + 0x61
1145    end
1146end
1147
1148function vfmath.setdigits(font_encoding, name, digits)
1149    local enc = font_encoding[name]
1150    for i = 0,9 do
1151        enc[digits+i] = i + 0x30
1152    end
1153end
1154