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