if not modules then modules = { } end modules ['typo-mir'] = { version = 1.001, comment = "companion to typo-mir.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local utf = unicode.utf8 local next, type = next, type local format, insert, sub, find, match = string.format, table.insert, string.sub, string.find, string.match local utfchar = utf.char -- vertical space handler local trace_mirroring = false trackers.register("nodes.mirroring", function(v) trace_mirroring = v end) local has_attribute = node.has_attribute local unset_attribute = node.unset_attribute local set_attribute = node.set_attribute local traverse_id = node.traverse_id local insert_node_before = node.insert_before local insert_node_after = node.insert_after local remove_node = nodes.remove local glyph = node.id("glyph") local whatsit = node.id("whatsit") local mthnode = node.id('math') local fontdata = fonts.ids local fontchar = fonts.chr local chardata = characters.data --~ Analysis by Idris: --~ --~ 1. Assuming the reading- vs word-order distinction (bidi-char types) is governing; --~ 2. Assuming that 'ARAB' represents an actual arabic string in raw input order, not word-order; --~ 3. Assuming that 'BARA' represent the correct RL word order; --~ --~ Then we have, with input: LATIN ARAB --~ --~ \textdir TLT LATIN ARAB => LATIN BARA --~ \textdir TRT LATIN ARAB => LATIN BARA --~ \textdir TRT LRO LATIN ARAB => LATIN ARAB --~ \textdir TLT LRO LATIN ARAB => LATIN ARAB --~ \textdir TLT RLO LATIN ARAB => NITAL ARAB --~ \textdir TRT RLO LATIN ARAB => NITAL ARAB -- elseif d == "es" then -- European Number Separator -- elseif d == "et" then -- European Number Terminator -- elseif d == "cs" then -- Common Number Separator -- elseif d == "nsm" then -- Non-Spacing Mark -- elseif d == "bn" then -- Boundary Neutral -- elseif d == "b" then -- Paragraph Separator -- elseif d == "s" then -- Segment Separator -- elseif d == "ws" then -- Whitespace -- elseif d == "on" then -- Other Neutrals mirroring = mirroring or { } mirroring.strip = false mirroring.attribute = attributes.private("mirroring") local state = attributes.private('state') local mirrora = attributes.private('mirroring') local directions = characters.directions -- maybe make a special mirror table -- todo: delayed inserts here -- todo: get rid of local functions here -- beware, math adds whatsits afterwards so that will mess things up local skipmath = true local finish, autodir, embedded, override, done = nil, 0, 0, 0, false local list, glyphs = nil, false local finished, finidir, finipos = nil, nil, 1 local head, current, inserted = nil, nil, nil local function finish_auto_before() head, inserted = insert_node_before(head,current,nodes.textdir("-"..finish)) finished, finidir = inserted, finish if trace_mirroring then insert(list,#list,format("finish %s",finish)) finipos = #list-1 end finish, autodir, done = nil, 0, true end local function finish_auto_after() head, current = insert_node_after(head,current,nodes.textdir("-"..finish)) finished, finidir = current, finish if trace_mirroring then list[#list+1] = format("finish %s",finish) finipos = #list end finish, autodir, done = nil, 0, true end local function force_auto_left_before() if finish then finish_auto_before() end if embedded >= 0 then finish, autodir, done = "TLT", 1, true else finish, autodir, done = "TRT", -1, true end if finidir == finish then remove_node(head,finished,true) if trace_mirroring then list[finipos] = list[finipos].." (deleted)" insert(list,#list,format("start %s (deleted)",finish)) end else head, inserted = insert_node_before(head,current,nodes.textdir("+"..finish)) if trace_mirroring then insert(list,#list,format("start %s",finish)) end end end local function force_auto_right_before() if finish then finish_auto_before() end if embedded <= 0 then finish, autodir, done = "TRT", -1, true else finish, autodir, done = "TLT", 1, true end if finidir == finish then remove_node(head,finished,true) if trace_mirroring then list[finipos] = list[finipos].." (deleted)" insert(list,#list,format("start %s (deleted)",finish)) end else head, inserted = insert_node_before(head,current,nodes.textdir("+"..finish)) if trace_mirroring then insert(list,#list,format("start %s",finish)) end end end function mirroring.process(namespace,attribute,start) -- todo: make faster if not start.next then return start, false end head, current, inserted = start, start, nil finish, autodir, embedded, override, done = nil, 0, 0, 0, false list, glyphs = trace_mirroring and { }, false finished, finidir, finipos = nil, nil, 1 local stack, top, obsolete = { }, 0, { } local lro, rlo, prevattr, inmath = false, false, 0, false while current do local id = current.id --~ print(id,attribute,has_attribute(current,attribute)) if skipmath and id == mthnode then local subtype = current.subtype if subtype == 0 then -- begin math inmath = true elseif subtype == 1 then inmath = false else -- todo end current = current.next elseif inmath then current = current.next else local attr = has_attribute(current,attribute) if attr and attr > 0 then -- unset_attribute(current,attribute) -- slow, needed? if attr == 1 then -- bidi parsing mode elseif attr ~= prevattr then -- no pop, grouped driven (2=normal,3=lro,4=rlo) if attr == 3 then if trace_mirroring then list[#list+1] = format("override right -> left (lro) (bidi=%s)",attr) end lro, rlo = true, false elseif attr == 4 then if trace_mirroring then list[#list+1] = format("override left -> right (rlo) (bidi=%s)",attr) end lro, rlo = false, true else if trace_mirroring and current ~= head then list[#list+1] = format("override reset (bidi=%s)",attr) end lro, rlo = false, false end prevattr = attr end end if id == glyph then glyphs = true if attr and attr > 0 then local char = current.char local d = directions[char] if rlo or override > 0 then if d == "l" then if trace_mirroring then list[#list+1] = format("char %s (%s / U+%04X) of class %s overidden to r (bidi=%s)",utfchar(char),char,char,d,attr) end d = "r" elseif trace_mirroring then if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) else -- todo: rle lre list[#list+1] = format("char %s (%s / U+%04X) of class %s (bidi=%s)",utfchar(char),char,char,d,attr) end end elseif lro or override < 0 then if d == "r" or d == "al" then set_attribute(current,state,4) -- maybe better have a special bidi attr value -> override (9) -> todo if trace_mirroring then list[#list+1] = format("char %s (%s / U+%04X) of class %s overidden to l (bidi=%s) (state=isol)",utfchar(char),char,char,d,attr) end d = "l" elseif trace_mirroring then if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) else -- todo: rle lre list[#list+1] = format("char %s (%s / U+%04X) of class %s (bidi=%s)",utfchar(char),char,char,d,attr) end end elseif trace_mirroring then if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) else -- todo: rle lre list[#list+1] = format("char %s (%s / U+%04X) of class %s (bidi=%s)",utfchar(char),char,char,d,attr) end end if d == "on" then local mirror = chardata[char].mirror -- maybe make a special mirror table if mirror and fontchar[current.font][mirror] then -- todo: set attribute if autodir < 0 then current.char = mirror done = true --~ elseif left or autodir > 0 then --~ if not is_right(current.prev) then --~ current.char = mirror --~ done = true --~ end end end elseif d == "l" or d == "en" then -- european number if autodir <= 0 then force_auto_left_before() end elseif d == "r" or d == "al" then -- arabic number if autodir >= 0 then force_auto_right_before() end elseif d == "an" then -- arabic number -- actually this is language dependent ... if autodir <= 0 then --~ force_auto_right_before() force_auto_left_before() end elseif d == "lro" then -- Left-to-Right Override -> right becomes left if trace_mirroring then list[#list+1] = "override right -> left" end top = top + 1 stack[top] = { override, embedded } override = -1 obsolete[#obsolete+1] = current elseif d == "rlo" then -- Right-to-Left Override -> left becomes right if trace_mirroring then list[#list+1] = "override left -> right" end top = top + 1 stack[top] = { override, embedded } override = 1 obsolete[#obsolete+1] = current elseif d == "lre" then -- Left-to-Right Embedding -> TLT if trace_mirroring then list[#list+1] = "embedding left -> right" end top = top + 1 stack[top] = { override, embedded } embedded = 1 obsolete[#obsolete+1] = current elseif d == "rle" then -- Right-to-Left Embedding -> TRT if trace_mirroring then list[#list+1] = "embedding right -> left" end top = top + 1 stack[top] = { override, embedded } embedded = 1 obsolete[#obsolete+1] = current elseif d == "pdf" then -- Pop Directional Format -- override = 0 if top > 0 then local s = stack[top] override, embedded = s[1], s[2] top = top - 1 if trace_mirroring then list[#list+1] = format("state: override: %s, embedded: %s, autodir: %s",override,embedded,autodir) end else if trace_mirroring then list[#list+1] = "pop (error, too many pops)" end end obsolete[#obsolete+1] = current end elseif trace_mirroring then local char = current.char local d = directions[char] list[#list+1] = format("char %s (%s / U+%04X) of class %s (no bidi)",utfchar(char),char,char,d or "?") end elseif id == whatsit then if finish then finish_auto_before() end local subtype = current.subtype if subtype == 6 then local dir = current.dir local d = sub(dir,2,2) if d == 'R' then -- find(dir,".R.") / dir == "TRT" autodir = -1 else autodir = 1 end embeddded = autodir if trace_mirroring then list[#list+1] = format("pardir %s",dir) end elseif subtype == 7 then local dir = current.dir -- local sign = sub(dir,1,1) -- local dire = sub(dir,3,3) local sign, dire = match(dir,"^(.).(.)") if dire == "R" then if sign == "+" then finish, autodir = "TRT", -1 else finish, autodir = nil, 0 end else if sign == "+" then finish, autodir = "TLT", 1 else finish, autodir = nil, 0 end end if trace_mirroring then list[#list+1] = format("textdir %s",dir) end end else if trace_mirroring then list[#list+1] = format("node %s (subtype %s)",node.type(id),current.subtype) end if finish then finish_auto_before() end end local cn = current.next if not cn then if finish then finish_auto_after() end end current = cn end end if trace_mirroring and glyphs then logs.report("bidi","start log") for i=1,#list do logs.report("bidi","%02i: %s",i,list[i]) end logs.report("bidi","stop log") end if done and mirroring.strip then local n = #obsolete if n > 0 then for i=1,n do remove_node(head,obsolete[i],true) end logs.report("bidi","%s character nodes removed",n) end end return head, done end --~ local function is_right(n) -- keep ! --~ if n then --~ local id = n.id --~ if id == glyph then --~ local attr = has_attribute(n,attribute) --~ if attr and attr > 0 then --~ local d = directions[n.char] --~ if d == "r" or d == "al" then -- override --~ return true --~ end --~ end --~ end --~ end --~ return false --~ end chars.handle_mirroring = nodes.install_attribute_handler { name = "mirroring", namespace = mirroring, processor = mirroring.process, } function mirroring.enable() tasks.enableaction("processors","chars.handle_mirroring") end