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