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