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
14local type, next = type, next
15local fastcopy, insert, remove, copytable = table.fastcopy, table.insert, table.remove, table.copy
16local formatters = string.formatters
17
18local trace_defining = false trackers.register("math.defining", function(v) trace_defining = v end)
19local trace_collecting = false trackers.register("math.collecting", function(v) trace_collecting = v end)
20
21local report_math = logs.reporter("mathematics","initializing")
22
23local context = context
24local commands = commands
25local mathematics = mathematics
26local texsetdimen = tex.setdimen
27local abs = math.abs
28
29local helpers = fonts.helpers
30local upcommand = helpers.commands.up
31local rightcommand = helpers.commands.right
32local charcommand = helpers.commands.char
33local prependcommands = helpers.prependcommands
34
35local sequencers = utilities.sequencers
36local appendgroup = sequencers.appendgroup
37local appendaction = sequencers.appendaction
38
39local fontchars = fonts.hashes.characters
40local fontproperties = fonts.hashes.properties
41
42local mathfontparameteractions = sequencers.new {
43 name = "mathparameters",
44 arguments = "target,original",
45}
46
47appendgroup("mathparameters","before")
48appendgroup("mathparameters","system")
49appendgroup("mathparameters","after" )
50
51function fonts.constructors.assignmathparameters(original,target)
52 local runner = mathfontparameteractions.runner
53 if runner then
54 runner(original,target)
55 end
56end
57
58function mathematics.initializeparameters(target,original)
59 local mathparameters = original.mathparameters
60 if mathparameters and next(mathparameters) then
61 mathparameters = mathematics.dimensions(mathparameters)
62 if not mathparameters.SpaceBeforeScript then
63 mathparameters.SpaceBeforeScript = mathparameters.SpaceAfterScript
64 end
65 if not mathparameters.SubscriptShiftDownWithSuperscript then
66 mathparameters.SubscriptShiftDownWithSuperscript = mathparameters.SubscriptShiftDown * 1.5
67 end
68 target.mathparameters = mathparameters
69 end
70end
71
72sequencers.appendaction("mathparameters","system","mathematics.initializeparameters")
73
74local how = {
75
76
77 ScriptPercentScaleDown = "unscaled",
78 ScriptScriptPercentScaleDown = "unscaled",
79 RadicalDegreeBottomRaisePercent = "unscaled",
80 NoLimitSupFactor = "unscaled",
81 NoLimitSubFactor = "unscaled",
82}
83
84local function scaleparameters(mathparameters,parameters)
85 if mathparameters and next(mathparameters) and parameters then
86 local factor = parameters.factor
87 local hfactor = parameters.hfactor
88 local vfactor = parameters.vfactor
89 for name, value in next, mathparameters do
90 local h = how[name]
91 if h == "unscaled" then
92
93 elseif h == "horizontal" then
94 value = value * hfactor
95 elseif h == "vertical"then
96 value = value * vfactor
97 else
98 value = value * factor
99 end
100 mathparameters[name] = value
101 end
102 end
103end
104
105function mathematics.scaleparameters(target,original)
106 if not target.properties.math_is_scaled then
107 scaleparameters(target.mathparameters,target.parameters)
108 target.properties.math_is_scaled = true
109 end
110end
111
112
113
114
115
116
117
118
119
120
121function mathematics.checkprivateparameters(target,original)
122 local mathparameters = target.mathparameters
123 if mathparameters then
124 local parameters = target.parameters
125 local properties = target.properties
126 if parameters then
127 local size = parameters.size
128 if size then
129 if not mathparameters.FractionDelimiterSize then
130 mathparameters.FractionDelimiterSize = 1.01 * size
131 end
132 if not mathparameters.FractionDelimiterDisplayStyleSize then
133 mathparameters.FractionDelimiterDisplayStyleSize = 2.40 * size
134 end
135 elseif properties then
136 report_math("invalid parameters in font %a",properties.fullname or "?")
137 else
138 report_math("invalid parameters in font")
139 end
140 elseif properties then
141 report_math("no parameters in font %a",properties.fullname or "?")
142 else
143 report_math("no parameters and properties in font")
144 end
145 end
146end
147
148function mathematics.overloadparameters(target,original)
149 local mathparameters = target.mathparameters
150 if mathparameters and next(mathparameters) then
151 local goodies = target.goodies
152 if goodies then
153 for i=1,#goodies do
154 local goodie = goodies[i]
155 local mathematics = goodie.mathematics
156 local parameters = mathematics and mathematics.parameters
157 if parameters then
158 if trace_defining then
159 report_math("overloading math parameters in %a @ %p",target.properties.fullname,target.parameters.size)
160 end
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 for name, value in next, parameters do
186 local tvalue = type(value)
187 local oldvalue = mathparameters[name]
188 local newvalue = oldvalue
189 if tvalue == "number" then
190 newvalue = value
191 elseif tvalue == "string" then
192
193 elseif tvalue == "function" then
194 newvalue = value(oldvalue,target,original)
195 elseif not tvalue then
196 newvalue = nil
197 end
198 if trace_defining and oldvalue ~= newvalue then
199 report_math("overloading math parameter %a: %S => %S",name,oldvalue or 0,newvalue)
200 end
201 mathparameters[name] = newvalue
202 end
203 for name, value in next, parameters do
204 local tvalue = type(value)
205 if tvalue == "string" then
206 local newvalue = mathparameters[value]
207 if not newvalue then
208 local code = loadstring("return " .. value,"","t",mathparameters)
209 if type(code) == "function" then
210 local okay, v = pcall(code)
211 if okay then
212 newvalue = v
213 end
214 end
215 end
216 if newvalue then
217
218 mathparameters[name] = newvalue
219 elseif trace_defining then
220 report_math("ignoring math parameter %a: %S",name,value)
221 end
222 end
223 end
224 end
225 end
226 end
227 end
228end
229
230local mathtweaks = { subsets = table.setmetatableindex("table") }
231mathematics.tweaks = mathtweaks
232
233local apply_tweaks = true
234
235directives.register("math.applytweaks", function(v)
236 apply_tweaks = v;
237end)
238
239local function applytweaks(when,target,original)
240 if apply_tweaks then
241 local goodies = original.goodies
242 if goodies then
243 local tweaked = target.tweaked or { }
244 if tweaked[when] then
245 if trace_defining then
246 report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"done")
247 end
248 else
249 for i=1,#goodies do
250 local goodie = goodies[i]
251 local mathematics = goodie.mathematics
252 local tweaks = mathematics and mathematics.tweaks
253 if type(tweaks) == "table" then
254 tweaks = tweaks[when]
255 if type(tweaks) == "table" then
256 if trace_defining then
257 report_math("tweaking math of %a @ %p (%s: %s)",target.properties.fullname,target.parameters.size,when,"okay")
258 end
259 for i=1,#tweaks do
260 local tweak = tweaks[i]
261 local tvalue = type(tweak)
262 if type(tweak) == "table" then
263 local action = mathtweaks[tweak.tweak or ""]
264 if action then
265 local feature = tweak.feature
266 local features = target.specification.features.normal
267 if not feature or features[feature] == true then
268 local version = tweak.version
269 if version and version ~= target.tweakversion then
270 report_math("skipping tweak %a version %a",tweak.tweak,version)
271 elseif original then
272 action(target,original,tweak)
273 else
274 action(target,tweak)
275 end
276 end
277 end
278 end
279 end
280 end
281 end
282 end
283 tweaked[when] = true
284 target.tweaked = tweaked
285 end
286 end
287 else
288 report_math("not tweaking math of %a @ %p (%s)",target.properties.fullname,target.parameters.size,when)
289 end
290end
291
292function mathematics.tweakbeforecopyingfont(target,original)
293 local mathparameters = target.mathparameters
294 if mathparameters then
295 applytweaks("beforecopying",target,original)
296 end
297end
298
299function mathematics.tweakaftercopyingfont(target,original)
300 local mathparameters = target.mathparameters
301 if mathparameters then
302 applytweaks("aftercopying",target,original)
303 end
304end
305
306sequencers.appendaction("mathparameters","system","mathematics.overloadparameters")
307sequencers.appendaction("mathparameters","system","mathematics.scaleparameters")
308
309sequencers.appendaction("mathparameters","system","mathematics.checkprivateparameters")
310
311sequencers.appendaction("beforecopyingcharacters","system","mathematics.tweakbeforecopyingfont")
312sequencers.appendaction("aftercopyingcharacters", "system","mathematics.tweakaftercopyingfont")
313
314do
315
316
317
318
319
320
321
322
323
324
325
326
327 local italics = nil
328 local integrals = table.tohash {
329 0x0222B, 0x0222C, 0x0222D, 0x0222E, 0x0222F, 0x02230, 0x02231, 0x02232, 0x02233,
330 0x02A0B, 0x02A0C, 0x02A0D, 0x02A0E, 0x02A0F, 0x02A10, 0x02A11, 0x02A12, 0x02A13,
331 0x02A14, 0x02A15, 0x02A16, 0x02A17, 0x02A18, 0x02A19, 0x02A1A, 0x02A1B, 0x02A1C,
332 0x02320, 0x02321
333 }
334
335 function mathtweaks.emulatelmtx(target,original,parameters)
336
337 if not italic then
338 italics = { }
339 local gaps = mathematics.gaps
340 for name, data in next, characters.blocks do
341 if data.math and data.italic then
342 for i=data.first,data.last do
343 italics[i] = true
344 local g = gaps[i]
345 if g then
346 italics[g] = true
347 end
348 end
349 end
350 end
351
352 end
353
354 local targetcharacters = target.characters
355 local targetdescriptions = target.descriptions
356 local factor = target.parameters.factor
357 local function getllx(u)
358 local d = targetdescriptions[u]
359 if d then
360 local b = d.boundingbox
361 if b then
362 local llx = b[1]
363 if llx < 0 then
364 return - llx
365 end
366 end
367 end
368 return false
369 end
370
371 for u, c in next, targetcharacters do
372 local uc = c.unicode or u
373 if integrals[uc] then
374
375 else
376 local accent = c.top_accent
377 local italic = c.italic
378 local width = c.width or 0
379 local llx = getllx(u)
380 local bl, br, tl, tr
381 if llx then
382 llx = llx * factor
383 width = width + llx
384 bl = - llx
385 tl = bl
386 c.commands = { rightcommand[llx], charcommand[u] }
387 if accent then
388 accent = accent + llx
389 end
390 end
391 if accent then
392 if italics[uc] then
393 c.top_accent = accent
394 else
395 c.top_accent = nil
396 end
397 end
398 if italic and italic ~= 0 then
399 width = width + italic
400 br = - italic
401 end
402 c.width = width
403 if italic then
404 c.italic = nil
405 end
406 if bl or br or tl or tr then
407
408 c.mathkern = {
409 bottom_left = bl and { { height = 0, kern = bl } } or nil,
410 bottom_right = br and { { height = 0, kern = br } } or nil,
411 top_left = tl and { { height = c.height or 0, kern = tl } } or nil,
412 top_right = tr and { { height = c.height or 0, kern = tr } } or nil,
413 }
414 end
415 end
416 end
417 end
418
419 function mathtweaks.parameters(target,original,parameters)
420 local newparameters = parameters.list
421 local oldparameters = target.mathparameters
422 if newparameters and oldparameters then
423 newparameters = copytable(newparameters)
424 scaleparameters(newparameters,target.parameters)
425 for name, newvalue in next, newparameters do
426 oldparameters[name] = newvalue
427 end
428 end
429 end
430
431end
432
433local setmetatableindex = table.setmetatableindex
434
435local getfontoffamily = tex.getfontoffamily
436
437local fontcharacters = fonts.hashes.characters
438local extensibles = utilities.storage.allocate()
439fonts.hashes.extensibles = extensibles
440
441local chardata = characters.data
442local extensibles = mathematics.extensibles
443
444
445
446local e_left = extensibles.left
447local e_right = extensibles.right
448local e_horizontal = extensibles.horizontal
449local e_mixed = extensibles.mixed
450local e_unknown = extensibles.unknown
451
452local unknown = { e_unknown, false, false }
453
454local function extensiblecode(font,unicode)
455 local characters = fontcharacters[font]
456 local character = characters[unicode]
457 if not character then
458 return unknown
459 end
460 local first = character.next
461 local code = unicode
462 local next = first
463 while next do
464 code = next
465 character = characters[next]
466 next = character.next
467 end
468 local char = chardata[unicode]
469 if not char then
470 return unknown
471 end
472 if character.horiz_variants then
473 if character.vert_variants then
474 return { e_mixed, code, character }
475 else
476 local m = char.mathextensible
477 local e = m and extensibles[m]
478 return e and { e, code, character } or unknown
479 end
480 elseif character.vert_variants then
481 local m = char.mathextensible
482 local e = m and extensibles[m]
483 return e and { e, code, character } or unknown
484 elseif first then
485
486 local m = char.mathextensible or char.mathstretch
487 local e = m and extensibles[m]
488 return e and { e, code, character } or unknown
489 else
490 return unknown
491 end
492end
493
494setmetatableindex(extensibles,function(extensibles,font)
495 local codes = { }
496 setmetatableindex(codes, function(codes,unicode)
497 local status = extensiblecode(font,unicode)
498 codes[unicode] = status
499 return status
500 end)
501 extensibles[font] = codes
502 return codes
503end)
504
505local function extensiblecode(family,unicode)
506 return extensibles[getfontoffamily(family or 0)][unicode][1]
507end
508
509
510
511
512
513
514
515local function horizontalcode(family,unicode)
516 local font = getfontoffamily(family or 0)
517 local data = extensibles[font][unicode]
518 local kind = data[1]
519 local loffset = 0
520 local roffset = 0
521 if kind == e_left then
522 local charlist = data[3].horiz_variants
523 if charlist then
524 local left = charlist[1]
525 loffset = abs((left["start"] or 0) - (left["end"] or 0))
526 end
527 elseif kind == e_right then
528 local charlist = data[3].horiz_variants
529 if charlist then
530 local right = charlist[#charlist]
531 roffset = abs((right["start"] or 0) - (right["end"] or 0))
532 end
533 elseif kind == e_horizontal then
534 local charlist = data[3].horiz_variants
535 if charlist then
536 local left = charlist[1]
537 local right = charlist[#charlist]
538 loffset = abs((left ["start"] or 0) - (left ["end"] or 0))
539 roffset = abs((right["start"] or 0) - (right["end"] or 0))
540 end
541 end
542 return kind, loffset, roffset
543end
544
545mathematics.extensiblecode = extensiblecode
546mathematics.horizontalcode = horizontalcode
547
548interfaces.implement {
549 name = "extensiblecode",
550 arguments = { "integer", "integer" },
551 actions = { extensiblecode, context }
552}
553
554interfaces.implement {
555 name = "horizontalcode",
556 arguments = { "integer", "integer" },
557 actions = function(family,unicode)
558 local kind, loffset, roffset = horizontalcode(family,unicode)
559 texsetdimen("scratchleftoffset", loffset)
560 texsetdimen("scratchrightoffset",roffset)
561 context(kind)
562 end
563}
564
565local stack = { }
566
567function mathematics.registerfallbackid(n,id,name)
568 if trace_collecting then
569 report_math("resolved fallback font %i, name %a, id %a, used %a",
570 n,name,id,fontproperties[id].fontname)
571 end
572 stack[#stack][n] = id
573end
574
575interfaces.implement {
576 name = "registerfontfallbackid",
577 arguments = { "integer", "integer", "string" },
578 actions = mathematics.registerfallbackid,
579}
580
581function mathematics.resolvefallbacks(target,specification,fallbacks)
582 local definitions = fonts.collections.definitions[fallbacks]
583 if definitions then
584 local size = specification.size
585 local list = { }
586 insert(stack,list)
587 context.pushcatcodes("prt")
588 for i=1,#definitions do
589 local definition = definitions[i]
590 local name = definition.font
591 local features = definition.features or ""
592 local size = size * (definition.rscale or 1)
593 context.font_fallbacks_register_math(i,name,features,size)
594 if trace_collecting then
595 report_math("registering fallback font %i, name %a, size %a, features %a",i,name,size,features)
596 end
597 end
598 context.popcatcodes()
599 end
600end
601
602function mathematics.finishfallbacks(target,specification,fallbacks)
603 local list = remove(stack)
604 if list and #list > 0 then
605 local definitions = fonts.collections.definitions[fallbacks]
606 if definitions and #definitions > 0 then
607 if trace_collecting then
608 report_math("adding fallback characters to font %a",specification.hash)
609 end
610 local definedfont = fonts.definers.internal
611 local copiedglyph = fonts.handlers.vf.math.copy_glyph
612 local fonts = target.fonts
613 local size = specification.size
614 local characters = target.characters
615 if not fonts then
616 fonts = { }
617 target.fonts = fonts
618 end
619
620 target.type = "virtual"
621 target.properties.virtualized = true
622
623 if #fonts == 0 then
624 fonts[1] = { id = 0, size = size }
625 end
626 local done = { }
627 for i=1,#definitions do
628 local definition = definitions[i]
629 local name = definition.font
630 local start = definition.start
631 local stop = definition.stop
632 local gaps = definition.gaps
633 local check = definition.check
634 local force = definition.force
635 local rscale = definition.rscale or 1
636 local offset = definition.offset or start
637 local id = list[i]
638 if id then
639 local index = #fonts + 1
640 fonts[index] = { id = id, size = size }
641 local chars = fontchars[id]
642 local function remap(unic,unicode,gap)
643 if check and not chars[unicode] then
644 return
645 end
646 if force or (not done[unic] and not characters[unic]) then
647 if trace_collecting then
648 report_math("replacing math character %C by %C using vector %a and font id %a for %a%s%s",
649 unic,unicode,fallbacks,id,fontproperties[id].fontname,check and ", checked",gap and ", gap plugged")
650 end
651 characters[unic] = copiedglyph(target,characters,chars,unicode,index)
652 done[unic] = true
653 end
654 end
655 local step = offset - start
656 for unicode = start, stop do
657 remap(unicode + step,unicode,false)
658 end
659 if gaps then
660 for unic, unicode in next, gaps do
661 remap(unic,unicode,true)
662 remap(unicode,unicode,true)
663 end
664 end
665 end
666 end
667 elseif trace_collecting then
668 report_math("no fallback characters added to font %a",specification.hash)
669 end
670 end
671end
672 |