1if not modules then modules = { } end modules ['math-tag'] = {
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
17
18
19local find, match = string.find, string.match
20local insert, remove, concat = table.insert, table.remove, table.concat
21
22local attributes = attributes
23local nodes = nodes
24
25local nuts = nodes.nuts
26local tonut = nuts.tonut
27
28local getchar = nuts.getchar
29local getprev = nuts.getprev
30local getcharspec = nuts.getcharspec
31local getdata = nuts.getdata
32local getlist = nuts.getlist
33local getfield = nuts.getfield
34local getdisc = nuts.getdisc
35local getattr = nuts.getattr
36local getattrlist = nuts.getattrlist
37local setattr = nuts.setattr
38local getwidth = nuts.getwidth
39
40local getnumerator = nuts.getnumerator
41local getdenominator = nuts.getdenominator
42local getdelimiter = nuts.getdelimiter
43local getleftdelimiter = nuts.getleftdelimiter
44local getrightdelimiter = nuts.getrightdelimiter
45local getdegree = nuts.getdegree
46local gettop = nuts.gettop
47local getbottom = nuts.getbottom
48local getchoice = nuts.getchoice
49
50local getnucleus = nuts.getnucleus
51
52local setattributes = nuts.setattributes
53
54local nextnode = nuts.traversers.node
55
56local nodecodes = nodes.nodecodes
57
58local noad_code = nodecodes.noad
59local accent_code = nodecodes.accent
60local radical_code = nodecodes.radical
61local fraction_code = nodecodes.fraction
62local subbox_code = nodecodes.subbox
63local submlist_code = nodecodes.submlist
64local mathchar_code = nodecodes.mathchar
65local mathtextchar_code = nodecodes.mathtextchar
66local delimiter_code = nodecodes.delimiter
67local style_code = nodecodes.style
68local choice_code = nodecodes.choice
69local fence_code = nodecodes.fence
70
71local accentcodes = nodes.accentcodes
72local fencecodes = nodes.fencecodes
73
74local fixedtopaccent_code = accentcodes.fixedtop
75local fixedbottomaccent_code = accentcodes.fixedbottom
76local fixedbothaccent_code = accentcodes.fixedboth
77
78local hextensible_code = nodes.radicalcodes.hextensible
79local lextensible_code = nodes.listcodes.hextensible
80local gextensible_code = nodes.glyphcodes.extensible
81
82local leftfence_code = fencecodes.left
83local middlefence_code = fencecodes.middle
84local rightfence_code = fencecodes.right
85local operatorfence_code = fencecodes.operator
86
87local kerncodes = nodes.kerncodes
88
89local fontkern_code = kerncodes.fontkern
90local italickern_code = kerncodes.italickern
91
92local hlist_code = nodecodes.hlist
93local vlist_code = nodecodes.vlist
94local glyph_code = nodecodes.glyph
95local disc_code = nodecodes.disc
96local glue_code = nodecodes.glue
97local kern_code = nodecodes.kern
98local math_code = nodecodes.math
99
100local processnoads = noads.process
101
102local a_tagged = attributes.private('tagged')
103local a_mathcategory = attributes.private('mathcategory')
104local a_mathmode = attributes.private('mathmode')
105
106local tags = structures.tags
107
108local start_tagged = tags.start
109local restart_tagged = tags.restart
110local stop_tagged = tags.stop
111local taglist = tags.taglist
112
113
114
115local getmathcodes = tex.getmathcodes
116
117local mathcodes = mathematics.classes
118local ordinary_mathcode = mathcodes.ordinary
119local digit_mathcode = mathcodes.digit
120local punctuation_mathcode = mathcodes.punctuation
121local active_mathcode = mathcodes.active
122
123local fromunicode16 = fonts.mappings.fromunicode16
124local fontcharacters = fonts.hashes.characters
125
126local report_tags = logs.reporter("structure","tags")
127
128local process
129
130local function processnucleus(nucleus,prime)
131 if prime then
132
133
134
135
136
137 start_tagged("msup", { prime = true })
138 process(nucleus)
139 process(prime)
140 stop_tagged()
141 else
142 process(nucleus)
143 end
144end
145
146local function processsubsup(start)
147
148
149
150 local nucleus, prime, sup, sub, presup, presub = getnucleus(start,true)
151 if sub then
152 if sup then
153 setattr(start,a_tagged,start_tagged("msubsup"))
154 processnucleus(nucleus,prime)
155 start_tagged("mrow", { subscript = true })
156 process(sub)
157 stop_tagged()
158 start_tagged("mrow", { superscript = true })
159 process(sup)
160 stop_tagged()
161 stop_tagged()
162 else
163 setattr(start,a_tagged,start_tagged("msub"))
164 processnucleus(nucleus,prime)
165 start_tagged("mrow")
166 process(sub)
167 stop_tagged()
168 stop_tagged()
169 end
170 elseif sup then
171 setattr(start,a_tagged,start_tagged("msup"))
172 processnucleus(nucleus,prime)
173 start_tagged("mrow")
174 process(sup)
175 stop_tagged()
176 stop_tagged()
177 else
178 processnucleus(nucleus,prime)
179 end
180end
181
182
183
184
185
186local actionstack = { }
187local fencesstack = { }
188
189
190
191local function getunicode(n)
192 local char, font = getcharspec(n)
193 local data = fontcharacters[font][char]
194 return data.unicode or char
195end
196
197
198
199local content = { }
200local found = false
201
202content[mathchar_code] = function() found = true end
203
204local function hascontent(head)
205 found = false
206 processnoads(head,content,"content")
207 return found
208end
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239process = function(start)
240 local mtexttag = nil
241 for start, id, subtype in nextnode, start do
242 if id == glyph_code or id == disc_code then
243 if not mtexttag then
244 mtexttag = start_tagged("mtext")
245 end
246 setattr(start,a_tagged,mtexttag)
247 elseif mtexttag and id == kern_code and subtype == fontkern_code or subtype == italickern_code then
248 setattr(start,a_tagged,mtexttag)
249 else
250 if mtexttag then
251 stop_tagged()
252 mtexttag = nil
253 end
254 if id == mathchar_code then
255 local char = getchar(start)
256 local code = getmathcodes(char)
257
258
259 local tag
260 local properties= { class = code }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277 if code == ordinary_mathcode then
278 tag = "mi"
279 elseif code == digit_mathcode then
280 tag = "mn"
281
282
283 else
284 tag = "mo"
285 end
286
287
288
289 local a = getattr(start,a_mathcategory)
290 if a then
291
292 if properties then
293 properties.mathcategory = a
294 else
295 properties = { mathcategory = a }
296 end
297 end
298 setattr(start,a_tagged,start_tagged(tag,properties))
299 stop_tagged()
300
301 break
302 elseif id == mathtextchar_code then
303
304 local a = getattr(start,a_mathcategory)
305 if a then
306 setattr(start,a_tagged,start_tagged("ms",{ mathcategory = a }))
307 else
308 setattr(start,a_tagged,start_tagged("ms"))
309 end
310 stop_tagged()
311
312 break
313 elseif id == delimiter_code then
314
315 setattr(start,a_tagged,start_tagged("mo"))
316 stop_tagged()
317
318 break
319 elseif id == style_code then
320
321 elseif id == noad_code then
322
323 processsubsup(start)
324 elseif id == subbox_code or id == hlist_code or id == vlist_code then
325
326
327 local attr = getattr(start,a_tagged)
328 if not attr then
329
330 else
331 local specification = taglist[attr]
332 if specification then
333 local tag = specification.tagname
334 if tag == "formulacaption" then
335
336 elseif tag == "mstacker" then
337 local list = getlist(start)
338 if list then
339 process(list)
340 end
341 else
342 if tag ~= "mtable" and tag ~= "mstackertop" and tag ~= "mstackermid" and tag ~= "mstackerbot" then
343 tag = "mtext"
344 end
345 local detail = specification.detail
346 local text = start_tagged(tag, detail and { detail = detail } or nil)
347 setattr(start,a_tagged,text)
348 local list = getlist(start)
349 if not list then
350
351 elseif not attr then
352
353 setattributes(list,a_tagged,text)
354 else
355
356
357
358
359
360
361
362 local tagdata = specification.taglist
363 local common = #tagdata + 1
364 local function runner(list,depth)
365 local cache = { }
366 local keep = nil
367
368 for n, id, subtype in nextnode, list do
369 local mth = id == math_code and subtype
370 if mth == 0 then
371
372 keep = text
373 text = start_tagged("mrow")
374 common = common + 1
375 end
376 local aa = getattr(n,a_tagged)
377 if aa then
378 local ac = cache[aa]
379 if not ac then
380 local tagdata = taglist[aa].taglist
381 local extra = #tagdata
382 if common <= extra then
383 for i=common,extra do
384 ac = restart_tagged(tagdata[i])
385 end
386 for i=common,extra do
387 stop_tagged()
388 end
389 else
390 ac = text
391 end
392 cache[aa] = ac
393 end
394 setattr(n,a_tagged,ac)
395 else
396 setattr(n,a_tagged,text)
397 end
398 if id == hlist_code or id == vlist_code then
399
400
401
402 runner(getlist(n),depth+1)
403 elseif id == glyph_code then
404
405
406
407 elseif id == disc_code then
408
409 local pre, post, replace = getdisc(n)
410 if pre then
411 runner(pre,depth+1)
412 end
413 if post then
414 runner(post,depth+1)
415 end
416 if replace then
417 runner(replace,depth+1)
418 end
419 end
420 if mth == 1 then
421 stop_tagged()
422
423 text = keep
424 common = common - 1
425 end
426 end
427 end
428 runner(list,0)
429 end
430 stop_tagged()
431 end
432 end
433 end
434 elseif id == submlist_code then
435 local list = getlist(start)
436 if list then
437 local attr = getattr(start,a_tagged)
438 local last = attr and taglist[attr]
439 if last then
440 local tag = last.tagname
441 local detail = last.detail
442 if tag == "maction" then
443 if detail == "" then
444 setattr(start,a_tagged,start_tagged("mrow"))
445 process(list)
446 stop_tagged()
447 elseif actionstack[#actionstack] == action then
448 setattr(start,a_tagged,start_tagged("mrow"))
449 process(list)
450 stop_tagged()
451 else
452 insert(actionstack,action)
453 setattr(start,a_tagged,start_tagged("mrow",{ detail = action }))
454 process(list)
455 stop_tagged()
456 remove(actionstack)
457 end
458 elseif tag == "mstacker" then
459
460
461 setattr(start,a_tagged,restart_tagged(attr))
462 process(list)
463 stop_tagged()
464 else
465 setattr(start,a_tagged,start_tagged("mrow"))
466 process(list)
467 stop_tagged()
468 end
469 else
470 setattr(start,a_tagged,start_tagged("mrow"))
471 process(list)
472 stop_tagged()
473 end
474 end
475 elseif id == fraction_code then
476
477
478
479 local num = getnumerator(start)
480 local denom = getdenominator(start)
481 local left = getleftdelimiter(start)
482 local right = getrightdelimiter(start)
483 if left then
484 setattr(left,a_tagged,start_tagged("mo"))
485 process(left)
486 stop_tagged()
487 end
488 setattr(start,a_tagged,start_tagged("mfrac"))
489 process(num)
490 process(denom)
491 stop_tagged()
492 if right then
493 setattr(right,a_tagged,start_tagged("mo"))
494 process(right)
495 stop_tagged()
496 end
497 elseif id == choice_code then
498 local display = getchoice(start,1)
499 local text = getchoice(start,2)
500 local script = getchoice(start,3)
501 local scriptscript = getchoice(start,4)
502 if display then
503 process(display)
504 end
505 if text then
506 process(text)
507 end
508 if script then
509 process(script)
510 end
511 if scriptscript then
512 process(scriptscript)
513 end
514 elseif id == fence_code then
515 local delimiter = getdelimiter(start)
516
517 if subtype == leftfence_code then
518 local properties = { }
519 insert(fencesstack,properties)
520 setattr(start,a_tagged,start_tagged("mfenced",properties))
521 if delimiter then
522 start_tagged("ignore")
523 local chr = getchar(delimiter)
524 if chr ~= 0 then
525 properties.left = chr
526 end
527 process(delimiter)
528 stop_tagged()
529 end
530 start_tagged("mrow")
531 elseif subtype == middlefence_code then
532 if delimiter then
533 start_tagged("ignore")
534 local top = fencesstack[#fencesstack]
535 local chr = getchar(delimiter)
536 if chr ~= 0 then
537 local mid = top.middle
538 if mid then
539 mid[#mid+1] = chr
540 else
541 top.middle = { chr }
542 end
543 end
544 process(delimiter)
545 stop_tagged()
546 end
547 stop_tagged()
548 start_tagged("mrow")
549 elseif subtype == rightfence_code then
550 local properties = remove(fencesstack)
551 if not properties then
552 report_tags("missing right fence")
553 properties = { }
554 end
555 if delimiter then
556 start_tagged("ignore")
557 local chr = getchar(delimiter)
558 if chr ~= 0 then
559 properties.right = chr
560 end
561 process(delimiter)
562 stop_tagged()
563 end
564 stop_tagged()
565 stop_tagged()
566 elseif subtype == operatorfence_code then
567
568 local properties = { }
569 insert(fencesstack,properties)
570 setattr(start,a_tagged,start_tagged("mfenced",properties))
571 if delimiter then
572 local chr = getchar(delimiter)
573 if chr ~= 0 then
574 properties.operator = chr
575 end
576 process(delimiter)
577 end
578 processsubsup(start)
579 start_tagged("mrow")
580 end
581 elseif id == radical_code then
582 if subtype == hextensible_code then
583
584 else
585 local left = getleftdelimiter(start)
586 local right = getrightdelimiter(start)
587 local degree = getdegree(start)
588 if left then
589 start_tagged("ignore")
590 process(left)
591 stop_tagged()
592 end
593 if right then
594 start_tagged("ignore")
595 process(lright)
596 stop_tagged()
597 end
598 if degree and hascontent(degree) then
599 setattr(start,a_tagged,start_tagged("mroot"))
600 processsubsup(start)
601 process(degree)
602 stop_tagged()
603 else
604 setattr(start,a_tagged,start_tagged("msqrt"))
605 processsubsup(start)
606 stop_tagged()
607 end
608 end
609 elseif id == accent_code then
610 local topaccent = gettop(start)
611 local bottomaccent = getbottom(start)
612 if bottomaccent then
613 if topaccent then
614 setattr(start,a_tagged,start_tagged("munderover", {
615 accent = true,
616 top = getunicode(topaccent),
617 bottom = getunicode(bottomaccent),
618 topfixed = subtype == fixedtopaccent_code or subtype == fixedbothaccent_code,
619 bottomfixed = subtype == fixedbottomaccent_code or subtype == fixedbothaccent_code,
620 }))
621 processsubsup(start)
622 process(bottomaccent)
623 process(topaccent)
624 stop_tagged()
625 else
626 setattr(start,a_tagged,start_tagged("munder", {
627 accent = true,
628 bottom = getunicode(bottomaccent),
629 bottomfixed = subtype == fixedbottomaccent_code or subtype == fixedbothaccent_code,
630 }))
631 processsubsup(start)
632 process(bottomaccent)
633 stop_tagged()
634 end
635 elseif topaccent then
636 setattr(start,a_tagged,start_tagged("mover", {
637 accent = true,
638 top = getunicode(topaccent),
639 topfixed = subtype == fixedtopaccent_code or subtype == fixedbothaccent_code,
640 }))
641 processsubsup(start)
642 process(topaccent)
643 stop_tagged()
644 else
645 processsubsup(start)
646 end
647 elseif id == glue_code then
648
649 local em = fonts.hashes.emwidths[nuts.getfont(start)]
650 local wd = getwidth(start)
651 if em and wd then
652 setattr(start,a_tagged,start_tagged("mspace",{ emfactor = wd/em }))
653 stop_tagged()
654 end
655 else
656
657
658
659 end
660 end
661
662 end
663 if mtexttag then
664 stop_tagged()
665 end
666end
667
668function noads.handlers.tags(head,style,penalties)
669 start_tagged("math", { mode = (getattr(head,a_mathmode) == 1) and "display" or "inline" })
670 setattr(head,a_tagged,start_tagged("mrow"))
671
672 process(head)
673
674 stop_tagged()
675 stop_tagged()
676end
677
678do
679
680
681
682
683
684
685
686 local enabled = false
687 local export = false
688 local allmath = false
689 local warned = false
690
691 function mathematics.startcollecting()
692 if structures.tags.enabled() then
693 if not enabled then
694 nodes.tasks.enableaction("math", "noads.handlers.export")
695 end
696 enabled = true
697 export = structures.tags.localexport
698 allmath = { }
699 elseif not warned then
700 report_tags("math collecting only works when tagging is enabled")
701 warned = true
702 end
703 end
704
705 function mathematics.stopcollecting()
706 export = false
707 end
708
709 local function collected(asstring)
710 local a = allmath or { }
711 return asstring and concat(a) or a
712 end
713
714 mathematics.collected = collected
715
716 interfaces.implement {
717 name = "startcollectingmath",
718
719 protected = true,
720 actions = mathematics.startcollecting
721 }
722
723 interfaces.implement {
724 name = "stopcollectingmath",
725
726 protected = true,
727 actions = mathematics.stopcollecting
728 }
729
730 interfaces.implement {
731 name = "processcollectedmath",
732
733 protected = true,
734 arguments = "2 strings",
735 actions = function(filename,buffername)
736 if filename and filename ~= "" then
737 io.savedata(filename,collected(true))
738 elseif buffername then
739 buffers.assign(buffername == interfaces.variables.yes and "" or buffername,collected(true))
740 else
741 return collected
742 end
743 end
744 }
745
746 interfaces.implement {
747 name = "collectedmath",
748 usage = "value",
749 protected = true,
750 public = true,
751 actions = function(what)
752 if what == "value" then
753 return tokens.values.integer, allmath and #allmath or 0
754 else
755 context(allmath and allmath[tokens.scanners.integer()] or nil)
756 end
757 end
758 }
759
760 function noads.handlers.export(head)
761 if export then
762 allmath[#allmath+1] = export(head)
763 end
764 return head
765 end
766
767 nodes.tasks.appendaction("math", "finalizers", "noads.handlers.export", nil, "nonut", "disabled")
768
769end
770 |