1if not modules then modules = { } end modules ['typo-dub'] = {
2 version = 1.001,
3 comment = "companion to typo-dir.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files",
7 comment = "Unicode bidi (sort of) variant b",
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
48local insert, remove, unpack, concat = table.insert, table.remove, table.unpack, table.concat
49local utfchar = utf.char
50local setmetatable = setmetatable
51local formatters = string.formatters
52
53local directiondata = characters.directions
54local mirrordata = characters.mirrors
55local textclassdata = characters.textclasses
56
57local nuts = nodes.nuts
58
59local getnext = nuts.getnext
60local getid = nuts.getid
61local getsubtype = nuts.getsubtype
62local getlist = nuts.getlist
63local getchar = nuts.getchar
64local getattr = nuts.getattr
65local getprop = nuts.getprop
66local getdirection = nuts.getdirection
67
68local setprop = nuts.setprop
69local setchar = nuts.setchar
70local setdirection = nuts.setdirection
71local setattrlist = nuts.setattrlist
72
73local remove_node = nuts.remove
74local insertnodeafter = nuts.insertafter
75local insertnodebefore = nuts.insertbefore
76
77local startofpar = nuts.startofpar
78
79local nodepool = nuts.pool
80local new_direction = nodepool.direction
81
82local nodecodes = nodes.nodecodes
83local gluecodes = nodes.gluecodes
84
85local glyph_code = nodecodes.glyph
86local glue_code = nodecodes.glue
87local hlist_code = nodecodes.hlist
88local vlist_code = nodecodes.vlist
89local math_code = nodecodes.math
90local dir_code = nodecodes.dir
91local par_code = nodecodes.par
92
93local parfillskip_code = gluecodes.parfillskip
94
95local dirvalues = nodes.dirvalues
96local lefttoright_code = dirvalues.lefttoright
97local righttoleft_code = dirvalues.righttoleft
98
99local maximum_stack = 0xFF
100
101local a_directions = attributes.private('directions')
102
103local directions = typesetters.directions
104local setcolor = directions.setcolor
105local getfences = directions.getfences
106
107local remove_controls = true directives.register("typesetters.directions.removecontrols",function(v) remove_controls = v end)
108
109
110local report_directions = logs.reporter("typesetting","directions three")
111
112local trace_directions = false trackers.register("typesetters.directions", function(v) trace_directions = v end)
113local trace_details = false trackers.register("typesetters.directions.details", function(v) trace_details = v end)
114local trace_list = false trackers.register("typesetters.directions.list", function(v) trace_list = v end)
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164local whitespace = {
165 lre = true,
166 rle = true,
167 lro = true,
168 rlo = true,
169 pdf = true,
170 bn = true,
171 ws = true,
172}
173
174local b_s_ws_on = {
175 b = true,
176 s = true,
177 ws = true,
178 on = true
179}
180
181
182
183local function show_list(list,size,what)
184 local what = what or "direction"
185 local joiner = utfchar(0x200C)
186 local result = { }
187 for i=1,size do
188 local entry = list[i]
189 local character = entry.char
190 local direction = entry[what]
191 if character == 0xFFFC then
192 local first = entry.id
193 local last = entry.last
194 local skip = entry.skip
195 if last then
196 result[i] = formatters["%-3s:%s %s..%s (%i)"](direction,joiner,nodecodes[first],nodecodes[last],skip or 0)
197 else
198 result[i] = formatters["%-3s:%s %s (%i)"](direction,joiner,nodecodes[first],skip or 0)
199 end
200 elseif character >= 0x202A and character <= 0x202C then
201 result[i] = formatters["%-3s:%s %U"](direction,joiner,character)
202 else
203 result[i] = formatters["%-3s:%s %c %U"](direction,joiner,character,character)
204 end
205 end
206 return concat(result,joiner .. " | " .. joiner)
207end
208
209
210
211local function show_done(list,size)
212 local joiner = utfchar(0x200C)
213 local result = { }
214 local format = formatters["<%s>"]
215 for i=1,size do
216 local entry = list[i]
217 local character = entry.char
218 local begindir = entry.begindir
219 local enddir = entry.enddir
220 if begindir then
221 result[#result+1] = format(begindir)
222 end
223 if entry.remove then
224
225 elseif character == 0xFFFC then
226 result[#result+1] = format("?")
227 elseif character == 0x0020 then
228 result[#result+1] = format(" ")
229 elseif character >= 0x202A and character <= 0x202C then
230 result[#result+1] = format(entry.original)
231 else
232 result[#result+1] = utfchar(character)
233 end
234 if enddir then
235 result[#result+1] = format(enddir)
236 end
237 end
238 return concat(result,joiner)
239end
240
241
242
243
244
245local function build_list(head)
246
247 local current = head
248 local list = { }
249 local size = 0
250 while current do
251 size = size + 1
252 local id = getid(current)
253 if getprop(current,"directions") then
254 local skip = 0
255 local last = id
256 current = getnext(current)
257 while current do
258 local id = getid(current)
259 if getprop(current,"directions") then
260 skip = skip + 1
261 last = id
262 current = getnext(current)
263 else
264 break
265 end
266 end
267 if id == last then
268 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
269 else
270 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id, last = last }
271 end
272 elseif id == glyph_code then
273 local chr = getchar(current)
274 local dir = directiondata[chr]
275 list[size] = { char = chr, direction = dir, original = dir, level = 0 }
276 current = getnext(current)
277 elseif id == glue_code then
278 list[size] = { char = 0x0020, direction = "ws", original = "ws", level = 0 }
279 current = getnext(current)
280 elseif id == dir_code then
281 local direction, pop = getdirection(current)
282 if direction == lefttoright_code then
283 if pop then
284 list[size] = { char = 0x202C, direction = "pdf", original = "pdf", level = 0 }
285 else
286 list[size] = { char = 0x202A, direction = "lre", original = "lre", level = 0 }
287 end
288 elseif direction == righttoleft_code then
289 if pop then
290 list[size] = { char = 0x202C, direction = "pdf", original = "pdf", level = 0 }
291 else
292 list[size] = { char = 0x202B, direction = "rle", original = "rle", level = 0 }
293 end
294 else
295 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, id = id }
296 end
297 current = getnext(current)
298 elseif id == math_code then
299 local skip = 0
300 current = getnext(current)
301 while getid(current) ~= math_code do
302 skip = skip + 1
303 current = getnext(current)
304 end
305 skip = skip + 1
306 current = getnext(current)
307 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
308 else
309 local skip = 0
310 local last = id
311 current = getnext(current)
312 while n do
313 local id = getid(current)
314 if id ~= glyph_code and id ~= glue_code and id ~= dir_code then
315 skip = skip + 1
316 last = id
317 current = getnext(current)
318 else
319 break
320 end
321 end
322 if id == last then
323 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
324 else
325 list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id, last = last }
326 end
327 end
328 end
329 return list, size
330end
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346local function resolve_fences(list,size,start,limit)
347
348 local stack = { }
349 local nofstack = 0
350 for i=start,limit do
351 local entry = list[i]
352 if entry.direction == "on" then
353 local char = entry.char
354 local mirror = mirrordata[char]
355 if mirror then
356 local class = textclassdata[char]
357 entry.mirror = mirror
358 entry.class = class
359 if class == "open" then
360 nofstack = nofstack + 1
361 stack[nofstack] = { mirror, i, false }
362 elseif nofstack == 0 then
363
364 elseif class == "close" then
365 while nofstack > 0 do
366 local stacktop = stack[nofstack]
367 if stacktop[1] == char then
368 local open = stacktop[2]
369 local close = i
370 list[open ].paired = close
371 list[close].paired = open
372 break
373 else
374
375 end
376 nofstack = nofstack - 1
377 end
378 end
379 end
380 end
381 end
382end
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399local function get_baselevel(head,list,size,direction)
400 if direction == lefttoright_code or direction == righttoleft_code then
401 return direction, true
402 elseif getid(head) == par_code and startofpar(head) then
403 direction = getdirection(head)
404 if direction == lefttoright_code or direction == righttoleft_code then
405 return direction, true
406 end
407 end
408
409 for i=1,size do
410 local entry = list[i]
411 local direction = entry.direction
412 if direction == "r" or direction == "al" then
413 return righttoleft_code, true
414 elseif direction == "l" then
415 return lefttoright_code, true
416 end
417 end
418 return lefttoright_code, false
419end
420
421local function resolve_explicit(list,size,baselevel)
422
423 local level = baselevel
424 local override = "on"
425 local stack = { }
426 local nofstack = 0
427 for i=1,size do
428 local entry = list[i]
429 local direction = entry.direction
430
431 if direction == "rle" then
432 if nofstack < maximum_stack then
433 nofstack = nofstack + 1
434 stack[nofstack] = { level, override }
435 level = level + (level % 2 == 1 and 2 or 1)
436 override = "on"
437 entry.level = level
438 entry.direction = "bn"
439 entry.remove = true
440 elseif trace_directions then
441 report_directions("stack overflow at position %a with direction %a",i,direction)
442 end
443
444 elseif direction == "lre" then
445 if nofstack < maximum_stack then
446 nofstack = nofstack + 1
447 stack[nofstack] = { level, override }
448 level = level + (level % 2 == 1 and 1 or 2)
449 override = "on"
450 entry.level = level
451 entry.direction = "bn"
452 entry.remove = true
453 elseif trace_directions then
454 report_directions("stack overflow at position %a with direction %a",i,direction)
455 end
456
457 elseif direction == "rlo" then
458 if nofstack < maximum_stack then
459 nofstack = nofstack + 1
460 stack[nofstack] = { level, override }
461 level = level + (level % 2 == 1 and 2 or 1)
462 override = "r"
463 entry.level = level
464 entry.direction = "bn"
465 entry.remove = true
466 elseif trace_directions then
467 report_directions("stack overflow at position %a with direction %a",i,direction)
468 end
469
470 elseif direction == "lro" then
471 if nofstack < maximum_stack then
472 nofstack = nofstack + 1
473 stack[nofstack] = { level, override }
474 level = level + (level % 2 == 1 and 1 or 2)
475 override = "l"
476 entry.level = level
477 entry.direction = "bn"
478 entry.remove = true
479 elseif trace_directions then
480 report_directions("stack overflow at position %a with direction %a",i,direction)
481 end
482
483 elseif direction == "pdf" then
484 if nofstack > 0 then
485 local stacktop = stack[nofstack]
486 level = stacktop[1]
487 override = stacktop[2]
488 nofstack = nofstack - 1
489 entry.level = level
490 entry.direction = "bn"
491 entry.remove = true
492 elseif trace_directions then
493 report_directions("stack underflow at position %a with direction %a",
494 i, direction)
495 end
496
497 else
498 entry.level = level
499 if override ~= "on" then
500 entry.direction = override
501 end
502 end
503 end
504
505end
506
507local function resolve_weak(list,size,start,limit,orderbefore,orderafter)
508
509 for i=start,limit do
510 local entry = list[i]
511 if entry.direction == "nsm" then
512 if i == start then
513 entry.direction = orderbefore
514 else
515 entry.direction = list[i-1].direction
516 end
517 end
518 end
519
520 for i=start,limit do
521 local entry = list[i]
522 if entry.direction == "en" then
523 for j=i-1,start,-1 do
524 local prev = list[j]
525 local direction = prev.direction
526 if direction == "al" then
527 entry.direction = "an"
528 break
529 elseif direction == "r" or direction == "l" then
530 break
531 end
532 end
533 end
534 end
535
536 for i=start,limit do
537 local entry = list[i]
538 if entry.direction == "al" then
539 entry.direction = "r"
540 end
541 end
542
543 if true then
544 for i=start+1,limit-1 do
545 local entry = list[i]
546 local direction = entry.direction
547 if direction == "es" then
548 if list[i-1].direction == "en" and list[i+1].direction == "en" then
549 entry.direction = "en"
550 end
551 elseif direction == "cs" then
552 local prevdirection = list[i-1].direction
553 if prevdirection == "en" then
554 if list[i+1].direction == "en" then
555 entry.direction = "en"
556 end
557 elseif prevdirection == "an" and list[i+1].direction == "an" then
558 entry.direction = "an"
559 end
560 end
561 end
562 else
563 local runner = start + 2
564 if runner <= limit then
565 local before = list[start]
566 local entry = list[start + 1]
567 local after = list[runner]
568 while after do
569 local direction = entry.direction
570 if direction == "es" then
571 if before.direction == "en" and after.direction == "en" then
572 entry.direction = "en"
573 end
574 elseif direction == "cs" then
575 local prevdirection = before.direction
576 if prevdirection == "en" then
577 if after.direction == "en" then
578 entry.direction = "en"
579 end
580 elseif prevdirection == "an" and after.direction == "an" then
581 entry.direction = "an"
582 end
583 end
584 before = current
585 current = after
586 after = list[runner]
587 end
588 end
589 end
590
591 local i = start
592 while i <= limit do
593 if list[i].direction == "et" then
594 local runstart = i
595 local runlimit = runstart
596 for i=runstart,limit do
597 if list[i].direction == "et" then
598 runlimit = i
599 else
600 break
601 end
602 end
603 local rundirection = runstart == start and sor or list[runstart-1].direction
604 if rundirection ~= "en" then
605 rundirection = runlimit == limit and orderafter or list[runlimit+1].direction
606 end
607 if rundirection == "en" then
608 for j=runstart,runlimit do
609 list[j].direction = "en"
610 end
611 end
612 i = runlimit
613 end
614 i = i + 1
615 end
616
617 for i=start,limit do
618 local entry = list[i]
619 local direction = entry.direction
620 if direction == "es" or direction == "et" or direction == "cs" then
621 entry.direction = "on"
622 end
623 end
624
625 for i=start,limit do
626 local entry = list[i]
627 if entry.direction == "en" then
628 local prev_strong = orderbefore
629 for j=i-1,start,-1 do
630 local direction = list[j].direction
631 if direction == "l" or direction == "r" then
632 prev_strong = direction
633 break
634 end
635 end
636 if prev_strong == "l" then
637 entry.direction = "l"
638 end
639 end
640 end
641end
642
643local function resolve_neutral(list,size,start,limit,orderbefore,orderafter)
644
645 for i=start,limit do
646 local entry = list[i]
647 if b_s_ws_on[entry.direction] then
648
649 local leading_direction, trailing_direction, resolved_direction
650 local runstart = i
651 local runlimit = runstart
652
653 for j=runstart+1,limit do
654 if b_s_ws_on[list[j].direction] then
655
656 runlimit = j
657 else
658 break
659 end
660 end
661 if runstart == start then
662 leading_direction = orderbefore
663 else
664 leading_direction = list[runstart-1].direction
665 if leading_direction == "en" or leading_direction == "an" then
666 leading_direction = "r"
667 end
668 end
669 if runlimit == limit then
670 trailing_direction = orderafter
671 else
672 trailing_direction = list[runlimit+1].direction
673 if trailing_direction == "en" or trailing_direction == "an" then
674 trailing_direction = "r"
675 end
676 end
677 if leading_direction == trailing_direction then
678
679 resolved_direction = leading_direction
680 else
681
682 resolved_direction = entry.level % 2 == 1 and "r" or "l"
683 end
684 for j=runstart,runlimit do
685 list[j].direction = resolved_direction
686 end
687 i = runlimit
688 end
689 i = i + 1
690 end
691end
692
693local function resolve_implicit(list,size,start,limit,orderbefore,orderafter,baselevel)
694 for i=start,limit do
695 local entry = list[i]
696 local level = entry.level
697 local direction = entry.direction
698 if level % 2 ~= 1 then
699
700 if direction == "r" then
701 entry.level = level + 1
702 elseif direction == "an" or direction == "en" then
703 entry.level = level + 2
704 end
705 else
706
707 if direction == "l" or direction == "en" or direction == "an" then
708 entry.level = level + 1
709 end
710 end
711 end
712end
713
714local function resolve_levels(list,size,baselevel,analyze_fences)
715
716 local start = 1
717 while start < size do
718 local level = list[start].level
719 local limit = start + 1
720 while limit < size and list[limit].level == level do
721 limit = limit + 1
722 end
723 local prev_level = start == 1 and baselevel or list[start-1].level
724 local next_level = limit == size and baselevel or list[limit+1].level
725 local orderbefore = (level > prev_level and level or prev_level) % 2 == 1 and "r" or "l"
726 local orderafter = (level > next_level and level or next_level) % 2 == 1 and "r" or "l"
727
728 resolve_weak(list,size,start,limit,orderbefore,orderafter)
729
730 if analyze_fences then
731 resolve_fences(list,size,start,limit)
732 end
733
734 resolve_neutral(list,size,start,limit,orderbefore,orderafter)
735
736 resolve_implicit(list,size,start,limit,orderbefore,orderafter,baselevel)
737 start = limit
738 end
739
740 for i=1,size do
741 local entry = list[i]
742 local direction = entry.original
743
744 if direction == "s" or direction == "b" then
745 entry.level = baselevel
746
747 for j=i-1,1,-1 do
748 local entry = list[j]
749 if whitespace[entry.original] then
750 entry.level = baselevel
751 else
752 break
753 end
754 end
755 end
756 end
757
758 for i=size,1,-1 do
759 local entry = list[i]
760 if whitespace[entry.original] then
761 entry.level = baselevel
762 else
763 break
764 end
765 end
766
767 if analyze_fences then
768 for i=1,size do
769 local entry = list[i]
770 if entry.level % 2 == 1 then
771 if entry.mirror and not entry.paired then
772 entry.mirror = false
773 end
774
775 elseif entry.mirror then
776 entry.mirror = false
777 end
778 end
779 else
780 for i=1,size do
781 local entry = list[i]
782 if entry.level % 2 == 1 then
783 local mirror = mirrordata[entry.char]
784 if mirror then
785 entry.mirror = mirror
786 end
787 end
788 end
789 end
790end
791
792local function insert_dir_points(list,size)
793
794
795 local maxlevel = 0
796 local toggle = true
797 for i=1,size do
798 local level = list[i].level
799 if level > maxlevel then
800 maxlevel = level
801 end
802 end
803 for level=0,maxlevel do
804 local started
805 local begindir
806 local enddir
807 local prev
808 if toggle then
809 begindir = lefttoright_code
810 enddir = lefttoright_code
811 toggle = false
812 else
813 begindir = righttoleft_code
814 enddir = righttoleft_code
815 toggle = true
816 end
817 for i=1,size do
818 local entry = list[i]
819 if entry.level >= level then
820 if not started then
821 entry.begindir = begindir
822 started = true
823 end
824 else
825 if started then
826 prev.enddir = enddir
827 started = false
828 end
829 end
830 prev = entry
831 end
832 end
833
834 local last = list[size]
835 if not last.enddir then
836 local s = { }
837 local n = 0
838 for i=1,size do
839 local entry = list[i]
840 local e = entry.enddir
841 local b = entry.begindir
842 if e then
843 n = n - 1
844 end
845 if b then
846 n = n + 1
847 s[n] = b
848 end
849 end
850 if n > 0 then
851 if trace_list and n > 1 then
852 report_directions("unbalanced list")
853 end
854 last.enddir = s[n] == righttoleft_code and righttoleft_code or lefttoright_code
855 end
856 end
857end
858
859local function apply_to_list(list,size,head,pardir)
860 local index = 1
861 local current = head
862 if trace_list then
863 report_directions("start run")
864 end
865 while current do
866 if index > size then
867 report_directions("fatal error, size mismatch")
868 break
869 end
870 local id = getid(current)
871 local entry = list[index]
872 local begindir = entry.begindir
873 local enddir = entry.enddir
874 setprop(current,"directions",true)
875 if id == glyph_code then
876 local mirror = entry.mirror
877 if mirror then
878 setchar(current,mirror)
879 end
880 if trace_directions then
881 local direction = entry.direction
882 if trace_list then
883 local original = entry.original
884 local char = entry.char
885 local level = entry.level
886 if direction == original then
887 report_directions("%2i : %C : %s",level,char,direction)
888 else
889 report_directions("%2i : %C : %s -> %s",level,char,original,direction)
890 end
891 end
892 setcolor(current,direction,false,mirror)
893 end
894 elseif id == hlist_code or id == vlist_code then
895 setdirection(current,pardir)
896 elseif id == glue_code then
897 if enddir and getsubtype(current) == parfillskip_code then
898
899 local d = new_direction(enddir,true)
900
901
902 head = insertnodebefore(head,current,d)
903 enddir = false
904 end
905 elseif begindir then
906 if id == par_code and startofpar(current) then
907
908 local d = new_direction(begindir)
909
910
911 head, current = insertnodeafter(head,current,d)
912 begindir = nil
913 end
914 end
915 if begindir then
916 local d = new_direction(begindir)
917
918
919 head = insertnodebefore(head,current,d)
920 end
921 local skip = entry.skip
922 if skip and skip > 0 then
923 for i=1,skip do
924 current = getnext(current)
925 setprop(current,"directions",true)
926 end
927 end
928 if enddir then
929 local d = new_direction(enddir,true)
930
931
932 head, current = insertnodeafter(head,current,d)
933 end
934 if not entry.remove then
935 current = getnext(current)
936 elseif remove_controls then
937
938 head, current = remove_node(head,current,true)
939 else
940 current = getnext(current)
941 end
942 index = index + 1
943 end
944 if trace_list then
945 report_directions("stop run")
946 end
947 return head
948end
949
950local function process(head,direction,only_one,where)
951
952 local attr = getattr(head,a_directions)
953 local analyze_fences = getfences(attr)
954
955 local list, size = build_list(head)
956 local baselevel, dirfound = get_baselevel(head,list,size,direction)
957 if not dirfound and trace_details then
958 report_directions("no initial direction found, gambling")
959 end
960 if trace_details then
961 report_directions("before : %s",show_list(list,size,"original"))
962 end
963 resolve_explicit(list,size,baselevel)
964 resolve_levels(list,size,baselevel,analyze_fences)
965 insert_dir_points(list,size)
966 if trace_details then
967 report_directions("after : %s",show_list(list,size,"direction"))
968 report_directions("result : %s",show_done(list,size))
969 end
970 return apply_to_list(list,size,head,baselevel)
971end
972
973directions.installhandler(interfaces.variables.two,process)
974 |