typo-dir.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['typo-dir'] = {
    version   = 1.001,
    comment   = "companion to typo-dir.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"

-- When we started with this, there were some issues in luatex so we needed to take care of
-- intereferences. Some has been improved but we stil might end up with each node having a
-- dir property. Now, the biggest problem is that there is an official bidi algorithm but
-- some searching on the web shows that there are many confusing aspects and therefore
-- proposals circulate about (sometimes imcompatible ?) improvements. In the end it all boils
-- down to the lack of willingness to tag an input source. Of course tagging of each number
-- and fenced strip is somewhat over the top, but now it has to be captured in logic. Texies
-- normally have no problem with tagging but we need to handle any input. So, what we have
-- done here (over the years) is starting from what we expect to see happen, especially with
-- respect to punctation, numbers and fences. Eventually alternative algorithms will be provides
-- so that users can choose (the reason why suggestion sfor improvements circulate on the web
-- is that it is non trivial to predict the expected behaviour so one hopes that the ditor
-- and the rest of the machinery match somehow. Anyway, the fun of tex is that it has no hard
-- coded behavior. And ... we also want to have more debugging and extras and ... so we want
-- a flexible approach. In the end we will have:
-- = full tagging (mechanism turned off)
-- = half tagging (the current implementation)
-- = unicode version x interpretation (several depending on the evolution)

local next, type = next, type
local format, insert, sub, find, match = string.format, table.insert, string.sub, string.find, string.match

local nodes, node = nodes, node

local trace_textdirections  = false  trackers.register("typesetters.directions.text", function(v) trace_textdirections = v end)
local trace_mathdirections  = false  trackers.register("typesetters.directions.math", function(v) trace_mathdirections = v end)
local trace_directions      = false  trackers.register("typesetters.directions",      function(v) trace_textdirections = v trace_mathdirections = v end)

local one_too               = false  directives.register("typesetters.directions.onetoo", function(v) one_too = v end)

local report_textdirections = logs.reporter("typesetting","text directions")
----- report_mathdirections = logs.reporter("typesetting","math directions")

local band                  = bit32.band

local texsetattribute       = tex.setattribute
local unsetvalue            = attributes.unsetvalue

local nuts                  = nodes.nuts
local getnext               = nuts.getnext
local getattr               = nuts.getattr

local enableaction          = nodes.tasks.enableaction
local tracers               = nodes.tracers
local setcolor              = tracers.colors.set
local resetcolor            = tracers.colors.reset

local implement             = interfaces.implement

local directions            = typesetters.directions or { }
typesetters.directions      = directions

local a_directions          = attributes.private('directions')

local variables             = interfaces.variables
local v_global              = variables["global"]
local v_local               = variables["local"]
local v_on                  = variables.on
local v_yes                 = variables.yes

local m_enabled             = 0x00000040 -- 2^6 64
local m_global              = 0x00000080 -- 2^7
local m_fences              = 0x00000100 -- 2^8

local handlers              = { }
local methods               = { }
local lastmethod            = 0

local function installhandler(name,handler)
    local method = methods[name]
    if not method then
        lastmethod    = lastmethod + 1
        method        = lastmethod
        methods[name] = method
    handlers[method] = handler
    return method

directions.handlers       = handlers
directions.installhandler = installhandler

local function tomode(specification)
    local scope = specification.scope
    local mode
    if scope == v_global or scope == v_on then
        mode = m_enabled + m_global
    elseif scope == v_local then
        mode = m_enabled
        return 0
    local method = methods[specification.method]
    if method then
        mode = mode + method
        return 0
    if specification.fences == v_yes then
        mode = mode + m_fences
    return mode

local function getglobal(a)
    return a and a > 0 and band(a,m_global) ~= 0

local function getfences(a)
    return a and a > 0 and band(a,m_fences) ~= 0

local function getmethod(a)
    return a and a > 0 and a % m_enabled or 0

directions.tomode         = tomode
directions.getglobal      = getglobal
directions.getfences      = getfences
directions.getmethod      = getmethod
directions.installhandler = installhandler

-- beware: in dha we have character properties and in dua|b we have direction properties

function directions.setcolor(current,direction,reversed,mirror)
    if mirror then
    elseif direction == "l" then
        setcolor(current,reversed and "bidi:left:reversed" or "bidi:left:original")
    elseif direction == "r" then
        setcolor(current,reversed and "bidi:right:reversed" or "bidi:right:original")

implement {
    name      = "getbidimode",
    actions   = { tomode, context },
    arguments = {
            { "scope" },
            { "method" },
            { "fences" },

local enabled = false

local starttiming = statistics.starttiming
local stoptiming  = statistics.stoptiming

-- If we have hbox{!} then the hbox determines the direction but we can consider
-- a fast analysis, not that it matters much because there's nothing to swap in
-- the list unless one glyphs becomes multiple (can that really happen?).
-- \enabledirectives[typesetters.directions.onetoo]

function directions.handler(head,where,_,_,direction)
    local only_one = not getnext(head)
    if only_one and not one_too then
        return head
    local attr = getattr(head,a_directions)
    if not attr or attr == 0 then
        return head
    local method  = getmethod(attr)
    local handler = handlers[method]
    if not handler then
        return head
    head = handler(head,direction,only_one,where)
    return head

statistics.register("text directions", function()
    if enabled then
        return statistics.elapsedseconds(directions)

function directions.set(n) -- todo: names and numbers
    if not enabled then
        if trace_textdirections then
            report_textdirections("enabling directions handler")
        enabled = true
    if not n or n == 0 then
        n = unsetvalue
        -- maybe tracing

implement {
    name      = "setdirection",
    arguments = "integer",
    actions   = directions.set