1if not modules then modules = { } end modules ['font-ots'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to font-ini.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files",
8}
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129local type, next, tonumber = type, next, tonumber
130local random = math.random
131local formatters = string.formatters
132local insert = table.insert
133
134local registertracker = trackers.register
135
136local logs = logs
137local trackers = trackers
138local nodes = nodes
139local attributes = attributes
140local fonts = fonts
141
142local otf = fonts.handlers.otf
143local tracers = nodes.tracers
144
145local trace_singles = false registertracker("otf.singles", function(v) trace_singles = v end)
146local trace_multiples = false registertracker("otf.multiples", function(v) trace_multiples = v end)
147local trace_alternatives = false registertracker("otf.alternatives", function(v) trace_alternatives = v end)
148local trace_ligatures = false registertracker("otf.ligatures", function(v) trace_ligatures = v end)
149local trace_contexts = false registertracker("otf.contexts", function(v) trace_contexts = v end)
150local trace_marks = false registertracker("otf.marks", function(v) trace_marks = v end)
151local trace_kerns = false registertracker("otf.kerns", function(v) trace_kerns = v end)
152local trace_cursive = false registertracker("otf.cursive", function(v) trace_cursive = v end)
153local trace_preparing = false registertracker("otf.preparing", function(v) trace_preparing = v end)
154local trace_bugs = false registertracker("otf.bugs", function(v) trace_bugs = v end)
155local trace_details = false registertracker("otf.details", function(v) trace_details = v end)
156local trace_steps = false registertracker("otf.steps", function(v) trace_steps = v end)
157local trace_skips = false registertracker("otf.skips", function(v) trace_skips = v end)
158local trace_plugins = false registertracker("otf.plugins", function(v) trace_plugins = v end)
159local trace_chains = false registertracker("otf.chains", function(v) trace_chains = v end)
160
161local trace_kernruns = false registertracker("otf.kernruns", function(v) trace_kernruns = v end)
162
163local trace_compruns = false registertracker("otf.compruns", function(v) trace_compruns = v end)
164local trace_testruns = false registertracker("otf.testruns", function(v) trace_testruns = v end)
165
166local forcediscretionaries = false
167local forcepairadvance = false
168
169local repeatablemultiples = context or false
170
171directives.register("otf.forcediscretionaries", function(v) forcediscretionaries = v end)
172directives.register("otf.forcepairadvance", function(v) forcepairadvance = v end)
173
174local report_direct = logs.reporter("fonts","otf direct")
175local report_subchain = logs.reporter("fonts","otf subchain")
176local report_chain = logs.reporter("fonts","otf chain")
177local report_process = logs.reporter("fonts","otf process")
178local report_warning = logs.reporter("fonts","otf warning")
179local report_run = logs.reporter("fonts","otf run")
180
181registertracker("otf.substitutions", "otf.singles","otf.multiples","otf.alternatives","otf.ligatures")
182registertracker("otf.positions", "otf.marks","otf.kerns","otf.cursive")
183registertracker("otf.actions", "otf.substitutions","otf.positions")
184registertracker("otf.sample", "otf.steps","otf.substitutions","otf.positions","otf.analyzing")
185registertracker("otf.sample.silent", "otf.steps=silent","otf.substitutions","otf.positions","otf.analyzing")
186
187local nuts = nodes.nuts
188
189local getnext = nuts.getnext
190local setnext = nuts.setnext
191local getprev = nuts.getprev
192local setprev = nuts.setprev
193local getboth = nuts.getboth
194local setboth = nuts.setboth
195local getid = nuts.getid
196local getstate = nuts.getstate
197local getsubtype = nuts.getsubtype
198local getchar = nuts.getchar
199local setchar = nuts.setchar
200local getdisc = nuts.getdisc
201local setdisc = nuts.setdisc
202local getreplace = nuts.getreplace
203local setlink = nuts.setlink
204local getwidth = nuts.getwidth
205local getattr = nuts.getattr
206
207local getglyphdata = nuts.getglyphdata
208
209
210
211
212
213
214
215local components = nuts.components
216local copynocomponents = components.copynocomponents
217local copyonlyglyphs = components.copyonlyglyphs
218local countcomponents = components.count
219local setcomponents = components.set
220local getcomponents = components.get
221local flushcomponents = components.flush
222
223
224
225local ischar = nuts.ischar
226local usesfont = nuts.usesfont
227
228local insertnodeafter = nuts.insertafter
229local copynode = nuts.copy
230local copynodelist = nuts.copylist
231local removenode = nuts.remove
232local findnodetail = nuts.tail
233local flushnodelist = nuts.flushlist
234local flushnode = nuts.flushnode
235local endofmath = nuts.endofmath
236
237local startofpar = nuts.startofpar
238
239local setmetatable = setmetatable
240local setmetatableindex = table.setmetatableindex
241
242local nextnode = nuts.traversers.node
243
244local nodecodes = nodes.nodecodes
245local glyphcodes = nodes.glyphcodes
246
247local glyph_code = nodecodes.glyph
248local glue_code = nodecodes.glue
249local disc_code = nodecodes.disc
250local math_code = nodecodes.math
251local dir_code = nodecodes.dir
252local par_code = nodecodes.par
253
254local lefttoright_code = nodes.dirvalues.lefttoright
255local righttoleft_code = nodes.dirvalues.righttoleft
256
257local discretionarydisc_code = nodes.disccodes.discretionary
258
259local a_noligature = attributes.private("noligature")
260
261local injections = nodes.injections
262local setmark = injections.setmark
263local setcursive = injections.setcursive
264local setkern = injections.setkern
265local setmove = injections.setmove
266local setposition = injections.setposition
267local resetinjection = injections.reset
268local copyinjection = injections.copy
269local setligaindex = injections.setligaindex
270local getligaindex = injections.getligaindex
271
272local fontdata = fonts.hashes.identifiers
273local fontfeatures = fonts.hashes.features
274
275local otffeatures = fonts.constructors.features.otf
276local registerotffeature = otffeatures.register
277
278local onetimemessage = fonts.loggers.onetimemessage or function() end
279
280local getrandom = utilities and utilities.randomizer and utilities.randomizer.get
281
282otf.defaultnodealternate = "none"
283
284
285
286
287local tfmdata = false
288local characters = false
289local descriptions = false
290local marks = false
291local classes = false
292local currentfont = false
293local factor = 0
294local threshold = 0
295local checkmarks = false
296
297local discs = false
298local spaces = false
299
300local sweepnode = nil
301local sweephead = { }
302
303local notmatchpre = { }
304local notmatchpost = { }
305local notmatchreplace = { }
306
307local handlers = { }
308
309local isspace = injections.isspace
310local getthreshold = injections.getthreshold
311
312local checkstep = (tracers and tracers.steppers.check) or function() end
313local registerstep = (tracers and tracers.steppers.register) or function() end
314local registermessage = (tracers and tracers.steppers.message) or function() end
315
316local function logprocess(...)
317 if trace_steps then
318 registermessage(...)
319 if trace_steps == "silent" then
320 return
321 end
322 end
323 report_direct(...)
324end
325
326local function logwarning(...)
327 report_direct(...)
328end
329
330local gref do
331
332 local f_unicode = formatters["U+%X"]
333 local f_uniname = formatters["U+%X (%s)"]
334 local f_unilist = formatters["% t"]
335
336 gref = function(n)
337 if type(n) == "number" then
338 local description = descriptions[n]
339 local name = description and description.name
340 if name then
341 return f_uniname(n,name)
342 else
343 return f_unicode(n)
344 end
345 elseif n then
346 local t = { }
347 for i=1,#n do
348 local ni = n[i]
349 if tonumber(ni) then
350 local di = descriptions[ni]
351 local nn = di and di.name
352 if nn then
353 t[#t+1] = f_uniname(ni,nn)
354 else
355 t[#t+1] = f_unicode(ni)
356 end
357 end
358 end
359 return f_unilist(t)
360 else
361 return "<error in node mode tracing>"
362 end
363 end
364
365end
366
367local function cref(dataset,sequence,index)
368 if not dataset then
369 return "no valid dataset"
370 end
371 local merged = sequence.merged and "merged " or ""
372 if index and index > 1 then
373 return formatters["feature %a, type %a, %schain lookup %a, index %a"](
374 dataset[4],sequence.type,merged,sequence.name,index)
375 else
376 return formatters["feature %a, type %a, %schain lookup %a"](
377 dataset[4],sequence.type,merged,sequence.name)
378 end
379end
380
381local function pref(dataset,sequence)
382 return formatters["feature %a, type %a, %slookup %a"](
383 dataset[4],sequence.type,sequence.merged and "merged " or "",sequence.name)
384end
385
386local function mref(rlmode)
387 if not rlmode or rlmode >= 0 then
388 return "l2r"
389 else
390 return "r2l"
391 end
392end
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410local function flattendisk(head,disc)
411 local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true)
412 local prev, next = getboth(disc)
413 local ishead = head == disc
414 setdisc(disc)
415 flushnode(disc)
416 if pre then
417 flushnodelist(pre)
418 end
419 if post then
420 flushnodelist(post)
421 end
422 if ishead then
423 if replace then
424 if next then
425 setlink(replacetail,next)
426 end
427 return replace, replace
428 elseif next then
429 return next, next
430 else
431
432 end
433 else
434 if replace then
435 if next then
436 setlink(replacetail,next)
437 end
438 setlink(prev,replace)
439 return head, replace
440 else
441 setlink(prev,next)
442 return head, next
443 end
444 end
445end
446
447local function appenddisc(disc,list)
448 local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true)
449 local posthead = list
450 local replacehead = copynodelist(list)
451 if post then
452 setlink(posttail,posthead)
453 else
454 post = posthead
455 end
456 if replace then
457 setlink(replacetail,replacehead)
458 else
459 replace = replacehead
460 end
461 setdisc(disc,pre,post,replace)
462end
463
464local function markstoligature(head,start,stop,char)
465 if start == stop and getchar(start) == char then
466 return head, start
467 else
468 local prev = getprev(start)
469 local next = getnext(stop)
470 setprev(start)
471 setnext(stop)
472 local base = copynocomponents(start,copyinjection)
473 if head == start then
474 head = base
475 end
476 resetinjection(base)
477 setchar(base,char)
478 setcomponents(base,start)
479 setlink(prev,base,next)
480 flushcomponents(start)
481 return head, base
482 end
483end
484
485
486
487
488
489
490
491
492
493
494
495local no_left_ligature_code = 1
496local no_right_ligature_code = 2
497local no_left_kern_code = 4
498local no_right_kern_code = 8
499
500local hasglyphoption = function(n,c)
501 if c == no_left_ligature_code or c == no_right_ligature_code then
502 return getattr(n,a_noligature) == 1
503 else
504 return false
505 end
506end
507
508
509
510local function toligature(head,start,stop,char,dataset,sequence,skiphash,discfound,hasmarks)
511 if hasglyphoption(start,no_right_ligature_code) then
512 return head, start
513 end
514 if start == stop and getchar(start) == char then
515 resetinjection(start)
516 setchar(start,char)
517 return head, start
518 end
519 local prev = getprev(start)
520 local next = getnext(stop)
521 local comp = start
522 setprev(start)
523 setnext(stop)
524 local base = copynocomponents(start,copyinjection)
525 if start == head then
526 head = base
527 end
528 resetinjection(base)
529 setchar(base,char)
530 setcomponents(base,comp)
531 setlink(prev,base,next)
532 if not discfound then
533 local deletemarks = not skiphash or hasmarks
534 local components = start
535 local baseindex = 0
536 local componentindex = 0
537 local head = base
538 local current = base
539
540 while start do
541 local char = getchar(start)
542 if not marks[char] then
543 baseindex = baseindex + componentindex
544 componentindex = countcomponents(start,marks)
545
546
547 elseif not deletemarks then
548
549 setligaindex(start,baseindex + getligaindex(start,componentindex))
550 if trace_marks then
551 logwarning("%s: keep ligature mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start))
552 end
553 local n = copynode(start)
554 copyinjection(n,start)
555 head, current = insertnodeafter(head,current,n)
556 elseif trace_marks then
557 logwarning("%s: delete ligature mark %s",pref(dataset,sequence),gref(char))
558 end
559 start = getnext(start)
560 end
561
562 local start = getnext(current)
563 while start do
564 local char = ischar(start)
565 if char then
566
567 if marks[char] then
568 setligaindex(start,baseindex + getligaindex(start,componentindex))
569 if trace_marks then
570 logwarning("%s: set ligature mark %s, gets index %s",pref(dataset,sequence),gref(char),getligaindex(start))
571 end
572 start = getnext(start)
573 else
574 break
575 end
576 else
577 break
578 end
579 end
580 flushcomponents(components)
581 else
582
583 local discprev, discnext = getboth(discfound)
584 if discprev and discnext then
585
586
587
588 local pre, post, replace, pretail, posttail, replacetail = getdisc(discfound,true)
589 if not replace then
590
591 local prev = getprev(base)
592
593 local copied = copyonlyglyphs(comp)
594 if pre then
595 setlink(discprev,pre)
596 else
597 setnext(discprev)
598 end
599 pre = comp
600 if post then
601 setlink(posttail,discnext)
602 setprev(post)
603 else
604 post = discnext
605 setprev(discnext)
606 end
607 setlink(prev,discfound,next)
608 setboth(base)
609
610 setcomponents(base,copied)
611 replace = base
612 if forcediscretionaries then
613 setdisc(discfound,pre,post,replace,discretionarydisc_code)
614 else
615 setdisc(discfound,pre,post,replace)
616 end
617 base = prev
618 end
619 end
620 end
621 return head, base
622end
623
624local function multiple_glyphs(head,start,multiple,skiphash,what,stop)
625 local nofmultiples = #multiple
626 if nofmultiples > 0 then
627 local first = start
628 resetinjection(start)
629 setchar(start,multiple[1])
630 if nofmultiples > 1 then
631
632 for i=2,nofmultiples do
633
634
635
636
637
638 local n = copynode(start)
639 resetinjection(n)
640 setchar(n,multiple[i])
641 insertnodeafter(head,start,n)
642 start = n
643 end
644 end
645 if what ~= true and repeatablemultiples then
646
647
648
649
650 local kind = type(what)
651 local m, f, l
652 if kind == "string" then
653 local what, n = string.match(what,"^repeat(.-)[:=](%d+)$")
654 if what == "middle" then
655 m = tonumber(n)
656 elseif what == "first" then
657 f = tonumber(n)
658 elseif what == "last" then
659 l = tonumber(n)
660 end
661 elseif kind == "table" then
662
663 m = what.middle
664 f = what.first
665 l = what.last
666 end
667 if f or m or l then
668 if m and m > 1 and nofmultiples == 3 then
669 local middle = getnext(first)
670 for i=2,m do
671 local n = copynode(middle)
672 resetinjection(n)
673 insertnodeafter(head,first,n)
674 end
675 end
676 if f and f > 1 then
677 for i=2,f do
678 local n = copynode(first)
679 resetinjection(n)
680 insertnodeafter(head,first,n)
681 end
682 end
683 if l and l > 1 then
684 for i=2,l do
685 local n = copynode(start)
686 resetinjection(n)
687 insertnodeafter(head,start,n)
688 start = n
689 end
690 end
691 end
692 end
693 return head, start, true
694 else
695 if trace_multiples then
696 logprocess("no multiple for %s",gref(getchar(start)))
697 end
698 return head, start, false
699 end
700end
701
702local function get_alternative_glyph(start,alternatives,value)
703 local n = #alternatives
704 if n == 1 then
705
706
707 return alternatives[1], trace_alternatives and "1 (only one present)"
708 elseif value == "random" then
709 local r = getrandom and getrandom("glyph",1,n) or random(1,n)
710 return alternatives[r], trace_alternatives and formatters["value %a, taking %a"](value,r)
711 elseif value == "first" then
712 return alternatives[1], trace_alternatives and formatters["value %a, taking %a"](value,1)
713 elseif value == "last" then
714 return alternatives[n], trace_alternatives and formatters["value %a, taking %a"](value,n)
715 end
716 value = value == true and 1 or tonumber(value)
717 if type(value) ~= "number" then
718 return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
719 end
720
721
722
723
724
725 if value > n then
726 local defaultalt = otf.defaultnodealternate
727 if defaultalt == "first" then
728 return alternatives[n], trace_alternatives and formatters["invalid value %s, taking %a"](value,1)
729 elseif defaultalt == "last" then
730 return alternatives[1], trace_alternatives and formatters["invalid value %s, taking %a"](value,n)
731 else
732 return false, trace_alternatives and formatters["invalid value %a, %s"](value,"out of range")
733 end
734 elseif value == 0 then
735 return getchar(start), trace_alternatives and formatters["invalid value %a, %s"](value,"no change")
736 elseif value < 1 then
737 return alternatives[1], trace_alternatives and formatters["invalid value %a, taking %a"](value,1)
738 else
739 return alternatives[value], trace_alternatives and formatters["value %a, taking %a"](value,value)
740 end
741end
742
743
744
745function handlers.gsub_single(head,start,dataset,sequence,replacement)
746 if trace_singles then
747 logprocess("%s: replacing %s by single %s",pref(dataset,sequence),gref(getchar(start)),gref(replacement))
748 end
749 resetinjection(start)
750 setchar(start,replacement)
751 return head, start, true
752end
753
754function handlers.gsub_alternate(head,start,dataset,sequence,alternative)
755 local kind = dataset[4]
756 local what = dataset[1]
757 local value = what == true and tfmdata.shared.features[kind] or what
758 local choice, comment = get_alternative_glyph(start,alternative,value)
759 if choice then
760 if trace_alternatives then
761 logprocess("%s: replacing %s by alternative %a to %s, %s",pref(dataset,sequence),gref(getchar(start)),gref(choice),comment)
762 end
763 resetinjection(start)
764 setchar(start,choice)
765 else
766 if trace_alternatives then
767 logwarning("%s: no variant %a for %s, %s",pref(dataset,sequence),value,gref(getchar(start)),comment)
768 end
769 end
770 return head, start, true
771end
772
773function handlers.gsub_multiple(head,start,dataset,sequence,multiple,rlmode,skiphash)
774 if trace_multiples then
775 logprocess("%s: replacing %s by multiple %s",pref(dataset,sequence),gref(getchar(start)),gref(multiple))
776 end
777 return multiple_glyphs(head,start,multiple,skiphash,dataset[1])
778end
779
780
781
782
783
784function handlers.gsub_ligature(head,start,dataset,sequence,ligature,rlmode,skiphash)
785 local current = getnext(start)
786 if not current then
787 return head, start, false, nil
788 end
789 local stop = nil
790 local startchar = getchar(start)
791 if skiphash and skiphash[startchar] then
792 while current do
793 local char = ischar(current,currentfont)
794 if char then
795 local lg = not tonumber(ligature) and ligature[char]
796 if lg then
797 stop = current
798 ligature = lg
799 current = getnext(current)
800 else
801 break
802 end
803 else
804 break
805 end
806 end
807 if stop then
808 local ligature = tonumber(ligature) or ligature.ligature
809 if ligature then
810 if trace_ligatures then
811 local stopchar = getchar(stop)
812 head, start = markstoligature(head,start,stop,ligature)
813 logprocess("%s: replacing %s upto %s by ligature %s case 1",pref(dataset,sequence),gref(startchar),gref(stopchar),gref(getchar(start)))
814 else
815 head, start = markstoligature(head,start,stop,ligature)
816 end
817 return head, start, true, false
818 else
819
820 end
821 end
822 else
823 local discfound = false
824 local hasmarks = marks[startchar]
825 while current do
826 local char, id = ischar(current,currentfont)
827 if char then
828 if skiphash and skiphash[char] then
829 current = getnext(current)
830 else
831 local lg = not tonumber(ligature) and ligature[char]
832 if lg then
833 if marks[char] then
834 hasmarks = true
835 end
836 stop = current
837 ligature = lg
838 current = getnext(current)
839 else
840 break
841 end
842 end
843 elseif char == false then
844
845 break
846 elseif id == disc_code then
847 discfound = current
848 break
849 else
850 break
851 end
852 end
853
854
855
856
857
858
859 if discfound then
860
861 local pre, post, replace = getdisc(discfound)
862 local match
863 if replace then
864 local char = ischar(replace,currentfont)
865 if char and (not tonumber(ligature) and ligature[char]) then
866 match = true
867 end
868 end
869 if not match and pre then
870 local char = ischar(pre,currentfont)
871 if char and (not tonumber(ligature) and ligature[char]) then
872 match = true
873 end
874 end
875 if not match and not pre or not replace then
876 local n = getnext(discfound)
877 local char = ischar(n,currentfont)
878 if char and (not tonumber(ligature) and ligature[char]) then
879 match = true
880 end
881 end
882 if match then
883
884 local ishead = head == start
885 local prev = getprev(start)
886 if stop then
887 setnext(stop)
888 local copy = copynodelist(start)
889 local tail = stop
890 local liat = findnodetail(copy)
891 if pre then
892 setlink(liat,pre)
893 end
894 if replace then
895 setlink(tail,replace)
896 end
897 pre = copy
898 replace = start
899 else
900 setnext(start)
901 local copy = copynode(start)
902 if pre then
903 setlink(copy,pre)
904 end
905 if replace then
906 setlink(start,replace)
907 end
908 pre = copy
909 replace = start
910 end
911 setdisc(discfound,pre,post,replace)
912 if prev then
913 setlink(prev,discfound)
914 else
915 setprev(discfound)
916 head = discfound
917 end
918 start = discfound
919 return head, start, true, true
920 end
921 end
922 local ligature = tonumber(ligature) or ligature.ligature
923 if ligature then
924 if stop then
925 if trace_ligatures then
926 local stopchar = getchar(stop)
927
928 head, start = toligature(head,start,stop,ligature,dataset,sequence,skiphash,false,hasmarks)
929 logprocess("%s: replacing %s upto %s by ligature %s case 2",pref(dataset,sequence),gref(startchar),gref(stopchar),gref(ligature))
930
931
932 else
933
934 head, start = toligature(head,start,stop,ligature,dataset,sequence,skiphash,false,hasmarks)
935 end
936 else
937
938 resetinjection(start)
939 setchar(start,ligature)
940 if trace_ligatures then
941 logprocess("%s: replacing %s by (no real) ligature %s case 3",pref(dataset,sequence),gref(startchar),gref(ligature))
942 end
943 end
944 return head, start, true, false
945 else
946
947 end
948 end
949 return head, start, false, false
950end
951
952function handlers.gpos_single(head,start,dataset,sequence,kerns,rlmode,skiphash,step,injection)
953 if hasglyphoption(start,no_right_kern_code) then
954 return head, start, false
955 else
956 local startchar = getchar(start)
957 local format = step.format
958 if format == "single" or type(kerns) == "table" then
959 local dx, dy, w, h = setposition(0,start,factor,rlmode,kerns,injection)
960 if trace_kerns then
961 logprocess("%s: shifting single %s by %s xy (%p,%p) and wh (%p,%p)",pref(dataset,sequence),gref(startchar),format,dx,dy,w,h)
962 end
963 else
964 local k = (format == "move" and setmove or setkern)(start,factor,rlmode,kerns,injection)
965 if trace_kerns then
966 logprocess("%s: shifting single %s by %s %p",pref(dataset,sequence),gref(startchar),format,k)
967 end
968 end
969 return head, start, true
970 end
971end
972
973function handlers.gpos_pair(head,start,dataset,sequence,kerns,rlmode,skiphash,step,injection)
974 if hasglyphoption(start,no_right_kern_code) then
975 return head, start, false
976 else
977 local snext = getnext(start)
978 if not snext then
979 return head, start, false
980 else
981 local prev = start
982 while snext do
983 local nextchar = ischar(snext,currentfont)
984 if nextchar then
985 if skiphash and skiphash[nextchar] then
986 prev = snext
987 snext = getnext(snext)
988 else
989 local krn = kerns[nextchar]
990 if not krn then
991 break
992 end
993 local format = step.format
994 if format == "pair" then
995 local a = krn[1]
996 local b = krn[2]
997 if a == true then
998
999 elseif a then
1000 local x, y, w, h = setposition(1,start,factor,rlmode,a,injection)
1001 if trace_kerns then
1002 local startchar = getchar(start)
1003 logprocess("%s: shifting first of pair %s and %s by xy (%p,%p) and wh (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections")
1004 end
1005 end
1006 if b == true then
1007
1008 start = snext
1009 elseif b then
1010 local x, y, w, h = setposition(2,snext,factor,rlmode,b,injection)
1011 if trace_kerns then
1012 local startchar = getchar(start)
1013 logprocess("%s: shifting second of pair %s and %s by xy (%p,%p) and wh (%p,%p) as %s",pref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h,injection or "injections")
1014 end
1015 start = snext
1016 elseif forcepairadvance then
1017 start = snext
1018 end
1019 return head, start, true
1020 elseif krn ~= 0 then
1021 local k = (format == "move" and setmove or setkern)(snext,factor,rlmode,krn,injection)
1022 if trace_kerns then
1023 logprocess("%s: inserting %s %p between %s and %s as %s",pref(dataset,sequence),format,k,gref(getchar(prev)),gref(nextchar),injection or "injections")
1024 end
1025 return head, start, true
1026 else
1027 break
1028 end
1029 end
1030 else
1031 break
1032 end
1033 end
1034 return head, start, false
1035 end
1036 end
1037end
1038
1039
1040
1041
1042function handlers.gpos_mark2base(head,start,dataset,sequence,markanchors,rlmode,skiphash)
1043 local markchar = getchar(start)
1044 if marks[markchar] then
1045 local base = getprev(start)
1046 if base then
1047 local basechar = ischar(base,currentfont)
1048 if basechar then
1049 if marks[basechar] then
1050 while base do
1051 base = getprev(base)
1052 if base then
1053 basechar = ischar(base,currentfont)
1054 if basechar then
1055 if not marks[basechar] then
1056 break
1057 end
1058 else
1059 if trace_bugs then
1060 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1)
1061 end
1062 return head, start, false
1063 end
1064 else
1065 if trace_bugs then
1066 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2)
1067 end
1068 return head, start, false
1069 end
1070 end
1071 end
1072 local ba = markanchors[1][basechar]
1073 if ba then
1074 local ma = markanchors[2]
1075 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks)
1076 if trace_marks then
1077 logprocess("%s, bound %s, anchoring mark %s to basechar %s => (%p,%p)",
1078 pref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy)
1079 end
1080 return head, start, true
1081 elseif trace_bugs then
1082
1083 logwarning("%s: mark %s is not anchored to %s",pref(dataset,sequence),gref(markchar),gref(basechar))
1084 end
1085 elseif trace_bugs then
1086 logwarning("%s: nothing preceding, case %i",pref(dataset,sequence),1)
1087 end
1088 elseif trace_bugs then
1089 logwarning("%s: nothing preceding, case %i",pref(dataset,sequence),2)
1090 end
1091 elseif trace_bugs then
1092 logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))
1093 end
1094 return head, start, false
1095end
1096
1097function handlers.gpos_mark2ligature(head,start,dataset,sequence,markanchors,rlmode,skiphash)
1098 local markchar = getchar(start)
1099 if marks[markchar] then
1100 local base = getprev(start)
1101 if base then
1102 local basechar = ischar(base,currentfont)
1103 if basechar then
1104 if marks[basechar] then
1105 while base do
1106 base = getprev(base)
1107 if base then
1108 basechar = ischar(base,currentfont)
1109 if basechar then
1110 if not marks[basechar] then
1111 break
1112 end
1113 else
1114 if trace_bugs then
1115 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1)
1116 end
1117 return head, start, false
1118 end
1119 else
1120 if trace_bugs then
1121 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2)
1122 end
1123 return head, start, false
1124 end
1125 end
1126 end
1127 local ba = markanchors[1][basechar]
1128 if ba then
1129 local ma = markanchors[2]
1130 if ma then
1131 local index = getligaindex(start)
1132 ba = ba[index]
1133 if ba then
1134 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks)
1135 if trace_marks then
1136 logprocess("%s, index %s, bound %s, anchoring mark %s to baselig %s at index %s => (%p,%p)",
1137 pref(dataset,sequence),index,bound,gref(markchar),gref(basechar),index,dx,dy)
1138 end
1139 return head, start, true
1140 else
1141 if trace_bugs then
1142 logwarning("%s: no matching anchors for mark %s and baselig %s with index %a",pref(dataset,sequence),gref(markchar),gref(basechar),index)
1143 end
1144 end
1145 end
1146 elseif trace_bugs then
1147
1148 onetimemessage(currentfont,basechar,"no base anchors")
1149 end
1150 elseif trace_bugs then
1151 logwarning("%s: prev node is no char, case %i",pref(dataset,sequence),1)
1152 end
1153 elseif trace_bugs then
1154 logwarning("%s: prev node is no char, case %i",pref(dataset,sequence),2)
1155 end
1156 elseif trace_bugs then
1157 logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))
1158 end
1159 return head, start, false
1160end
1161
1162function handlers.gpos_mark2mark(head,start,dataset,sequence,markanchors,rlmode,skiphash)
1163 local markchar = getchar(start)
1164 if marks[markchar] then
1165 local base = getprev(start)
1166 local slc = getligaindex(start)
1167 if slc then
1168 while base do
1169 local blc = getligaindex(base)
1170 if blc and blc ~= slc then
1171 base = getprev(base)
1172 else
1173 break
1174 end
1175 end
1176 end
1177 if base then
1178 local basechar = ischar(base,currentfont)
1179 if basechar then
1180 local ba = markanchors[1][basechar]
1181 if ba then
1182 local ma = markanchors[2]
1183 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks)
1184 if trace_marks then
1185 logprocess("%s, bound %s, anchoring mark %s to basemark %s => (%p,%p)",
1186 pref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy)
1187 end
1188 return head, start, true
1189 end
1190 end
1191 end
1192 elseif trace_bugs then
1193 logwarning("%s: mark %s is no mark",pref(dataset,sequence),gref(markchar))
1194 end
1195 return head, start, false
1196end
1197
1198function handlers.gpos_cursive(head,start,dataset,sequence,exitanchors,rlmode,skiphash,step)
1199 local startchar = getchar(start)
1200 if marks[startchar] then
1201 if trace_cursive then
1202 logprocess("%s: ignoring cursive for mark %s",pref(dataset,sequence),gref(startchar))
1203 end
1204 else
1205 local nxt = getnext(start)
1206 while nxt do
1207 local nextchar = ischar(nxt,currentfont)
1208 if not nextchar then
1209 break
1210 elseif marks[nextchar] then
1211 nxt = getnext(nxt)
1212 else
1213 local exit = exitanchors[3]
1214 if exit then
1215 local entry = exitanchors[1][nextchar]
1216 if entry then
1217 entry = entry[2]
1218 if entry then
1219 local r2lflag = sequence.flags[4]
1220 local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar],r2lflag)
1221 if trace_cursive then
1222 logprocess("%s: moving %s to %s cursive (%p,%p) using bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,bound,mref(rlmode))
1223 end
1224 return head, start, true
1225 end
1226 end
1227 end
1228 break
1229 end
1230 end
1231 end
1232 return head, start, false
1233end
1234
1235
1236
1237
1238local chainprocs = { }
1239
1240local function logprocess(...)
1241 if trace_steps then
1242 registermessage(...)
1243 if trace_steps == "silent" then
1244 return
1245 end
1246 end
1247 report_subchain(...)
1248end
1249
1250local logwarning = report_subchain
1251
1252local function logprocess(...)
1253 if trace_steps then
1254 registermessage(...)
1255 if trace_steps == "silent" then
1256 return
1257 end
1258 end
1259 report_chain(...)
1260end
1261
1262local logwarning = report_chain
1263
1264
1265
1266
1267
1268
1269
1270
1271local function reversesub(head,start,stop,dataset,sequence,replacements,rlmode,skiphash)
1272 local char = getchar(start)
1273 local replacement = replacements[char]
1274 if replacement then
1275 if trace_singles then
1276 logprocess("%s: single reverse replacement of %s by %s",cref(dataset,sequence),gref(char),gref(replacement))
1277 end
1278 resetinjection(start)
1279 setchar(start,replacement)
1280 return head, start, true
1281 else
1282 return head, start, false
1283 end
1284end
1285
1286
1287chainprocs.reversesub = reversesub
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306local function reportzerosteps(dataset,sequence)
1307 logwarning("%s: no steps",cref(dataset,sequence))
1308end
1309
1310local function reportmoresteps(dataset,sequence)
1311 logwarning("%s: more than 1 step",cref(dataset,sequence))
1312end
1313
1314
1315
1316
1317
1318local function getmapping(dataset,sequence,currentlookup)
1319 local steps = currentlookup.steps
1320 local nofsteps = currentlookup.nofsteps
1321 if nofsteps == 0 then
1322 reportzerosteps(dataset,sequence)
1323 currentlookup.mapping = false
1324 return false
1325 else
1326 if nofsteps > 1 then
1327 reportmoresteps(dataset,sequence)
1328 end
1329 local mapping = steps[1].coverage
1330 currentlookup.mapping = mapping
1331 currentlookup.format = steps[1].format
1332 return mapping
1333 end
1334end
1335
1336function chainprocs.gsub_remove(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1337 if trace_chains then
1338 logprocess("%s: removing character %s",cref(dataset,sequence,chainindex),gref(getchar(start)))
1339 end
1340 head, start = removenode(head,start,true)
1341 return head, getprev(start), true
1342end
1343
1344function chainprocs.gsub_single(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1345 local mapping = currentlookup.mapping
1346 if mapping == nil then
1347 mapping = getmapping(dataset,sequence,currentlookup)
1348 end
1349 if mapping then
1350 local current = start
1351 while current do
1352 local currentchar = ischar(current)
1353 if currentchar then
1354 local replacement = mapping[currentchar]
1355 if not replacement or replacement == "" then
1356 if trace_bugs then
1357 logwarning("%s: no single for %s",cref(dataset,sequence,chainindex),gref(currentchar))
1358 end
1359 else
1360 if trace_singles then
1361 logprocess("%s: replacing single %s by %s",cref(dataset,sequence,chainindex),gref(currentchar),gref(replacement))
1362 end
1363 resetinjection(current)
1364 setchar(current,replacement)
1365 end
1366 return head, start, true
1367 elseif currentchar == false then
1368
1369 break
1370 elseif current == stop then
1371 break
1372 else
1373 current = getnext(current)
1374 end
1375 end
1376 end
1377 return head, start, false
1378end
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390function chainprocs.gsub_alternate(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1391 local mapping = currentlookup.mapping
1392 if mapping == nil then
1393 mapping = getmapping(dataset,sequence,currentlookup)
1394 end
1395 if mapping then
1396 local kind = dataset[4]
1397 local what = dataset[1]
1398 local value = what == true and tfmdata.shared.features[kind] or what
1399 local current = start
1400 while current do
1401 local currentchar = ischar(current)
1402 if currentchar then
1403 local alternatives = mapping[currentchar]
1404 if alternatives then
1405 local choice, comment = get_alternative_glyph(current,alternatives,value)
1406 if choice then
1407 if trace_alternatives then
1408 logprocess("%s: replacing %s by alternative %a to %s, %s",cref(dataset,sequence),gref(currentchar),choice,gref(choice),comment)
1409 end
1410 resetinjection(start)
1411 setchar(start,choice)
1412 else
1413 if trace_alternatives then
1414 logwarning("%s: no variant %a for %s, %s",cref(dataset,sequence),value,gref(currentchar),comment)
1415 end
1416 end
1417 end
1418 return head, start, true
1419 elseif currentchar == false then
1420
1421 break
1422 elseif current == stop then
1423 break
1424 else
1425 current = getnext(current)
1426 end
1427 end
1428 end
1429 return head, start, false
1430end
1431
1432
1433
1434function chainprocs.gsub_multiple(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1435 local mapping = currentlookup.mapping
1436 if mapping == nil then
1437 mapping = getmapping(dataset,sequence,currentlookup)
1438 end
1439 if mapping then
1440 local startchar = getchar(start)
1441 local replacement = mapping[startchar]
1442 if not replacement or replacement == "" then
1443 if trace_bugs then
1444 logwarning("%s: no multiple for %s",cref(dataset,sequence),gref(startchar))
1445 end
1446 else
1447 if trace_multiples then
1448 logprocess("%s: replacing %s by multiple characters %s",cref(dataset,sequence),gref(startchar),gref(replacement))
1449 end
1450 return multiple_glyphs(head,start,replacement,skiphash,dataset[1],stop)
1451 end
1452 end
1453 return head, start, false
1454end
1455
1456
1457
1458
1459
1460
1461
1462function chainprocs.gsub_ligature(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1463 local mapping = currentlookup.mapping
1464 if mapping == nil then
1465 mapping = getmapping(dataset,sequence,currentlookup)
1466 end
1467 if mapping then
1468 local startchar = getchar(start)
1469 local ligatures = mapping[startchar]
1470 if not ligatures then
1471 if trace_bugs then
1472 logwarning("%s: no ligatures starting with %s",cref(dataset,sequence,chainindex),gref(startchar))
1473 end
1474 else
1475 local hasmarks = marks[startchar]
1476 local current = getnext(start)
1477 local discfound = false
1478 local last = stop
1479 local nofreplacements = 1
1480 while current do
1481
1482 local id = getid(current)
1483 if id == disc_code then
1484 if not discfound then
1485 discfound = current
1486 end
1487 if current == stop then
1488 break
1489 else
1490 current = getnext(current)
1491 end
1492 else
1493 local schar = getchar(current)
1494 if skiphash and skiphash[schar] then
1495
1496
1497
1498 current = getnext(current)
1499
1500 else
1501 local lg = not tonumber(ligatures) and ligatures[schar]
1502 if lg then
1503 ligatures = lg
1504 last = current
1505 nofreplacements = nofreplacements + 1
1506 if marks[char] then
1507 hasmarks = true
1508 end
1509 if current == stop then
1510 break
1511 else
1512 current = getnext(current)
1513 end
1514 else
1515 break
1516 end
1517 end
1518 end
1519 end
1520 local ligature = tonumber(ligatures) or ligatures.ligature
1521 if ligature then
1522 if chainindex then
1523 stop = last
1524 end
1525 if trace_ligatures then
1526 if start == stop then
1527 logprocess("%s: replacing character %s by ligature %s case 3",cref(dataset,sequence,chainindex),gref(startchar),gref(ligature))
1528 else
1529 logprocess("%s: replacing character %s upto %s by ligature %s case 4",cref(dataset,sequence,chainindex),gref(startchar),gref(getchar(stop)),gref(ligature))
1530 end
1531 end
1532 head, start = toligature(head,start,stop,ligature,dataset,sequence,skiphash,discfound,hasmarks)
1533 return head, start, true, nofreplacements, discfound
1534 elseif trace_bugs then
1535 if start == stop then
1536 logwarning("%s: replacing character %s by ligature fails",cref(dataset,sequence,chainindex),gref(startchar))
1537 else
1538 logwarning("%s: replacing character %s upto %s by ligature fails",cref(dataset,sequence,chainindex),gref(startchar),gref(getchar(stop)))
1539 end
1540 end
1541 end
1542 end
1543 return head, start, false, 0, false
1544end
1545
1546function chainprocs.gpos_single(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1547
1548 if not hasglyphoption(start,no_right_kern_code) then
1549 local mapping = currentlookup.mapping
1550 if mapping == nil then
1551 mapping = getmapping(dataset,sequence,currentlookup)
1552 end
1553 if mapping then
1554 local startchar = getchar(start)
1555 local kerns = mapping[startchar]
1556 if kerns then
1557 local format = currentlookup.format
1558 if format == "single" then
1559 local dx, dy, w, h = setposition(0,start,factor,rlmode,kerns)
1560 if trace_kerns then
1561 logprocess("%s: shifting single %s by %s (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),format,dx,dy,w,h)
1562 end
1563 else
1564 local k = (format == "move" and setmove or setkern)(start,factor,rlmode,kerns,injection)
1565 if trace_kerns then
1566 logprocess("%s: shifting single %s by %s %p",cref(dataset,sequence),gref(startchar),format,k)
1567 end
1568 end
1569 return head, start, true
1570 end
1571 end
1572 end
1573 return head, start, false
1574end
1575
1576function chainprocs.gpos_pair(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1577
1578 if not hasglyphoption(start,no_right_kern_code) then
1579 local mapping = currentlookup.mapping
1580 if mapping == nil then
1581 mapping = getmapping(dataset,sequence,currentlookup)
1582 end
1583 if mapping then
1584 local snext = getnext(start)
1585 if snext then
1586 local startchar = getchar(start)
1587 local kerns = mapping[startchar]
1588 if kerns then
1589 local prev = start
1590 while snext do
1591 local nextchar = ischar(snext,currentfont)
1592 if not nextchar then
1593 break
1594 end
1595 if skiphash and skiphash[nextchar] then
1596 prev = snext
1597 snext = getnext(snext)
1598 else
1599 local krn = kerns[nextchar]
1600 if not krn then
1601 break
1602 end
1603 local format = currentlookup.format
1604 if format == "pair" then
1605 local a = krn[1]
1606 local b = krn[2]
1607 if a == true then
1608
1609 elseif a then
1610 local x, y, w, h = setposition(1,start,factor,rlmode,a,"injections")
1611 if trace_kerns then
1612 local startchar = getchar(start)
1613 logprocess("%s: shifting first of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h)
1614 end
1615 end
1616 if b == true then
1617
1618 start = snext
1619 elseif b then
1620 local x, y, w, h = setposition(2,snext,factor,rlmode,b,"injections")
1621 if trace_kerns then
1622 local startchar = getchar(start)
1623 logprocess("%s: shifting second of pair %s and %s by (%p,%p) and correction (%p,%p)",cref(dataset,sequence),gref(startchar),gref(nextchar),x,y,w,h)
1624 end
1625 start = snext
1626 elseif forcepairadvance then
1627 start = snext
1628 end
1629 return head, start, true
1630 elseif krn ~= 0 then
1631 local k = (format == "move" and setmove or setkern)(snext,factor,rlmode,krn)
1632 if trace_kerns then
1633 logprocess("%s: inserting %s %p between %s and %s",cref(dataset,sequence),format,k,gref(getchar(prev)),gref(nextchar))
1634 end
1635 return head, start, true
1636 else
1637 break
1638 end
1639 end
1640 end
1641 end
1642 end
1643 end
1644 end
1645 return head, start, false
1646end
1647
1648function chainprocs.gpos_mark2base(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1649 local mapping = currentlookup.mapping
1650 if mapping == nil then
1651 mapping = getmapping(dataset,sequence,currentlookup)
1652 end
1653 if mapping then
1654 local markchar = getchar(start)
1655 if marks[markchar] then
1656 local markanchors = mapping[markchar]
1657 if markanchors then
1658 local base = getprev(start)
1659 if base then
1660 local basechar = ischar(base,currentfont)
1661 if basechar then
1662 if marks[basechar] then
1663 while base do
1664 base = getprev(base)
1665 if base then
1666 local basechar = ischar(base,currentfont)
1667 if basechar then
1668 if not marks[basechar] then
1669 break
1670 end
1671 else
1672 if trace_bugs then
1673 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),1)
1674 end
1675 return head, start, false
1676 end
1677 else
1678 if trace_bugs then
1679 logwarning("%s: no base for mark %s, case %i",pref(dataset,sequence),gref(markchar),2)
1680 end
1681 return head, start, false
1682 end
1683 end
1684 end
1685 local ba = markanchors[1][basechar]
1686 if ba then
1687 local ma = markanchors[2]
1688 if ma then
1689 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks)
1690 if trace_marks then
1691 logprocess("%s, bound %s, anchoring mark %s to basechar %s => (%p,%p)",
1692 cref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy)
1693 end
1694 return head, start, true
1695 end
1696 end
1697 elseif trace_bugs then
1698 logwarning("%s: prev node is no char, case %i",cref(dataset,sequence),1)
1699 end
1700 elseif trace_bugs then
1701 logwarning("%s: prev node is no char, case %i",cref(dataset,sequence),2)
1702 end
1703 elseif trace_bugs then
1704 logwarning("%s: mark %s has no anchors",cref(dataset,sequence),gref(markchar))
1705 end
1706 elseif trace_bugs then
1707 logwarning("%s: mark %s is no mark",cref(dataset,sequence),gref(markchar))
1708 end
1709 end
1710 return head, start, false
1711end
1712
1713function chainprocs.gpos_mark2ligature(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1714 local mapping = currentlookup.mapping
1715 if mapping == nil then
1716 mapping = getmapping(dataset,sequence,currentlookup)
1717 end
1718 if mapping then
1719 local markchar = getchar(start)
1720 if marks[markchar] then
1721 local markanchors = mapping[markchar]
1722 if markanchors then
1723 local base = getprev(start)
1724 if base then
1725 local basechar = ischar(base,currentfont)
1726 if basechar then
1727 if marks[basechar] then
1728 while base do
1729 base = getprev(base)
1730 if base then
1731 local basechar = ischar(base,currentfont)
1732 if basechar then
1733 if not marks[basechar] then
1734 break
1735 end
1736 else
1737 if trace_bugs then
1738 logwarning("%s: no base for mark %s, case %i",cref(dataset,sequence),markchar,1)
1739 end
1740 return head, start, false
1741 end
1742 else
1743 if trace_bugs then
1744 logwarning("%s: no base for mark %s, case %i",cref(dataset,sequence),markchar,2)
1745 end
1746 return head, start, false
1747 end
1748 end
1749 end
1750 local ba = markanchors[1][basechar]
1751 if ba then
1752 local ma = markanchors[2]
1753 if ma then
1754 local index = getligaindex(start)
1755 ba = ba[index]
1756 if ba then
1757 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],false,checkmarks)
1758 if trace_marks then
1759 logprocess("%s, bound %s, anchoring mark %s to baselig %s at index %s => (%p,%p)",
1760 cref(dataset,sequence),a or bound,gref(markchar),gref(basechar),index,dx,dy)
1761 end
1762 return head, start, true
1763 end
1764 end
1765 end
1766 elseif trace_bugs then
1767 logwarning("%s, prev node is no char, case %i",cref(dataset,sequence),1)
1768 end
1769 elseif trace_bugs then
1770 logwarning("%s, prev node is no char, case %i",cref(dataset,sequence),2)
1771 end
1772 elseif trace_bugs then
1773 logwarning("%s, mark %s has no anchors",cref(dataset,sequence),gref(markchar))
1774 end
1775 elseif trace_bugs then
1776 logwarning("%s, mark %s is no mark",cref(dataset,sequence),gref(markchar))
1777 end
1778 end
1779 return head, start, false
1780end
1781
1782function chainprocs.gpos_mark2mark(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1783 local mapping = currentlookup.mapping
1784 if mapping == nil then
1785 mapping = getmapping(dataset,sequence,currentlookup)
1786 end
1787 if mapping then
1788 local markchar = getchar(start)
1789 if marks[markchar] then
1790 local markanchors = mapping[markchar]
1791 if markanchors then
1792 local base = getprev(start)
1793 local slc = getligaindex(start)
1794 if slc then
1795 while base do
1796 local blc = getligaindex(base)
1797 if blc and blc ~= slc then
1798 base = getprev(base)
1799 else
1800 break
1801 end
1802 end
1803 end
1804 if base then
1805 local basechar = ischar(base,currentfont)
1806 if basechar then
1807 local ba = markanchors[1][basechar]
1808 if ba then
1809 local ma = markanchors[2]
1810 if ma then
1811 local dx, dy, bound = setmark(start,base,factor,rlmode,ba,ma,characters[basechar],true,checkmarks)
1812 if trace_marks then
1813 logprocess("%s, bound %s, anchoring mark %s to basemark %s => (%p,%p)",
1814 cref(dataset,sequence),bound,gref(markchar),gref(basechar),dx,dy)
1815 end
1816 return head, start, true
1817 end
1818 end
1819 elseif trace_bugs then
1820 logwarning("%s: prev node is no mark, case %i",cref(dataset,sequence),1)
1821 end
1822 elseif trace_bugs then
1823 logwarning("%s: prev node is no mark, case %i",cref(dataset,sequence),2)
1824 end
1825 elseif trace_bugs then
1826 logwarning("%s: mark %s has no anchors",cref(dataset,sequence),gref(markchar))
1827 end
1828 elseif trace_bugs then
1829 logwarning("%s: mark %s is no mark",cref(dataset,sequence),gref(markchar))
1830 end
1831 end
1832 return head, start, false
1833end
1834
1835function chainprocs.gpos_cursive(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash,chainindex)
1836 local mapping = currentlookup.mapping
1837 if mapping == nil then
1838 mapping = getmapping(dataset,sequence,currentlookup)
1839 end
1840 if mapping then
1841 local startchar = getchar(start)
1842 local exitanchors = mapping[startchar]
1843 if exitanchors then
1844 if marks[startchar] then
1845 if trace_cursive then
1846 logprocess("%s: ignoring cursive for mark %s",pref(dataset,sequence),gref(startchar))
1847 end
1848 else
1849 local nxt = getnext(start)
1850 while nxt do
1851 local nextchar = ischar(nxt,currentfont)
1852 if not nextchar then
1853 break
1854 elseif marks[nextchar] then
1855
1856 nxt = getnext(nxt)
1857 else
1858 local exit = exitanchors[3]
1859 if exit then
1860 local entry = exitanchors[1][nextchar]
1861 if entry then
1862 entry = entry[2]
1863 if entry then
1864 local r2lflag = sequence.flags[4]
1865 local dx, dy, bound = setcursive(start,nxt,factor,rlmode,exit,entry,characters[startchar],characters[nextchar],r2lflag)
1866 if trace_cursive then
1867 logprocess("%s: moving %s to %s cursive (%p,%p) using bound %s in %s mode",pref(dataset,sequence),gref(startchar),gref(nextchar),dx,dy,bound,mref(rlmode))
1868 end
1869 return head, start, true
1870 end
1871 end
1872 elseif trace_bugs then
1873 onetimemessage(currentfont,startchar,"no entry anchors")
1874 end
1875 break
1876 end
1877 end
1878 end
1879 elseif trace_cursive and trace_details then
1880 logprocess("%s, cursive %s is already done",pref(dataset,sequence),gref(getchar(start)),alreadydone)
1881 end
1882 end
1883 return head, start, false
1884end
1885
1886
1887
1888
1889
1890local function show_skip(dataset,sequence,char,ck,class)
1891 logwarning("%s: skipping char %s, class %a, rule %a, lookuptype %a",cref(dataset,sequence),gref(char),class,ck[1],ck[8] or ck[2])
1892end
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921local userkern = nuts.pool and nuts.pool.newkern
1922
1923do if not userkern then
1924
1925 local thekern = nuts.new("kern",1)
1926 local setkern = nuts.setkern
1927
1928 userkern = function(k)
1929 local n = copynode(thekern)
1930 setkern(n,k)
1931 return n
1932 end
1933
1934end end
1935
1936local function checked(head)
1937 local current = head
1938 while current do
1939 if getid(current) == glue_code then
1940 local kern = userkern(getwidth(current))
1941 if head == current then
1942 local next = getnext(current)
1943 if next then
1944 setlink(kern,next)
1945 end
1946 flushnode(current)
1947 head = kern
1948 current = next
1949 else
1950 local prev, next = getboth(current)
1951 setlink(prev,kern,next)
1952 flushnode(current)
1953 current = next
1954 end
1955 else
1956 current = getnext(current)
1957 end
1958 end
1959 return head
1960end
1961
1962local function setdiscchecked(d,pre,post,replace)
1963 if pre then pre = checked(pre) end
1964 if post then post = checked(post) end
1965 if replace then replace = checked(replace) end
1966 setdisc(d,pre,post,replace)
1967end
1968
1969local noflags = { false, false, false, false }
1970
1971local function chainrun(head,start,last,dataset,sequence,rlmode,skiphash,ck,where)
1972
1973 local size = ck[5] - ck[4] + 1
1974 local chainlookups = ck[6]
1975 local done = false
1976
1977 if chainlookups then
1978
1979
1980
1981 if size == 1 then
1982
1983
1984
1985
1986 local chainlookup = chainlookups[1]
1987 if chainlookup then
1988 for j=1,#chainlookup do
1989 local chainstep = chainlookup[j]
1990 if chainstep then
1991 local chainkind = chainstep.type
1992 local chainproc = chainprocs[chainkind]
1993 if chainproc then
1994 local ok
1995
1996
1997 head, start, ok = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,skiphash,1)
1998 if ok then
1999 done = true
2000 end
2001 else
2002 logprocess("%s: %s is not yet supported (1)",cref(dataset,sequence),chainkind)
2003 end
2004 else
2005 logprocess("%s: has an issue (1)",cref(dataset,sequence))
2006 end
2007 end
2008 else
2009
2010 end
2011
2012 else
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028 local i = 1
2029 local laststart = start
2030 local nofchainlookups = #chainlookups
2031 while start do
2032 if skiphash then
2033 while start do
2034 local char = ischar(start,currentfont)
2035 if char then
2036 if skiphash and skiphash[char] then
2037 start = getnext(start)
2038 else
2039 break
2040 end
2041 else
2042 break
2043 end
2044 end
2045 end
2046 local chainlookup = chainlookups[i]
2047 if chainlookup then
2048 for j=1,#chainlookup do
2049 local chainstep = chainlookup[j]
2050 if chainstep then
2051 local chainkind = chainstep.type
2052 local chainproc = chainprocs[chainkind]
2053 if chainproc then
2054 local ok, n
2055 head, start, ok, n = chainproc(head,start,last,dataset,sequence,chainstep,rlmode,skiphash,i)
2056
2057 if ok then
2058 done = true
2059 if n and n > 1 and i + n > nofchainlookups then
2060
2061 i = size
2062 break
2063 end
2064 end
2065 else
2066
2067 logprocess("%s: %s is not yet supported (2)",cref(dataset,sequence),chainkind)
2068 end
2069 else
2070
2071 logprocess("%s: has an issue (2)",cref(dataset,sequence))
2072 end
2073 end
2074 else
2075
2076
2077 end
2078 i = i + 1
2079 if i > size or not start then
2080 break
2081 elseif start then
2082 laststart = start
2083 start = getnext(start)
2084 end
2085 end
2086 if not start then
2087 start = laststart
2088 end
2089
2090 end
2091 else
2092
2093 local replacements = ck[7]
2094 if replacements then
2095 head, start, done = reversesub(head,start,last,dataset,sequence,replacements,rlmode,skiphash)
2096 else
2097 done = true
2098 if trace_contexts then
2099 logprocess("%s: skipping match @ %i",cref(dataset,sequence),where)
2100 end
2101 end
2102 end
2103 return head, start, done
2104end
2105
2106local function chaindisk(head,start,dataset,sequence,rlmode,skiphash,ck)
2107
2108 if not start then
2109 return head, start, false
2110 end
2111
2112 local startishead = start == head
2113 local seq = ck[3]
2114 local f = ck[4]
2115 local l = ck[5]
2116 local s = #seq
2117 local done = false
2118 local sweepnode = sweepnode
2119 local sweeptype = sweeptype
2120 local sweepoverflow = false
2121 local checkdisc = getprev(head)
2122 local keepdisc = not sweepnode
2123 local lookaheaddisc = nil
2124 local backtrackdisc = nil
2125 local current = start
2126 local last = start
2127 local prev = getprev(start)
2128 local hasglue = false
2129 local useddisc = nil
2130 local usedstart = start
2131
2132
2133
2134
2135 local i = f
2136 while i <= l do
2137 local id = getid(current)
2138 if id == glyph_code then
2139 i = i + 1
2140 last = current
2141 current = getnext(current)
2142 elseif id == glue_code then
2143 i = i + 1
2144 last = current
2145 current = getnext(current)
2146 hasglue = true
2147 elseif id == disc_code then
2148 if keepdisc then
2149 keepdisc = false
2150 lookaheaddisc = current
2151 local replace = getreplace(current)
2152 if not replace then
2153 sweepoverflow = true
2154 sweepnode = current
2155 current = getnext(current)
2156 else
2157
2158 while replace and i <= l do
2159 if getid(replace) == glyph_code then
2160 i = i + 1
2161 end
2162 replace = getnext(replace)
2163 end
2164 current = getnext(replace)
2165 end
2166 last = current
2167 else
2168 head, current = flattendisk(head,current)
2169 end
2170 else
2171 last = current
2172 current = getnext(current)
2173 end
2174 if current then
2175
2176 elseif sweepoverflow then
2177
2178 break
2179 elseif sweeptype == "post" or sweeptype == "replace" then
2180 current = getnext(sweepnode)
2181 if current then
2182 sweeptype = nil
2183 sweepoverflow = true
2184 else
2185 break
2186 end
2187 else
2188 break
2189 end
2190 end
2191
2192 if sweepoverflow then
2193 local prev = current and getprev(current)
2194 if not current or prev ~= sweepnode then
2195 local head = getnext(sweepnode)
2196 local tail = nil
2197 if prev then
2198 tail = prev
2199 setprev(current,sweepnode)
2200 else
2201 tail = findnodetail(head)
2202 end
2203 setnext(sweepnode,current)
2204 setprev(head)
2205 setnext(tail)
2206 appenddisc(sweepnode,head)
2207 end
2208 end
2209
2210 if l < s then
2211 local i = l
2212 local t = sweeptype == "post" or sweeptype == "replace"
2213 while current and i < s do
2214 local id = getid(current)
2215 if id == glyph_code then
2216 i = i + 1
2217 current = getnext(current)
2218 elseif id == glue_code then
2219 i = i + 1
2220 current = getnext(current)
2221 hasglue = true
2222 elseif id == disc_code then
2223 if keepdisc then
2224 keepdisc = false
2225 if notmatchpre[current] ~= notmatchreplace[current] then
2226 lookaheaddisc = current
2227 end
2228
2229 local replace = getreplace(current)
2230 while replace and i < s do
2231 if getid(replace) == glyph_code then
2232 i = i + 1
2233 end
2234 replace = getnext(replace)
2235 end
2236 current = getnext(current)
2237 elseif notmatchpre[current] ~= notmatchreplace[current] then
2238 head, current = flattendisk(head,current)
2239 else
2240 current = getnext(current)
2241 end
2242 else
2243 current = getnext(current)
2244 end
2245 if not current and t then
2246 current = getnext(sweepnode)
2247 if current then
2248 sweeptype = nil
2249 end
2250 end
2251 end
2252 end
2253
2254 if f > 1 then
2255 local current = prev
2256 local i = f
2257 local t = sweeptype == "pre" or sweeptype == "replace"
2258 if not current and t and current == checkdisc then
2259 current = getprev(sweepnode)
2260 end
2261 while current and i > 1 do
2262 local id = getid(current)
2263 if id == glyph_code then
2264 i = i - 1
2265 elseif id == glue_code then
2266 i = i - 1
2267 hasglue = true
2268 elseif id == disc_code then
2269 if keepdisc then
2270 keepdisc = false
2271 if notmatchpost[current] ~= notmatchreplace[current] then
2272 backtrackdisc = current
2273 end
2274
2275 local replace = getreplace(current)
2276 while replace and i > 1 do
2277 if getid(replace) == glyph_code then
2278 i = i - 1
2279 end
2280 replace = getnext(replace)
2281 end
2282 elseif notmatchpost[current] ~= notmatchreplace[current] then
2283 head, current = flattendisk(head,current)
2284 end
2285 end
2286 current = getprev(current)
2287 if t and current == checkdisc then
2288 current = getprev(sweepnode)
2289 end
2290 end
2291 end
2292
2293 local done = false
2294
2295 if lookaheaddisc then
2296 local cf = start
2297 local cl = getprev(lookaheaddisc)
2298 local cprev = getprev(start)
2299 local insertedmarks = 0
2300 while cprev do
2301 local char = ischar(cf,currentfont)
2302 if char and marks[char] then
2303 insertedmarks = insertedmarks + 1
2304 cf = cprev
2305 startishead = cf == head
2306 cprev = getprev(cprev)
2307 else
2308 break
2309 end
2310 end
2311 setlink(cprev,lookaheaddisc)
2312 setprev(cf)
2313 setnext(cl)
2314 if startishead then
2315 head = lookaheaddisc
2316 end
2317 local pre, post, replace = getdisc(lookaheaddisc)
2318 local new = copynodelist(cf)
2319 local cnew = new
2320 if pre then
2321 setlink(findnodetail(cf),pre)
2322 end
2323 if replace then
2324 local tail = findnodetail(new)
2325 setlink(tail,replace)
2326 end
2327 for i=1,insertedmarks do
2328 cnew = getnext(cnew)
2329 end
2330 cl = start
2331 local clast = cnew
2332 for i=f,l do
2333 cl = getnext(cl)
2334 clast = getnext(clast)
2335 end
2336 if not notmatchpre[lookaheaddisc] then
2337 local ok = false
2338 cf, start, ok = chainrun(cf,start,cl,dataset,sequence,rlmode,skiphash,ck,1)
2339 if ok then
2340 done = true
2341 end
2342 end
2343 if not notmatchreplace[lookaheaddisc] then
2344 local ok = false
2345 new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,skiphash,ck,2)
2346 if ok then
2347 done = true
2348 end
2349 end
2350 if hasglue then
2351 setdiscchecked(lookaheaddisc,cf,post,new)
2352 else
2353 setdisc(lookaheaddisc,cf,post,new)
2354 end
2355 start = getprev(lookaheaddisc)
2356 useddisc = lookaheaddisc
2357 sweephead[cf] = getnext(clast) or false
2358 sweephead[new] = getnext(cl) or false
2359 elseif backtrackdisc then
2360 local cf = getnext(backtrackdisc)
2361 local cl = start
2362 local cnext = getnext(start)
2363 local insertedmarks = 0
2364 while cnext do
2365 local char = ischar(cnext,currentfont)
2366 if char and marks[char] then
2367 insertedmarks = insertedmarks + 1
2368 cl = cnext
2369 cnext = getnext(cnext)
2370 else
2371 break
2372 end
2373 end
2374 setlink(backtrackdisc,cnext)
2375 setprev(cf)
2376 setnext(cl)
2377 local pre, post, replace, pretail, posttail, replacetail = getdisc(backtrackdisc,true)
2378 local new = copynodelist(cf)
2379 local cnew = findnodetail(new)
2380 for i=1,insertedmarks do
2381 cnew = getprev(cnew)
2382 end
2383 local clast = cnew
2384 for i=f,l do
2385 clast = getnext(clast)
2386 end
2387 if not notmatchpost[backtrackdisc] then
2388 local ok = false
2389 cf, start, ok = chainrun(cf,start,last,dataset,sequence,rlmode,skiphash,ck,3)
2390 if ok then
2391 done = true
2392 end
2393 end
2394 if not notmatchreplace[backtrackdisc] then
2395 local ok = false
2396 new, cnew, ok = chainrun(new,cnew,clast,dataset,sequence,rlmode,skiphash,ck,4)
2397 if ok then
2398 done = true
2399 end
2400 end
2401 if post then
2402 setlink(posttail,cf)
2403 else
2404 post = cf
2405 end
2406 if replace then
2407 setlink(replacetail,new)
2408 else
2409 replace = new
2410 end
2411 if hasglue then
2412 setdiscchecked(backtrackdisc,pre,post,replace)
2413 else
2414 setdisc(backtrackdisc,pre,post,replace)
2415 end
2416 start = getprev(backtrackdisc)
2417 useddisc = backtrackdisc
2418 sweephead[post] = getnext(clast) or false
2419 sweephead[replace] = getnext(last) or false
2420 else
2421
2422 local ok = false
2423 head, start, ok = chainrun(head,start,last,dataset,sequence,rlmode,skiphash,ck,5)
2424 if ok then
2425 done = true
2426 end
2427
2428 end
2429 if useddisc and start ~= usedstart then
2430 start = getnext(start)
2431 end
2432 return head, start, done, useddisc
2433end
2434
2435local chaintrac do
2436
2437 local level = 0
2438 local last = { }
2439
2440 chaintrac = function(head,start,dataset,sequence,rlmode,skiphash,ck,match,discseen,sweepnode)
2441 if dataset then
2442 level = level + 1
2443 last[level] = start
2444 local rule = ck[1]
2445 local lookuptype = ck[8] or ck[2]
2446 local nofseq = #ck[3]
2447 local first = ck[4]
2448 local last = ck[5]
2449 local char = getchar(start)
2450 logwarning("+ %i : %s: rule %s %s at char %s for (%s,%s,%s) chars, lookuptype %a, %sdisc seen, %ssweeping",
2451 level,cref(dataset,sequence),rule,match and "matches" or "nomatch",
2452 gref(char),first-1,last-first+1,nofseq-last,lookuptype,
2453 discseen and "" or "no ", sweepnode and "" or "not ")
2454 else
2455
2456 local what = start and "done" or "continue"
2457 local where = head == last[level] and "same" or "different"
2458 local char = getchar(head)
2459 if char then
2460 logwarning("- %i : %s at char %s, %s node",level,what,gref(char),where)
2461 else
2462 logwarning("- %i : %s, %s node",level,what,where)
2463 end
2464 level = level - 1
2465 end
2466 end
2467
2468end
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480local function handle_contextchain(head,start,dataset,sequence,contexts,rlmode,skiphash)
2481 if not contexts then
2482 return head, start, false
2483 end
2484
2485 local sweepnode = sweepnode
2486 local sweeptype = sweeptype
2487 local postreplace
2488 local prereplace
2489 local checkdisc
2490 local discseen
2491 if sweeptype then
2492 if sweeptype == "replace" then
2493 postreplace = true
2494 prereplace = true
2495 else
2496 postreplace = sweeptype == "post"
2497 prereplace = sweeptype == "pre"
2498 end
2499 checkdisc = getprev(head)
2500 end
2501 local currentfont = currentfont
2502
2503 local skipped
2504
2505 local startprev,
2506 startnext = getboth(start)
2507 local done
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518 local nofcontexts = contexts.n
2519
2520 local startchar = nofcontext == 1 or ischar(start,currentfont)
2521
2522 for k=1,nofcontexts do
2523
2524 local ck = contexts[k]
2525 local seq = ck[3]
2526 local f = ck[4]
2527 local last = start
2528 if not startchar or not seq[f][startchar] then
2529
2530 goto next
2531 end
2532 local s = seq.n
2533 if s == 1 then
2534
2535 else
2536 local l = ck[5]
2537 local current = start
2538
2539
2540
2541
2542 if l > f then
2543
2544 local discfound
2545 local n = f + 1
2546 last = startnext
2547 while n <= l do
2548 if postreplace and not last then
2549 last = getnext(sweepnode)
2550 sweeptype = nil
2551 end
2552 if last then
2553 local char, id = ischar(last,currentfont)
2554 if char then
2555 if seq[n][char] then
2556 if n < l then
2557 last = getnext(last)
2558 end
2559 n = n + 1
2560 elseif skiphash and skiphash[char] then
2561 skipped = true
2562 if trace_skips then
2563 show_skip(dataset,sequence,char,ck,classes[char])
2564 end
2565 last = getnext(last)
2566 elseif discfound then
2567 notmatchreplace[discfound] = true
2568 if notmatchpre[discfound] then
2569 goto next
2570 else
2571 break
2572 end
2573 else
2574 goto next
2575 end
2576 elseif char == false then
2577 if discfound then
2578 notmatchreplace[discfound] = true
2579 if notmatchpre[discfound] then
2580 goto next
2581 else
2582 break
2583 end
2584 else
2585 goto next
2586 end
2587 elseif id == disc_code then
2588
2589 discseen = true
2590 discfound = last
2591 notmatchpre[last] = nil
2592 notmatchpost[last] = true
2593 notmatchreplace[last] = nil
2594 local pre, post, replace = getdisc(last)
2595 if pre then
2596 local n = n
2597 while pre do
2598 if seq[n][getchar(pre)] then
2599 n = n + 1
2600 if n > l then
2601 break
2602 end
2603 pre = getnext(pre)
2604 else
2605 notmatchpre[last] = true
2606 break
2607 end
2608 end
2609 if n <= l then
2610 notmatchpre[last] = true
2611 end
2612 else
2613 notmatchpre[last] = true
2614 end
2615 if replace then
2616
2617 while replace do
2618 if seq[n][getchar(replace)] then
2619 n = n + 1
2620 if n > l then
2621 break
2622 end
2623 replace = getnext(replace)
2624 else
2625 notmatchreplace[last] = true
2626 if notmatchpre[last] then
2627 goto next
2628 else
2629 break
2630 end
2631 end
2632 end
2633
2634 if notmatchpre[last] then
2635 goto next
2636 end
2637 end
2638
2639 last = getnext(last)
2640 else
2641 goto next
2642 end
2643 else
2644 goto next
2645 end
2646 end
2647 end
2648
2649
2650
2651 if f > 1 then
2652
2653 local prev = startprev
2654 if prereplace and prev == checkdisc then
2655 prev = getprev(sweepnode)
2656 end
2657 if prev then
2658 local discfound
2659 local n = f - 1
2660 while n >= 1 do
2661 if prev then
2662 local char, id = ischar(prev,currentfont)
2663 if char then
2664 if seq[n][char] then
2665 if n > 1 then
2666 prev = getprev(prev)
2667 end
2668 n = n - 1
2669 elseif skiphash and skiphash[char] then
2670 skipped = true
2671 if trace_skips then
2672 show_skip(dataset,sequence,char,ck,classes[char])
2673 end
2674 prev = getprev(prev)
2675 elseif discfound then
2676 notmatchreplace[discfound] = true
2677 if notmatchpost[discfound] then
2678 goto next
2679 else
2680 break
2681 end
2682 else
2683 goto next
2684 end
2685 elseif char == false then
2686 if discfound then
2687 notmatchreplace[discfound] = true
2688 if notmatchpost[discfound] then
2689 goto next
2690 end
2691 else
2692 goto next
2693 end
2694 break
2695 elseif id == disc_code then
2696
2697
2698 discseen = true
2699 discfound = prev
2700 notmatchpre[prev] = true
2701 notmatchpost[prev] = nil
2702 notmatchreplace[prev] = nil
2703 local pre, post, replace, pretail, posttail, replacetail = getdisc(prev,true)
2704
2705 if pre ~= start and post ~= start and replace ~= start then
2706 if post then
2707 local n = n
2708 while posttail do
2709 if seq[n][getchar(posttail)] then
2710 n = n - 1
2711 if posttail == post or n < 1 then
2712 break
2713 else
2714 posttail = getprev(posttail)
2715 end
2716 else
2717 notmatchpost[prev] = true
2718 break
2719 end
2720 end
2721 if n >= 1 then
2722 notmatchpost[prev] = true
2723 end
2724 else
2725 notmatchpost[prev] = true
2726 end
2727 if replace then
2728
2729 while replacetail do
2730 if seq[n][getchar(replacetail)] then
2731 n = n - 1
2732 if replacetail == replace or n < 1 then
2733 break
2734 else
2735 replacetail = getprev(replacetail)
2736 end
2737 else
2738 notmatchreplace[prev] = true
2739 if notmatchpost[prev] then
2740 goto next
2741 else
2742 break
2743 end
2744 end
2745 end
2746 else
2747
2748 end
2749 end
2750 prev = getprev(prev)
2751
2752
2753
2754
2755 elseif id == glue_code then
2756 local sn = seq[n]
2757 if (sn[32] and spaces[prev]) or sn[0xFFFC] then
2758 n = n - 1
2759 prev = getprev(prev)
2760 else
2761 goto next
2762 end
2763 elseif seq[n][0xFFFC] then
2764 n = n - 1
2765 prev = getprev(prev)
2766 else
2767 goto next
2768 end
2769 else
2770 goto next
2771 end
2772 end
2773 else
2774 goto next
2775 end
2776
2777
2778
2779 end
2780
2781
2782
2783 if s > l then
2784 local current = last and getnext(last)
2785 if not current and postreplace then
2786 current = getnext(sweepnode)
2787 end
2788 if current then
2789 local discfound
2790 local n = l + 1
2791 while n <= s do
2792 if current then
2793 local char, id = ischar(current,currentfont)
2794 if char then
2795 if seq[n][char] then
2796 if n < s then
2797 current = getnext(current)
2798 end
2799 n = n + 1
2800 elseif skiphash and skiphash[char] then
2801 skipped = true
2802 if trace_skips then
2803 show_skip(dataset,sequence,char,ck,classes[char])
2804 end
2805 current = getnext(current)
2806 elseif discfound then
2807 notmatchreplace[discfound] = true
2808 if notmatchpre[discfound] then
2809 goto next
2810 else
2811 break
2812 end
2813 else
2814 goto next
2815 end
2816 elseif char == false then
2817 if discfound then
2818 notmatchreplace[discfound] = true
2819 if notmatchpre[discfound] then
2820 goto next
2821 else
2822 break
2823 end
2824 else
2825 goto next
2826 end
2827 elseif id == disc_code then
2828
2829 discseen = true
2830 discfound = current
2831 notmatchpre[current] = nil
2832 notmatchpost[current] = true
2833 notmatchreplace[current] = nil
2834 local pre, post, replace = getdisc(current)
2835 if pre then
2836 local n = n
2837 while pre do
2838 if seq[n][getchar(pre)] then
2839 n = n + 1
2840 if n > s then
2841 break
2842 else
2843 pre = getnext(pre)
2844 end
2845 else
2846 notmatchpre[current] = true
2847 break
2848 end
2849 end
2850 if n <= s then
2851 notmatchpre[current] = true
2852 end
2853 else
2854 notmatchpre[current] = true
2855 end
2856 if replace then
2857
2858 while replace do
2859 if seq[n][getchar(replace)] then
2860 n = n + 1
2861 if n > s then
2862 break
2863 else
2864 replace = getnext(replace)
2865 end
2866 else
2867 notmatchreplace[current] = true
2868 if notmatchpre[current] then
2869 goto next
2870 else
2871 break
2872 end
2873 end
2874 end
2875 else
2876
2877 end
2878 current = getnext(current)
2879 elseif id == glue_code then
2880 local sn = seq[n]
2881 if (sn[32] and spaces[current]) or sn[0xFFFC] then
2882 n = n + 1
2883 current = getnext(current)
2884 else
2885 goto next
2886 end
2887 elseif seq[n][0xFFFC] then
2888 n = n + 1
2889 current = getnext(current)
2890 else
2891 goto next
2892 end
2893 else
2894 goto next
2895 end
2896 end
2897 else
2898 goto next
2899 end
2900 end
2901 end
2902 if trace_contexts then
2903 chaintrac(head,start,dataset,sequence,rlmode,skipped and skiphash,ck,true,discseen,sweepnode)
2904 end
2905 if discseen or sweepnode then
2906
2907
2908 head, start, done = chaindisk(head,start,dataset,sequence,rlmode,skipped and skiphash,ck)
2909 else
2910 head, start, done = chainrun(head,start,last,dataset,sequence,rlmode,skipped and skiphash,ck,6)
2911 end
2912 if trace_contexts then
2913 chaintrac(start,done)
2914 end
2915 if done then
2916 break
2917
2918
2919 end
2920 ::next::
2921 end
2922 if discseen then
2923 notmatchpre = { }
2924 notmatchpost = { }
2925 notmatchreplace = { }
2926
2927
2928
2929 end
2930 return head, start, done
2931end
2932
2933handlers.gsub_context = handle_contextchain
2934handlers.gsub_contextchain = handle_contextchain
2935handlers.gsub_reversecontextchain = handle_contextchain
2936handlers.gpos_contextchain = handle_contextchain
2937handlers.gpos_context = handle_contextchain
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956local function chained_contextchain(head,start,stop,dataset,sequence,currentlookup,rlmode,skiphash)
2957 local steps = currentlookup.steps
2958 local nofsteps = currentlookup.nofsteps
2959 local char = getchar(start)
2960 if nofsteps == 1 then
2961 local s = steps[1]
2962 local l = s.coverage[char]
2963 if l then
2964 return handle_contextchain(head,start,dataset,sequence,l,rlmode,skiphash)
2965 end
2966 else
2967 for i=1,nofsteps do
2968 local s = steps[i]
2969 local l = s.coverage[char]
2970 if l then
2971 local h, s, d = handle_contextchain(head,start,dataset,sequence,l,rlmode,skiphash)
2972 if d then
2973 return h, s, d
2974 end
2975 end
2976 end
2977 end
2978 return head, start, false
2979end
2980
2981chainprocs.gsub_context = chained_contextchain
2982chainprocs.gsub_contextchain = chained_contextchain
2983chainprocs.gsub_reversecontextchain = chained_contextchain
2984chainprocs.gpos_contextchain = chained_contextchain
2985chainprocs.gpos_context = chained_contextchain
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007local missing = setmetatableindex("table")
3008local logwarning = report_process
3009local resolved = { }
3010
3011local function logprocess(...)
3012 if trace_steps then
3013 registermessage(...)
3014 if trace_steps == "silent" then
3015 return
3016 end
3017 end
3018 report_process(...)
3019end
3020
3021
3022
3023local sequencelists = setmetatableindex(function(t,font)
3024 local sequences = fontdata[font].resources.sequences
3025 if not sequences or not next(sequences) then
3026 sequences = false
3027 end
3028 t[font] = sequences
3029 return sequences
3030end)
3031
3032
3033
3034do
3035
3036 local autofeatures = fonts.analyzers.features
3037 local featuretypes = otf.tables.featuretypes
3038 local defaultscript = otf.features.checkeddefaultscript
3039 local defaultlanguage = otf.features.checkeddefaultlanguage
3040
3041 local wildcard = "*"
3042 local default = "dflt"
3043
3044 local function initialize(sequence,script,language,enabled,autoscript,autolanguage)
3045 local features = sequence.features
3046 if features then
3047 local order = sequence.order
3048 if order then
3049 local featuretype = featuretypes[sequence.type or "unknown"]
3050 for i=1,#order do
3051 local kind = order[i]
3052 local valid = enabled[kind]
3053 if valid then
3054 local scripts = features[kind]
3055 local languages = scripts and (
3056 scripts[script] or
3057 scripts[wildcard] or
3058 (autoscript and defaultscript(featuretype,autoscript,scripts))
3059 )
3060 local enabled = languages and (
3061 languages[language] or
3062 languages[wildcard] or
3063 (autolanguage and defaultlanguage(featuretype,autolanguage,languages))
3064 )
3065 if enabled then
3066 return { valid, autofeatures[kind] or false, sequence, kind }
3067 end
3068 end
3069 end
3070 else
3071
3072 end
3073 end
3074 return false
3075 end
3076
3077 function otf.dataset(tfmdata,font)
3078 local shared = tfmdata.shared
3079 local properties = tfmdata.properties
3080 local language = properties.language or "dflt"
3081 local script = properties.script or "dflt"
3082 local enabled = shared.features
3083 local autoscript = enabled and enabled.autoscript
3084 local autolanguage = enabled and enabled.autolanguage
3085 local res = resolved[font]
3086 if not res then
3087 res = { }
3088 resolved[font] = res
3089 end
3090 local rs = res[script]
3091 if not rs then
3092 rs = { }
3093 res[script] = rs
3094 end
3095 local rl = rs[language]
3096 if not rl then
3097 rl = {
3098
3099 }
3100 rs[language] = rl
3101 local sequences = tfmdata.resources.sequences
3102 if sequences then
3103 for s=1,#sequences do
3104 local v = enabled and initialize(sequences[s],script,language,enabled,autoscript,autolanguage)
3105 if v then
3106 rl[#rl+1] = v
3107 end
3108 end
3109 end
3110 end
3111 return rl
3112 end
3113
3114end
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124local function report_disc(what,n)
3125 report_run("%s: %s > %s",what,n,languages.serializediscretionary(n))
3126end
3127
3128local function kernrun(disc,k_run,font,attr,...)
3129
3130
3131
3132 if trace_kernruns then
3133 report_disc("kern",disc)
3134 end
3135
3136 local prev, next = getboth(disc)
3137
3138 local nextstart = next
3139 local done = false
3140
3141 local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true)
3142
3143 local prevmarks = prev
3144
3145
3146
3147
3148 while prevmarks do
3149 local char = ischar(prevmarks,font)
3150 if char and marks[char] then
3151 prevmarks = getprev(prevmarks)
3152 else
3153 break
3154 end
3155 end
3156
3157 if prev and not ischar(prev,font) then
3158 prev = false
3159 end
3160 if next and not ischar(next,font) then
3161 next = false
3162 end
3163
3164
3165
3166 if pre then
3167 if k_run(pre,"injections",nil,font,attr,...) then
3168 done = true
3169 end
3170 if prev then
3171 setlink(prev,pre)
3172 if k_run(prevmarks,"preinjections",pre,font,attr,...) then
3173 done = true
3174 end
3175 setprev(pre)
3176 setlink(prev,disc)
3177 end
3178 end
3179
3180 if post then
3181 if k_run(post,"injections",nil,font,attr,...) then
3182 done = true
3183 end
3184 if next then
3185 setlink(posttail,next)
3186 if k_run(posttail,"postinjections",next,font,attr,...) then
3187 done = true
3188 end
3189 setnext(posttail)
3190 setlink(disc,next)
3191 end
3192 end
3193
3194 if replace then
3195 if k_run(replace,"injections",nil,font,attr,...) then
3196 done = true
3197 end
3198 if prev then
3199 setlink(prev,replace)
3200 if k_run(prevmarks,"replaceinjections",replace,font,attr,...) then
3201 done = true
3202 end
3203 setprev(replace)
3204 setlink(prev,disc)
3205 end
3206 if next then
3207 setlink(replacetail,next)
3208 if k_run(replacetail,"replaceinjections",next,font,attr,...) then
3209 done = true
3210 end
3211 setnext(replacetail)
3212 setlink(disc,next)
3213 end
3214 elseif prev and next then
3215 setlink(prev,next)
3216 if k_run(prevmarks,"emptyinjections",next,font,attr,...) then
3217 done = true
3218 end
3219 setlink(prev,disc,next)
3220 end
3221 if done and trace_testruns then
3222 report_disc("done",disc)
3223 end
3224
3225 return nextstart
3226end
3227
3228
3229
3230
3231local function comprun(disc,c_run,...)
3232 if trace_compruns then
3233 report_disc("comp",disc)
3234 end
3235
3236 local pre, post, replace = getdisc(disc)
3237 local renewed = false
3238
3239 if pre then
3240 sweepnode = disc
3241 sweeptype = "pre"
3242 local new, done = c_run(pre,...)
3243 if done then
3244 pre = new
3245 renewed = true
3246 end
3247 end
3248
3249 if post then
3250 sweepnode = disc
3251 sweeptype = "post"
3252 local new, done = c_run(post,...)
3253 if done then
3254 post = new
3255 renewed = true
3256 end
3257 end
3258
3259 if replace then
3260 sweepnode = disc
3261 sweeptype = "replace"
3262 local new, done = c_run(replace,...)
3263 if done then
3264 replace = new
3265 renewed = true
3266 end
3267 end
3268
3269 sweepnode = nil
3270 sweeptype = nil
3271 if renewed then
3272 if trace_testruns then
3273 report_disc("done",disc)
3274 end
3275 setdisc(disc,pre,post,replace)
3276 end
3277
3278
3279 return getnext(disc)
3280end
3281
3282
3283
3284
3285local test_flatten_start = 2
3286
3287directives.register("otf.testrun.forceflatten", function(v)
3288 test_flatten_start = v and 1 or 2
3289end)
3290
3291local function testrun(disc,t_run,c_run,...)
3292 if trace_testruns then
3293 report_disc("test",disc)
3294 end
3295 local prev, next = getboth(disc)
3296 if not next then
3297
3298 return
3299 end
3300 local pre, post, replace, pretail, posttail, replacetail = getdisc(disc,true)
3301 local renewed = false
3302 if post or replace then
3303 if post then
3304 setlink(posttail,next)
3305 else
3306 post = next
3307 end
3308 if replace then
3309 setlink(replacetail,next)
3310 else
3311 replace = next
3312 end
3313 local d_post = t_run(post,next,...)
3314 local d_replace = t_run(replace,next,...)
3315 if d_post > 0 or d_replace > 0 then
3316 local d = d_replace > d_post and d_replace or d_post
3317 local head = getnext(disc)
3318 local tail = head
3319 for i=test_flatten_start,d do
3320 local nx = getnext(tail)
3321 local id = getid(nx)
3322 if id == disc_code then
3323 head, tail = flattendisk(head,nx)
3324 elseif id == glyph_code then
3325 tail = nx
3326 else
3327
3328 break
3329 end
3330 end
3331 next = getnext(tail)
3332 setnext(tail)
3333 setprev(head)
3334 local new = copynodelist(head)
3335 if posttail then
3336 setlink(posttail,head)
3337 else
3338 post = head
3339 end
3340 if replacetail then
3341 setlink(replacetail,new)
3342 else
3343 replace = new
3344 end
3345 else
3346
3347 if posttail then
3348 setnext(posttail)
3349 else
3350 post = nil
3351 end
3352 if replacetail then
3353 setnext(replacetail)
3354 else
3355 replace = nil
3356 end
3357 end
3358 setlink(disc,next)
3359
3360 end
3361
3362
3363
3364 if trace_testruns then
3365 report_disc("more",disc)
3366 end
3367
3368 if pre then
3369 sweepnode = disc
3370 sweeptype = "pre"
3371 local new, ok = c_run(pre,...)
3372 if ok then
3373 pre = new
3374 renewed = true
3375 end
3376 end
3377
3378 if post then
3379 sweepnode = disc
3380 sweeptype = "post"
3381 local new, ok = c_run(post,...)
3382 if ok then
3383 post = new
3384 renewed = true
3385 end
3386 end
3387
3388 if replace then
3389 sweepnode = disc
3390 sweeptype = "replace"
3391 local new, ok = c_run(replace,...)
3392 if ok then
3393 replace = new
3394 renewed = true
3395 end
3396 end
3397
3398 sweepnode = nil
3399 sweeptype = nil
3400 if renewed then
3401 setdisc(disc,pre,post,replace)
3402 if trace_testruns then
3403 report_disc("done",disc)
3404 end
3405 end
3406
3407
3408 return getnext(disc)
3409end
3410
3411
3412
3413
3414local nesting = 0
3415
3416local function c_run_single(head,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler)
3417 local done = false
3418 local sweep = sweephead[head]
3419 local start
3420 if sweep then
3421 start = sweep
3422
3423 sweephead[head] = false
3424 else
3425 start = head
3426 end
3427 while start do
3428 local char, id = ischar(start,font)
3429 if char then
3430 local a
3431 if attr then
3432 a = getglyphdata(start)
3433 end
3434 if not a or (a == attr) then
3435 local lookupmatch = lookupcache[char]
3436 if lookupmatch then
3437 local ok
3438 head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
3439 if ok then
3440 done = true
3441 end
3442 end
3443 if start then
3444 start = getnext(start)
3445 end
3446 else
3447
3448 start = getnext(start)
3449 end
3450 elseif char == false then
3451 return head, done
3452 elseif sweep then
3453
3454 return head, done
3455 else
3456
3457 start = getnext(start)
3458 end
3459 end
3460 return head, done
3461end
3462
3463
3464
3465local function t_run_single(start,stop,font,attr,lookupcache)
3466 local lastd = nil
3467 while start ~= stop do
3468 local char = ischar(start,font)
3469 if char then
3470 local a
3471 if attr then
3472 a = getglyphdata(start)
3473 end
3474 local startnext = getnext(start)
3475 if not a or (a == attr) then
3476 local lookupmatch = lookupcache[char]
3477 if lookupmatch then
3478
3479 local s = startnext
3480 local ss = nil
3481 local sstop = s == stop
3482 if not s then
3483 s = ss
3484 ss = nil
3485 end
3486
3487
3488 while getid(s) == disc_code do
3489 ss = getnext(s)
3490 s = getreplace(s)
3491 if not s then
3492 s = ss
3493 ss = nil
3494 end
3495 end
3496 local l = nil
3497 local d = 0
3498 while s do
3499 local char = ischar(s,font)
3500 if char then
3501 local lg = not tonumber(lookupmatch) and lookupmatch[char]
3502 if lg then
3503 if sstop then
3504 d = 1
3505 elseif d > 0 then
3506 d = d + 1
3507 end
3508 l = lg
3509 s = getnext(s)
3510 sstop = s == stop
3511 if not s then
3512 s = ss
3513 ss = nil
3514 end
3515 while getid(s) == disc_code do
3516 ss = getnext(s)
3517 s = getreplace(s)
3518 if not s then
3519 s = ss
3520 ss = nil
3521 end
3522 end
3523 lookupmatch = lg
3524 else
3525 break
3526 end
3527 else
3528 break
3529 end
3530 end
3531 if l and (tonumber(l) or l.ligature) then
3532 lastd = d
3533 end
3534
3535 else
3536
3537
3538 end
3539 else
3540
3541
3542 end
3543 if lastd then
3544 return lastd
3545 end
3546 start = startnext
3547 else
3548 break
3549 end
3550 end
3551 return 0
3552end
3553
3554local function k_run_single(sub,injection,last,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler)
3555 local a
3556 if attr then
3557 a = getglyphdata(sub)
3558 end
3559 if not a or (a == attr) then
3560 for n in nextnode, sub do
3561 if n == last then
3562 break
3563 end
3564 local char = ischar(n,font)
3565 if char then
3566 local lookupmatch = lookupcache[char]
3567 if lookupmatch then
3568 local h, d, ok = handler(sub,n,dataset,sequence,lookupmatch,rlmode,skiphash,step,injection)
3569 if ok then
3570 return true
3571 end
3572 end
3573 end
3574 end
3575 end
3576end
3577
3578local function c_run_multiple(head,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler)
3579 local done = false
3580 local sweep = sweephead[head]
3581 local start
3582 if sweep then
3583 start = sweep
3584
3585 sweephead[head] = false
3586 else
3587 start = head
3588 end
3589 while start do
3590 local char = ischar(start,font)
3591 if char then
3592 local a
3593 if attr then
3594 a = getglyphdata(start)
3595 end
3596 if not a or (a == attr) then
3597 for i=1,nofsteps do
3598 local step = steps[i]
3599 local lookupcache = step.coverage
3600 local lookupmatch = lookupcache[char]
3601 if lookupmatch then
3602
3603 local ok
3604 head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
3605 if ok then
3606 done = true
3607 break
3608 elseif not start then
3609
3610 break
3611 end
3612 end
3613 end
3614 if start then
3615 start = getnext(start)
3616 end
3617 else
3618
3619 start = getnext(start)
3620 end
3621 elseif char == false then
3622
3623 return head, done
3624 elseif sweep then
3625
3626 return head, done
3627 else
3628
3629 start = getnext(start)
3630 end
3631 end
3632 return head, done
3633end
3634
3635local function t_run_multiple(start,stop,font,attr,steps,nofsteps)
3636 local lastd = nil
3637 while start ~= stop do
3638 local char = ischar(start,font)
3639 if char then
3640 local a
3641 if attr then
3642 a = getglyphdata(start)
3643 end
3644 local startnext = getnext(start)
3645 if not a or (a == attr) then
3646 for i=1,nofsteps do
3647 local step = steps[i]
3648 local lookupcache = step.coverage
3649 local lookupmatch = lookupcache[char]
3650 if lookupmatch then
3651
3652 local s = startnext
3653 local ss = nil
3654 local sstop = s == stop
3655 if not s then
3656 s = ss
3657 ss = nil
3658 end
3659 while getid(s) == disc_code do
3660 ss = getnext(s)
3661 s = getreplace(s)
3662 if not s then
3663 s = ss
3664 ss = nil
3665 end
3666 end
3667 local l = nil
3668 local d = 0
3669 while s do
3670 local char = ischar(s)
3671 if char then
3672 local lg = not tonumber(lookupmatch) and lookupmatch[char]
3673 if lg then
3674 if sstop then
3675 d = 1
3676 elseif d > 0 then
3677 d = d + 1
3678 end
3679 l = lg
3680 s = getnext(s)
3681 sstop = s == stop
3682 if not s then
3683 s = ss
3684 ss = nil
3685 end
3686 while getid(s) == disc_code do
3687 ss = getnext(s)
3688 s = getreplace(s)
3689 if not s then
3690 s = ss
3691 ss = nil
3692 end
3693 end
3694 lookupmatch = lg
3695 else
3696 break
3697 end
3698 else
3699 break
3700 end
3701 end
3702 if l and (tonumber(l) or l.ligature) then
3703 lastd = d
3704 end
3705 end
3706 end
3707 else
3708
3709 end
3710 if lastd then
3711 return lastd
3712 end
3713 start = startnext
3714 else
3715 break
3716 end
3717 end
3718 return 0
3719end
3720
3721local function k_run_multiple(sub,injection,last,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler)
3722 local a
3723 if attr then
3724 a = getglyphdata(sub)
3725 end
3726 if not a or (a == attr) then
3727 for n in nextnode, sub do
3728 if n == last then
3729 break
3730 end
3731 local char = ischar(n)
3732 if char then
3733 for i=1,nofsteps do
3734 local step = steps[i]
3735 local lookupcache = step.coverage
3736 local lookupmatch = lookupcache[char]
3737 if lookupmatch then
3738 local h, d, ok = handler(sub,n,dataset,sequence,lookupmatch,rlmode,skiphash,step,injection)
3739 if ok then
3740 return true
3741 end
3742 end
3743 end
3744 end
3745 end
3746 end
3747end
3748
3749local txtdirstate, pardirstate do
3750
3751 local getdirection = nuts.getdirection
3752
3753 txtdirstate = function(start,stack,top,rlparmode)
3754 local dir, pop = getdirection(start)
3755 if pop then
3756 if top == 1 then
3757 return 0, rlparmode
3758 else
3759 top = top - 1
3760 if stack[top] == righttoleft_code then
3761 return top, -1
3762 else
3763 return top, 1
3764 end
3765 end
3766 elseif dir == lefttoright_code then
3767 top = top + 1
3768 stack[top] = lefttoright_code
3769 return top, 1
3770 elseif dir == righttoleft_code then
3771 top = top + 1
3772 stack[top] = righttoleft_code
3773 return top, -1
3774 else
3775 return top, rlparmode
3776 end
3777 end
3778
3779 pardirstate = function(start)
3780 local dir = getdirection(start)
3781 if dir == lefttoright_code then
3782 return 1, 1
3783 elseif dir == righttoleft_code then
3784 return -1, -1
3785 else
3786 return 0, 0
3787 end
3788 end
3789
3790end
3791
3792
3793
3794otf.helpers = otf.helpers or { }
3795otf.helpers.txtdirstate = txtdirstate
3796otf.helpers.pardirstate = pardirstate
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806do
3807
3808
3809
3810
3811 local fastdisc = true
3812 local testdics = false
3813
3814 directives.register("otf.fastdisc",function(v) fastdisc = v end)
3815
3816
3817
3818
3819
3820 local otfdataset = nil
3821
3822 local getfastdisc = { __index = function(t,k)
3823 local v = usesfont(k,currentfont)
3824 t[k] = v
3825 return v
3826 end }
3827
3828 local getfastspace = { __index = function(t,k)
3829
3830 local v = isspace(k,threshold) or false
3831 t[k] = v
3832 return v
3833 end }
3834
3835 function otf.featuresprocessor(head,font,attr,direction,n)
3836
3837 local sequences = sequencelists[font]
3838
3839 nesting = nesting + 1
3840
3841 if nesting == 1 then
3842 currentfont = font
3843 tfmdata = fontdata[font]
3844 descriptions = tfmdata.descriptions
3845 characters = tfmdata.characters
3846 local resources = tfmdata.resources
3847 marks = resources.marks
3848 classes = resources.classes
3849 threshold,
3850 factor = getthreshold(font)
3851 checkmarks = tfmdata.properties.checkmarks
3852
3853 if not otfdataset then
3854 otfdataset = otf.dataset
3855 end
3856
3857 discs = fastdisc and n and n > 1 and setmetatable({},getfastdisc)
3858 spaces = setmetatable({},getfastspace)
3859
3860 elseif currentfont ~= font then
3861
3862 report_warning("nested call with a different font, level %s, quitting",nesting)
3863 nesting = nesting - 1
3864 return head, false
3865
3866 end
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876 if trace_steps then
3877 checkstep(head)
3878 end
3879
3880 local initialrl = 0
3881
3882 if getid(head) == par_code and startofpar(head) then
3883 initialrl = pardirstate(head)
3884 elseif direction == righttoleft_code then
3885 initialrl = -1
3886 end
3887
3888
3889 local datasets = otfdataset(tfmdata,font,attr)
3890 local dirstack = { nil }
3891 sweephead = { }
3892
3893
3894
3895
3896
3897
3898
3899
3900 for s=1,#datasets do
3901 local dataset = datasets[s]
3902 local attribute = dataset[2]
3903 local sequence = dataset[3]
3904 local rlparmode = initialrl
3905 local topstack = 0
3906 local typ = sequence.type
3907 local gpossing = typ == "gpos_single" or typ == "gpos_pair"
3908 local forcetestrun = typ == "gsub_ligature"
3909 local handler = handlers[typ]
3910 local steps = sequence.steps
3911 local nofsteps = sequence.nofsteps
3912 local skiphash = sequence.skiphash
3913
3914 if not steps then
3915
3916
3917
3918 local h, ok = handler(head,dataset,sequence,initialrl,font,attr)
3919
3920
3921
3922 if h and h ~= head then
3923 head = h
3924 end
3925 elseif typ == "gsub_reversecontextchain" then
3926
3927
3928
3929
3930
3931 local start = findnodetail(head)
3932 local rlmode = 0
3933 local merged = steps.merged
3934 while start do
3935 local char = ischar(start,font)
3936 if char then
3937 local m = merged[char]
3938 if m then
3939 local a
3940 if attr then
3941 a = getglyphdata(start)
3942 end
3943 if not a or (a == attr) then
3944 for i=m[1],m[2] do
3945 local step = steps[i]
3946
3947
3948 local lookupcache = step.coverage
3949 local lookupmatch = lookupcache[char]
3950 if lookupmatch then
3951 local ok
3952 head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
3953 if ok then
3954
3955 break
3956 end
3957 end
3958 end
3959 if start then
3960 start = getprev(start)
3961 end
3962 else
3963 start = getprev(start)
3964 end
3965 else
3966 start = getprev(start)
3967 end
3968 else
3969 start = getprev(start)
3970 end
3971 end
3972 else
3973 local start = head
3974 local rlmode = initialrl
3975 if nofsteps == 1 then
3976 local step = steps[1]
3977 local lookupcache = step.coverage
3978 while start do
3979 local char, id = ischar(start,font)
3980 if char then
3981 local lookupmatch = lookupcache[char]
3982 if lookupmatch then
3983 local a
3984 if attr then
3985 if getglyphdata(start) == attr and (not attribute or getstate(start,attribute)) then
3986 a = true
3987 end
3988 elseif not attribute or getstate(start,attribute) then
3989 a = true
3990 end
3991 if a then
3992 local ok, df
3993 head, start, ok, df = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
3994
3995
3996
3997 if df then
3998
3999 elseif start then
4000 start = getnext(start)
4001 end
4002 else
4003 start = getnext(start)
4004 end
4005 else
4006 start = getnext(start)
4007 end
4008 elseif char == false or id == glue_code then
4009
4010 start = getnext(start)
4011 elseif id == disc_code then
4012 if not discs or discs[start] == true then
4013 if gpossing then
4014 start = kernrun(start,k_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler)
4015 elseif forcetestrun then
4016 start = testrun(start,t_run_single,c_run_single,font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler)
4017 else
4018 start = comprun(start,c_run_single, font,attr,lookupcache,step,dataset,sequence,rlmode,skiphash,handler)
4019 end
4020 else
4021 start = getnext(start)
4022 end
4023 elseif id == math_code then
4024 start = getnext(endofmath(start))
4025 elseif id == dir_code then
4026 topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode)
4027 start = getnext(start)
4028
4029
4030
4031 else
4032 start = getnext(start)
4033 end
4034 end
4035 else
4036 local merged = steps.merged
4037 while start do
4038 local char, id = ischar(start,font)
4039 if char then
4040 local m = merged[char]
4041 if m then
4042 local a
4043 if attr then
4044 if getglyphdata(start) == attr and (not attribute or getstate(start,attribute)) then
4045 a = true
4046 end
4047 elseif not attribute or getstate(start,attribute) then
4048 a = true
4049 end
4050 if a then
4051 local ok, df
4052 for i=m[1],m[2] do
4053 local step = steps[i]
4054
4055
4056 local lookupcache = step.coverage
4057 local lookupmatch = lookupcache[char]
4058 if lookupmatch then
4059
4060
4061 head, start, ok, df = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
4062 if df then
4063 break
4064 elseif ok then
4065
4066 break
4067 elseif not start then
4068
4069 break
4070 end
4071 end
4072 end
4073 if df then
4074
4075 elseif start then
4076 start = getnext(start)
4077 end
4078 else
4079 start = getnext(start)
4080 end
4081 else
4082 start = getnext(start)
4083 end
4084 elseif char == false or id == glue_code then
4085
4086 start = getnext(start)
4087 elseif id == disc_code then
4088 if not discs or discs[start] == true then
4089 if gpossing then
4090 start = kernrun(start,k_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler)
4091 elseif forcetestrun then
4092 start = testrun(start,t_run_multiple,c_run_multiple,font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler)
4093 else
4094 start = comprun(start,c_run_multiple, font,attr,steps,nofsteps,dataset,sequence,rlmode,skiphash,handler)
4095 end
4096 else
4097 start = getnext(start)
4098 end
4099 elseif id == math_code then
4100 start = getnext(endofmath(start))
4101 elseif id == dir_code then
4102 topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode)
4103 start = getnext(start)
4104
4105
4106
4107 else
4108 start = getnext(start)
4109 end
4110 end
4111 end
4112 end
4113
4114 if trace_steps then
4115 registerstep(head)
4116 end
4117
4118 end
4119
4120 nesting = nesting - 1
4121
4122
4123 return head
4124 end
4125
4126
4127
4128
4129 function otf.datasetpositionprocessor(head,font,direction,dataset)
4130
4131 currentfont = font
4132 tfmdata = fontdata[font]
4133 descriptions = tfmdata.descriptions
4134 characters = tfmdata.characters
4135 local resources = tfmdata.resources
4136 marks = resources.marks
4137 classes = resources.classes
4138 threshold,
4139 factor = getthreshold(font)
4140 checkmarks = tfmdata.properties.checkmarks
4141
4142 if type(dataset) == "number" then
4143 dataset = otfdataset(tfmdata,font,0)[dataset]
4144 end
4145
4146 local sequence = dataset[3]
4147 local typ = sequence.type
4148
4149
4150
4151
4152
4153
4154
4155
4156 local handler = handlers[typ]
4157 local steps = sequence.steps
4158 local nofsteps = sequence.nofsteps
4159
4160 local done = false
4161 local dirstack = { nil }
4162 local start = head
4163 local initialrl = (direction == righttoleft_code) and -1 or 0
4164 local rlmode = initialrl
4165 local rlparmode = initialrl
4166 local topstack = 0
4167 local merged = steps.merged
4168
4169
4170 local position = 0
4171
4172 while start do
4173 local char, id = ischar(start,font)
4174 if char then
4175 position = position + 1
4176 local m = merged[char]
4177 if m then
4178 for i=m[1],m[2] do
4179 local step = steps[i]
4180 local lookupcache = step.coverage
4181 local lookupmatch = lookupcache[char]
4182 if lookupmatch then
4183 local ok
4184 head, start, ok = handler(head,start,dataset,sequence,lookupmatch,rlmode,skiphash,step)
4185 if ok then
4186
4187
4188
4189
4190
4191 break
4192 elseif not start then
4193 break
4194 end
4195 end
4196 end
4197 if start then
4198 start = getnext(start)
4199 end
4200 else
4201 start = getnext(start)
4202 end
4203 elseif char == false or id == glue_code then
4204
4205 start = getnext(start)
4206 elseif id == math_code then
4207 start = getnext(endofmath(start))
4208 elseif id == dir_code then
4209 topstack, rlmode = txtdirstate(start,dirstack,topstack,rlparmode)
4210 start = getnext(start)
4211
4212
4213
4214 else
4215 start = getnext(start)
4216 end
4217 end
4218
4219 return head
4220 end
4221
4222
4223
4224end
4225
4226
4227
4228do
4229
4230 local plugins = { }
4231 otf.plugins = plugins
4232
4233 local report = logs.reporter("fonts")
4234 local warned = false
4235 local okay = { text = true }
4236
4237 function otf.registerplugin(name,f)
4238 if type(name) == "string" and type(f) == "function" then
4239 plugins[name] = { name, f }
4240 if okay[name] then
4241
4242 else
4243 report("plugin %a has been loaded, please be aware of possible side effects",name)
4244 if not warned then
4245 if logs.pushtarget then
4246 logs.pushtarget("log")
4247 end
4248 report("Plugins are not officially supported unless stated otherwise. This is because")
4249 report("they bypass the regular font handling and therefore some features in ConTeXt")
4250 report("(especially those related to fonts) might not work as expected or might not work")
4251 report("at all. Some plugins are for testing and development only and might change")
4252 report("whenever we feel the need for it.")
4253 report()
4254 if logs.poptarget then
4255 logs.poptarget()
4256 end
4257 warned = true
4258 end
4259 end
4260 end
4261 end
4262
4263 function otf.plugininitializer(tfmdata,value)
4264 if type(value) == "string" then
4265 tfmdata.shared.plugin = plugins[value]
4266 end
4267 end
4268
4269 function otf.pluginprocessor(head,font,dynamic,direction)
4270 local s = fontdata[font].shared
4271 local p = s and s.plugin
4272 if p then
4273 if trace_plugins then
4274 report_process("applying plugin %a",p[1])
4275 end
4276 return p[2](head,font,dynamic,direction)
4277 else
4278 return head, false
4279 end
4280 end
4281
4282end
4283
4284function otf.featuresinitializer(tfmdata,value)
4285
4286end
4287
4288registerotffeature {
4289 name = "features",
4290 description = "features",
4291 default = true,
4292 initializers = {
4293 position = 1,
4294 node = otf.featuresinitializer,
4295 plug = otf.plugininitializer,
4296 },
4297 processors = {
4298 node = otf.featuresprocessor,
4299 plug = otf.pluginprocessor,
4300 }
4301}
4302
4303
4304
4305
4306local function markinitializer(tfmdata,value)
4307 local properties = tfmdata.properties
4308 properties.checkmarks = value
4309end
4310
4311registerotffeature {
4312 name = "checkmarks",
4313 description = "check mark widths",
4314 default = true,
4315 initializers = {
4316 node = markinitializer,
4317 },
4318}
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331otf.handlers = handlers
4332
4333if context then
4334 return
4335else
4336
4337end
4338
4339local setspacekerns = nodes.injections.setspacekerns if not setspacekerns then os.exit() end
4340
4341local tag = "kern"
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356 function handlers.trigger_space_kerns(head,dataset,sequence,initialrl,font,attr)
4357 local shared = fontdata[font].shared
4358 local features = shared and shared.features
4359 local enabled = features and features.spacekern and features[tag]
4360 if enabled then
4361 setspacekerns(font,sequence)
4362 end
4363 return head, enabled
4364 end
4365
4366
4367
4368
4369
4370
4371local function hasspacekerns(data)
4372 local resources = data.resources
4373 local sequences = resources.sequences
4374 local validgpos = resources.features.gpos
4375 if validgpos and sequences then
4376 for i=1,#sequences do
4377 local sequence = sequences[i]
4378 local steps = sequence.steps
4379 if steps and sequence.features[tag] then
4380 local kind = sequence.type
4381 if kind == "gpos_pair" or kind == "gpos_single" then
4382 for i=1,#steps do
4383 local step = steps[i]
4384 local coverage = step.coverage
4385 local rules = step.rules
4386 if rules then
4387
4388 elseif not coverage then
4389
4390 elseif kind == "gpos_single" then
4391
4392 elseif kind == "gpos_pair" then
4393 local format = step.format
4394 if format == "move" or format == "kern" then
4395 local kerns = coverage[32]
4396 if kerns then
4397 return true
4398 end
4399 for k, v in next, coverage do
4400 if v[32] then
4401 return true
4402 end
4403 end
4404 elseif format == "pair" then
4405 local kerns = coverage[32]
4406 if kerns then
4407 for k, v in next, kerns do
4408 local one = v[1]
4409 if one and one ~= true then
4410 return true
4411 end
4412 end
4413 end
4414 for k, v in next, coverage do
4415 local kern = v[32]
4416 if kern then
4417 local one = kern[1]
4418 if one and one ~= true then
4419 return true
4420 end
4421 end
4422 end
4423 end
4424 end
4425 end
4426 end
4427 end
4428 end
4429 end
4430 return false
4431end
4432
4433otf.readers.registerextender {
4434 name = "spacekerns",
4435 action = function(data)
4436 data.properties.hasspacekerns = hasspacekerns(data)
4437 end
4438}
4439
4440local function spaceinitializer(tfmdata,value)
4441 local resources = tfmdata.resources
4442 local spacekerns = resources and resources.spacekerns
4443 if value and spacekerns == nil then
4444 local rawdata = tfmdata.shared and tfmdata.shared.rawdata
4445 local properties = rawdata.properties
4446 if properties and properties.hasspacekerns then
4447 local sequences = resources.sequences
4448 local validgpos = resources.features.gpos
4449 if validgpos and sequences then
4450 local left = { }
4451 local right = { }
4452 local last = 0
4453 local feat = nil
4454 for i=1,#sequences do
4455 local sequence = sequences[i]
4456 local steps = sequence.steps
4457 if steps then
4458
4459 local kern = sequence.features[tag]
4460 if kern then
4461 local kind = sequence.type
4462 if kind == "gpos_pair" or kind == "gpos_single" then
4463 if feat then
4464 for script, languages in next, kern do
4465 local f = feat[script]
4466 if f then
4467 for l in next, languages do
4468 f[l] = true
4469 end
4470 else
4471 feat[script] = languages
4472 end
4473 end
4474 else
4475 feat = kern
4476 end
4477 for i=1,#steps do
4478 local step = steps[i]
4479 local coverage = step.coverage
4480 local rules = step.rules
4481 if rules then
4482
4483 elseif not coverage then
4484
4485 elseif kind == "gpos_single" then
4486
|