%% This file is luabidi.sty
%% This is part of the luabidi package
%%
%% Copyright © 2009 Vafa Khalighi, 2013, 2019--2026 Arthur Reutenauer,
%% 2019--2026 Jürgen Spitzmüller, 2026 Udi Fogiel
%%
%%%% It may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3c
%% of this license or (at your option) any later version.
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{luabidi}[2026/04/10 v1.0
	Bidirectional typesetting in LuaTeX]

%
% General command and switches
%

\newif\if@RTL
\newif\if@RTLmain

\protected\def\setRTLmain{%
  \@RTLmaintrue
  \pagedirection \@ne
  \chardef\luabidi@currvlist@dir \@ne
  \setRTL
}

  % If the direction of the current vertical list
  % (\luabidi@currvlist@dir) is different from \pardir, then
  % \shapemode needs to be set to mirror \parshape in lists
  % and hangindent in @hangfrom. A value of 3 mirrors both;
  % -3 is persistent between paragraphs.

\chardef\luabidi@currvlist@dir\z@

\protected\def\luabidi@update@shapemode{%
  \ifnum\luabidi@currvlist@dir=\pardirection
    \shapemode \z@
  \else
    \shapemode=-3\relax
  \fi
}

\let\luabidi@everyvbox\everyvbox
\newtoks\everyvbox
\everyvbox\expandafter{\the\luabidi@everyvbox}
\luabidi@everyvbox{%
  \chardef\luabidi@currvlist@dir\bodydirection
  \luabidi@update@shapemode
  \the\everyvbox}

  % Most direction registers only serve internally as registers that are read
  % in different parts of LuaTeX's code, but \textdirection/\linedirection has two
  % additional effects: it updates a special direction stack (a stack that is used
  % to respect grouping and to correct the direction in line breaks), and if used
  % in horizontal mode it also creates dir nodes. To keep the stack simple and
  % avoid creating unnecessary nodes, we should probably update \textdirection only
  % when needed.
  
\def\luabidi@setdir#1#2{\ifnum #1=#2\else #1=#2\fi}

\protected\def\setRTL{%
  \@RTLtrue
  \bodydirection \@ne 
  \pardirection  \@ne
  \luabidi@setdir \textdirection \@ne
  \luabidi@update@shapemode
}

\protected\def\setLTR{%
  \@RTLfalse
  \bodydirection \z@
  \pardirection  \z@
  \luabidi@setdir \textdirection \z@
  \luabidi@update@shapemode
}

\protected\def\RTL{\par\setRTL}
\protected\def\endRTL{\par}
\protected\def\LTR{\par\setLTR}
\protected\def\endLTR{\par}

  % \LRE/\RLE are defined this way to avoid reading the text
  % as an argument, so that category code changes can be made inside
  % (to use verbatim, for example). Similarly to how plain TeX defines \footnote.
  
\let\n@xt=\
\protected\def\LRE{\afterassignment\luabidi@LRE \let\n@xt}
\protected\def\RLE{\afterassignment\luabidi@RLE \let\n@xt}
\def\luabidi@bracetext#1{\ifx\n@xt\bgroup\else
  \PackageError{luabidi}{Missing left brace after \string#1 has been substituted}{}\fi\bgroup}
\def\luabidi@LRE{\leavevmode\luabidi@bracetext\LRE\@RTLfalse\luabidi@setdir\linedirection\z@}
\def\luabidi@RLE{\leavevmode\luabidi@bracetext\RLE\@RTLtrue\luabidi@setdir\linedirection\@ne}

\protected\def\hboxR#1#{\hbox bdir\@ne #1\bgroup\@RTLtrue\let\n@xt}
\protected\def\vboxR#1#{\vbox bdir\@ne #1\bgroup\@RTLtrue\let\n@xt}
\protected\def\vtopR#1#{\vtop bdir\@ne #1\bgroup\@RTLtrue\let\n@xt}

\protected\def\hboxL#1#{\hbox bdir\z@  #1\bgroup\@RTLfalse\let\n@xt}
\protected\def\vboxL#1#{\vbox bdir\z@  #1\bgroup\@RTLfalse\let\n@xt}
\protected\def\vtopL#1#{\vtop bdir\z@  #1\bgroup\@RTLfalse\let\n@xt}

  % Aliases
\let\setRL=\setRTL
\let\setLR=\setLTR
\let\unsetRTL=\setLTR
\let\unsetLTR=\setRTL
\let\LR=\LRE
\let\RL=\RLE

%
% by default \leqno and \eqno are influenced by \predisplaydirection, which is set to -1 if
% \pardir and \mathdir differ at the start of display, so to make sure \eqno is on the right
% we need to test \predisplaydirection (remove if will be in kernel https://github.com/latex3/latex2e/issues/1956)
%

\let\luabidi@eqno=\@kernel@eqno
\let\luabidi@leqno=\@kernel@leqno
\protected\def\@kernel@eqno{\ifnum\predisplaydirection<\z@ \luabidi@leqno \else \luabidi@eqno \fi}
\protected\def\@kernel@leqno{\ifnum\predisplaydirection<\z@ \luabidi@eqno \else \luabidi@leqno \fi}

%
% The following registers are set to 1 to fix some bugs in the engine. See sections 3.3.3 and 7.5.3 of LuaTeX's manual.
%

\matheqdirmode \@ne
\breakafterdirmode \@ne
\mathemptydisplaymode \@ne   
\fixupboxesmode \@ne

%
% A helper macro for patching
%

\newcatcodetable\luabidi@cctab
\ExplSyntaxOn
\savecatcodetable\luabidi@cctab
\ExplSyntaxOff

\def\luabidi@replmacro #1{\expandafter\luabidi@replmacroA\expandafter#1\meaning#1\relax}
\def\luabidi@replmacroA #1#2:#3->#4\relax#5#6{%
   \expandafter\luabidi@replmacroB\expandafter#1\directlua{
      local prefixes="\luaescapestring{#2}"
      local params="\luaescapestring{#3}"
      local body="\luaescapestring{#4}"
      local find = "\luaescapestring{\unexpanded{#5}}"
      local replace = "\luaescapestring{\unexpanded{#6}}"
      prefixes = string.gsub(prefixes,"macro","")
      body     = string.gsub(body, find, replace, 1)
      tex.sprint(\the\luabidi@cctab, "{"..prefixes.."}{"..params.."}{"..body.."}")
   }%
}
\long\def\luabidi@replmacroB #1#2#3#4{\@firstofone#2\def#1#3{#4}}

%
% patches to math, tabulars and graphics
%

\luabidi@replmacro\equation\hb@xt@{\hbox bdir\mathdirection to}
\ExpandArgs{c}\luabidi@replmacro{[ }\hb@xt@{\hbox bdir\mathdirection to}

\AddToHook{package/amstext/after}{\luabidi@replmacro\textdef@\hbox{\hbox bdir\if@RTL\@ne\else\z@\fi}}

\AddToHook{package/amsmath/after}{%
  \luabidi@replmacro\intertext@\vbox{\vbox bdir\if@RTL\@ne\else\z@\fi}
  \AddToHook{env/align*/end}{\iftag@\else\tag*{}\fi}%
  \AddToHook{env/alignat*/end}{\iftag@\else\tag*{}\fi}%
}

\AtBeginDocument{
  \luabidi@replmacro\@tabular{\hbox\bgroup}
    {\hbox\bgroup\etokspre\everyhbox{%
      \everyhbox{\the\everyhbox}%
      \everyvbox{\the\everyvbox}%
      \mathdirection=\the\mathdirection}%
    \etokspre\everyvbox{%
      \everyhbox{\the\everyhbox}%
      \everyvbox{\the\everyvbox}%
      \mathdirection=\the\mathdirection}%
    \mathdirection=\if@RTL\@ne\else\z@\fi}%
}

\ExplSyntaxOn
\AddToHook{package/tabularray/after}{%
  \luabidi@replmacro \__tblr_get_vcenter_box:N
    {\tex_vcenter:D}
    {\bodydirection=\if@RTL\@ne\else\z@\fi\tex_vcenter:D}
}
\ExplSyntaxOff

\ExpandArgs{c}\luabidi@replmacro{underline }\hbox{\hbox bdir\if@RTL\@ne\else\z@\fi}

\AddToHook{package/graphics/after}{%
\luabidi@replmacro\rotatebox{\setbox\z@\hbox{{#2}}}
  {\setbox\z@\hbox bdir0{\hbox bdir\if@RTL\@ne\else\z@\fi{#2}}}%
}

\AddToHook{package/graphicx/after}{%
\luabidi@replmacro\Grot@box@std{\setbox\z@\hbox{{#2}}}
  {\setbox\z@\hbox bdir0{\hbox bdir\if@RTL\@ne\else\z@\fi{#2}}}%
\luabidi@replmacro\Grot@box@kv{\@begin@tempboxa\hbox{#3}}
  {\@begin@tempboxa{\hbox bdir0}{\hbox bdir\if@RTL\@ne\else\z@\fi{#3}}}%
\luabidi@replmacro\Grot@box{\setbox\z@\hbox}{\setbox\z@\hbox bdir0}
}

\AtBeginDocument{%
  \ifdefined\pgfpicture
    \luabidi@replmacro\pgfinterruptpicture${\if@RTL\setRTL\fi}%
    \luabidi@replmacro\pgfsys@typesetpicturebox\hbox{\hbox bdir\z@}%
    \luabidi@replmacro\pgf@picture\hbox{\hbox bdir\z@}%
  \fi
}

%
% Footnotes
%

  % These are defined in polyglossia. Provide simple fallbacks
\ProvideDocumentCommand\localnumeral{s}{\IfBooleanTF{#1}\@@localnumeral\@localnumeral}
\providecommand*\@localnumeral[1]{#1}
\providecommand*\@@localnumeral[1]{\ExpandArgs{c}\@arabic{c@#1}}

\let\footnotemarkLR\footnotemark
\let\footnotemarkRL\footnotemark

\DeclareDocumentCommand\LTRfootnote{o+m}{%
  \begingroup
    \IfNoValueTF{#1}%
      {\footnotemarkLR
       \renewcommand\thefootnote{\localnumeral*{footnote}}}%
      {\footnotemarkLR[#1]%
       \renewcommand\thefootnote{\localnumeral{#1}}}%
    \setLTR\footnotetext{#2}%
  \endgroup
}

\DeclareDocumentCommand\RTLfootnote{o+m}{%
  \begingroup
    \IfNoValueTF{#1}%
      {\footnotemarkRL
       \renewcommand\thefootnote{\localnumeral*{footnote}}}%
      {\footnotemarkRL[#1]%
       \renewcommand\thefootnote{\localnumeral{#1}}}%
    \setRTL\footnotetext{#2}%
  \endgroup
}

  % backwards compatibility
\let\Footnote\LTRfootnote

%
% Footnote rules
%

  % adjustable rule length
\newlength\footnoterulewidth
\setlength\footnoterulewidth{.4\columnwidth}

  % The left, right and full width rules
\chardef\luabidi@fnr@dir\z@
\def\luabidi@fnr{%
  \hbox bdir\luabidi@fnr@dir to \columnwidth
  {\vbox{\kern -3\p@
   \hrule\@width\footnoterulewidth\kern2.6\p@}\hfil}}
\def\luabidi@textwidth@fnr{%
   \kern-3\p@\hrule\@width\textwidth\kern2.6\p@}

  % auto rule
  % this simply traverse the node list of the footnote box,
  % and checks the direction of the first line.
  % TODO what if the lines are inside a box?
\directlua{
  local line_sub = table.swapped(node.subtypes("hlist")).line
  local luafnalloc = luatexbase.new_luafunction('luabidi@auto@fnr')
  token.set_lua('luabidi@auto@fnr', luafnalloc)
  lua.get_functions_table()[luafnalloc] = function() 
    local fnbox = tex.box["footins"]
    for n, sb in node.traverse_id(node.id('hlist'),fnbox.list) do
      if sb == line_sub then
        token.set_char("luabidi@fnr@dir",n.direction)
        return token.unchecked_put_next({token.create("luabidi@fnr")})
      end      
    end
  end
}

  % switches for the rule position
\def\leftfootnoterule{\glet\footnoterule\luabidi@fnr\chardef\luabidi@fnr@dir\z@}
\def\rightfootnoterule{\glet\footnoterule\luabidi@fnr\chardef\luabidi@fnr@dir\@ne}
\def\textwidthfootnoterule{\glet\footnoterule\luabidi@textwidth@fnr}
\def\autofootnoterule{\glet\footnoterule\luabidi@auto@fnr}

%
% Package options
%

\DeclareOption{arabmaths}{\mathdirection=\@ne}
\DeclareOption{autofootnoterule}{\autofootnoterule}
\DeclareOption{textwidthfootnoterule}{\textwidthfootnoterule}
\ProcessOptions

%
% output routine
%

  % When a page box is shipped, the box direction most likely,
  % needs to have the direction of \pagedirection,
  % otherwise most of the text will be outside of the media box (see the documentation).
  % Since the box is created in vertical mode, and its direction is not specified,
  % the direction is determined by \bodydirection. Also a horizontal box containing
  % several columns should probably have that direction, so that the columns will be
  % in the correct order. If there is a column break in the middle of a paragraph of a RTL
  % text, the value of \bodydirection will be 1 in the output routine, so we need
  % to set the correct value there as it might be wrong.

\directlua{
local runtoks, putnext, currbodydir = tex.runtoks, token.unchecked_put_next
local zero, one = token.create("z@"), token.create("@ne")
local bodydir, pagedir = token.create("bodydir"), token.create("pagedir")
local bodydirection = token.create("bodydirection")
luatexbase.add_to_callback("pre_output_filter", function() 
  currbodydir = tex.bodydirection
  runtoks(function()
    putnext({bodydir,pagedir})
  end)
  return true end, "luabidi.pre_output")
luatexbase.add_to_callback("buildpage_filter", function(info) 
  if info \string~= "after_output" then return true end
  runtoks(function()
    local num = currbodydir == 0 and zero or one
    putnext({bodydirection,num})
  end)
  return true end, "luabidi.post_output")
}

%
% workaround LuaTeX bug (see https://mailman.ntg.nl/archives/list/dev-luatex@ntg.nl/thread/2PZX3W7322ZZZM4YB3WZVTO5EF4YKXHY/)
%

\directlua{
luatexbase.add_to_callback("pre_linebreak_filter",
function(head)
  local stack = 0
  for n, sb in node.traverse_id(node.id('dir'),head) do
    stack = stack + 1-2*sb
    if stack == -1 then
      head, n = node.remove(head,n)
      return head
    end
  end
  return head
end, "luabidi.remove_spurious_enddir")
}

\endinput
