% Optional comments
\_def \_unibidilua_version {1.1, 2026-09-18}
\_codedecl \unibidilua {Unicode Bidi Algorithm for OpTeX <\_unibidilua_version>}

\_initunifonts
\_directlua{
  local unibidi = require('unibidi-lua-interface')
  luatexbase.add_to_callback("pre_shaping_filter",unibidi.reorder,"unibidi-lua")
  luatexbase.add_to_callback("post_linebreak_filter",unibidi.resetlines,"unibidi-lua")
}

\_endcode

\sec Usage

To use the package, as with other packages, you can do ¦\load[unibidi-lua]¦,
¦\usepackage{unibidi-lua}¦ or ¦\input unibidi-lua¦ if you are using \OpTeX,
\LaTeX or Plain respectively. The process function is added to the
¦pre_shaping_filter¦ when you load the package.

To modify things you can use the ¦\unibidilua¦ macro,
which accepts key-value pairs separated by a space.

\def\key #1:{{\bf #1:}\hskip1em\ignorespaces}

The macro accepts the following keywords:

\begitems \style O
* \key enable: This key accepts a boolean value. When true, the unibidi-lua
   process function is active in the ¦pre_shaping_filter¦ callback. When false,
   the callback remains registered but processing is skipped. Default is true
   when the package is loaded.

* \key fences: This key accepts a boolean value. It allows to disable or
   enable to consider matched brackets when resolving directional levels
   (\rulelink{N0}). 
   Default is true when the package is loaded.
   
* \key nsm: This key accepts a boolean value. It allows to disable or
   enable reordering of combining marks after the reordering of the text
   (\rulelink{L3}).
   This key is only relevant for the string functions, as there is
   no reordering in the nodes functions (dir nodes are used there instead).
   Default is true when the package is loaded.
   
* \key mirror: This key accepts a boolean value. It allows to disable or
   enable mirroring of characters (\rulelink{L4}).
   Default is true when the package is loaded.

* \key linereset: This key accepts a boolean value. When true, a
   ¦post_linebreak_filter¦ pass applies rule \rulelink{L1} clause four on each
   line: it moves the trailing whitespace of every line out of 
   the inner directional runs, so it renders in
   the paragraph embedding direction, at the visual end of the line.
   Because the node path resolves the paragraph as a single line, without this
   the trailing whitespace of an interior line keeps the direction of the run it
   ends, which can misplace it in bidirectional text.
   This key is only relevant for the node path (the string functions already
   apply \rulelink{L1} per line, given the line breaks). It is on by default.

* \key remove: This key accepts one of ¦none¦, ¦controls¦, or ¦full¦. This controls
   which characters are removed during processing. With ¦none¦, nothing is removed,
   with ¦controls¦ only ¦LRE¦, ¦RLE¦, ¦LRO¦, ¦RLO¦, ¦PDF¦ characters will be removed,
   and with ¦full¦ all characters specified in
   \rulelink{X9} are removed.

* \key setdir: This key accepts an integer (or a range of integers)
   representing Unicode code points, and a direction value. It sets the
   directional property for the specified character(s).
   Example: ¦setdir `\A l¦ or ¦setdir `\A-`\Z al¦.

* \key setmirror: This key accepts an integer (or a range of integers)
   representing Unicode code points, and mirror data. It sets the mirroring
   property for the specified character(s), indicating what character they
   should mirror to in RTL contexts.
   Example: ¦setmirror `\( `\)¦.

* \key setbracket: This key accepts an integer (or a range of integers)
   representing Unicode code points, and a bracket type value (o (open) or c (close)) . It sets the
   bracket type property for the specified character(s). Example: ¦setbracket `\( o¦.

* \key baselevel: This key accepts a Lua function that determines the base
   directionality level of the text. See ¦bidi.set("baselevel")¦ in \ref[lua]{section~@}.

* \key mirrorchar: This key accepts a Lua function that applies the mirroring on nodes.
   See ¦bidi.set("mirrorchar")¦ in \ref[lua]{section~@}.

* \key startlevel: This key accepts a Lua function that determines from which level
  to insert direction nodes. See ¦bidi.set("startlevel")¦ in \ref[lua]{section~@}.
\enditems

Note that you can also load the \TeX/ side interface without loading the package file using
\begtt
\directlua{require('unibidi-lua-interface')}
\endtt
This sets up the \TeX/ interface macros but does not add any function to a callback,
and does not ensure ¦luaotfload¦ is loaded. The return value is a table with a
¦reorder¦ function and a ¦resetlines¦ function that can be added to callbacks
manually:
\begtt
\directlua{
  local unibidi = require('unibidi-lua-interface')
  luatexbase.add_to_callback("pre_shaping_filter", unibidi.reorder, "unibidi-lua")
  luatexbase.add_to_callback("post_linebreak_filter", unibidi.resetlines, "unibidi-lua")
}
\endtt

\sec[lua] Lua API

If you just want the Lua API, you can load it directly using

\begtt
local bidi = require('unibidi-lua')
\endtt

This does not define the \TeX/ interface nor add a function to a callback,
and works with ¦texlua¦ and plain ¦lua¦ as well as \LuaTeX/. Note that the
node functions are only available when running under \LuaTeX/.

\secc Options

\begitems
* ¦bidi.set(key, value)¦: Set an option. The following keys are accepted:
  \begitems \style -
  * ¦"fences"¦: boolean, enable/disable bracket pair resolution (\rulelink{N0}).
  * ¦"nsm"¦: boolean, enable/disable combining mark reordering (\rulelink{L3}).
    Only relevant for string processing functions.
  * ¦"mirror"¦: boolean, enable/disable character mirroring (\rulelink{L4}).
  * ¦"remove"¦: string, one of ¦"none"¦, ¦"controls"¦, or ¦"full"¦.
    Controls which characters are removed during processing.
  * ¦"baselevel"¦: function, a custom function to determine the base
    directionality level of the paragraph. See the source code for more
    details.
  * ¦"mirrorchar"¦: function, called for each glyph that has a mirror
    character. Receives the direct node and the mirror codepoint, and is responsible
    for applying the mirroring. The default implementation sets the character
    to its mirror for all non HarfBuzz fonts,
    as HarfBuzz handles mirroring (including using OpenType features).
    Note that this does not check OpenType mirroring features
    (¦rtlm¦/¦rtla¦); fonts that rely on those features for mirroring may
    not render correctly in node mode\fnote{If you want you can implement it using this hook.
    See for example\nl \url{https://github.com/latex3/luaotfload/blob/bidi-dev/src/luaotfload-mirror.lua}}.
    Only relevant for node processing functions.
  * ¦"startlevel"¦: function, called with the base level to determine
    the minimum embedding level from which direction nodes are inserted.
    The default returns the base level itself.
    
    For example consider the following
    \begtt
\hbox bdir1 {קו 42}
    \endtt
    The list of nodes we start with is something like
    \nodesinit
    With the default ¦baselevel¦ function, the bidi algorithm is run
    with base level 1 (the direction of the hbox containing the text). The resolved levels are
    {\tt\levels}.
    Direction nodes are then inserted starting from ¦startlevel¦ to mark
    the boundaries of each directional run. With the default ¦startlevel¦
    function the resulting node list is:
    \nodesdefault
    The default inserts direction nodes starting from the base level, ensuring
    that the logical reading order is preserved if the box is later unboxed
    (since unboxing discards the box direction), or if a custom ¦baselevel¦
    function assigns a different level than the containing box direction.
    If unboxing and custom base level functions are not a concern, the
    outer direction nodes can be omitted by returning ¦baselevel+1¦:
    \begtt
bidi.set("startlevel", function(baselevel) return baselevel+1 end)
    \endtt
    which produces:
    \nodescustom
    Only relevant for node processing functions.
  \enditems
* ¦bidi.get(key)¦: Get the current value of an option. Accepts the same
  keys as ¦bidi.set¦.
\enditems

\secc Data tables

\begitems
* ¦bidi.directions¦: Table containing character directional properties.
  Indexed by Unicode code point, returns the bidi class string
  (e.g. ¦"l"¦, ¦"r"¦, ¦"al"¦, ¦"an"¦, etc.).
* ¦bidi.mirrors¦: Table containing character mirroring mappings.
  Indexed by Unicode code point, returns the code point of the
  mirrored character.
* ¦bidi.brackets¦: Table containing bracket type classifications.
  Indexed by Unicode code point, returns ¦"o"¦ for opening brackets,
  ¦"c"¦ for closing brackets.
\enditems

\secc String functions

\begitems
* ¦bidi.string.reorder(str, direction, where)¦: Apply the Unicode Bidirectional
  Algorithm to the string ¦str¦ and return the visually reordered string.
  ¦direction¦ is ¦0¦ for LTR, ¦1¦ for RTL, or ¦-1¦ (or ¦nil¦) for auto-detect
  (or it can mean something else if the base level function is replaced).
  ¦where¦ is just a value that is passed to the base level function,
  and the default one does not use it.
* ¦bidi.string.levels(str, direction, where)¦: Apply the Unicode Bidirectional
  Algorithm to the string ¦str¦ and return two values: a levels array and
  a reorder array. The levels array maps each character position to its
  resolved bidi level, or ¦false¦ if the character was removed by \rulelink{X9}.
  The reorder array maps each visual position to the original logical index
  of the character.
* ¦bidi.codepoints.reorder(codepoints, direction, where)¦: Same as
  ¦bidi.string.reorder¦ but accepts and returns an array of codepoints
  instead of a string.
* ¦bidi.codepoints.levels(codepoints, direction, where)¦: Same as
  ¦bidi.string.levels¦ but accepts an array of codepoints instead of a string.
\enditems

The functions above treat the whole input as a single line. When the text is
to be broken into several lines, the per-line rules \rulelink{L1} (resetting
separators and trailing whitespace to the base level) and \rulelink{L2}
(reordering) must be applied on each line, at line boundaries that are usually
only known after shaping and line breaking. Since the base level resolution
(rules \rulelink{P2} through \rulelink{I2}) is independent of the line breaks
(and of glyph widths), it can be computed once and reused. This is what the
paragraph objects are for.

\begitems
* ¦bidi.string.paragraph(str, direction, where)¦: Resolve the embedding levels
  of ¦str¦ (rules \rulelink{P2} through \rulelink{I2}) and return a paragraph
  object. The ¦direction¦ and ¦where¦ arguments are as in ¦bidi.string.reorder¦.
  Unlike the flat functions above, a paragraph object owns its own storage and
  remains valid across later calls into the module, so it can be reused for
  several line breakings. The object provides two methods, both taking an
  optional ¦linebreaks¦ argument:
  \begitems \style -
  * ¦paragraph:reorder(linebreaks)¦: Apply \rulelink{L1} and \rulelink{L2} on
    each line and return the visually reordered string.
  * ¦paragraph:levels(linebreaks)¦: Return a levels array and a reorder array,
    as in ¦bidi.string.levels¦, but with \rulelink{L1} applied on each line.
  \enditems
  ¦linebreaks¦ is an array of line ends, each the position one past the last
  character of a line, so line ¦k¦ spans the characters from the previous entry
  (or ¦1¦) up to ¦linebreaks[k]-1¦. The values must be strictly increasing, and
  the last must equal the length of the text plus one. When ¦linebreaks¦ is
  omitted (or ¦nil¦), the whole text is treated as a single line, so that
  ¦paragraph:reorder()¦ matches ¦bidi.string.reorder(str, direction, where)¦.
* ¦bidi.codepoints.paragraph(codepoints, direction, where)¦: Same as
  ¦bidi.string.paragraph¦ but accepts an array of codepoints; the resulting
  object's ¦:reorder¦ returns an array of codepoints instead of a string.
\enditems

The options affecting level resolution and mirroring (¦fences¦ and ¦mirror¦)
are applied when the object is constructed, while ¦nsm¦ and ¦remove¦ take effect
when ¦:reorder¦ or ¦:levels¦ is called. Change these options before constructing
the paragraph if in doubt.

\secc Node functions
There are two variants of the node functions: ¦bidi.direct¦ which operates
on direct node references (as used in ¦node.direct¦), and ¦bidi.node¦ which
operates on regular node references.
\begitems
* ¦bidi.direct.reorder(head, direction, where)¦: Apply the Unicode
  Bidirectional Algorithm to the node list starting at ¦head¦ (a direct
  node reference). Direction nodes are inserted to mark directional runs.
  Returns the (possibly new) head of the node list.
* ¦bidi.direct.levels(head, direction, where)¦: Apply the Unicode Bidirectional
  Algorithm to the node list starting at ¦head¦ (a direct node reference)
  and return a levels array. The levels array maps each node position to
  its resolved bidi level, or ¦false¦ if the node was removed by \rulelink{X9}.
* ¦bidi.node.reorder(head, direction, where)¦: Same as ¦bidi.direct.reorder¦
  but accepts and returns regular node references.
* ¦bidi.node.levels(head, direction, where)¦: Same as ¦bidi.direct.levels¦ but
  accepts regular node references.
* ¦bidi.direct.resetlines(head)¦: Apply rule \rulelink{L1} clause four to a list
  of lines (as passed to ¦post_linebreak_filter¦), ¦head¦ being a direct node
  reference. For each line it moves the trailing whitespace out of the inner
  directional runs, keeping it inside the base level scope, so it renders in the
  paragraph embedding direction. It relies on the base level tagged onto the
  paragraph node by ¦bidi.direct.reorder¦.
* ¦bidi.node.resetlines(head)¦: Same as ¦bidi.direct.resetlines¦ but accepts and
  returns regular node references.
\enditems

\_doc
%  optex -jobname unibidi-lua-doc '\docgen unibidi-lua'
\load [doc]
\verbchar¦
\directlua{
local bidi = require('unibidi-lua')
local function show_nodes(head,csname)
    local function nodename(n)
        if n.id == node.id("glyph") then
            return string.format('glyph  "\%s"', utf8.char(node.getchar(n)))
        elseif n.id == node.id("glue") then
            return string.format('glue   " "')
        elseif n.id == node.id("dir") then
            return string.format('dir    \%s', n.dir)
        else
            return string.format('\%s \%d', node.type(n.id), sub)
        end
    end
    local lines = {"\nbb begtt"}
    local n = head
    while n do
        lines[\csstring\#lines+1] = nodename(n)
        n = n.next
    end
    lines[\csstring\#lines+1] = "\nbb endtt"
    lines[\csstring\#lines+1] = ""
    define_lua_command(csname,function() tex.print(lines) end)
end
callback.add_to_callback("pre_shaping_filter",function(head,_,dir)
    show_nodes(head,"nodesinit")
    local levels = bidi.node.levels(head,dir)
    define_lua_command("levels",function() tex.print(levels) end)
    show_nodes(bidi.node.reorder(head,dir),"nodesdefault")
    bidi.set("startlevel",function(baselevel) return baselevel+1 end)
    show_nodes(bidi.node.reorder(head,dir),"nodescustom")
    callback.remove_from_callback("pre_shaping_filter","foo")
end,"foo")
}
\hbox bdir1 {קו 42}
\edef\rulelink#1{\noexpand\ulink[https://www.unicode.org/reports/tr9/\csstring\##1]{rule #1}}
\addto\_fontfeatures{fallback=miriam;}
\directlua{luaotfload.add_fallback("miriam", {"name:MiriamMonoCLM:mode=harf;"})}
\overfullrule=0pt
\tit Unicode Bidi Algorithm Implementation for \OpTeX, \LaTeX, and Plain
\hfill Version: \_unibidilua_version \par
\centerline{\it Udi Fogiel, 2025-2026}
\parindent0pt\parskip5pt\parfillskip=20ptplus1fill

The unibidi-lua LuaTeX package is an implementation of the
\ulink[https://www.unicode.org/reports/tr9/]{Unicode Annex \#9} for \OpTeX,
\LaTeX/ and Plain \LuaTeX/ formats. The core Lua module also works
with ¦texlua¦ and plain ¦lua¦. It allows typesetting bidirectional
documents without the need of special markup.

\printdoctail unibidi-lua.opm % prints the documentation written after \_endcode
\bye
\_cod
\endinput
