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