1if not modules then modules = { } end modules ['math-act'] = {
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
10
11
12
13
14
15
16
17local type, next, tonumber = type, next, tonumber
18local fastcopy, copytable, insert, remove, concat = table.fastcopy, table.copy, table.insert, table.remove, table.concat
19local formatters = string.formatters
20local byte = string.byte
21local max = math.max
22local setmetatableindex, sortedkeys, sortedhash = table.setmetatableindex, table.sortedkeys, table.sortedhash
23local lpegmatch = lpeg.match
24
25local trace_defining = false trackers.register("math.defining", function(v) trace_defining = v end)
26local trace_collecting = false trackers.register("math.collecting", function(v) trace_collecting = v end)
27local trace_tweaking = false trackers.register("math.tweaks", function(v) trace_tweaking = v end)
28
29local report_math = logs.reporter("mathematics","initializing")
30local report_mathtweak = logs.reporter("mathematics","tweak")
31
32local getfontoffamily = tex.getfontoffamily
33local texget = tex.get
34local fontcharacters = fonts.hashes.characters
35local chardata = characters.data
36local extensibles = mathematics.extensibles
37
38local context = context
39local commands = commands
40local mathematics = mathematics
41local texsetdimen = tex.setdimen
42local abs = math.abs
43
44local blocks = characters.blocks
45local stepper = utilities.parsers.stepper
46
47local helpers = fonts.helpers
48local prependcommands = helpers.prependcommands
49
50local vfcommands = helpers.commands
51local upcommand = vfcommands.up
52local downcommand = vfcommands.down
53local rightcommand = vfcommands.right
54local leftcommand = vfcommands.left
55local slotcommand = vfcommands.slot
56local charcommand = vfcommands.char
57local push = vfcommands.push
58local pop = vfcommands.pop
59
60local sequencers = utilities.sequencers
61local appendgroup = sequencers.appendgroup
62local appendaction = sequencers.appendaction
63
64local fontchars = fonts.hashes.characters
65local fontproperties = fonts.hashes.properties
66
67local mathgaps = mathematics.gaps
68
69local d_scratchleftoffset <const> = tex.isdimen("scratchleftoffset")
70local d_scratchrightoffset <const> = tex.isdimen("scratchrightoffset")
71
72local use_math_goodies = true directives.register("math.nogoodies", function(v) use_math_goodies = not v end)
73local checkitalics = false trackers .register("math.checkitalics", function(v) checkitalics = v end)
74
75local function registerdone(done,unicode)
76 if not trace_tweaking then
77 done = true
78 elseif done then
79 done[unicode] = true
80 else
81 done = { [unicode] = true }
82 end
83 return done
84end
85
86local mathfontparameteractions = sequencers.new {
87 name = "mathparameters",
88 arguments = "target,original",
89}
90
91appendgroup("mathparameters","before")
92appendgroup("mathparameters","system")
93appendgroup("mathparameters","after" )
94
95function fonts.constructors.assignmathparameters(original,target)
96 local runner = mathfontparameteractions.runner
97 if runner then
98 runner(original,target)
99 end
100end
101
102
103
104local undefined <const> = 0x3FFFFFFF
105
106function mathematics.initializeparameters(target,original,nodimensions)
107 local mathparameters = original.mathparameters
108 if mathparameters and next(mathparameters) then
109 if nodimensions ~= "noscale" then
110 mathparameters = mathematics.dimensions(mathparameters)
111 end
112
113
114
115
116 if not mathparameters.SubscriptShiftDownWithSuperscript then mathparameters.SubscriptShiftDownWithSuperscript = mathparameters.SubscriptShiftDown * 1.5 end
117
118
119
120
121 if not mathparameters.SpaceAfterScript then mathparameters.SpaceAfterScript = 0 end
122 if not mathparameters.SpaceBeforeScript then mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript end
123 if not mathparameters.SpaceBetweenScript then mathparameters.SpaceBetweenScript = mathparameters.SpaceAfterScript end
124 if not mathparameters.PrimeRaisePercent then mathparameters.PrimeRaisePercent = 0 end
125 if not mathparameters.PrimeRaiseComposedPercent then mathparameters.PrimeRaiseComposedPercent = 0 end
126 if not mathparameters.PrimeShiftUp then mathparameters.PrimeShiftUp = mathparameters.SuperscriptShiftUp end
127 if not mathparameters.PrimeBaselineDropMax then mathparameters.PrimeBaselineDropMax = mathparameters.SuperscriptBaselineDropMax end
128 if not mathparameters.PrimeShiftUpCramped then mathparameters.PrimeShiftUpCramped = mathparameters.SuperscriptShiftUpCramped end
129 if not mathparameters.PrimeSpaceAfter then mathparameters.PrimeSpaceAfter = mathparameters.SpaceAfterScript end
130 if not mathparameters.NoLimitSupFactor then mathparameters.NoLimitSupFactor = 0 end
131 if not mathparameters.NoLimitSubFactor then mathparameters.NoLimitSubFactor = 0 end
132 if not mathparameters.AccentTopShiftUp then mathparameters.AccentTopShiftUp = 0 end
133 if not mathparameters.AccentBottomShiftDown then mathparameters.AccentBottomShiftDown = 0 end
134 if not mathparameters.FlattenedAccentTopShiftUp then mathparameters.AccentTopShiftUp = 0 end
135 if not mathparameters.FlattenedAccentBottomShiftDown then mathparameters.AccentBottomShiftDown = 0 end
136 if not mathparameters.AccentBaseDepth then mathparameters.AccentBaseDepth = 0 end
137 if not mathparameters.AccentFlattenedBaseDepth then mathparameters.AccentFlattenedBaseDepth = 0 end
138 if not mathparameters.AccentTopOvershoot then mathparameters.AccentTopOvershoot = 0 end
139 if not mathparameters.AccentBottomOvershoot then mathparameters.AccentBottomOvershoot = 0 end
140 if not mathparameters.AccentSuperscriptDrop then mathparameters.AccentSuperscriptDrop = 0 end
141 if not mathparameters.AccentSuperscriptPercent then mathparameters.AccentSuperscriptPercent = 0 end
142 if not mathparameters.AccentExtendMargin then mathparameters.AccentExtendMargin = 50 end
143 if not mathparameters.DelimiterPercent then mathparameters.DelimiterPercent = 100 end
144 if not mathparameters.DelimiterShortfall then mathparameters.DelimiterShortfall = 0 end
145 if not mathparameters.DelimiterDisplayPercent then mathparameters.DelimiterDisplayPercent = 100 end
146 if not mathparameters.DelimiterDisplayShortfall then mathparameters.DelimiterDisplayShortfall = 0 end
147 if not mathparameters.RadicalKernAfterExtensible then mathparameters.RadicalKernAfterExtensible = 0 end
148 if not mathparameters.RadicalKernBeforeExtensible then mathparameters.RadicalKernBeforeExtensible = 0 end
149 if not mathparameters.SuperscriptSnap then mathparameters.SuperscriptSnap = 750 end
150 if not mathparameters.SubscriptSnap then mathparameters.SubscriptSnap = 250 end
151
152
153
154
155
156
157
158
159 target.mathparameters = mathparameters
160 end
161end
162
163sequencers.appendaction("mathparameters","system","mathematics.initializeparameters")
164
165local how = {
166
167
168 ScriptPercentScaleDown = "unscaled",
169 ScriptScriptPercentScaleDown = "unscaled",
170 RadicalDegreeBottomRaisePercent = "unscaled",
171 NoLimitSupFactor = "unscaled",
172 NoLimitSubFactor = "unscaled",
173 PrimeRaisePercent = "unscaled",
174 PrimeRaiseComposedPercent = "unscaled",
175 AccentTopOvershoot = "unscaled",
176 AccentBottomOvershoot = "unscaled",
177 AccentSuperscriptPercent = "unscaled",
178 DelimiterPercent = "unscaled",
179 DelimiterDisplayPercent = "unscaled",
180
181 RadicalRuleThickness = "vertical",
182 OverbarRuleThickness = "vertical",
183 FractionRuleThickness = "vertical",
184 UnderbarRuleThickness = "vertical",
185}
186
187local function scaleparameters(mathparameters,parameters)
188 if mathparameters and next(mathparameters) and parameters then
189 local factor = parameters.factor
190 local hfactor = parameters.hfactor
191 local vfactor = parameters.vfactor
192 for name, value in next, mathparameters do
193 local h = how[name]
194 if h == "unscaled" then
195
196 elseif h == "horizontal" then
197 value = value * hfactor
198 elseif h == "vertical" then
199 value = value * vfactor
200 else
201 value = value * factor
202 end
203 mathparameters[name] = value
204 end
205 end
206end
207
208function mathematics.scaleparameters(target,original)
209 if not target.properties.math_is_scaled then
210 scaleparameters(target.mathparameters,target.parameters)
211 target.properties.math_is_scaled = true
212 end
213end
214
215function mathematics.overloadparameters(target,original)
216 if use_math_goodies then
217 local mathparameters = target.mathparameters
218 if mathparameters and next(mathparameters) then
219 local goodies = target.goodies
220 if goodies then
221 for i=1,#goodies do
222 local goodie = goodies[i]
223 local mathematics = goodie.mathematics
224 if mathematics then
225 local parameters = mathematics.parameters
226 local bigslots = mathematics.bigslots or mathematics.bigs
227 local scales = mathematics.scales
228 if parameters then
229 if trace_defining then
230 report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size)
231 end
232 for name, value in next, parameters do
233 local tvalue = type(value)
234 local oldvalue = mathparameters[name]
235 local newvalue = oldvalue
236 if tvalue == "number" then
237 newvalue = value
238 elseif tvalue == "string" then
239
240 elseif tvalue == "function" then
241 newvalue = value(oldvalue,target,original)
242 elseif not tvalue then
243 newvalue = nil
244 end
245 if trace_defining and oldvalue ~= newvalue then
246 report_math("overloading math parameter %a: %S => %S",name,oldvalue or 0,newvalue)
247 end
248 mathparameters[name] = newvalue
249 end
250 for name, value in next, parameters do
251 local tvalue = type(value)
252 if tvalue == "string" then
253 local newvalue = mathparameters[value]
254 if not newvalue then
255 local code = loadstring("return " .. value,"","t",mathparameters)
256 if type(code) == "function" then
257 local okay, v = pcall(code)
258 if okay then
259 newvalue = v
260 end
261 end
262 end
263 if newvalue then
264
265 mathparameters[name] = newvalue
266 elseif trace_defining then
267 report_math("ignoring math parameter %a: %S",name,value)
268 end
269 end
270 end
271 end
272 if bigslots then
273 target.bigslots = bigslots
274 end
275 if scales then
276 local feature = scales.feature
277 local features = target.specification.features.normal
278 if feature == nil or features[feature] then
279 target.scriptxscale = scales.scriptxscale
280 target.scriptyscale = scales.scriptyscale
281 target.scriptweight = scales.scriptweight
282 target.scriptscriptxscale = scales.scriptscriptxscale
283 target.scriptscriptyscale = scales.scriptscriptyscale
284 target.scriptscriptweight = scales.scriptscriptweight
285 end
286 end
287 end
288 end
289 end
290 end
291 end
292end
293
294
295
296local datasets = { }
297local mathtweaks = { datasets = datasets }
298mathematics.tweaks = mathtweaks
299
300
301
302local f_u = formatters["%U"]
303
304local function unicodecharlist(t)
305 local r = { }
306 local n = 0
307 for u in sortedhash(t) do
308 n = n + 1 ; r[n] = f_u(u)
309 end
310 return concat(r," ")
311end
312
313local function report_tweak(fmt,target,original,...)
314 if fmt then
315 local metadata = (original and original.shared.rawdata.metadata) or
316 (target and target .shared.rawdata.metadata)
317 local parameters = target.parameters
318 if parameters then
319 report_mathtweak(
320 "%a, size %P, math size %i, %s",
321 metadata and metadata.fontname or "unknown",
322 parameters.size or 655360,
323 parameters.mathsize or 1,
324 formatters[fmt](...)
325 )
326 else
327 print("something is wrong")
328 end
329 else
330 report_mathtweak("")
331 end
332end
333
334local function feedback_tweak(tweak,target,original,done)
335 if not done or (type(done) == "table" and not next(done)) then
336if trace_tweaking then
337 report_tweak("no need for %a",target,original,tweak)
338end
339 elseif trace_tweaking then
340 report_tweak("tweak %a applied to: %s",target,original,tweak,unicodecharlist(done))
341 end
342end
343
344mathtweaks.subsets = {
345 acenorsuvxz = { 0x1D44E, 0x1D450, 0x1D452, 0x1D45B, 0x1D45C, 0x1D45F, 0x1D460, 0x1D462, 0x1D463, 0x1D465, 0x1D467 },
346 bhklt = { 0x1D44F, 0x1D455, 0x1D458, 0x1D459, 0x1D461 },
347 d = { 0x1D451 },
348 f = { 0x1D453 },
349 gjqy = { 0x1D454, 0x1D457, 0x1D45E, 0x1D466 },
350 i = { 0x1D456 },
351 mw = { 0x1D45A, 0x1D464 },
352 p = { 0x1D45D },
353 letterlike = { 0x02202 },
354 dotless = { 0x00049, 0x0004A, 0x00131, 0x00237, 0x1D6A4, 0x1D6A5 },
355 integrals = { 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233, 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13, 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C, 0x02320, 0x02321 },
356 horizontalfences = { 0x0203E, 0x023B4, 0x023B5, 0x023DC, 0x023DD, 0x023DE, 0x023DF, 0x023E0, 0x023E1 },
357}
358
359local function getalso(target,original)
360 local also = target.tweakalso
361 if not also then
362 also = { }
363
364 for k, v in next, target.characters do
365 local u = v.unicode
366 if u and k ~= u then
367 local a = also[u]
368 if a then
369 a[#a+1] = k
370 else
371 also[u] = { k }
372 end
373 end
374 end
375 target.tweakalso = also
376 end
377 return also
378end
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398local detail do
399
400 local splitter = lpeg.tsplitat(".")
401 local private = fonts.helpers.privateslot
402
403 detail = function(characters,k)
404 if type(k) == "string" then
405 local t = lpegmatch(splitter,k)
406 local n = #t
407 if n > 0 then
408 local slot = t[1]
409 local base = tonumber(slot) or tonumber(slot,16) or private(slot)
410 if base then
411 local c = characters[base]
412 if c and n > 1 then
413 local list = t[2]
414 if list == "parts" then
415 local nxt = c.next
416 while nxt do
417 c = characters[nxt]
418 nxt = c.next
419 end
420 c = c.parts
421 if c then
422 local index = t[3]
423 if index == "*" then
424 return t
425 else
426 if index == "top" then
427 index = #c
428 elseif index == "bottom" then
429 index = 1
430 else
431 index = tonumber(index)
432 end
433 if index then
434 c = c[index]
435 if c then
436 return c.glyph
437 end
438 end
439 end
440 end
441 elseif list == "variants" then
442 local index = t[3]
443 if index == "*" then
444 local t = { }
445 local nxt = c.next
446 while nxt do
447 t[#t+1] = nxt
448 c = characters[nxt]
449 nxt = c.next
450 end
451 return t
452 else
453 index = tonumber(index)
454 if index then
455 local nxt = c.next
456 while nxt and index > 1 do
457 c = characters[nxt]
458 nxt = c.next
459 index = index - 1
460 end
461 return nxt
462 end
463 end
464 elseif list == "flataccent" then
465 return c.flataccent
466 end
467 end
468 end
469 end
470 else
471 return k
472 end
473 end
474
475end
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555do
556
557 local stepper = utilities.parsers.stepper
558 local count = 0
559 local toeffect = fonts.toeffect
560 local privateslot = fonts.helpers.privateslot
561
562 local function adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,n)
563 k = mathgaps[k] or k
564 local character = targetcharacters[k]
565 if character then
566
567 local t = type(v)
568 if t == "number" then
569 v = list[v]
570 t = type(v)
571 end
572 if t == "table" and next(v) then
573
574 local axis = tonumber(v.axis)
575 if axis then
576 axis = target.mathparameters.AxisHeight * axis
577 end
578
579 local factor = v.factor
580 if factor then
581 local m = v
582 v = setmetatableindex({
583 width = factor,
584 height = factor,
585 depth = factor,
586 squeeze = factor,
587 extend = factor,
588 }, v)
589 end
590 local originalslot = v.original
591 if not originalslot then
592 local delta = v.delta
593 if delta then
594 originalslot = k + delta
595 end
596 end
597 if originalslot then
598 originalslot = mathgaps[originalslot] or originalslot
599 local data = targetcharacters[originalslot]
600 if data then
601 data = copytable(data)
602 data.unicode = originalslot
603 targetcharacters[k] = data
604 character = data
605 else
606 report_mathtweak("no slot %U",originalslot)
607 return
608 end
609 end
610
611 local width = character.width
612 local height = character.height
613 local depth = character.depth
614 local italic = character.italic
615 local topanchor = character.topanchor
616 local bottomanchor = character.bottomanchor
617
618 local widthfactor = v.width
619 local heightfactor = v.height
620 local depthfactor = v.depth
621 local italicfactor = v.italic
622 local anchorfactor = v.anchor
623 local advancefactor = v.advance
624 local xoffsetfactor = v.xoffset
625 local yoffsetfactor = v.yoffset
626 local scalefactor = v.scale
627 local total = (height or 0) + (depth or 0)
628 if scalefactor ~= 1 then
629 character.scale = scalefactor
630 end
631 if width and width ~= 0 then
632 if advancefactor then
633 character.advance = advancefactor * width
634 else
635 character.advance = character.advance or width
636 end
637 if widthfactor then
638 character.width = widthfactor * width
639 end
640 if xoffsetfactor then
641 character.xoffset = xoffsetfactor * width
642 end
643 end
644 if height and height ~= 0 then
645 if heightfactor then
646 character.height = heightfactor * height
647 end
648 end
649 if depth and depthfactor then
650 character.depth = depthfactor * depth
651 end
652 if yoffsetfactor then
653 character.yoffset = yoffsetfactor * total
654 end
655
656 if axis then
657 character.height = (character.height or 0) - axis
658 character.depth = (character.depth or 0) + axis
659 character.yoffset = (character.yoffset or 0) + axis
660 end
661
662 if italicfactor then
663 if italic then
664 character.italic = italicfactor * italic
665 elseif width and italicfactor ~= 1 then
666 character.italic = italicfactor * width
667 end
668 end
669 if anchorfactor then
670 character.topanchor = anchorfactor * (topanchor or width)
671 end
672
673
674
675
676 local line = v.wline
677 if line then
678 local parameters = target.parameters
679 v.line = parameters.hfactor * line / parameters.units
680 end
681
682 character.effect = toeffect(v)
683
684 v.line = line
685
686 if trace_tweaking then
687 report_tweak("adapting dimensions of %U ",target,original,k)
688 end
689
690 local originaldata = originalcharacters[k]
691 local smaller = originaldata and originaldata.smaller
692 if compact and smaller and smaller ~= k then
693 adapt(list,target,original,targetcharacters,originalcharacters,smaller,v,compact,n+1)
694 end
695 count = count + 1
696 else
697 report_mathtweak("invalid dimension entry %U",k)
698 end
699
700 if v.all then
701 local nxt = character.next
702 if nxt then
703 adapt(list,target,original,targetcharacters,originalcharacters,nxt,v,compact,n)
704 else
705 local parts = character.parts
706 if parts then
707 for i=1,#parts do
708 adapt(list,target,original,targetcharacters,originalcharacters,parts[i],v,compact,n)
709 end
710 end
711 end
712 end
713
714 else
715 report_tweak("no character %U",target,original,k)
716 end
717 end
718
719 function mathtweaks.dimensions(target,original,parameters)
720 local list = parameters.list
721 if list then
722 local targetcharacters = target.characters
723 local originalcharacters = original.characters
724 local compact = target.parameters.textscale and true or false
725 count = 0
726 for k, v in sortedhash(list) do
727 local t = type(k)
728 if t == "number" then
729 adapt(list,target,original,targetcharacters,originalcharacters,k,v,compact,1)
730 elseif t == "string" then
731 local d = privateslot(k) or detail(targetcharacters,k)
732 local t = type(d)
733 if t == "table" then
734 for i=1,#d do
735 adapt(list,target,original,targetcharacters,originalcharacters,d[i],v,compact,1)
736 end
737 elseif t == "number" then
738 adapt(list,target,original,targetcharacters,originalcharacters,d,v,compact,1)
739 elseif d then
740
741 else
742 local r = blocks[k]
743 if r then
744 local done = false
745 for i=r.first,r.last do
746 adapt(list,target,original,targetcharacters,originalcharacters,i,v,compact,1)
747 end
748 else
749 stepper(k,function(n)
750 adapt(list,target,original,targetcharacters,originalcharacters,n,v,compact,1)
751 end)
752 end
753 end
754
755
756
757
758 end
759 end
760 if trace_tweaking and count > 0 then
761 report_mathtweak("%i dimensions adapted",count)
762 end
763 end
764 end
765
766end
767
768do
769
770 function mathtweaks.message(target,original,parameters)
771 report_mathtweak(parameters.text or "no message")
772 end
773
774 function mathtweaks.showinfo(target,original,parameters)
775 local mathparameters = target.mathparameters
776 for k, v in sortedhash(mathparameters) do
777 report_mathtweak("%s : %s",k,v)
778 end
779 end
780
781end
782
783do
784
785 function mathtweaks.wipevariants(target,original,parameters)
786 local list = parameters.list
787 if list then
788 local targetcharacters = target.characters
789
790 local count = 0
791
792 local done = false
793 for k, v in sortedhash(list) do
794 local ori = targetcharacters[k]
795 local nxt = ori.next
796 local cnt = v
797 if nxt then
798 local prt = nil
799 local pro = nil
800 local lst = { }
801 while nxt do
802 local chr = targetcharacters[nxt]
803 lst[#lst+1] = chr
804 nxt = chr.next
805 if not nxt then
806 prt = chr.parts
807 pro = chr.partsorientation
808 break
809 end
810 end
811 if prt then
812 count = count + 1
813 if cnt ~= "*" then
814 if #lst < cnt then
815 cnt = #lst
816 end
817 ori = lst[cnt]
818 end
819 ori.parts = prt
820 ori.partsorientation = pro
821 end
822 done = registerdone(done,k)
823 end
824 end
825 feedback_tweak("wipevariants",target,original,done)
826 end
827 end
828
829end
830
831do
832
833
834
835 local function find(targetcharacters,slot,n)
836 local chr = targetcharacters[slot]
837 while chr do
838 slot = chr.next
839 if not slot then
840 break
841 elseif n == 1 then
842 return slot
843 end
844 n = n - 1
845 chr = targetcharacters[slot]
846 end
847 return nil
848 end
849
850 function mathtweaks.fixvariants(target,original,parameters)
851 local list = parameters.list
852 if list then
853 local targetcharacters = target.characters
854 local done = false
855 for k, v in sortedhash(list) do
856 local tmp = v.template
857 local idx = v.index
858 local dy = v.yoffset or 0
859 if tmp and idx then
860 local olduni = find(targetcharacters,k,idx)
861 local tmpuni = find(targetcharacters,tmp,idx)
862 local axis = target.mathparameters.AxisHeight
863 if axis then
864 while olduni and tmpuni do
865 local oldchr = targetcharacters[olduni]
866 local tmpchr = targetcharacters[tmpuni]
867 local oldht = oldchr.height or 0
868 local olddp = oldchr.depth or 0
869 local tmpht = tmpchr.height or 0
870 local tmpdp = tmpchr.depth or 0
871 local scale = (tmpht + tmpdp) / (oldht + olddp)
872 local total = (oldht - olddp) * scale
873 local yoffset = axis - total/2
874 oldchr.effect = { squeeze = scale }
875 oldchr.height = scale * oldht + yoffset
876 oldchr.depth = scale * olddp - yoffset
877 oldchr.yoffset = yoffset
878 olduni = oldchr.next
879 tmpuni = tmpchr.next
880 end
881 done = registerdone(done,k)
882 end
883 end
884 end
885 feedback_tweak("addvariants",target,original,done)
886 end
887 end
888
889end
890
891do
892
893 function mathtweaks.replace(target,original,parameters)
894 local list = parameters.list
895 if list then
896 local targetcharacters = target.characters
897 local originalcharacters = original.characters
898 local unicodes = original.resources.unicodes
899 if unicodes then
900 local count = 0
901 for k, v in sortedhash(list) do
902 if type(v) == "string" then
903 v = unicodes[v]
904 end
905 if type(v) == "number" then
906 targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[v] or v]
907 count = count + 1
908 end
909 end
910 if trace_tweaking and count > 0 then
911 report_tweak("%i permanent replacements",target,original,count)
912 end
913 end
914 end
915 end
916
917 function mathtweaks.substitute(target,original,parameters)
918 local list = parameters.list
919 if list then
920 local targetcharacters = target.characters
921 local originalcharacters = original.characters
922 local getsubstitution = fonts.handlers.otf.getsubstitution
923 local count = 0
924 for k, v in next, list do
925 local sub = getsubstitution(original,k,v,true)
926 if sub then
927 targetcharacters[mathgaps[k] or k] = targetcharacters[mathgaps[sub] or sub]
928 count = count + 1
929 end
930 end
931 if trace_tweaking and count > 0 then
932 report_tweak("%i permanent substitutions",target,original,count)
933 end
934 end
935 end
936
937end
938
939do
940
941
942
943 function mathtweaks.kernpairs(target,original,parameters)
944 local list = parameters.list
945 if list then
946 local targetcharacters = target.characters
947 local originalcharacters = original.characters
948 local done = false
949
950 local function add(v,n)
951 local chardata = targetcharacters[mathgaps[n] or n]
952 if chardata then
953 local width = chardata.width
954 if width then
955 local kerns = chardata.kerns or { }
956 for kk, vv in next, v do
957 stepper(kk,function(nn)
958 local t = mathgaps[nn] or nn
959 if t then
960 kerns[t] = vv * width
961 done = registerdone(done,t)
962 end
963 end)
964 end
965 chardata.kerns = kerns
966 end
967 end
968 end
969
970 for k, v in next, list do
971 stepper(k,function(n)
972 add(v,n)
973 end)
974 end
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994 feedback_tweak("kernpairs",target,original,done)
995 end
996 end
997
998end
999
1000do
1001
1002 local list = {
1003 { 0x2032, 1 },
1004 { 0x2033, 2, 0x2032 },
1005 { 0x2034, 3, 0x2032 },
1006 { 0x2057, 4, 0x2032 },
1007 { 0x2035, 1 },
1008 { 0x2036, 2, 0x2035 },
1009 { 0x2037, 3, 0x2035 },
1010 }
1011
1012 datasets.fixprimes = list
1013
1014 function mathtweaks.fixprimes(target,original,parameters)
1015 local targetcharacters = target.characters
1016 local factor = parameters.factor or 1
1017 local fake = tonumber(parameters.fake)
1018 for i=1,#list do
1019 local entry = list[i]
1020 local unicode = entry[1]
1021 local count = entry[2]
1022 local used = fonts.handlers.otf.getsubstitution(target,unicode,"ssty",true,"math","dflt") or unicode
1023 local data = targetcharacters[used]
1024 if data then
1025 targetcharacters[unicode] = data
1026 local oldheight = data.height or 0
1027 local newheight = factor * oldheight
1028 data.yoffset = newheight - (oldheight or 0)
1029 data.height = newheight
1030 data.smaller = nil
1031 elseif not fake then
1032 report_tweak("missing %i prime %U",target,original,count,unicode)
1033 end
1034 end
1035 if fake then
1036 for i=1,#list do
1037 local entry = list[i]
1038 local count = entry[2]
1039 if count > 1 then
1040 local unicode = entry[1]
1041 local original = entry[3]
1042 local data = targetcharacters[original]
1043 if data then
1044 local oldwidth = data.width
1045 local xoffset = fake * oldwidth
1046 local newwidth = oldwidth + (count - 1) * xoffset
1047 targetcharacters[unicode] = {
1048 width = newwidth,
1049 height = data.height,
1050 unicode = unicode,
1051 commands = {
1052 { "offset", 0, 0, original },
1053 { "offset", xoffset, 0, original },
1054 count > 2 and { "offset", 2 * xoffset, 0, original } or nil,
1055 count > 3 and { "offset", 3 * xoffset, 0, original } or nil,
1056 },
1057 }
1058 end
1059 end
1060 end
1061 end
1062 end
1063
1064end
1065
1066do
1067
1068 local nps = fonts.helpers.newprivateslot
1069
1070 local privates = {
1071 [0x2212] = nps("unary minus"),
1072 [0x002B] = nps("unary plus"),
1073 [0x00B1] = nps("unary plus minus"),
1074 [0x2213] = nps("unary minus plus"),
1075 }
1076
1077
1078
1079 local predefined = {
1080 ["unary minus"] = {
1081 original = 0x2212,
1082 extend = .5,
1083 width = .5,
1084 unicode = 0x002D,
1085 },
1086 ["unary plus"] = {
1087 original = 0x002B,
1088 extend = .5,
1089 squeeze = .5,
1090 width = .5,
1091 height = .5,
1092 yoffset = .2,
1093 mode = 2,
1094 wline = .5,
1095 unicode = 0x002B,
1096 },
1097 ["unary plus minus"] = {
1098 original = 0x00B1,
1099 extend = .5,
1100 squeeze = .5,
1101 width = .5,
1102 height = .5,
1103 yoffset = .2,
1104 mode = 2,
1105 wline = .5,
1106 },
1107 ["unary minus plus"] = {
1108 original = 0x2213,
1109 extend = .5,
1110 squeeze = .5,
1111 width = .5,
1112 height = .5,
1113 yoffset = .2,
1114 mode = 2,
1115 wline = .5,
1116 },
1117 }
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130 function mathtweaks.addprivates(target,original,parameters)
1131 local list = parameters.list or predefined
1132 if list then
1133 local targetcharacters = target.characters
1134 local targetparameters = target.parameters
1135 local originalcharacters = original.characters
1136 local processedprivates = { }
1137 for name, v in sortedhash(list) do
1138 if type(v) == "table" then
1139 local preset = v.preset
1140 if preset then
1141 local p = predefined[preset]
1142 if p then
1143 v = table.combine(p,v)
1144 p.preset = nil
1145 else
1146 goto next
1147 end
1148 end
1149 local charslot = v.original
1150 if charslot then
1151 local chardata = targetcharacters[charslot]
1152 if chardata then
1153 local clonedata = copytable(chardata)
1154 local cloneslot = nps(name)
1155 local unicode = v.unicode or clonedata.unicode
1156 clonedata.uncode = unicode
1157 targetcharacters[cloneslot] = clonedata
1158 if trace_tweaking then
1159 report_tweak("cloning %a from %C into %U with tounicode %U",target,original,name,charslot,cloneslot,unicode)
1160 end
1161 end
1162 processedprivates[name] = v
1163 end
1164 ::next::
1165 end
1166 end
1167 mathtweaks.dimensions(target,original,{
1168 tweak = parameters.tweak,
1169 list = processedprivates,
1170 })
1171 end
1172 end
1173
1174end
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225do
1226
1227 function mathtweaks.simplifykerns(target,original,parameters)
1228 local characters = target.characters
1229 local done = false
1230
1231 for u, v in next, characters do
1232 local mathkerns = v.mathkerns
1233 if mathkerns then
1234 local k = mathkerns.topleft
1235 if k then
1236 k = k[#k].kern
1237 if k then
1238 v.topleft = k
1239 end
1240 end
1241 local k = mathkerns.topright
1242 if k then
1243 k = k[#k].kern
1244 if k then
1245 v.topright = k
1246 end
1247 end
1248 local k = mathkerns.bottomleft
1249 if k then
1250 k = k[1].kern
1251 if k then
1252 v.bottomleft = k
1253 end
1254 end
1255 local k = mathkerns.bottomright
1256 if k then
1257 k = k[1].kern
1258 if k then
1259 v.bottomright = k
1260 end
1261 end
1262 v.mathkerns = nil
1263 done = registerdone(done,u)
1264 end
1265 end
1266 feedback_tweak("simplifykerns",target,original,done)
1267 end
1268
1269end
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286do
1287
1288 local function wipe(whatever,target,original,parameters,field,move,integrals)
1289 local targetcharacters = target.characters
1290 local targetdescriptions = target.descriptions
1291 local factor = target.parameters.factor
1292 local correct = parameters.correct
1293 local done = false
1294 local function getllx(u)
1295 local d = targetdescriptions[u]
1296 if d then
1297 local b = d.boundingbox
1298 if b then
1299 local llx = b[1]
1300 if llx < 0 then
1301 return - llx
1302 end
1303 end
1304 end
1305 return false
1306 end
1307 local function step(s)
1308 while s do
1309 local u = mathgaps[s] or s
1310 local c = targetcharacters[u]
1311 if c then
1312 if field == "topanchor" then
1313 if c.topanchor then
1314 c.topanchor = nil
1315 else
1316 goto smaller
1317 end
1318 else
1319 local okay = false
1320 local italic = c.italic
1321 if move and not c.advance then
1322 local width = c.width or 0
1323 c.advance = width
1324 if correct then
1325 local llx = getllx(u)
1326 if llx then
1327 local topanchor = c.topanchor
1328 llx = llx * factor
1329 width = width + llx
1330 c.xoffset = llx
1331 if topanchor then
1332 c.topanchor = topanchor + llx
1333 end
1334
1335
1336
1337 okay = true
1338 end
1339 end
1340 if italic and italic ~= 0 then
1341 c.width = width + italic
1342 c.bottomright = - italic
1343 okay = true
1344 else
1345 c.width = width
1346 end
1347 c.bottomanchor = width/2
1348 end
1349 if italic then
1350 c.italic = nil
1351 okay = true
1352 end
1353 if okay then
1354 done = registerdone(done,u)
1355 else
1356 goto smaller
1357 end
1358 end
1359 goto smaller
1360 ::smaller::
1361 s = c.smaller
1362 ::variants::
1363
1364 else
1365 break
1366 end
1367 end
1368 end
1369 local list = parameters.list
1370 if list == "letters" or parameters.letters then
1371 local chardata = characters.data
1372
1373 for k, v in next, targetcharacters do
1374 if v.italic then
1375 local d = chardata[v.unicode]
1376 local c = d and d.category
1377 if c == "ll" or c == "lu" then
1378 step(k)
1379 end
1380 end
1381 end
1382 goto done
1383 end
1384 if not list or list == "all" or list == true or parameters.all then
1385 list = sortedkeys(targetcharacters)
1386 elseif type(list) == "string" then
1387 list = { list }
1388 end
1389 for i=1,#list do
1390 local l = list[i]
1391 local t = type(l)
1392 if not l then
1393
1394 elseif t == "table" then
1395 for i=1,#l do
1396 step(l[i])
1397 end
1398 elseif t == "number" then
1399 step(l)
1400 else
1401 local r = blocks[l]
1402 if r then
1403 for i=r.first,r.last do
1404 step(i)
1405 end
1406 else
1407 stepper(l,step)
1408 end
1409 end
1410 end
1411 ::done::
1412 feedback_tweak(whatever,target,original,done)
1413 end
1414
1415 function mathtweaks.wipeanchors(target,original,parameters)
1416 wipe("wipeanchors",target,original,parameters,"topanchor")
1417 end
1418
1419 function mathtweaks.wipeitalics(target,original,parameters)
1420 if not checkitalics then
1421 wipe("wipeitalics",target,original,parameters,"italic")
1422 end
1423 end
1424
1425 function mathtweaks.moveitalics(target,original,parameters)
1426 wipe("moveitalics",target,original,parameters,"italic",true)
1427 end
1428
1429
1430
1431
1432
1433end
1434
1435do
1436
1437 function mathtweaks.topanchors(target,original,parameters)
1438 local characters = target.characters
1439 local list = parameters.list
1440 if list then
1441 local done = false
1442 for u, v in sortedhash(list) do
1443 local c = characters[k]
1444 if c then
1445 local w = c.width
1446 if w and w ~= 0 then
1447 c.topanchor = v * w
1448 done = registerdone(done,u)
1449 end
1450 end
1451 end
1452 feedback_tweak("topanchors",target,original,done)
1453 end
1454 end
1455
1456 function mathtweaks.movelimits(target,original,parameters)
1457 local characters = target.characters
1458 local list = parameters.list
1459 if list then
1460 local factor = parameters.factor or 1
1461 local also = getalso(target,original)
1462 local done = { }
1463 local function relocate(u,factor)
1464 if done[u] then
1465 return
1466 end
1467 done[u] = true
1468 local c = characters[u]
1469 if c then
1470 local italic = c.italic
1471 if italic then
1472 if italic ~= 0 then
1473 local width = c.width or 0
1474 local half = (italic/2) * factor
1475 c.topanchor = width/2 + half
1476 c.bottomanchor = width/2 - half
1477 c.bottomright = - italic * (parameters.icfactor or 1)
1478 if trace_tweaking then
1479
1480 end
1481 end
1482 c.anchored = true
1483 c.italic = nil
1484 end
1485 local s = c.smaller
1486 if s then
1487 relocate(s,factor)
1488 end
1489 local n = c.next
1490 if n then
1491 relocate(n,factor)
1492 end
1493
1494
1495 local parts = c.parts
1496 local italic = c.partsitalic
1497 if parts and italic then
1498 if italic ~= 0 then
1499 local tchar = characters[parts[#parts].glyph]
1500 local bchar = characters[parts[1].glyph]
1501 local width = tchar.width or 0
1502 local half = (italic/2) * factor
1503 tchar.topanchor = width/2 + half
1504 bchar.bottomanchor = width/2 - half
1505 bchar.bottomright = - italic
1506 if trace_tweaking then
1507
1508 end
1509 tchar.italic = nil
1510 bchar.italic = nil
1511 tchar.anchored = true
1512 bchar.anchored = true
1513 end
1514 c.vitalic = nil
1515 end
1516 if also then
1517 local a = also[u]
1518 if a then
1519 for i=1,#a do
1520 relocate(a[i],factor)
1521 end
1522 end
1523 end
1524 end
1525 end
1526 if #list > 0 then
1527 for i=1,#list do
1528 relocate(list[i],factor)
1529 end
1530 else
1531 for k, v in sortedhash(list) do
1532 relocate(k,tonumber(v) or factor)
1533 end
1534 end
1535 feedback_tweak("movelimits",target,original,done)
1536 end
1537 end
1538
1539end
1540
1541do
1542
1543
1544
1545 function mathtweaks.kerns(target,original,parameters)
1546 local kerns = parameters.list
1547 if kerns then
1548 local characters = target.characters
1549 local done = false
1550 local function setone(uc,data)
1551 local function set(unicode)
1552 unicode = mathgaps[unicode] or unicode
1553 local chardata = characters[unicode]
1554 if chardata then
1555 local width = chardata.width or 0
1556 local k = data.topleft ; if k and k ~= 0 then chardata.topleft = k * width end
1557 local k = data.topright ; if k and k ~= 0 then chardata.topright = k * width end
1558 local k = data.bottomleft ; if k and k ~= 0 then chardata.bottomleft = k * width end
1559 local k = data.bottomright ; if k and k ~= 0 then chardata.bottomright = k * width end
1560 done = registerdone(done,unicode)
1561 end
1562 end
1563 local unicode = detail(characters,uc)
1564 if type(unicode) == "table" then
1565 for i=1,#unicode do
1566 set(unicode[i])
1567 end
1568 elseif unicode then
1569 set(unicode)
1570 end
1571 end
1572 for unicode, data in next, kerns do
1573 setone(unicode,data)
1574
1575 end
1576 feedback_tweak("kerns",target,original,done)
1577 end
1578 end
1579
1580end
1581
1582do
1583
1584 function mathtweaks.margins(target,original,parameters)
1585 local kerns = parameters.list
1586 if kerns then
1587 local characters = target.characters
1588 local done = false
1589 local function setone(uc,data)
1590 local function set(unicode)
1591 unicode = mathgaps[unicode] or unicode
1592 local chardata = characters[unicode]
1593 if chardata then
1594 local width = chardata.width or 0
1595 local k = data.left ; if k and k ~= 0 then chardata.leftmargin = k * width end
1596 local k = data.right ; if k and k ~= 0 then chardata.rightmargin = k * width end
1597 local k = data.top ; if k and k ~= 0 then chardata.topmargin = k * width end
1598 local k = data.bottom ; if k and k ~= 0 then chardata.bottommargin = k * width end
1599 done = registerdone(done,unicode)
1600 end
1601 end
1602 local unicode = detail(characters,uc)
1603 if type(unicode) == "table" then
1604 for i=1,#unicode do
1605 set(unicode[i])
1606 end
1607 elseif unicode then
1608 set(unicode)
1609 end
1610 end
1611 for unicode, data in next, kerns do
1612 setone(unicode,data)
1613
1614 end
1615 feedback_tweak("margins",target,original,done)
1616 end
1617 end
1618
1619
1620end
1621
1622do
1623
1624
1625
1626
1627
1628 local function scale(t,width,total)
1629 local r = { }
1630 for i=1,#t do
1631 local ti = t[i]
1632 local kern = ti.kern
1633 local height = ti.height
1634 if kern then
1635 kern = width * kern
1636 end
1637 if height then
1638 height = total * height
1639 end
1640 r[i] = {
1641 kern = kern or 0,
1642 height = height or 0,
1643 }
1644 end
1645 return r
1646 end
1647
1648 function mathtweaks.staircase(target,original,parameters)
1649 local kerns = parameters.list
1650 if kerns then
1651 local characters = target.characters
1652 local function kernone(unicode,data)
1653 local chardata = characters[mathgaps[unicode] or unicode]
1654 local total = (chardata.height or 0) + (chardata.depth or 0)
1655 local width = chardata.width or 0
1656 if data then
1657 local tl = data.topleft ; if tl then tl = scale(tl,width,total) end
1658 local tr = data.topright ; if tr then tr = scale(tr,width,total) end
1659 local bl = data.bottomleft ; if bl then bl = scale(bl,width,total) end
1660 local br = data.bottomright ; if br then br = scale(br,width,total) end
1661 chardata.mathkerns = {
1662 topleft = tl,
1663 ropright = tr,
1664 bottomleft = bl,
1665 bottomright = br,
1666 }
1667 else
1668 chardata.mathkerns = nil
1669 end
1670 end
1671 for unicode, data in next, kerns do
1672 kernone(unicode,data)
1673
1674 end
1675 end
1676 end
1677
1678end
1679
1680do
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699 local nps = fonts.helpers.newprivateslot
1700
1701 local umbracepiece = nps("um brace piece")
1702 local lmbracepiece = nps("lm brace piece")
1703 local cmbracepiece = nps("cm brace piece")
1704
1705 local ulbracepiece = nps("ul brace piece")
1706 local urbracepiece = nps("ur brace piece")
1707 local llbracepiece = nps("ll brace piece")
1708 local lrbracepiece = nps("lr brace piece")
1709
1710 local over = { factor = "over" }
1711 local under = { factor = "under" }
1712
1713 local candidates = {
1714 over = {
1715 [0x203E] = over,
1716 [0x23DE] = over,
1717 [0x23DC] = over,
1718 [0x23B4] = over,
1719 },
1720 under = {
1721 [0x23DF] = under,
1722 [0x23DD] = under,
1723 [0x23B5] = under,
1724 },
1725 accent = {
1726 [0x0300] = over,
1727 [0x0308] = over,
1728 [0x0304] = over,
1729 [0x0305] = over,
1730 [0x0301] = over,
1731 [0x0302] = over,
1732 [0x030C] = over,
1733 [0x0306] = over,
1734 [0x0307] = over,
1735 [0x030A] = over,
1736 [0x0303] = over,
1737 [0x20DB] = over,
1738 },
1739 }
1740
1741 datasets.accentdimensions = candidates
1742
1743 local function adapt(c,factor,baseheight,basedepth)
1744 if not c.tweaked then
1745 local height = c.height or 0
1746 local depth = c.depth or 0
1747 local yoffset = 0
1748 if factor == "over" then
1749 local h = height - baseheight
1750 yoffset = h - height
1751 height = h
1752 depth = depth - baseheight
1753 elseif factor == "under" then
1754 local d = depth - basedepth
1755 yoffset = depth - d
1756 depth = d
1757 height = height - baseheight
1758 elseif height > 0 then
1759 local h = tonumber(factor) * height
1760 yoffset = h - height
1761 height = h
1762 elseif depth > 0 then
1763 local d = tonumber(factor) * depth
1764 yoffset = depth - d
1765 depth = d
1766 end
1767 c.yoffset = yoffset ~= 0 and yoffset or nil
1768 c.height = height > 0 and height or nil
1769 c.depth = depth > 0 and depth or nil
1770 c.tweaked = true
1771 end
1772 end
1773
1774 local function process(target,original,characters,list,baseheight,basedepth)
1775 if list then
1776 for k, v in sortedhash(list) do
1777 local c = characters[k]
1778 if c and not c.yoffset then
1779 local factor = v.factor
1780 if factor then
1781 adapt(c,factor,baseheight,basedepth)
1782 local nc = c.next
1783 local nv = 0
1784 local ns = 0
1785 while nc do
1786 local c = characters[nc]
1787 if c then
1788 adapt(c,factor,baseheight,basedepth)
1789 nv = nv + 1
1790 nc = c.next
1791 if not nc then
1792 local hv = c.parts
1793 if hv then
1794 for i=1,#hv do
1795 local c = characters[hv[i].glyph]
1796 if c then
1797 adapt(c,factor,baseheight,basedepth)
1798 ns = ns + 1
1799 end
1800 end
1801 end
1802 break
1803 end
1804 else
1805 break
1806 end
1807 end
1808 if trace_tweaking then
1809 report_tweak("adapting extensible (%i sizes, %i parts) %U",target,original,k,nv,ns)
1810 end
1811 end
1812 end
1813 end
1814 end
1815 end
1816
1817 function mathtweaks.accentdimensions(target,original,parameters)
1818 local list = parameters.list or { "over", "under" }
1819 if list then
1820 local characters = target.characters
1821 local baseheight = target.mathparameters.AccentBaseHeight or 0
1822 local basedepth = target.mathparameters.AccentBaseDepth or 0
1823 for k, v in sortedhash(list) do
1824 local t = type(v)
1825 if t == "string" then
1826 v = candidates[v]
1827 t = type(v)
1828 end
1829 if t == "table" then
1830 process(target,original,characters,v,baseheight,basedepth)
1831 end
1832 end
1833 end
1834 end
1835
1836end
1837
1838do
1839
1840 local addprivate = fonts.helpers.addprivate
1841 local privateslot = fonts.helpers.privateslot
1842 local newprivateslot = fonts.helpers.newprivateslot
1843
1844
1845
1846 function mathtweaks.addrules(target,original,parameters)
1847 local characters = target.characters
1848 local thickness = target.mathparameters.OverbarRuleThickness
1849 local width = target.parameters.quad / 3
1850 local step = width / 2
1851 local quarter = thickness / 4
1852 local half = thickness / 2
1853 local double = thickness * 2
1854 local done = false
1855 characters[0x203E] = {
1856 width = width,
1857 height = half,
1858 depth = half,
1859 yoffset = - half,
1860 unicode = 0x203E,
1861 commands = { { "rule", thickness, width } },
1862 parts = {
1863 { advance = width, ["end"] = step, glyph = 0x203E, start = 0 },
1864 { advance = width, ["end"] = 0, glyph = 0x203E, start = step, extender = 1 },
1865 },
1866 partsorientation = "horizontal",
1867 }
1868 local function build(target,leftarrow,rightarrow)
1869 if leftarrow and rightarrow then
1870
1871 local left = leftarrow.parts
1872 local right = rightarrow.parts
1873 if left and right then
1874 local leftline = right[1].glyph
1875 local rightline = left[#left].glyph
1876 local leftdata = characters[leftline]
1877 local rightdata = characters[rightline]
1878 local leftwidth = leftdata.width
1879 local rightwidth = rightdata.width
1880 local result = characters[target]
1881 if not result or result.width == 0 then
1882 result = {
1883 height = leftdata.height,
1884 depth = leftdata.depth,
1885 width = 0.9*leftwidth + rightwidth,
1886 unicode = target,
1887 commands = {
1888 slotcommand[0][leftline],
1889 leftcommand[0.1*leftwidth],
1890 slotcommand[0][rightline],
1891 },
1892 }
1893 characters[target] = result
1894 end
1895 result.parts = {
1896 { advance = leftwidth, glyph = leftline, ["end"] = .9*leftwidth, start = 0 },
1897 { advance = rightwidth, glyph = rightline, ["end"] = .1*leftwidth, start = .9*rightwidth, extender = 1 },
1898 }
1899 result.partsorientation = "horizontal"
1900 done = registerdone(done,target)
1901 end
1902 end
1903 end
1904 build(0x305,characters[0x20D6],characters[0x20D7])
1905 build(0x332,characters[0x20EE],characters[0x20EF])
1906
1907
1908
1909 if not characters[0x23B4] then
1910 local depth = 0
1911 local height = 5 * thickness
1912 local tpiece = addprivate(target,"bracket-piece-top",{
1913 width = thickness,
1914 height = height,
1915 depth = depth,
1916 commands = { upcommand[thickness], { "rule", 4 * thickness, thickness } },
1917 })
1918 local mpiece = addprivate(target,"bracket-piece-top-middle",{
1919 width = width,
1920 height = height,
1921 depth = depth,
1922 commands = { upcommand[4*thickness], { "rule", thickness, width } },
1923 })
1924 characters[0x23B4] = {
1925 width = double + width,
1926 height = height,
1927 depth = depth,
1928 unicode = 0x23B4,
1929 extensible = false,
1930 commands = {
1931 slotcommand[0][tpiece],
1932 slotcommand[0][mpiece],
1933 slotcommand[0][tpiece],
1934 },
1935 parts = {
1936 { advance = thickness, glyph = tpiece, ["end"] = 0, start = half },
1937 { advance = width, glyph = mpiece, ["end"] = step, start = step, extender = 1 },
1938 { advance = thickness, glyph = tpiece, ["end"] = half, start = 0 },
1939 },
1940 partsorientation = "horizontal",
1941 }
1942 done = registerdone(done,0x23B4)
1943 end
1944 if not characters[0x23B5] then
1945 local depth = 0
1946 local height = 5 * thickness
1947 local bpiece = addprivate(target,"bracket-piece-bottom",{
1948 width = thickness,
1949 height = height,
1950 depth = depth,
1951 yoffset = depth,
1952 commands = { { "rule", 4 * thickness, thickness } },
1953 })
1954 local mpiece = addprivate(target,"bracket-piece-bottom-middle",{
1955 width = width,
1956 height = height,
1957 depth = depth,
1958 commands = { { "rule", thickness, width } },
1959 })
1960 characters[0x23B5] = {
1961 width = double + width,
1962 height = height,
1963 depth = depth,
1964 unicode = 0x23B5,
1965 extensible = false,
1966 commands = {
1967 slotcommand[0][bpiece],
1968 slotcommand[0][mpiece],
1969 slotcommand[0][bpiece],
1970 },
1971 parts = {
1972 { advance = thickness, glyph = bpiece, ["end"] = 0, start = half },
1973 { advance = width, glyph = mpiece, ["end"] = step, start = step, extender = 1 },
1974 { advance = thickness, glyph = bpiece, ["end"] = half, start = 0 },
1975 },
1976 partsorientation = "horizontal",
1977 }
1978 done = registerdone(done,0x23B5)
1979 end
1980
1981 feedback_tweak("rules",target,original,done)
1982 end
1983
1984
1985
1986 local rbe = newprivateslot("radical bar extender")
1987 local fbe = newprivateslot("fraction bar extender")
1988
1989 local frp = {
1990 newprivateslot("flat rule left piece"),
1991 newprivateslot("flat rule middle piece"),
1992 newprivateslot("flat rule right piece"),
1993 }
1994
1995 local rrp = {
1996 newprivateslot("radical rule left piece"),
1997 newprivateslot("radical rule middle piece"),
1998 newprivateslot("radical rule right piece"),
1999 }
2000
2001 local mrp = {
2002 newprivateslot("minus rule left piece"),
2003 newprivateslot("minus rule middle piece"),
2004 newprivateslot("minus rule right piece"),
2005 }
2006
2007 local forceextensible_tag <const> = tex.charactertagcodes.forceextensible
2008
2009 local function useminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
2010 local minus = characters[0x2212]
2011 local parts = minus.parts
2012 if parameters == true then
2013 parameters = { }
2014 end
2015 if parts then
2016 minus.tag = forceextensible_tag
2017 what = copytable(what)
2018 parts = copytable(parts)
2019 local xscale = parameters.xscale or 1
2020 local yscale = parameters.yscale or 1
2021 local mwidth = minus.width
2022 local mheight = minus.height
2023 local height = (parameters.height or 1) * mheight
2024 local yshift = (parameters.yoffset or 0) * mheight
2025 local loverlap = parameters.leftoverlap or 0
2026 local roverlap = parameters.rightoverlap or 0
2027 local loffset = parameters.leftoffset or 0
2028 local roffset = parameters.rightoffset or 0
2029 if skipfirst then
2030 remove(parts,1)
2031 remove(what,1)
2032 end
2033 height = height / 2
2034 yshift = yshift + height
2035 for i=1,#parts do
2036 local part = parts[i]
2037 local glyph = part.glyph
2038 local gdata = characters[glyph]
2039 local width = gdata.width
2040 local xshift = 0
2041 if i == 1 and loverlap ~= 0 then
2042 xshift = loverlap * width
2043 width = width - xshift
2044 elseif i == #parts and roverlap ~= 0 then
2045 width = width - roverlap * width
2046 end
2047 characters[what[i]] = {
2048 height = height,
2049 depth = height,
2050 width = width,
2051 advance = gdata.width,
2052 unicode = 0xFFFD,
2053 commands = {
2054 leftcommand[xshift],
2055 downcommand[yshift],
2056 { "slot", 0, glyph, xscale, yscale },
2057 },
2058 }
2059
2060 if part["start"] >= width then
2061 part["start"] = width
2062 end
2063 if part["end"] >= width then
2064 part["end"] = width
2065 end
2066 part.advance = width
2067 part.glyph = what[i]
2068 end
2069 characters[what[1]].unicode = unicode
2070 xshift = loffset * mwidth + loverlap * mwidth
2071 width = mwidth - xshift - roffset * mwidth - roverlap * mwidth
2072 characters[unicode] = {
2073
2074 height = height,
2075 depth = height,
2076 width = width,
2077 tag = forceextensible_tag,
2078
2079 commands = {
2080 leftcommand[xshift],
2081 downcommand[yshift],
2082 { "slot", 0, 0x2212, xscale, yscale },
2083 },
2084 unicode = tounicode or unicode,
2085
2086 parts = parts,
2087 partsorientation = "horizontal",
2088 }
2089 end
2090 end
2091
2092
2093
2094 local function checkminus(target,unicode,characters,parameters,skipfirst,what,tounicode)
2095 local minus = characters[unicode]
2096 local parts = minus.parts
2097 if parameters == true then
2098 parameters = { }
2099 end
2100 local p_normal = 0
2101 local p_flat = 0
2102 local mwidth = minus.width
2103 local height = minus.height
2104 local depth = minus.depth
2105 local loffset = parameters.leftoffset or 0
2106 local roffset = parameters.rightoffset or 0
2107 local lshift = mwidth * loffset
2108 local rshift = mwidth * roffset
2109 local width = mwidth - lshift - rshift
2110 if parts then
2111
2112 if lshift ~= 0 or width ~= mwidth then
2113 parts = copytable(parts)
2114 for i=1,#parts do
2115 local part = parts[i]
2116 local glyph = part.glyph
2117 local gdata = characters[glyph]
2118 local width = gdata.width
2119 local advance = part.advance
2120 local lshift = 0
2121 if i == 1 and left ~= 0 then
2122 lshift = loffset * width
2123 width = width - lshift
2124 advance = advance - lshift
2125 elseif i == #parts and roffset ~= 0 then
2126 width = width - rshift
2127 advance = advance - rshift
2128 end
2129 characters[what[i]] = {
2130 height = height,
2131 depth = depth,
2132 width = width,
2133 commands = {
2134
2135 leftcommand[lshift],
2136 slotcommand[0][glyph],
2137 },
2138 }
2139 part.glyph = what[i]
2140 part.advance = advance
2141 end
2142 minus.parts = parts
2143 minus.partsorientation = "horizontal"
2144
2145 end
2146 else
2147 local f_normal = formatters["M-NORMAL-%H"](unicode)
2148
2149 p_normal = addprivate(target,f_normal,{
2150 height = height,
2151 width = width,
2152 commands = {
2153
2154 push,
2155 leftcommand[lshift],
2156 slotcommand[0][unicode],
2157 pop,
2158 },
2159 })
2160
2161 local step = .8*width
2162 minus.parts = {
2163 { extender = 0, glyph = p_normal, ["end"] = step, start = 0, advance = width },
2164 { extender = 1, glyph = p_normal, ["end"] = step, start = step, advance = width },
2165 { extender = 0, glyph = p_normal, ["end"] = 0, start = step, advance = width },
2166 }
2167 minus.partsorientation = "horizontal"
2168 end
2169 minus.unicode = tounicode or unicode
2170 end
2171
2172 function mathtweaks.replacerules(target,original,parameters)
2173 local characters = target.characters
2174 local minus = parameters.minus
2175 local fraction = parameters.fraction
2176 local radical = parameters.radical
2177 local stacker = parameters.stacker
2178 if minus then
2179 checkminus(target,0x2212,characters,minus,false,mrp)
2180 end
2181 if fraction then
2182 useminus(target,fbe,characters,fraction,false,frp,0x2044)
2183 end
2184 if radical and not characters[rbe] then
2185 local skipfirst = true
2186 if radical.skipfirst == false then
2187 skipfirst = false
2188 end
2189 useminus(target,rbe,characters,radical,skipfirst,rrp,0x2061)
2190 end
2191 if stacker then
2192 useminus(target,0x203E,characters,stacker,false,frp)
2193 end
2194 end
2195
2196 local force = false experiments.register("math.arrows", function(v) force = v end)
2197
2198 local function tighten(target,unicode,left,right,squeeze,yoffset)
2199 local name = formatters["math tightened %U %.3N %.3N %.3N %.3N"](unicode,left,right,squeeze,yoffset)
2200 local slot = privateslot(target,name)
2201 if not slot then
2202 local characters = target.characters
2203 local data = copytable(characters[unicode])
2204 local width = data.width
2205 data.advance = width
2206 data.width = width * (1-left-right)
2207 data.xoffset = width * -left
2208 if squeeze ~= 1 then
2209 data.effect = { squeeze = squeeze }
2210 end
2211 if yoffset ~= 0 then
2212 data.yoffset = (data.height or 0) * yoffset
2213 end
2214 slot = addprivate(target,name,data)
2215 end
2216 return slot
2217 end
2218
2219 local function create(target,unicode,list,overloads)
2220 local characters = target.characters
2221 local chardata = characters[unicode]
2222 if chardata then
2223 local endpoint = unicode
2224 while chardata.next do
2225 chardata = characters[chardata.next]
2226 end
2227 if chardata and (force or overloads[unicode] == false or not chardata.parts) then
2228 if not list then
2229
2230 else
2231 local overload = overloads[unicode]
2232 local parts = { }
2233 for i=1,#list do
2234 local part = list[i]
2235 local glyph = part.glyph or unicode
2236 local check = overloads[glyph]
2237 local left = (check and check.left ) or part.left or 0
2238 local right = (check and check.right ) or part.right or 0
2239 local squeeze = check and check.squeeze or 1
2240 local yoffset = check and check.yoffset or 0
2241 if left~= 0 or right ~= 0 or squeeze ~= 1 or yoffset ~= 0 then
2242 glyph = tighten(target,glyph,left,right,squeeze,yoffset)
2243 end
2244 local width = characters[glyph].width
2245 local step = width/2
2246 if part.extensible then
2247 parts[#parts+1] = {
2248 advance = width,
2249 glyph = glyph,
2250 ["end"] = step,
2251 start = step,
2252 extender = 1,
2253 }
2254 else
2255 parts[#parts+1] = {
2256 advance = width,
2257 glyph = glyph,
2258 ["end"] = 0,
2259 start = step,
2260 }
2261 end
2262 end
2263 if #parts == #list then
2264 chardata.parts = parts
2265 chardata.partsorientation = "horizontal"
2266 end
2267 end
2268 end
2269 end
2270 end
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290 local function initialize(left, right, slack)
2291
2292
2293 local single = { glyph = 0x2212, left = slack, right = slack, extensible = true }
2294 local double = { glyph = 0x003D, left = slack, right = slack, extensible = true }
2295 local triple = { glyph = 0x2261, left = slack, right = slack, extensible = true }
2296
2297 local slackslack = { left = slack, right = slack }
2298 local leftslack = { left = left, right = slack }
2299 local slackright = { left = slack, right = right }
2300
2301 local centered = false
2302 local singleright = { single, slackright }
2303 local leftsingle = { leftslack, single }
2304 return {
2305
2306 [0x002D] = { { left = slack, right = slack, glyph = 0x2212 }, single },
2307
2308
2309 [0x2190] = leftsingle,
2310 [0x219E] = leftsingle,
2311 [0x21BC] = leftsingle,
2312 [0x21BD] = leftsingle,
2313
2314 [0x2192] = singleright,
2315 [0x21A0] = singleright,
2316 [0x21C0] = singleright,
2317 [0x21C1] = singleright,
2318
2319 [0x003D] = { slackslack, double },
2320 [0x2261] = { slackslack, triple },
2321 [0x27F8] = { leftslack, double },
2322 [0x27F9] = { double, slackright },
2323
2324 [0x21A9] = centered,
2325 [0x21AA] = centered,
2326 [0x21CB] = centered,
2327 [0x21CC] = centered,
2328 [0x21C4] = centered,
2329 [0x21C6] = centered,
2330 [0x21A6] = centered,
2331
2332 [0x203E] = { slackslack, { left = slack, right = slack, extensible = true } },
2333
2334 [0x27F7] = { { glyph = 0x2190, left = left, right = slack }, single, { glyph = 0x2192, left = slack, right = right } },
2335 [0x27FA] = { { glyph = 0x27F8, left = left, right = slack }, double, { glyph = 0x27F9, left = slack, right = right } },
2336 }
2337 end
2338
2339 datasets.addarrows = { }
2340
2341 function mathtweaks.addarrows(target,original,parameters)
2342 local overloads = parameters.list or { }
2343 local left = parameters.left or 0.05
2344 local right = parameters.right or 0.05
2345 local slack = parameters.slack or 0.1
2346 local arrows = initialize(left,right,slack)
2347
2348 for unicode, list in sortedhash(arrows) do
2349 create(target,unicode,list,overloads)
2350 end
2351 datasets.addarrows = sortedkeys(arrows)
2352 if trace_tweaking then
2353 report_tweak("arrows added",target,original)
2354 end
2355
2356 end
2357
2358end
2359
2360
2361
2362do
2363
2364 function mathtweaks.addparts(target,original,parameters)
2365 local characters = target.characters
2366 local list = parameters.list
2367 if list then
2368 for unicode, data in sortedhash(list) do
2369 local template = data.template
2370 if template then
2371 local source = characters[template]
2372 local target = characters[unicode]
2373 if source and target then
2374 local sequence = data.sequence
2375 if sequence then
2376
2377 local parts = source.parts
2378 if parts then
2379 local p = { }
2380 for i=1,#sequence do
2381 local step = sequence[i]
2382 local glyph = step.glyph
2383 if glyph == "first" or glyph == "last" then
2384 local g = glyph == "first" and 1 or #parts
2385 local c = fastcopy(parts[g])
2386 local f = step.factor
2387 if f then
2388 c["end"] = f * (c["end"] or 0)
2389 c.start = f * (c.start or 0)
2390 end
2391 p[#p+1] = c
2392 else
2393 local c = characters[glyph]
2394 if c then
2395 p[#p+1] = {
2396 glyph = glyph,
2397 advance = c.width,
2398 start = 0,
2399 ["end"] = 0,
2400 }
2401 end
2402 end
2403 end
2404 if #p > 0 then
2405 target.parts = p
2406 if not data.horizontal then
2407 target.partsorientation = "vertical"
2408 end
2409 end
2410 end
2411 end
2412 end
2413 end
2414 end
2415 end
2416 end
2417
2418end
2419
2420function mathtweaks.action(target,original,parameters)
2421 local action = parameters.action
2422 if type(action) == "function" then
2423 action(target,original,parameters)
2424 end
2425end
2426
2427do
2428
2429 local list = {
2430 { 0x00A0, "s", 1 },
2431 { 0x2000, "q", 1/2 },
2432 { 0x2001, "q", 1 },
2433 { 0x2002, "q", 1/2 },
2434 { 0x2003, "q", 1 },
2435 { 0x2004, "q", 1/3 },
2436 { 0x2005, "q", 1/4 },
2437 { 0x2006, "q", 1/6 },
2438 { 0x2007, "c", byte('0') },
2439 { 0x2008, "c", byte('.') },
2440 { 0x2009, "q", 1/8 },
2441 { 0x200A, "q", 1/8 },
2442 { 0x200B, "q", 0 },
2443 { 0x202F, "q", 1/8 },
2444 { 0x205F, "s", 1/2 },
2445 }
2446
2447 datasets.checkspacing = list
2448
2449 function mathtweaks.checkspacing(target,original,parameters)
2450 local characters = target.characters
2451 local parameters = target.parameters
2452 for i=1,#list do
2453 local entry = list[i]
2454 local unicode = entry[1]
2455 local data = characters[unicode]
2456 if not data then
2457 local method = entry[2]
2458 local fraction = entry[3]
2459 local width = 0
2460 local height = 0
2461
2462 if method == "c" then
2463 local template = characters[fraction]
2464 width = template.width
2465 height = template.height
2466
2467 elseif method == "s" then
2468 width = fraction * parameters.space
2469 height = 0
2470
2471 else
2472 width = fraction * parameters.quad
2473 height = 0
2474
2475 end
2476 if trace_tweaking then
2477 report_tweak("setting width of %U to %p",target,original,unicode,width)
2478 end
2479 characters[unicode] = {
2480 width = width,
2481
2482 height = height,
2483
2484 unicode = unicode,
2485 commands = {
2486
2487 },
2488 }
2489 end
2490 end
2491 end
2492
2493end
2494
2495do
2496
2497
2498
2499
2500 local nps = fonts.helpers.newprivateslot
2501
2502 local radicalbarextender = nps("radical bar extender")
2503
2504 local list = {
2505 0x221A,
2506 }
2507
2508 local function fix(target,original,characters,unicode)
2509 local data = characters[unicode]
2510 if data then
2511 local height = data.height or 0
2512 local depth = data.depth or 0
2513 if depth > height then
2514 if trace_tweaking then
2515 report_tweak("swapping height and depth of radical %U",target,original,unicode)
2516 end
2517 if data.rorrim then
2518
2519 else
2520 data.height = (data.height or 0) + (data.depth or 0)
2521 data.yoffset = data.depth
2522 data.depth = 0
2523 end
2524 end
2525 local smaller = data.smaller
2526 if smaller then
2527 fix(target,original,characters,smaller)
2528 end
2529
2530
2531
2532
2533 local next = data.next
2534 if next then
2535 fix(target,original,characters,next)
2536 else
2537
2538
2539
2540
2541
2542 end
2543 end
2544 end
2545
2546 function mathtweaks.fixradicals(target,original,parameters)
2547 local characters = target.characters
2548 for i=1,#list do
2549 local unicode = list[i]
2550 fix(target,original,characters,unicode)
2551 end
2552 end
2553
2554 local function fix(target,original,characters,u,l)
2555 local data = characters[u]
2556 if data then
2557
2558 data.innerlocation = l.location == "right" and "right" or "left"
2559 data.innerxoffset = (l.hfactor or 1) * (data.width or 0)
2560 data.inneryoffset = (l.vfactor or 1) * ((data.height or 0) + (data.depth or 0))
2561 end
2562 end
2563
2564 function mathtweaks.radicaldegreeanchors(target,original,parameters)
2565 local list = parameters.list
2566 if list then
2567 local characters = target.characters
2568 for unicode, l in sortedhash(list) do
2569 local u = detail(characters,unicode) or unicode
2570 if type(u) == "table" then
2571 for i=1,#u do
2572 fix(target,original,characters,u[i],l)
2573 end
2574 else
2575 fix(target,original,characters,u,l)
2576 end
2577 end
2578 end
2579 end
2580
2581 local function fix(target,original,characters,u,l)
2582 local data = characters[u]
2583 if data then
2584 data.leftmargin = (l.left or 1) * (data.width or 0)
2585 data.rightmargin = (l.right or 1) * (data.width or 0)
2586 end
2587 end
2588
2589 function mathtweaks.radicalbodymargins(target,original,parameters)
2590 local list = parameters.list
2591 if list then
2592 local characters = target.characters
2593 for unicode, l in sortedhash(list) do
2594 local u = detail(characters,unicode) or unicode
2595 if type(u) == "table" then
2596 for i=1,#u do
2597 fix(target,original,characters,u[i],l)
2598 end
2599 else
2600 fix(target,original,characters,u,l)
2601 end
2602 end
2603 end
2604 end
2605
2606end
2607
2608do
2609
2610 local done = nil
2611
2612 local function fix(target,original,characters,unicode,axis)
2613 if done[unicode] then
2614 return
2615 end
2616 done[unicode] = true
2617 local data = characters[unicode]
2618 if data then
2619 local height = data.height or 0
2620 local depth = data.depth or 0
2621 if trace_tweaking then
2622 report_tweak("swapping height and depth of %U",target,original,unicode)
2623 end
2624 local half = (height + depth)/2
2625 if data.rorrim then
2626
2627 else
2628 data.yoffset = depth - (half - axis)
2629 end
2630 height = half + axis
2631 depth = half - axis
2632 data.height = height
2633 data.depth = depth
2634 local smaller = data.smaller
2635 if smaller then
2636 fix(target,original,characters,smaller,axis)
2637 end
2638 local mirror = data.mirror
2639 if mirror then
2640 fix(target,original,characters,mirror,axis)
2641 end
2642 local next = data.next
2643 if next then
2644 fix(target,original,characters,next,axis)
2645 end
2646 end
2647 end
2648
2649 function mathtweaks.fixoldschool(target,original,parameters)
2650 local characters = target.characters
2651 local list = mathtweaks.subsets.integrals
2652 local also = getalso(target,original)
2653 local axis = target.mathparameters.AxisHeight
2654 done = { }
2655 for i=1,#list do
2656 local unicode = list[i]
2657 fix(target,original,characters,unicode,axis)
2658 end
2659 if also then
2660 local a = also[u]
2661 if a then
2662 for i=1,#a do
2663 fix(target,original,characters,a[i],axis)
2664 end
2665 end
2666 end
2667 done = nil
2668 end
2669
2670
2671
2672
2673
2674 function mathtweaks.fixintegrals(target,original,parameters)
2675 local characters = target.characters
2676 local integral = characters[0x222B]
2677 while integral do
2678 local next = integral.next
2679 if next then
2680 integral = characters[next]
2681 else
2682 break
2683 end
2684 end
2685 if integral and not integral.parts then
2686 local top = characters[0x2320]
2687 local mid = characters[0x23AE]
2688 local bot = characters[0x2321]
2689 if top and mid and bot then
2690 top = top.height
2691 mid = mid.height
2692 bot = bot.height
2693 integral.partsitalic = integral.italic
2694 integral.parts = {
2695 { advance = bot, ["end"] = bot/3, glyph = 0x2321, start = bot/3 },
2696 { advance = mid, ["end"] = mid/2, glyph = 0x23AE, start = mid/2, extender = 1 },
2697 { advance = top, ["end"] = top/3, glyph = 0x2320, start = top/3 },
2698 }
2699 integral.partsorientation = "vertical"
2700 if trace_tweaking then
2701 report_tweak("fixing the integral extensible",target,original)
2702 end
2703 end
2704 else
2705 report_tweak("no need to fix the integral extensible",target,original)
2706 end
2707 end
2708
2709end
2710
2711do
2712
2713 local list = { 0x2061, 0x2062, 0x2063, 0x2064 }
2714
2715 datasets.wipecues = list
2716
2717 function mathtweaks.wipecues(target,original,parameters)
2718 local characters = target.characters
2719 local tobewiped = parameters.list or list
2720 local done = false
2721 for i=1,#tobewiped do
2722 local unicode = tobewiped[i]
2723 characters[unicode] = {
2724 width = 0,
2725 height = 0,
2726 depth = 0,
2727 unicode = unicode,
2728 commands = {
2729 { "rule" , 0, 0 }
2730 }
2731 }
2732 done = registerdone(done,unicode)
2733 end
2734 feedback_tweak("wipecues",target,original,done)
2735 end
2736
2737end
2738
2739do
2740
2741 local mapping = {
2742 [0x002F] = 0x2044,
2743 }
2744
2745 datasets.fixslashes = mapping
2746
2747 function mathtweaks.fixslashes(target,original,parameters)
2748 local characters = target.characters
2749
2750 for normal, weird in sortedhash(mapping) do
2751 local normalone = characters[normal]
2752 local weirdone = characters[weird]
2753 if normalone and weirdone and not normalone.next then
2754 normalone.next = weirdone.next
2755
2756 end
2757 weirdone = copytable(normalone)
2758 characters[weird] = weirdone
2759 weirdone.unicode = weird
2760 end
2761
2762 if trace_tweaking then
2763 report_tweak("slashes fixed",target,original)
2764 end
2765 end
2766
2767end
2768
2769do
2770
2771 local nps = fonts.helpers.newprivateslot
2772
2773 local mapping = {
2774 [0x0300] = { 0x0060, false },
2775 [0x0308] = { 0x00A8, false },
2776 [0x0304] = { 0x00AF, false },
2777 [0x0301] = { 0x00B4, false },
2778 [0x0302] = { 0x02C6, true },
2779 [0x030C] = { 0x02C7, true },
2780 [0x0306] = { 0x02D8, false },
2781 [0x0307] = { 0x02D9, false },
2782 [0x030A] = { 0x02DA, false },
2783 [0x0303] = { 0x02DC, true },
2784 [0x20DB] = { 0x20DB, false },
2785
2786 }
2787
2788 datasets.fixaccents = mapping
2789 datasets.extendaccents = mapping
2790 datasets.flattenaccents = mapping
2791 datasets.copyaccents = mapping
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803local cdata = characters.data
2804
2805 function mathtweaks.fixaccents(target,original,parameters)
2806 local characters = target.characters
2807 local done = false
2808 for stretching, entry in sortedhash(mapping) do
2809 local alias = entry[1]
2810 local stretchingdata = characters[stretching]
2811 if stretchingdata and stretchingdata.width == 0 then
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827 local topanchor = stretchingdata.topanchor or 0
2828 local width = -topanchor
2829 topanchor = width/2
2830 stretchingdata.width = width
2831 stretchingdata.advance = 0
2832 if not stretchingdata.anchored then
2833 stretchingdata.topanchor = topanchor
2834 end
2835 stretchingdata.commands = { rightcommand[width + topanchor], charcommand[stretching] }
2836
2837 done = registerdone(done,stretching)
2838 end
2839 end
2840 feedback_tweak("fixaccents",target,original,done)
2841 end
2842
2843 function mathtweaks.checkaccents(target,original,parameters)
2844 local characters = target.characters
2845 local done = false
2846 local factor = target.parameters.hfactor
2847 for unicode, data in sortedhash(characters) do
2848 local width = data.width
2849 if width == 0 then
2850 local d = chardata[data.unicode or unicode]
2851 if d then
2852 local c = d and d.category
2853 if c == "mn" or c == "sk" or c == "lm" then
2854 local b = target.descriptions[unicode]
2855 if b then
2856 b = b.boundingbox
2857 end
2858 if b and not d.anchored then
2859 local topanchor = data.topanchor or 0
2860 local llx = b[1] * factor
2861 local urx = b[3] * factor
2862 data.advance = data.width
2863 if true then
2864
2865 width = 2 * (topanchor - llx)
2866
2867
2868
2869
2870
2871 data.xoffset = -llx
2872 else
2873 width = urx - llx
2874 data.commands = {
2875 leftcommand[llx],
2876 slotcommand[0][unicode]
2877 }
2878 end
2879 data.width = width
2880 data.topanchor = width/2
2881 data.bottomanchor = width/2
2882 end
2883 done = registerdone(done,unicode)
2884 end
2885 end
2886 end
2887 end
2888 feedback_tweak("checkaccents",target,original,done)
2889 end
2890
2891
2892
2893 function mathtweaks.extendaccents(target,original,parameters)
2894 local characters = target.characters
2895 local all = parameters.all
2896 local count = tonumber(all)
2897 local done = false
2898 for stretching, entry in sortedhash(mapping) do
2899 local extend = entry[2]
2900 if extend then
2901 local last = characters[stretching]
2902 local cnt = 1
2903 local okay = false
2904 while last do
2905 if all or (count and cnt > count) then
2906 last.extensible = true
2907 local flataccent = last.flataccent
2908 if flataccent then
2909 characters[flataccent].extensible = true
2910 okay = true
2911 end
2912 end
2913 local n = last.next
2914 if n then
2915 last = characters[n]
2916 else
2917 last.extensible = true
2918 local flataccent = last.flataccent
2919 if flataccent then
2920 characters[flataccent].extensible = true
2921 okay = true
2922 end
2923 break
2924 end
2925 cnt = cnt + 1
2926 end
2927 if okay then
2928 done = registerdone(done,stretching)
2929 end
2930 end
2931 end
2932 feedback_tweak("extendaccents",target,original,done)
2933 end
2934
2935
2936
2937
2938
2939
2940 local f_flat = formatters["flat accent %05X"]
2941
2942 function mathtweaks.flattenaccents(target,original,parameters)
2943 local characters = target.characters
2944 local force = parameters.force
2945 local squeeze = parameters.squeeze or 0.85
2946 local ofactor = parameters.offset or (squeeze/8.5)
2947 local hfactor = parameters.height or 0.95
2948 local done = false
2949 for stretching, entry in sortedhash(mapping) do
2950 local code = stretching
2951 local last = characters[stretching]
2952 while last do
2953 if force or not last.flataccent then
2954 local slot = nps(f_flat(code))
2955 local height = last.height or 0
2956 characters[slot] = {
2957 width = last.width,
2958 depth = last.depth,
2959 height = last.height * hfactor,
2960 topanchor = last.topanchor,
2961 bottomanchor = last.bottomanchor,
2962 commands = { { "offset", 0, ofactor * height, code, 1, squeeze } },
2963
2964
2965
2966 unicode = last.unicode,
2967 }
2968
2969 last.flataccent = slot
2970 done = registerdone(done,stretching)
2971 end
2972 code = last.next
2973 if code then
2974 last = characters[code]
2975 else
2976 break
2977 end
2978 end
2979 end
2980 feedback_tweak("flattenaccents",target,original,done)
2981 end
2982
2983 function mathtweaks.copyaccents(target,original,parameters)
2984 local characters = target.characters
2985 local done = false
2986 for stretching, entry in sortedhash(mapping) do
2987 local alias = entry[1]
2988 if alias ~= stretching then
2989 local stretchingdata = characters[stretching]
2990 if stretchingdata then
2991
2992 characters[alias] = {
2993 width = stretchingdata.width,
2994 height = stretchingdata.height,
2995 depth = stretchingdata.depth,
2996 next = stretchingdata.next,
2997
2998 commands = stretchingdata.commands or { charcommand[stretching] },
2999 topanchor = stretchingdata.topanchor,
3000
3001 unicode = alias,
3002 }
3003 done = registerdone(done,stretching)
3004 end
3005 end
3006 end
3007 feedback_tweak("copyaccents",target,original,done)
3008 end
3009
3010 function mathtweaks.keepbases(target,original,parameters)
3011 local characters = target.characters
3012 local done = false
3013 local list = parameters.list
3014 if list == "default" then
3015 list = sortedkeys(mapping)
3016 end
3017 if list and #list > 0 then
3018 for i=1,#list do
3019 local unicode = list[i]
3020 local chardata = characters[unicode]
3021 if chardata then
3022 chardata.keepbase = true
3023 done = registerdone(done,unicode)
3024 end
3025 end
3026 else
3027
3028 end
3029 feedback_tweak("keepbases",target,original,done)
3030 end
3031
3032end
3033
3034do
3035
3036 function mathtweaks.inspect(target,original,parameters)
3037 local characters = target.characters
3038 local slot = parameters.slot or parameters.unicode
3039 if slot then
3040
3041 report_math(formatters["%C data:"](slot))
3042 inspect(characters[slot])
3043 end
3044 end
3045
3046end
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084do
3085
3086 local function jointwo(characters,force,unicode,ds,u1,d12,u2)
3087 if force or not characters[unicode] then
3088 local c1 = characters[u1]
3089 local c2 = characters[u2]
3090 if c1 and c2 then
3091 local w1 = c1.width
3092 local w2 = c2.width
3093 local width
3094 if d12 == false then
3095 d12 = 0
3096 width = w2
3097 elseif d12 < 0 then
3098 d12 = d12 * w2
3099 width = w2
3100 else
3101 d12 = d12 * ds
3102 width = w1 + w2 - d12
3103 end
3104 characters[unicode] = {
3105 unicode = unicode,
3106 width = width,
3107 height = max(c1.height or 0, c2.height or 0),
3108 depth = max(c1.depth or 0, c2.depth or 0),
3109 keepvirtual = true,
3110 commands = {
3111
3112
3113 slotcommand[0][u1],
3114
3115 d12 ~= 0 and leftcommand[d12] or false,
3116 slotcommand[0][u2],
3117
3118 },
3119 }
3120 end
3121 end
3122 end
3123
3124 local function jointhree(characters,force,unicode,ds,u1,d12,u2,d23,u3)
3125 if force or not characters[unicode] then
3126 local c1 = characters[u1]
3127 local c2 = characters[u2]
3128 local c3 = characters[u3]
3129 if c1 and c2 and c3 then
3130 local w1 = c1.width
3131 local w2 = c2.width
3132 local w3 = c3.width
3133 d12 = d12 * ds
3134 d23 = d23 * ds
3135 characters[unicode] = {
3136 unicode = unicode,
3137 width = w1 + w2 + w3 - d12 - d23,
3138 height = max(c1.height or 0, c2.height or 0, c3.height or 0),
3139 depth = max(c1.depth or 0, c2.depth or 0, c3.depth or 0),
3140 commands = {
3141 slotcommand[0][u1],
3142 d12 ~= 0 and leftcommand[d12] or false,
3143 slotcommand[0][u2],
3144 d23 ~= 0 and leftcommand[d23] or false,
3145 slotcommand[0][u3],
3146 }
3147 }
3148 end
3149 end
3150 end
3151
3152 function mathtweaks.addequals(target,original,parameters)
3153 local characters = target.characters
3154 local step = target.parameters.size/18
3155 local force = parameters.force
3156force = true
3157 jointwo (characters,force,0x2254,step,0x03A,0,0x03D)
3158 jointhree(characters,force,0x2A74,step,0x03A,0,0x03A,0,0x03D)
3159 jointwo (characters,force,0x2A75,step,0x03D,0,0x03D)
3160 jointhree(characters,force,0x2A76,step,0x03D,0,0x03D,0,0x03D)
3161 end
3162
3163end
3164
3165do
3166
3167
3168
3169
3170
3171 local nps = fonts.helpers.newprivateslot
3172
3173 local radical <const> = 0x0221A
3174 local actuarianrightlong <const> = nps("delimited right annuity long")
3175 local actuarianrightshort <const> = nps("delimited right annuity short")
3176 local actuarianleftlong <const> = nps("delimited left annuity long" )
3177 local actuarianleftshort <const> = nps("delimited left annuity short" )
3178 local placehold <const> = nps("delimited ghost annuity")
3179
3180 local actuarianbottomrightlong <const> = nps("delimited bottom right annuity long")
3181 local actuarianbottomrightshort <const> = nps("delimited bottom right annuity short")
3182 local actuarianbottomleftlong <const> = nps("delimited bottom left annuity long" )
3183 local actuarianbottomleftshort <const> = nps("delimited bottom left annuity short" )
3184
3185 function mathtweaks.addactuarian(target,original,parameters)
3186 local characters = target.characters
3187 local parameters = target.parameters
3188 local linewidth = target.MathConstants.RadicalRuleThickness
3189 local basechar = characters[radical]
3190 local baseheight = (basechar.height or 0)/2
3191 local basedepth = (basechar.depth or 0)/2
3192 local basetotal = baseheight + basedepth
3193 local used = baseheight
3194
3195 characters[placehold] = {
3196 width = 2*linewidth,
3197 height = baseheight,
3198 depth = basedepth,
3199 unicode = actuarian,
3200 callback = "devirtualize",
3201 commands = {
3202 rightcommand[linewidth],
3203 downcommand[basedepth],
3204 { "rule", basetotal, 0 },
3205 },
3206 }
3207
3208 characters[0x020E7] = {
3209 width = 6*linewidth,
3210 height = baseheight,
3211 depth = basedepth,
3212 unicode = actuarian,
3213 callback = "devirtualize",
3214 commands = {
3215 upcommand[baseheight-4*linewidth],
3216 { "rule", linewidth, 4*linewidth },
3217 downcommand[basetotal/2-linewidth],
3218 { "rule", basetotal/2, linewidth },
3219 },
3220 }
3221
3222 characters[actuarianrightlong] = {
3223 width = 2*linewidth,
3224 height = baseheight,
3225 depth = basedepth,
3226 unicode = actuarian,
3227 callback = "devirtualize",
3228 commands = {
3229 downcommand[basedepth],
3230 { "rule", basetotal, linewidth },
3231 },
3232 parts = {
3233 {
3234 advance = basetotal,
3235 ["end"] = used,
3236 glyph = actuarianrightlong,
3237 start = 0,
3238 },
3239 {
3240 advance = basetotal,
3241 ["end"] = 0,
3242 extender = 1,
3243 glyph = actuarianrightlong,
3244 start = used,
3245 },
3246 }
3247 }
3248 characters[actuarianbottomrightlong] =
3249 characters[actuarianrightlong]
3250 characters[actuarianrightshort] = {
3251 width = 2*linewidth,
3252 height = baseheight,
3253 depth = basedepth,
3254 unicode = actuarian,
3255 callback = "devirtualize",
3256 commands = {
3257 upcommand[baseheight-4*linewidth],
3258 { "rule", 4*linewidth, linewidth },
3259 },
3260 parts = {
3261 {
3262 advance = basetotal,
3263 ["end"] = used,
3264 glyph = actuarianrightshort,
3265 start = 0,
3266 },
3267 {
3268 advance = basetotal,
3269 ["end"] = 0,
3270 extender = 1,
3271 glyph = actuarianrightshort,
3272 start = used,
3273 },
3274 }
3275 }
3276 characters[actuarianbottomrightshort] = {
3277 width = 2*linewidth,
3278 height = linewidth,
3279 depth = basedepth,
3280 unicode = actuarian,
3281 callback = "devirtualize",
3282 commands = {
3283 downcommand[basedepth],
3284 { "rule", 4*linewidth, linewidth },
3285 },
3286 parts = {
3287 {
3288 advance = basetotal,
3289 ["end"] = used,
3290 glyph = actuarianrightshort,
3291 start = 0,
3292 },
3293 {
3294 advance = basetotal,
3295 ["end"] = 0,
3296 extender = 1,
3297 glyph = actuarianrightshort,
3298 start = used,
3299 },
3300 }
3301 }
3302
3303 characters[actuarianleftlong] = {
3304 width = 2*linewidth,
3305 height = baseheight,
3306 depth = basedepth,
3307 unicode = actuarian,
3308 callback = "devirtualize",
3309 commands = {
3310 rightcommand[linewidth],
3311 downcommand[basedepth],
3312 { "rule", basetotal, linewidth },
3313 },
3314 parts = {
3315 {
3316 advance = basetotal,
3317 ["end"] = used,
3318 extender = 1,
3319 glyph = placehold,
3320 start = 0,
3321 },
3322 {
3323 advance = basetotal,
3324 ["end"] = 0,
3325 glyph = actuarianleftlong,
3326 start = used,
3327 },
3328 }
3329 }
3330 characters[actuarianbottomleftlong] =
3331 characters[actuarianleftlong]
3332 characters[actuarianleftshort] = {
3333 width = 2*linewidth,
3334 height = baseheight,
3335 depth = basedepth,
3336 unicode = actuarian,
3337 callback = "devirtualize",
3338 commands = {
3339 rightcommand[linewidth],
3340 upcommand[baseheight-4*linewidth],
3341 { "rule", 4*linewidth, linewidth },
3342 },
3343 parts = {
3344 {
3345 advance = basetotal,
3346 ["end"] = used,
3347 extender = 1,
3348 glyph = placehold,
3349 start = 0,
3350 },
3351 {
3352 advance = basetotal,
3353 ["end"] = 0,
3354 glyph = actuarianleftshort,
3355 start = used,
3356 },
3357 }
3358 }
3359 characters[actuarianbottomleftshort] = {
3360 width = 2*linewidth,
3361
3362 height = linewidth,
3363 depth = basedepth,
3364 unicode = actuarian,
3365 callback = "devirtualize",
3366 commands = {
3367 rightcommand[linewidth],
3368 downcommand[basedepth],
3369 { "rule", 4*linewidth, linewidth },
3370 },
3371 parts = {
3372 {
3373 advance = basetotal,
3374 ["end"] = used,
3375 extender = 1,
3376 glyph = placehold,
3377 start = 0,
3378 },
3379 {
3380 advance = basetotal,
3381 ["end"] = 0,
3382 glyph = actuarianleftshort,
3383 start = used,
3384 },
3385 }
3386 }
3387
3388
3389
3390
3391 end
3392
3393end
3394
3395do
3396
3397
3398
3399
3400 local nps = fonts.helpers.newprivateslot
3401
3402 local list = {
3403
3404 { 0x0308, nps("delimited right ddot"), nps("delimited ghost ddot") },
3405 { 0x0304, nps("delimited right bar"), nps("delimited ghost bar") },
3406
3407 { 0x0302, nps("delimited right hat"), nps("delimited ghost hat") },
3408 { 0x030C, nps("delimited right check"), nps("delimited ghost check") },
3409 { 0x0306, nps("delimited right breve"), nps("delimited ghost breve") },
3410 { 0x0307, nps("delimited right dot"), nps("delimited ghost dot") },
3411 { 0x030A, nps("delimited right ring"), nps("delimited ghost ring") },
3412 { 0x0303, nps("delimited right tilde"), nps("delimited ghost tilde") },
3413 { 0x20DB, nps("delimited right dddot"), nps("delimited ghost dddot") },
3414
3415 { 0x2032, nps("delimited right prime"), nps("delimited ghost prime"), false, 1 },
3416 { 0x2033, nps("delimited right dprime"), nps("delimited ghost dprime"), false, 1 },
3417 { 0x2034, nps("delimited right tprime"), nps("delimited ghost tprime"), false, 1 },
3418 { 0x2057, nps("delimited right qprime"), nps("delimited ghost qprime"), false, 1 },
3419 { 0x2035, nps("delimited right rprime"), nps("delimited ghost rprime"), false, 1 },
3420 { 0x2036, nps("delimited right drprime"), nps("delimited ghost rdprime"), false, 1 },
3421 { 0x2037, nps("delimited right dtprime"), nps("delimited ghost rtprime"), false, 1 },
3422
3423 { 0x231C, nps("delimited left upper corner"), nps("delimited ghost upper corner") },
3424 { 0x231D, nps("delimited right upper corner"), nps("delimited ghost upper corner") },
3425 { 0x231E, nps("delimited left lower corner"), nps("delimited ghost lower corner"), true },
3426 { 0x231F, nps("delimited right lower corner"), nps("delimited ghost lower corner"), true },
3427
3428
3429
3430 { 0x2020, nps("delimited right dagger"), nps("delimited ghost dagger") },
3431 { 0x2021, nps("delimited right ddagger"), nps("delimited ghost ddagger") },
3432 { 0x2217, nps("delimited right ast"), nps("delimited ghost ast") },
3433 { 0x22C6, nps("delimited right star"), nps("delimited ghost star") },
3434
3435 { 0x2020, nps("delimited right dagger 1"), nps("delimited ghost dagger 1"), false, 1 },
3436 { 0x2021, nps("delimited right ddagger 1"), nps("delimited ghost ddagger 1"), false, 1 },
3437 { 0x2217, nps("delimited right ast 1"), nps("delimited ghost ast 1"), false, 1 },
3438 { 0x22C6, nps("delimited right star 1"), nps("delimited ghost star 1"), false, 1 },
3439 }
3440
3441 function mathtweaks.addfourier(target,original,parameters)
3442 local characters = target.characters
3443 for i=1,#list do
3444 local entry = list[i]
3445 local unicode = entry[1]
3446 local basecode = unicode
3447 local fouriercode = entry[2]
3448 local movecode = entry[3]
3449 local reverse = entry[4]
3450 local size = entry[5] or 0
3451 local basechar = characters[basecode]
3452 local compactscale = 1
3453
3454 if basechar and target.properties.compactmath and size > 0 then
3455 compactscale = target.parameters[size > 1 and "scriptscriptscale" or "scriptscale"] / 1000
3456 for i=1,size do
3457 basecode = basechar.smaller or basecode
3458 basechar = characters[basecode]
3459 end
3460 end
3461 if basechar then
3462 local scale = (parameters.scale or 1) * compactscale
3463 local variant = parameters.variant
3464 if variant then
3465 for i=1,variant do
3466 local okay = basechar.next
3467 if okay then
3468 basecode = okay
3469 basechar = characters[basecode]
3470 else
3471 break
3472 end
3473 end
3474 end
3475 local baseheight = scale * (basechar.height or 0)
3476 local basedepth = scale * (basechar.depth or 0)
3477 local basewidth = scale * (basechar.width or 0)
3478 local used = baseheight/2
3479 local total = baseheight + basedepth
3480 if reverse then
3481 used = total / 2
3482 end
3483 characters[movecode] = {
3484 width = basewidth,
3485 height = used,
3486 unicode = 0xFFFD,
3487 commands = {
3488 downcommand[used],
3489 { "rule", used, 0 },
3490 },
3491 }
3492 local parts = {
3493 {
3494 advance = used,
3495 ["end"] = used,
3496 extender = 1,
3497 glyph = movecode,
3498 start = used,
3499 },
3500 {
3501 advance = total,
3502 ["end"] = 0,
3503 glyph = fouriercode,
3504 start = total,
3505 },
3506 }
3507 if reverse then
3508 parts[1], parts[2] = parts[2], parts[1]
3509 end
3510 characters[fouriercode] = {
3511 width = basewidth,
3512 height = baseheight,
3513 depth = basedepth,
3514 unicode = unicode,
3515 commands = {
3516 scale == 1 and charcommand[basecode] or { "slot", 0, basecode, scale, scale },
3517 },
3518 partsorientation = "vertical",
3519 parts = parts,
3520
3521
3522 }
3523 if trace_tweaking then
3524 report_tweak("fourier %U added using %U",target,original,basecode,fouriercode)
3525 end
3526 end
3527 end
3528 end
3529
3530end
3531
3532do
3533
3534
3535
3536
3537
3538 local single <const> = 0x007C
3539 local double <const> = 0x2016
3540 local triple <const> = 0x2980
3541
3542 local function variantlist(unicode,chardata,total,used)
3543 chardata.varianttemplate = 0x0028
3544
3545 chardata.parts = {
3546 {
3547 advance = total,
3548 ["end"] = used,
3549 glyph = unicode,
3550 start = 0,
3551 },
3552 {
3553 advance = total,
3554
3555
3556 ["end"] = 4*used/5,
3557 extender = 1,
3558 glyph = unicode,
3559 start = used,
3560 },
3561 }
3562 chardata.partsorientation = "vertical"
3563 end
3564
3565
3566
3567
3568
3569 function mathtweaks.addbars(target,original,parameters)
3570 local characters = target.characters
3571 local template = single
3572 local basechar = characters[template]
3573 local tempchar = basechar
3574 local check = parameters.check
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587 local width = tempchar.width
3588 local height = tempchar.height
3589 local depth = tempchar.depth
3590 local advance = (parameters.advance or 1/10) * width
3591 local used = 1.2*height
3592 local total = height + depth
3593
3594 if not check or not basechar.parts then
3595 variantlist(template,basechar,total,used)
3596 end
3597 if not check or not characters[double] then
3598 basechar = {
3599 unicode = double,
3600 width = 2*width - 1*advance,
3601 height = height,
3602 depth = depth,
3603
3604 commands = {
3605 charcommand[template],
3606 leftcommand[advance],
3607 charcommand[template],
3608 },
3609 }
3610 characters[double] = basechar
3611 else
3612 basechar = characters[double]
3613 end
3614 if not check or not basechar.parts then
3615 variantlist(double,basechar,total,used)
3616 end
3617 if not check or not characters[triple] then
3618 basechar = {
3619 unicode = triple,
3620 width = 3*width - 2*advance,
3621 height = height,
3622 depth = depth,
3623
3624 commands = {
3625 charcommand[template],
3626 leftcommand[advance],
3627 charcommand[template],
3628 leftcommand[advance],
3629 charcommand[template],
3630 },
3631 }
3632 characters[triple] = basechar
3633 else
3634 basechar = characters[triple]
3635 end
3636 if not check or not basechar.parts then
3637 variantlist(triple,basechar,total,used)
3638 end
3639
3640 if trace_tweaking then
3641 report_tweak("single, double and triple bars added",target,original)
3642 end
3643 end
3644
3645end
3646
3647do
3648
3649
3650
3651 local nps = fonts.helpers.newprivateslot
3652
3653 local slot = nps("minus one composite")
3654
3655 local data = {
3656 category = "lu",
3657 description = "MATHEMATICAL PRE IMAGE",
3658 direction = "l",
3659 linebreak = "al",
3660 mathspec = {
3661 {
3662 class = mathematics.classes.prime,
3663 group = "postfix operator",
3664 meaning = "preimage",
3665 name = "preimagesymbol",
3666 },
3667 {
3668 class = mathematics.classes.prime,
3669 group = "postfix operator",
3670 meaning = "inverse",
3671 name = "inversesymbol",
3672 },
3673 },
3674 unicode = slot,
3675 recipe = { 0x2212, 0x31 },
3676 specials = { "char", 0x2212, 0x31 },
3677 unicodeslot = slot,
3678 }
3679
3680 characters.data[slot] = data
3681
3682 for i,d in next,data.mathspec do
3683 mathematics.setmathsymbol(
3684 d.name,
3685 d.class,
3686 0,
3687 data.unicodeslot,
3688 nil,
3689 d.group,
3690 d.meaning
3691 )
3692 end
3693
3694
3695
3696 local specifications = {
3697 data,
3698 }
3699
3700 function mathtweaks.addcomposites(target,original,parameters)
3701 local characters = target.characters
3702 for i=1,#specifications do
3703 local spec = specifications[i]
3704 local private = spec.unicodeslot
3705 local unicode = spec.recipe
3706 local commands = { }
3707 local width = 0
3708 local height = 0
3709 local depth = 0
3710 for i=1,#unicode do
3711 local uc = unicode[i]
3712 local cd = characters[uc]
3713 local wd = cd.width or 0
3714 local ht = cd.height or 0
3715 local dp = cd.depth or 0
3716 commands[#commands+1] = charcommand[uc]
3717 width = width + wd
3718 if ht > height then
3719 height = ht
3720 end
3721 if dp > depth then
3722 depth = dp
3723 end
3724 end
3725
3726 characters[private] = {
3727 unicode = unicode,
3728 commands = commands,
3729 width = width,
3730 height = height,
3731 depth = depth,
3732 }
3733
3734
3735
3736 end
3737 end
3738
3739end
3740
3741do
3742
3743
3744
3745 local snormal <const> = 0x002E
3746 local sraised <const> = 0x22C5
3747
3748 local tnormal <const> = 0x2026
3749 local traised <const> = 0x22EF
3750
3751 function mathtweaks.fixellipses(target,original,parameters)
3752 local characters = target.characters
3753 local function fix(normal,raised)
3754 local normaldata = characters[normal]
3755 if normaldata then
3756 local raiseddata = copytable(normaldata)
3757 characters[raised] = raiseddata
3758 raiseddata.unicode = raised
3759 local height = raiseddata.height
3760 local yoffset = (parameters.yoffset or 2) * height
3761 raiseddata.yoffset = yoffset
3762 raiseddata.height = height + yoffset
3763 if trace_tweaking then
3764 report_tweak("taking %U from %U",target,original,raised,normal)
3765 end
3766 end
3767 end
3768 fix(snormal,sraised)
3769 fix(tnormal,traised)
3770 end
3771
3772end
3773
3774do
3775
3776
3777
3778
3779 local list = {
3780 { 0x207A, 0x002B, true },
3781 { 0x207B, 0x2212, true },
3782 { 0x208A, 0x002B, false },
3783 { 0x208B, 0x2212, false },
3784 }
3785
3786 datasets.addscripts = list
3787
3788 local function add(target,original,characters,unicode,template,super,baseheight,scale)
3789 if not characters[unicode] then
3790 local origdata = characters[template]
3791 if origdata then
3792 local width = scale * (origdata.width or 0)
3793 local height = scale * (origdata.height or 0)
3794 local depth = scale * (origdata.depth or 0)
3795 local half = - (height + depth) / 2
3796 local offset = super and baseheight/2 or -baseheight/4
3797 characters[unicode] = {
3798 width = width,
3799 height = height + offset,
3800 depth = depth - offset,
3801 unicode = unicode,
3802 commands = {
3803 { "offset", 0, offset, template, scale, scale }
3804 },
3805 }
3806 if trace_tweaking then
3807 report_tweak("adding script %U scaled %0.3f",target,original,unicode,scale)
3808 end
3809
3810 end
3811 end
3812 end
3813
3814 function mathtweaks.addscripts(target,original,parameters)
3815 local characters = target.characters
3816 local baseheight = target.mathparameters.AccentBaseHeight
3817 local scaledown = parameters.scale or target.mathparameters.ScriptScriptPercentScaleDown / 100
3818 for i=1,#list do
3819 local entry = list[i]
3820 if entry then
3821 add(target,original,characters,entry[1],entry[2],entry[3],baseheight,scaledown)
3822 end
3823 end
3824 end
3825
3826end
3827
3828do
3829
3830 function mathtweaks.sortvariants(target,original,parameters)
3831 local list = parameters.list
3832 if list then
3833 local characters = target.characters
3834 local horizontal = parameters.orientation == "horizontal"
3835 for i=1,#list do
3836 local u = list[i]
3837 local c = characters[u]
3838 if c then
3839 local t = { }
3840 while true do
3841 local n = c.next
3842 if n then
3843 c = characters[n]
3844 end
3845 if c and not c.parts then
3846 if horizontal then
3847 t[c.width or 0] = n
3848 else
3849 t[(c.height or 0) + (c.depth or 0)] = n
3850 end
3851 else
3852 break
3853 end
3854 end
3855 local c = characters[u]
3856 for k, v in sortedhash(t) do
3857 c.next = v
3858 c = characters[v]
3859 end
3860 end
3861 end
3862 end
3863 end
3864
3865end
3866
3867do
3868
3869
3870
3871
3872
3873
3874
3875
3876 local mirrors = {
3877
3878 [0x0002F] = true,
3879 [0x0005C] = true,
3880 [0x000F7] = true,
3881 [0x02044] = true,
3882 [0x02215] = true,
3883
3884 [0x02032] = true,
3885 [0x02033] = true,
3886 [0x02034] = true,
3887 [0x02057] = true,
3888 [0x02035] = true,
3889 [0x02036] = true,
3890 [0x02037] = true,
3891
3892 [0x0221A] = true,
3893 [0x0221B] = true,
3894 [0x0221C] = true,
3895 [0x0221D] = true,
3896
3897 [0x0222B] = true,
3898 [0x0222C] = true,
3899 [0x0222D] = true,
3900 [0x0222E] = true,
3901 [0x0222F] = true,
3902 [0x02230] = true,
3903 [0x02231] = true,
3904 [0x02232] = true,
3905 [0x02233] = true,
3906
3907 [0x02A0A] = true,
3908 [0x02A0B] = true,
3909 [0x02A0C] = true,
3910 [0x02A0D] = true,
3911 [0x02A0E] = true,
3912
3913 [0x02140] = true,
3914 [0x02201] = true,
3915 [0x02202] = true,
3916 [0x02203] = true,
3917 [0x02204] = true,
3918 [0x02211] = true,
3919
3920 [0x02239] = true,
3921 [0x0225F] = true,
3922 [0x0228C] = true,
3923 [0x022A7] = true,
3924 [0x022AA] = true,
3925 [0x022AC] = true,
3926 [0x022AD] = true,
3927 [0x022AE] = true,
3928 [0x022AF] = true,
3929 [0x022F5] = true,
3930 [0x022F8] = true,
3931 [0x022F9] = true,
3932 [0x022FF] = true,
3933 [0x02320] = true,
3934 [0x02321] = true,
3935 [0x027C0] = true,
3936 [0x029DC] = true,
3937 [0x029F4] = true,
3938
3939 [0x02A0F] = true,
3940 [0x02A10] = true,
3941 [0x02A11] = true,
3942 [0x02A12] = true,
3943 [0x02A13] = true,
3944 [0x02A14] = true,
3945 [0x02A15] = true,
3946 [0x02A16] = true,
3947 [0x02A17] = true,
3948 [0x02A18] = true,
3949 [0x02A19] = true,
3950 [0x02A1A] = true,
3951 [0x02A1B] = true,
3952 [0x02A1C] = true,
3953 [0x02A20] = true,
3954
3955 [0x02A74] = true,
3956 [0x02AA3] = true,
3957 [0x02AE2] = true,
3958 [0x02AE6] = true,
3959 [0x1D715] = true,
3960 }
3961
3962 local new = fonts.helpers.newprivateslot
3963
3964 local function add(target,original,characters,unicode,what)
3965 local data = characters[unicode]
3966 if data then
3967 if not data.mirror then
3968 local slot = new("mirror."..unicode)
3969 local mirror = copytable(data)
3970 data.mirror = slot
3971 mirror.rorrim = unicode
3972 mirror.commands = {
3973 { "offset", data.width, 0, unicode, -1, 1 }
3974 }
3975 if trace_tweaking then
3976 report_tweak("adding mirror %U (%s)",target,original,unicode,what)
3977 end
3978 characters[slot] = mirror
3979 elseif trace_tweaking then
3980 report_tweak("skipping mirror %U (%s)",target,original,unicode,what)
3981 end
3982 local parts = data.parts
3983 if parts then
3984 for i=1,#parts do
3985 add(target,original,characters,parts[i],"hpart")
3986 end
3987 end
3988 local smaller = data.smaller
3989 if smaller then
3990 add(target,original,characters,"smaller")
3991 end
3992 local next = data.next
3993 if next then
3994 if next == unicode then
3995 if unicode == 0xFDFFF then
3996
3997 else
3998 report_tweak("skipping cyclic %U (%s)",target,original,unicode,"next")
3999 end
4000 else
4001 add(target,original,characters,next,"next")
4002 end
4003 end
4004 end
4005 end
4006
4007
4008
4009 function mathtweaks.addmirrors(target,original,parameters)
4010 local characters = target.characters
4011
4012 for unicode, detail in next, characters do
4013 local data = chardata[unicode]
4014 if data and data.mirror then
4015 add(target,original,characters,unicode,"mirror")
4016 end
4017 end
4018 for unicode, detail in sortedhash(mirrors) do
4019 if characters[unicode] then
4020 add(target,original,characters,unicode,"character")
4021 elseif trace_tweaking then
4022 report_tweak("ignoring mirror %U (%s)",target,original,unicode,what)
4023 end
4024 end
4025 end
4026
4027end
4028
4029do
4030
4031 local reported = { }
4032
4033 function mathtweaks.version(target,original,parameters)
4034 local metadata = original.shared.rawdata.metadata
4035 if metadata then
4036 local version = string.strip(metadata.version or "")
4037 if version then
4038 local expected = parameters.expected
4039 local fontname = metadata.fontname or false
4040 local message = parameters.message
4041
4042 if version ~= expected and not reported[fontname] then
4043 report_tweak("version %a found, version %a expected",target,original,version,expected)
4044 elseif trace_tweaking then
4045 report_tweak("version %a found",target,original,version)
4046 end
4047 if message and message ~= "" and not reported[fontname] then
4048 report_tweak()
4049 report_tweak("%s",target,original,message)
4050 report_tweak()
4051 end
4052 reported[fontname] = true
4053 target.tweakversion = version
4054 end
4055 end
4056 end
4057
4058end
4059
4060do
4061
4062 function mathtweaks.parameters(target,original,parameters)
4063 local newparameters = parameters.list
4064 local oldparameters = target.mathparameters
4065 if newparameters and oldparameters then
4066 newparameters = copytable(newparameters)
4067 scaleparameters(newparameters,target.parameters)
4068 for name, newvalue in next, newparameters do
4069 oldparameters[name] = newvalue
4070 end
4071 end
4072 end
4073
4074 function mathtweaks.bigslots(target,original,parameters)
4075 local list = parameters.list
4076 if list then
4077 target.bigslots = list
4078 end
4079 end
4080
4081end
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115do
4116
4117 function mathtweaks.setoptions(target,original,parameters)
4118 local setlist = parameters.set or parameters.list
4119 local resetlist = parameters.reset
4120 if setlist or resetlist then
4121 local properties = target.properties
4122 local codes = tex.mathcontrolcodes
4123 local oldcontrol = texget("mathfontcontrol")
4124 local newcontrol = oldcontrol
4125
4126 if resetlist then
4127 for i=1,#resetlist do
4128 local v = tonumber(codes[resetlist[i]])
4129 if v then
4130 newcontrol = newcontrol & (not v)
4131 end
4132 end
4133 end
4134 if setlist then
4135 for i=1,#setlist do
4136 local v = tonumber(codes[setlist[i]])
4137 if v then
4138 newcontrol = newcontrol | v
4139 end
4140 end
4141 end
4142 newcontrol = newcontrol | codes.usefontcontrol
4143 properties.mathcontrol = newcontrol
4144 target.mathcontrol = newcontrol
4145 if trace_tweaking then
4146 report_tweak("forcing math font options 0x%08X instead of 0x%08X",target,original,newcontrol,oldcontrol)
4147 end
4148 end
4149 end
4150
4151end
4152
4153do
4154
4155 function mathtweaks.setovershoots(target,original,parameters)
4156 local list = parameters.list
4157 if list then
4158 local characters = target.characters
4159 local emwidth = target.parameters.quad
4160 local done = false
4161 for i=1,#list do
4162 local entry = list[i]
4163 local target = entry.target
4164 local top = entry.topovershoot
4165 local quad = entry.quad
4166 if target and top then
4167 local range = blocks[target]
4168 if range then
4169 if quad then
4170 quad = emwidth
4171 end
4172 for r = range.first, range.last do
4173 local unicode = mathgaps[r] or r
4174 local data = characters[unicode]
4175 if data then
4176 data.topovershoot = top * (quad or data.width or 0)
4177 done = registerdone(done,r)
4178 end
4179 end
4180 end
4181 end
4182 end
4183 feedback_tweak("setovershoots",target,original,done)
4184 end
4185 end
4186
4187
4188
4189 local efindex = 0
4190 local effects = setmetatableindex (function (t,k)
4191 efindex = efindex + 1
4192 local v = "tweakreplacealphabets" .. efindex
4193 local e = fonts.specifiers.presetcontext(v,"",k)
4194
4195 t[k] = v
4196 return v
4197 end)
4198
4199 function mathtweaks.replacealphabets(target,original,parameters)
4200 local list = parameters.list
4201 if list then
4202 local features = target.specification.features.normal
4203 local definedfont = fonts.definers.internal
4204 local copiedglyph = fonts.handlers.vf.math.copy_glyph
4205
4206 local getsubstitution = fonts.handlers.otf.getsubstitution
4207 local fontdata = fonts.hashes.identifiers
4208
4209 local fonts = target.fonts
4210 local size = target.size
4211 local characters = target.characters
4212 if not fonts then
4213 fonts = { }
4214 target.fonts = fonts
4215 end
4216 if #fonts == 0 then
4217 fonts[1] = { id = 0, size = size }
4218 end
4219 for i=1,#list do
4220 local entry = list[i]
4221 local filename = entry.filename or parameters.filename
4222 local feature = entry.feature
4223 local thesource = entry.source
4224 local thetarget = entry.target or thesource
4225 local theunicode = entry.unicode
4226 local unicodes = entry.unicodes
4227 local keep = (entry.keep == true) or (parameters.keep == true)
4228 if (thesource and thetarget) or unicodes then
4229 local function getrange(t)
4230 return (type(t) == "table" and t) or (type(t) == "string" and blocks[t])
4231 end
4232 local sourcerange = thesource and getrange(thesource)
4233 local targetrange = thetarget and getrange(thetarget)
4234 local unicoderange = theunicode and getrange(theunicode)
4235 local firsttarget = targetrange and targetrange.first
4236 local firstsource = sourcerange and sourcerange.first
4237 local lastsource = sourcerange and sourcerange.last or firstsource
4238 if (firstsource and firsttarget) or unicodes then
4239 local offset = unicodes and 0 or (firsttarget - firstsource)
4240 if filename then
4241 local rscale = entry.rscale or 1
4242 size = size * rscale
4243
4244 local fullname = filename
4245 local effect = features.effect
4246 if effect then
4247 fullname = fullname .. "*" .. effects["effect={"..effect.."}"]
4248 end
4249 local id = definedfont {
4250 name = fullname,
4251 size = size,
4252 }
4253 if id <= 0 then
4254 report_math("font %a doesn't exist",fullname)
4255 else
4256 local chars = fontchars[id]
4257 local dropin = fontdata[id]
4258 local index = false
4259 for i=1,#fonts do
4260 local f = fonts[i]
4261 if f.id == id and f.size == size then
4262 index = i
4263 break
4264 end
4265 end
4266 if not index then
4267 index = #fonts + 1
4268 fonts[index] = { id = id, size = size }
4269 end
4270
4271 local function use(sourceunicode,targetunicode)
4272 if keep and characters[targetunicode] then
4273
4274 else
4275 if feature then
4276 sourceunicode = getsubstitution(dropin,sourceunicode,feature,true,"math","dflt") or sourceunicode
4277 end
4278 characters[targetunicode] = copiedglyph(target,characters,chars,sourceunicode,index)
4279 end
4280 end
4281 if unicodes then
4282 for i=1,#unicodes do
4283 local unicode = unicodes[i]
4284 if chars[unicode] then
4285 use(unicode,unicode)
4286 end
4287 end
4288 else
4289 for s=firstsource,lastsource do
4290 local t = s + offset
4291 local sourceunicode = mathgaps[s] or s
4292 if chars[sourceunicode] then
4293 use(sourceunicode,mathgaps[t] or t)
4294 end
4295 end
4296 end
4297
4298 local inherit = entry.inherit
4299 if inherit then
4300 local mathparameters = target.mathparameters
4301 local dropparameters = fontdata[id].mathparameters
4302 if dropparameters then
4303 for name in sortedhash(inherit) do
4304 local value = dropparameters[name]
4305 if value then
4306 mathparameters[name] = value
4307 end
4308 end
4309 end
4310 end
4311 end
4312 elseif feature then
4313 local function use(sourceunicode,targetunicode)
4314 local variant = getsubstitution(original,sourceunicode,feature,true,"math","dflt")
4315 local data = characters[variant]
4316 if data then
4317 characters[targetunicode] = copytable(data)
4318 end
4319 end
4320 if unicodes then
4321 for i=1,#unicodes do
4322 local unicode = unicodes[i]
4323 use(unicode,unicode)
4324 end
4325 else
4326 for s=firstsource,lastsource do
4327 local t = s + offset
4328 use(mathgaps[s] or s,mathgaps[t] or t)
4329 end
4330 end
4331 else
4332
4333 if unicodes then
4334
4335 else
4336 local firstcode = unicoderange and unicoderange.first
4337 local descriptions = original.descriptions
4338 for s=firstsource,lastsource do
4339 local t = s + offset
4340 local sourceunicode = mathgaps[s] or s
4341 local targetunicode = mathgaps[t] or t
4342 if sourceunicode ~= targetunicode then
4343 local data = characters[sourceunicode]
4344 if data then
4345 if firstcode then
4346 local d = descriptions[sourceunicode]
4347 if d then
4348 d.unicode = firstcode
4349 end
4350 data.unicode = firstcode
4351 firstcode = firstcode + 1
4352 end
4353 characters[targetunicode] = copytable(data)
4354 end
4355 end
4356 end
4357 end
4358 end
4359 end
4360 end
4361 end
4362 end
4363 end
4364
4365 function mathtweaks.fallbacks(target,original,parameters)
4366 local fallbacks = target.specification.fallbacks
4367 if fallbacks then
4368 local definitions = fonts.collections.definitions[fallbacks]
4369 if definitions then
4370 local list = { }
4371 for i=1,#definitions do
4372 local definition = definitions[i]
4373
4374
4375 local first = definition.start
4376 local last = definition.stop
4377 local offset = definition.offset or first
4378 list[#list+1] = {
4379 filename = definition.font,
4380 rscale = definition.rscale or 1,
4381 source = { first = first, last = last },
4382 target = { first = offset, last = offset + (last - first) },
4383 }
4384 end
4385 mathtweaks.replacealphabets(target,original,{
4386 tweak = "replacealphabets",
4387 list = list,
4388 } )
4389 end
4390 end
4391 end
4392
4393end
4394
4395local apply_tweaks = true directives.register("math.applytweaks", function(v) apply_tweaks = v end)
4396local applied_tweaks = 0
4397
4398local function tweaklist(target,original,tweaks)
4399 if type(tweaks) == "table" then
4400 for i=1,#tweaks do
4401 local tweak = tweaks[i]
4402 if type(tweak) == "table" then
4403 local action = mathtweaks[tweak.tweak or ""]
4404 if action then
4405 local feature = tweak.feature
4406 local features = target.specification.features.normal
4407 if feature == nil or features[feature] then
4408 local version = tweak.version
4409 if version and version ~= target.tweakversion then
4410 report_math("skipping tweak %a version %a",tweak.tweak,version)
4411 elseif original then
4412 action(target,original,tweak)
4413 else
4414 action(target,tweak)
4415 end
4416 end
4417 end
4418 end
4419 end
4420 end
4421end
4422
4423function mathtweaks.tweaks(target,original,parameters)
4424 tweaklist(target,original,parameters.list)
4425end
4426
4427local function applytweaks(when,target,original)
4428 if apply_tweaks then
4429 local goodies = original.goodies
4430 if goodies then
4431 local tweaked = target.tweaked or { }
4432 if tweaked[when] then
4433 if trace_defining then
4434 report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done")
4435 end
4436 else
4437 for i=1,#goodies do
4438 local goodie = goodies[i]
4439 local mathematics = goodie.mathematics
4440 local tweaks = mathematics and mathematics.tweaks
4441 if type(tweaks) == "table" then
4442 statistics.starttiming(mathtweaks)
4443 applied_tweaks = applied_tweaks + 1
4444 tweaks = tweaks[when]
4445 if trace_defining then
4446 report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"okay")
4447 end
4448 tweaklist(target,original,tweaks)
4449 end
4450 statistics.stoptiming(mathtweaks)
4451 end
4452 tweaked[when] = true
4453 target.tweaked = tweaked
4454 end
4455 end
4456 else
4457 report_math("not tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when)
4458 end
4459end
4460
4461local function tweakable(target)
4462 local mathparameters = target.mathparameters
4463
4464
4465
4466 return mathparameters
4467end
4468
4469function mathematics.tweakbeforecopyingfont(target,original)
4470 if use_math_goodies and tweakable(target) then
4471 applytweaks("beforecopying",target,original)
4472 end
4473end
4474
4475function mathematics.tweakaftercopyingfont(target,original)
4476 if use_math_goodies and tweakable(target) then
4477 applytweaks("aftercopying",target,original)
4478 target.properties.hasitalics = false
4479 end
4480end
4481
4482statistics.register("math tweaking time",function()
4483 if applied_tweaks > 0 then
4484 return string.format("%s seconds, %s math goodie tables", statistics.elapsedtime(mathtweaks),applied_tweaks)
4485 end
4486end)
4487
4488do
4489
4490 local defaults = {
4491 {
4492 source = "uppercasescript",
4493 target = "uppercasecalligraphic",
4494 },
4495 {
4496 source = "lowercasescript",
4497 target = "lowercasecalligraphic",
4498 },
4499 {
4500 source = "uppercaseboldscript",
4501 target = "uppercaseboldcalligraphic",
4502 },
4503 {
4504 source = "lowercaseboldscript",
4505 target = "lowercaseboldcalligraphic",
4506 },
4507 }
4508
4509 local reported = table.setmetatableindex("table")
4510
4511 function mathematics.checkaftercopyingfont(target,original)
4512 if tweakable(target) then
4513 local chardata = characters.data
4514 local characters = target.characters
4515
4516 for i=1,#defaults do
4517
4518 local default = defaults[i]
4519 local block = blocks[default.target]
4520 local first = block.first
4521 local last = block.last
4522 if not characters[mathgaps[first] or last] then
4523 mathtweaks.replacealphabets(target,original,{
4524 tweak = "replacealphabets",
4525 list = { default }
4526 })
4527 end
4528 end
4529
4530 local addvariant = mathematics.addvariant
4531 local function register(old,new)
4532 for i, cold in next, old do
4533 local cnew = new[i]
4534 addvariant(target,cold,cold,0xFE00)
4535 addvariant(target,cnew,cnew,0xFE01)
4536 addvariant(target,cnew,cold,0xFE00)
4537 addvariant(target,cold,cnew,0xFE01)
4538 end
4539 end
4540 local sr = mathematics.alphabets.sr.tf
4541 local ca = mathematics.alphabets.ca.tf
4542 register(sr.ucletters,ca.ucletters)
4543 register(sr.lcletters,ca.lcletters)
4544
4545 if checkitalics then
4546 local italics = 0
4547 local metadata = original.shared.rawdata.metadata
4548 local fontname = metadata and metadata.fontname or false
4549
4550 for k, v in next, characters do
4551 local italic = v.italic
4552 if italic then
4553 local unicode = v.unicode
4554 if unicode and not reported[fontname][unicode] then
4555 local data = chardata[unicode]
4556 local description = data.description or ""
4557 local category = data.category or "--"
4558 report_tweak("italics: %C %p %s %s",target,original,k,italic,category,description)
4559 reported[fontname][unicode] = true
4560 end
4561 italics = italics + 1
4562 end
4563 end
4564 if italics > 0 then
4565 report_tweak("still has %i italics",target,original,italics)
4566 goto NEXTSTEP
4567 end
4568 end
4569 ::NEXTSTEP::
4570
4571 end
4572 end
4573
4574end
4575
4576function mathematics.beforepassingfonttotex(target,original)
4577 if tweakable(target) then
4578 applytweaks("beforepassing",target,original)
4579 end
4580end
4581
4582sequencers.appendaction("mathparameters","system","mathematics.overloadparameters")
4583sequencers.appendaction("mathparameters","system","mathematics.scaleparameters")
4584
4585sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont")
4586sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont")
4587sequencers.appendaction("aftercopyingcharacters", "system","mathematics.checkaftercopyingfont")
4588sequencers.appendaction("beforepassingfonttotex", "system","mathematics.beforepassingfonttotex")
4589
4590
4591
4592
4593
4594
4595
4596local e_left = extensibles.left
4597local e_right = extensibles.right
4598local e_horizontal = extensibles.horizontal
4599local e_mixed = extensibles.mixed
4600local e_unknown = extensibles.unknown
4601
4602local unknown = { e_unknown, false, false }
4603
4604
4605
4606local function extensiblecode(font,unicode)
4607 local characters = fontcharacters[font]
4608 local character = characters[unicode]
4609 if not character then
4610 return unknown
4611 end
4612 local first = character.next
4613 local code = unicode
4614 local next = first
4615 while next do
4616 code = next
4617 character = characters[next]
4618 next = character.next
4619 end
4620 local char = chardata[unicode]
4621 if not char then
4622 return unknown
4623 end
4624 if character.parts then
4625 local m = char.mathextensible
4626 local e = m and extensibles[m]
4627 return e and { e, code, character } or unknown
4628 elseif first then
4629
4630 local m = char.mathextensible or char.mathstretch
4631 local e = m and extensibles[m]
4632 return e and { e, code, character } or unknown
4633 else
4634 return unknown
4635 end
4636end
4637
4638setmetatableindex(extensibles,function(extensibles,font)
4639 local codes = { }
4640 setmetatableindex(codes, function(codes,unicode)
4641 local status = extensiblecode(font,unicode)
4642 codes[unicode] = status
4643 return status
4644 end)
4645 extensibles[font] = codes
4646 return codes
4647end)
4648
4649local function extensiblecode(family,unicode)
4650 return extensibles[getfontoffamily(family or 0)][unicode][1]
4651end
4652
4653
4654
4655
4656
4657
4658
4659local function horizontalcode(family,unicode)
4660 local font = getfontoffamily(family or 0)
4661 local data = extensibles[font][unicode]
4662 local kind = data[1]
4663 local loffset = 0
4664 local roffset = 0
4665 if kind == e_left then
4666 local charlist = data[3].parts
4667 if charlist then
4668 local left = charlist[1]
4669 loffset = abs((left["start"] or 0) - (left["end"] or 0))
4670 end
4671 elseif kind == e_right then
4672 local charlist = data[3].parts
4673 if charlist then
4674 local right = charlist[#charlist]
4675 roffset = abs((right["start"] or 0) - (right["end"] or 0))
4676 end
4677 elseif kind == e_horizontal then
4678 local charlist = data[3].parts
4679 if charlist then
4680 local left = charlist[1]
4681 local right = charlist[#charlist]
4682 loffset = abs((left ["start"] or 0) - (left ["end"] or 0))
4683 roffset = abs((right["start"] or 0) - (right["end"] or 0))
4684 end
4685 end
4686 return kind, loffset, roffset
4687end
4688
4689mathematics.extensiblecode = extensiblecode
4690mathematics.horizontalcode = horizontalcode
4691
4692
4693
4694interfaces.implement {
4695 name = "extensiblecode",
4696 arguments = "2 integers",
4697
4698 public = true,
4699 actions = { extensiblecode, context }
4700}
4701
4702interfaces.implement {
4703 name = "mathhorizontalcode",
4704 arguments = "2 integers",
4705
4706 public = true,
4707 actions = function(family,unicode)
4708 local kind, loffset, roffset = horizontalcode(family,unicode)
4709 texsetdimen(d_scratchleftoffset, loffset)
4710 texsetdimen(d_scratchrightoffset,roffset)
4711 context(kind)
4712 end
4713}
4714
4715function mathematics.variantcode(unicode,variant)
4716 local data = fontcharacters[getfontoffamily(texget("fam"))]
4717 local char = data and data[unicode]
4718 if char then
4719 for i=1,variant do
4720 local next = char.next
4721 if next then
4722 unicode = next
4723 char = data[next]
4724 else
4725 break
4726 end
4727 end
4728 end
4729 return unicode
4730end
4731
4732function mathematics.variantcount(unicode)
4733 local data = fontcharacters[getfontoffamily(texget("fam"))]
4734 local char = data and data[unicode]
4735 local count = 0
4736 if char then
4737 while true do
4738 local next = char.next
4739 if next then
4740 count = count + 1
4741 char = data[next]
4742 else
4743 break
4744 end
4745 end
4746 end
4747 return count
4748end
4749
4750interfaces.implement {
4751 name = "mathvariantcode",
4752 public = true,
4753 arguments = "2 integers",
4754 actions = { mathematics.variantcode, context },
4755}
4756
4757interfaces.implement {
4758 name = "mathvariantcount",
4759 public = true,
4760 arguments = "integer",
4761 actions = { mathematics.variantcount, context },
4762}
4763 |