texlive[74566] Master/texmf-dist: babel (11mar25)

commits+karl at tug.org commits+karl at tug.org
Tue Mar 11 21:05:14 CET 2025


Revision: 74566
          https://tug.org/svn/texlive?view=revision&revision=74566
Author:   karl
Date:     2025-03-11 21:05:14 +0100 (Tue, 11 Mar 2025)
Log Message:
-----------
babel (11mar25)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/babel/README.md
    trunk/Master/texmf-dist/doc/latex/babel/babel-code.pdf
    trunk/Master/texmf-dist/doc/latex/babel/babel.pdf
    trunk/Master/texmf-dist/source/latex/babel/babel.dtx
    trunk/Master/texmf-dist/source/latex/babel/babel.ins
    trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx
    trunk/Master/texmf-dist/source/latex/babel/locale.zip
    trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic.lua
    trunk/Master/texmf-dist/tex/generic/babel/babel.def
    trunk/Master/texmf-dist/tex/generic/babel/babel.sty
    trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg
    trunk/Master/texmf-dist/tex/generic/babel/locale/cs/babel-cs.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1901.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1996.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1901.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1996.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1901.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1996.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/nl/babel-nl.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/sk/babel-sk.ini
    trunk/Master/texmf-dist/tex/generic/babel/luababel.def
    trunk/Master/texmf-dist/tex/generic/babel/nil.ldf
    trunk/Master/texmf-dist/tex/generic/babel/txtbabel.def
    trunk/Master/texmf-dist/tex/generic/babel/xebabel.def

Added Paths:
-----------
    trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-0.lua
    trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-1.lua
    trunk/Master/texmf-dist/tex/generic/babel/tests-babel-bidi-basic.lua

Modified: trunk/Master/texmf-dist/doc/latex/babel/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/babel/README.md	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/doc/latex/babel/README.md	2025-03-11 20:05:14 UTC (rev 74566)
@@ -1,6 +1,6 @@
-## Babel 25.4
+## Babel 25.5
 
-2025-02-14
+2025-03-10
 
 `Babel` is the multilingual framework to localize documents. It fully
 supports pdfLaTeX and the Unicode engines LuaLaTeX and XeLaTeX. A few
@@ -21,9 +21,9 @@
 
 The latest stable version is available on <https://ctan.org/pkg/babel>.
 
-Changes in version 25.4 are described in:
+Changes in version 25.5 are described in:
 
-https://latex3.github.io/babel/news/whats-new-in-babel-25.4.html
+https://latex3.github.io/babel/news/whats-new-in-babel-25.5.html
 
 Apart from the manual, you can find information and examples in:
 
@@ -59,9 +59,14 @@
 
 ### Summary of latest changes
 ```
-25.4   2025-02-14
-       New locales for ancient languages: Etruscan, Old Persian,
-       Old Irish, Ugaritic, Carian, Lycian, Lydian, Sabaean.
+25.5   2025-03-10
+       * German: new transform longs.unifraktur, with heuristic rules
+         for long s.
+       * Dutch: new transform diaeresis.hyphen.
+       * Fixes:
+         - Some wrong bidi text in \hbox.
+         - Extra space with CJK and lazy loading. 
+         - LaTeX hook for newly defined font families.
 ```
 
 ### Previous changes

Modified: trunk/Master/texmf-dist/doc/latex/babel/babel-code.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/babel/babel.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/source/latex/babel/babel.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/babel.dtx	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/source/latex/babel/babel.dtx	2025-03-11 20:05:14 UTC (rev 74566)
@@ -37,7 +37,7 @@
   lang       = en-001,
 }
 \ProvidesFile{babel.dtx}%
-  [2025/02/14 v25.4
+  [2025/03/10 v25.5
    The multilingual framework for pdfLaTeX, LuaLaTeX and XeLaTeX] 
 \documentclass{ltxdoc}
 \GetFileInfo{babel.dtx}
@@ -373,8 +373,10 @@
 
 \begin{note}
   Now that the recommend engine for \LaTeX\ is \luatex, you can read
-  \href{https://latex3.github.io/babel/guides/migrating-pdftex-luatex.html}{Migrating
-  from pdfTeX to LuaTeX}.
+  \href{https://latex3.github.io/babel/guides/migrating-pdftex-luatex.html}%
+  {Migrating from pdfTeX to LuaTeX} and
+  \href{https://latex3.github.io/babel/guides/migrating-xetex-luatex.html}%
+  {Migrating from XeTeX to LuaTeX}.
 \end{note}
 
 \section{The basic user interface}\label{U-I}
@@ -1962,15 +1964,18 @@
     which may break the vertical spacing in some cases (for example,
     between lists or at the beginning of a table cell). \New{3.64} The
     behavior can be adjusted with
-    |\babeladjust{select.write=<mode>}|, where \m{mode} is |shift|
-    (which shifts the skips down and adds a |\penalty|); |keep| (the
-    default -- with it the |\write| and the skips are kept in the order
-    they are written), and |omit| (which may seem a too drastic
-    solution, because nothing is written, but more often than not this
-    command is applied to more or less shorts texts with no sectioning
-    or similar commands, and therefore no language synchronization is
-    necessary). In a table cell, a |\leavevmode| just before the
-    selector may be enough.
+    |\babeladjust{select.write=<mode>}|, where \m{mode} is:
+   \begin{itemize} 
+    \item |keep|, the default – with it the |\write| and the skips are
+    kept in the order they are written);
+    \item |shift|, which shifts the skips down and adds a |\penalty|;
+    \item |omit|, which may seem a too drastic solution, because
+    nothing is written, but more often than not this command is applied
+    to more or less shorts texts with no sectioning or similar
+    commands, and therefore no language synchronization is necessary.
+    In a table cell, a |\leavevmode| just before the selector may be
+    enough.
+   \end{itemize}
 \end{itemize}
 \end{warning}
 
@@ -4292,6 +4297,9 @@
 \trans{Czech, Polish, Slovak}{oneletter.nobreak}{Converts a space after
 a non-syllabic preposition or conjunction into a non-breaking space.}
 
+\trans{Dutch}{diaeresis.hyphen}{Removes the diaeresis above a vowel if
+hyphenated just before.}
+
 \trans{Finnish}{prehyphen.nobreak}{Line breaks just after hyphens
 prepended to words are prevented, as in “pakastekaapit ja -arkut”.}
 
@@ -4298,6 +4306,13 @@
 \trans{French}{punctuation.space}{Rules for proper spacing with
 characters \textit{;:!?«»} are applied.}
 
+\trans{German}{longs.unifraktur}{Implements the basic heuristic rules
+for the long s (ſ) from those in Unifraktur Maguntia. Although
+discretionaries aren’t taken into account, the transform is declared in
+the posthyphenation group, to ease if necessary fine tuning the rules
+for, e.g., prefixes and compound words. They are available in all
+German locales. See \New{25.5} for further details and an example.}
+
 \trans{Greek}{diaeresis.hyphen}{Removes the diaeresis above iota and
 upsilon if hyphenated just before. It works with the three variants.}
 
@@ -4320,8 +4335,8 @@
 \trans{Hindi, Sanskrit}{transliteration.hk}{The Harvard-Kyoto system to
 romanize Devanagari.}
 
-\trans{Hindi, Sanskrit}{punctuation.space}{Inserts a space before the following
-four characters: \textit{!?:;}\,.}
+\trans{Hindi, Sanskrit}{punctuation.space}{Inserts a space before the
+following four characters: \textit{!?:;}\,.}
 
 \trans{Hungarian}{digraphs.hyphen}{Hyphenates the long digraphs
 \textit{ccs}, \textit{ddz}, \textit{ggy}, \textit{lly}, \textit{nny},
@@ -4332,6 +4347,9 @@
 danda or double danda if there is a space. For Assamese, Bengali,
 Gujarati, Hindi, Kannada, Malayalam, Marathi, Odia, Tamil, Telugu.}
 
+\trans{Japanese}{linebreak.strict}{Prevents line breaks before small
+kana variants.}
+
 \trans{Latin}{digraphs.ligatures}{Replaces the groups \textit{ae},
 \textit{AE}, \textit{oe}, \textit{OE} with \textit{æ}, \textit{Æ},
 \textit{œ}, \textit{Œ}.}
@@ -4502,7 +4520,8 @@
 \DescribeOther{\disablelocaletransform}{\marg{label}}
 
 \New{3.85} Enables and disables the transform with the given label in
-the current language.
+the current language. Font dependent transforms are always enabled and
+cannot be disabled.
 
 \subsection{Support for \xetex{} interchar}
 \label{interchar}
@@ -6050,8 +6069,8 @@
 % \section{Tools}
 %
 %    \begin{macrocode}
-%<<version=25.4>>
-%<<date=2025/02/14>>
+%<<version=25.5>>
+%<<date=2025/03/10>>
 %    \end{macrocode}
 %
 % \textbf{Do not use the following macros in \texttt{ldf} files. They
@@ -6248,6 +6267,18 @@
 \def\bbl at foreach#1{\expandafter\bbl at vforeach\expandafter{#1}}
 %    \end{macrocode}
 %
+% Some code should be executed once. The first argument is a flag.
+%
+%    \begin{macrocode}
+\global\let\bbl at done\@empty
+\def\bbl at once#1#2{%
+  \bbl at xin@{,#1,}{,\bbl at done,}%
+  \ifin@\else
+    #2%
+    \xdef\bbl at done{\bbl at done,#1,}%
+  \fi}
+%    \end{macrode}
+%
 % \macro{\bbl at replace}
 %
 % Returns implicitly |\toks@| with the modified string. 
@@ -6296,7 +6327,8 @@
         \def\bbl at tempc{%     Expanded an executed below as 'uplevel'
            \\\makeatletter % "internal" macros with @ are assumed
            \\\scantokens{%
-             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}}%
+             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}%
+             \noexpand\noexpand}%
            \catcode64=\the\catcode64\relax}%  Restore @
       \else
         \let\bbl at tempc\@empty  % Not \relax
@@ -8693,7 +8725,11 @@
 % change in casing to fix it in the same way languages names are fixed.
 %
 %    \begin{macrocode}
-\def\languageshorthands#1{\def\language at group{#1}}
+\def\languageshorthands#1{%
+  \bbl at ifsamestring{none}{#1}{}{%
+    \bbl at once{short-\localename-#1}{%
+      \bbl at info{'\localename' activates '#1' shorthands.\\Reported }}}%
+  \def\language at group{#1}}
 %    \end{macrocode}
 %
 % \macro{\aliasshorthand}
@@ -11211,8 +11247,8 @@
     \global\let\bbl at patchchapter\relax
     \gdef\bbl at chfmt{%
       \bbl at ifunset{bbl@\bbl at chaptype fmt@\languagename}%
-        {\@chapapp\space\thechapter}
-        {\@nameuse{bbl@\bbl at chaptype fmt@\languagename}}}
+        {\@chapapp\space\thechapter}%
+        {\@nameuse{bbl@\bbl at chaptype fmt@\languagename}}}%
     \bbl at add\appendix{\def\bbl at chaptype{appendix}}% Not harmful, I hope
     \bbl at sreplace\ps at headings{\@chapapp\ \thechapter}{\bbl at chfmt}%
     \bbl at sreplace\chaptermark{\@chapapp\ \thechapter}{\bbl at chfmt}%
@@ -11230,8 +11266,8 @@
     \global\let\bbl at patchpart\relax
     \gdef\bbl at partformat{%
       \bbl at ifunset{bbl at partfmt@\languagename}%
-        {\partname\nobreakspace\thepart}
-        {\@nameuse{bbl at partfmt@\languagename}}}
+        {\partname\nobreakspace\thepart}%
+        {\@nameuse{bbl at partfmt@\languagename}}}%
     \bbl at sreplace\@part{\partname\nobreakspace\thepart}{\bbl at partformat}%
     \bbl at toglobal\@part}
 \fi
@@ -11277,6 +11313,11 @@
       {\@nameuse{bbl at date@\languagename @\bbl at calendar}%
          \bbl at they\bbl at them\bbl at thed}%
   \endgroup}
+\def\bbl at printdate#1{%
+  \@ifnextchar[{\bbl at printdate@i{#1}}{\bbl at printdate@i{#1}[]}}
+\def\bbl at printdate@i#1[#2]#3#4#5{%
+  \bbl at usedategrouptrue
+  \@nameuse{bbl at ensure@#1}{\localedate[#2]{#3}{#4}{#5}}}
 % e.g.: 1=months, 2=wide, 3=1, 4=dummy, 5=value, 6=calendar
 \def\bbl at inidate#1.#2.#3.#4\relax#5#6{% TODO - ignore with 'captions'
   \bbl at trim@def\bbl at tempa{#1.#2}%
@@ -11304,34 +11345,8 @@
                   {\\\the\year}{\\\the\month}{\\\the\day}}}}%
        \fi}%
       {}}}
-\def\bbl at printdate#1{%
-  \@ifnextchar[{\bbl at printdate@i{#1}}{\bbl at printdate@i{#1}[]}}
-\def\bbl at printdate@i#1[#2]#3#4#5{%
-  \bbl at usedategrouptrue
-  \@nameuse{bbl at ensure@#1}{\localedate[#2]{#3}{#4}{#5}}}
 %    \end{macrocode}
 %
-% \subsection{French spacing (again)}
-% 
-% For the following declarations, see issue \#240. |\nonfrenchspacing|
-% is set by |document| too early, so it’s a hack.
-%
-%    \begin{macrocode}
-\AddToHook{begindocument/before}{%
-  \let\bbl at normalsf\normalsfcodes
-  \let\normalsfcodes\relax}
-\AtBeginDocument{%
-  \ifx\bbl at normalsf\@empty
-    \ifnum\sfcode`\.=\@m
-      \let\normalsfcodes\frenchspacing
-    \else
-      \let\normalsfcodes\nonfrenchspacing
-    \fi
-  \else
-    \let\normalsfcodes\bbl at normalsf
-  \fi}
-%    \end{macrocode}
-%
 % \textbf{Dates} will require some macros for the basic formatting.
 % They may be redefined by language, so ``semi-public'' names (camel
 % case) are used. Oddly enough, the CLDR places particles like “de”
@@ -11386,6 +11401,27 @@
 \def\bbl at xdatecntr[#1|#2]{\localenumeral{#2}{#1}}
 %    \end{macrocode}
 %
+% \subsection{French spacing (again)}
+% 
+% For the following declarations, see issue \#240. |\nonfrenchspacing|
+% is set by |document| too early, so it’s a hack.
+%
+%    \begin{macrocode}
+\AddToHook{begindocument/before}{%
+  \let\bbl at normalsf\normalsfcodes
+  \let\normalsfcodes\relax}
+\AtBeginDocument{%
+  \ifx\bbl at normalsf\@empty
+    \ifnum\sfcode`\.=\@m
+      \let\normalsfcodes\frenchspacing
+    \else
+      \let\normalsfcodes\nonfrenchspacing
+    \fi
+  \else
+    \let\normalsfcodes\bbl at normalsf
+  \fi}
+%    \end{macrocode}
+%
 % \textbf{Transforms.}
 %
 %    \begin{macrocode}
@@ -13780,11 +13816,12 @@
   \bbl at exp{%
     \\\newcommand\<#1default>{}% Just define it
     \\\bbl at add@list\\\bbl at font@fams{#1}%
+    \\\NewHook{#1family}%
     \\\DeclareRobustCommand\<#1family>{%
       \\\not at math@alphabet\<#1family>\relax
       % \\\prepare at family@series at update{#1}\<#1default>% TODO. Fails
       \\\fontfamily\<#1default>%
-      \<ifx>\\\UseHooks\\\@undefined\<else>\\\UseHook{#1family}\<fi>%
+      \\\UseHook{#1family}%
       \\\selectfont}%
     \\\DeclareTextFontCommand{\<text#1>}{\<#1family>}}}
 %    \end{macrocode}
@@ -13907,7 +13944,13 @@
 %    \end{macrocode}
 %
 % Loaded locally, which does its job, but very must be global. The
-% problem is how.
+% problem is how. This actually defines a font predeclared with
+% |\babelfont|, making sure |Script| and |Language| names are defined.
+% If they are not, the corresponding data in the ini file is used. The
+% font is actually set temporarily to get the family name
+% (|\f at family|). There is also a hack because by default some
+% replacements related to the bold series are sometimes assigned to the
+% wrong font (see issue \#92).
 %
 %    \begin{macrocode}
 \def\bbl at fontspec@set#1#2#3#4{% eg \bbl at rmdflt@lang fnt-opt fnt-nme \xxfamily
@@ -14263,8 +14306,6 @@
 %<*xetex|texxet>
 \providecommand\bbl at provide@intraspace{}
 \bbl at trace{Redefinitions for bidi layout}
-\def\bbl at sspre@caption{%  TODO: Unused!
-  \bbl at exp{\everyhbox{\\\bbl at textdir\bbl at cs{wdir@\bbl at main@language}}}}
 \ifx\bbl at opt@layout\@nnil\else % if layout=.. 
 \def\bbl at startskip{\ifcase\bbl at thepardir\leftskip\else\rightskip\fi}
 \def\bbl at endskip{\ifcase\bbl at thepardir\rightskip\else\leftskip\fi}
@@ -15049,8 +15090,7 @@
       end
     end,
     'Babel.hyphenate')
-  }
-}
+  }}
 \endgroup
 \def\bbl at provide@intraspace{%
   \bbl at ifunset{bbl at intsp@\languagename}{}%
@@ -15260,7 +15300,9 @@
         end
       end
 
-      % Tatwil
+      % Tatwil. First create a list of nodes marked with kashida. The
+      % rest of nodes can be ignored. The list of used weigths is build
+      % when transforms with the key kashida= are declared.
       if Babel.kashida_wts then
         local k_wt = node.get_attribute(n, KASHIDA)
         if k_wt > 0 then % todo. parameter for multi inserts
@@ -15295,6 +15337,11 @@
     end
 
     % == Tatwil ==
+    % Traverse the kashida node list so many times as required, until
+    % the line if filled. The first pass adds a tatweel after each
+    % node with kashida in the line, the second pass adds another one,
+    % and so on. In each pass, add first the kashida with the highest
+    % weight, then with lower weight and so on.
     if #k_list == 0 then goto next_line end
 
     width = node.dimensions(line.head)    % The 'natural' width
@@ -15378,7 +15425,7 @@
  ,Greek,Latin,Old Church Slavonic Cyrillic,}
 \ifnum\bbl at bidimode=102 % bidi-r
    \bbl at add\bbl at scr@node at list{Arabic,Hebrew,Syriac}
-\fi 
+\fi
 \def\bbl at set@renderer{%
   \bbl at xin@{\bbl at cl{sname}}{\bbl at scr@node at list}%
   \ifin@
@@ -16092,7 +16139,7 @@
             \let\@eqnnum\bbl at eqnum
           \fi
         \fi}
-      % Hack. YA luatex bug?:
+      % Hack for wrong vertical spacing with \[ \]. YA luatex bug?:
       \expandafter\bbl at sreplace\csname] \endcsname{$$}{\eqno\kern.001pt$$}%
     \else % amstex
       \bbl at exp{% Hack to hide maybe undefined conditionals:
@@ -16237,7 +16284,7 @@
     % TODO - catch non-valid values
   \fi
   % == mapfont ==
-  % For bidi texts, to switch the font based on direction
+  % For bidi texts, to switch the font based on direction. Old.
   \ifx\bbl at KVP@mapfont\@nnil\else
     \bbl at ifsamestring{\bbl at KVP@mapfont}{direction}{}%
       {\bbl at error{unknown-mapfont}{}{}{}}%
@@ -16260,7 +16307,7 @@
     \fi
     \bbl at exp{\\\bbl at add\\\bbl at mapselect{\\\bbl at mapdir{\languagename}}}%
   \fi
-  % == Line breaking: CJK quotes ==
+  % == Line breaking: CJK quotes == 
   \ifcase\bbl at engine\or
     \bbl at xin@{/c}{/\bbl at cl{lnbrk}}%
     \ifin@
@@ -23490,25 +23537,23 @@
   local has_hyperlink = false
 
   local ATDIR = Babel.attr_dir
-  local attr_d
+  local attr_d, temp
+  local locale_d
 
   local save_outer
-  local temp = node.get_attribute(head, ATDIR)
-  if temp then
-    temp = temp & 0x3
-    save_outer = (temp == 0 and 'l') or
-                 (temp == 1 and 'r') or
-                 (temp == 2 and 'al')
-  elseif ispar then            -- Or error? Shouldn't happen
+  local locale_d = node.get_attribute(head, ATDIR)
+  if locale_d then
+    locale_d = locale_d & 0x3
+    save_outer = (locale_d == 0 and 'l') or
+                 (locale_d == 1 and 'r') or
+                 (locale_d == 2 and 'al')
+  elseif ispar then       -- Or error? Shouldn't happen
+    -- when the callback is called, we are just _after_ the box,
+    -- and the textdir is that of the surrounding text
     save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
-  else                         -- Or error? Shouldn't happen
+  else                    -- Empty box 
     save_outer = ('TRT' == hdir) and 'r' or 'l'
   end
-    -- when the callback is called, we are just _after_ the box,
-    -- and the textdir is that of the surrounding text
-  -- if not ispar and hdir ~= tex.textdir then 
-  --   save_outer = ('TRT' == hdir) and 'r' or 'l'
-  -- end
   local outer = save_outer
   local last = outer
   -- 'al' is only taken into account in the first, current loop
@@ -23517,15 +23562,18 @@
   local fontmap = Babel.fontmap
 
   for item in node.traverse(head) do
-
+    
+    -- Mask: DxxxPPTT (Done, Pardir [0-2], Textdir [0-2])
+    locale_d = node.get_attribute(item, ATDIR)
+    node.set_attribute(item, ATDIR, 0x80)
+    
     -- In what follows, #node is the last (previous) node, because the
     -- current one is not added until we start processing the neutrals.
-
     -- three cases: glyph, dir, otherwise
     if glyph_not_symbol_font(item)
        or (item.id == 7 and item.subtype == 2) then
 
-      if node.get_attribute(item, ATDIR) == 128 then goto nextnode end
+      if locale_d == 0x80 then goto nextnode end
 
       local d_font = nil
       local item_r
@@ -23552,6 +23600,7 @@
       d = d or 'l'
 
       -- A short 'pause' in bidi for mapfont
+      -- %%%% TODO. move if fontmap here
       d_font = d_font or d
       d_font = (d_font == 'l' and 0) or
                (d_font == 'nsm' and 0) or
@@ -23567,8 +23616,7 @@
         if inmath then
           attr_d = 0
         else
-          attr_d = node.get_attribute(item, ATDIR)
-          attr_d = attr_d & 0x3
+          attr_d = locale_d & 0x3
         end
         if attr_d == 1 then
           outer_first = 'r'
@@ -23596,9 +23644,8 @@
 
     elseif item.id == DIR then
       d = nil
+      new_d = true
 
-      if head ~= item then new_d = true end
-
     elseif item.id == node.id'glue' and item.subtype == 13 then
       glue_d = d
       glue_i = item
@@ -23678,7 +23725,6 @@
       table.insert(nodes, {item, d, outer_first})
     end
 
-    node.set_attribute(item, ATDIR, 128)
     outer_first = nil
 
     ::nextnode::
@@ -23864,12 +23910,13 @@
 
   return head
 end
--- Make sure anything is marked as 'bidi done' (including nodes inserted
--- after the babel algorithm). 128 = 1000 0000.
+%    \end{macrocode}
+% 
+%    \begin{macrocode}
 function Babel.unset_atdir(head)
   local ATDIR = Babel.attr_dir
   for item in node.traverse(head) do
-    node.set_attribute(item, ATDIR, 128)
+    node.set_attribute(item, ATDIR, 0x80)
   end
   return head
 end

Modified: trunk/Master/texmf-dist/source/latex/babel/babel.ins
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/babel.ins	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/source/latex/babel/babel.ins	2025-03-11 20:05:14 UTC (rev 74566)
@@ -26,7 +26,7 @@
 %% and covered by LPPL is defined by the unpacking scripts (with
 %% extension .ins) which are part of the distribution.
 %%
-\def\filedate{2025/02/14}
+\def\filedate{2025/03/10}
 \def\batchfile{babel.ins}
 \input docstrip.tex
 

Modified: trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx	2025-03-11 20:05:14 UTC (rev 74566)
@@ -30,7 +30,7 @@
 %
 % \iffalse
 %<*dtx>
-\ProvidesFile{bbcompat.dtx}[2025/02/14 v25.4]
+\ProvidesFile{bbcompat.dtx}[2025/03/10 v25.5]
 %</dtx>
 %
 %% File 'bbcompat.dtx'

Modified: trunk/Master/texmf-dist/source/latex/babel/locale.zip
===================================================================
(Binary files differ)

Added: trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-0.lua
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-0.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-0.lua	2025-03-11 20:05:14 UTC (rev 74566)
@@ -0,0 +1,504 @@
+--
+-- This is file `babel-bidi-basic.lua',
+-- generated with the docstrip utility.
+--
+-- The original source files were:
+--
+-- babel.dtx  (with options: `basic')
+-- 
+--
+-- Copyright (C) 2012-2025 Javier Bezos and Johannes L. Braams.
+-- Copyright (C) 1989-2012 Johannes L. Braams and
+--           any individual authors listed elsewhere in this file.
+-- All rights reserved.
+--
+--
+-- This file is part of the Babel system.
+-- --------------------------------------
+--
+-- It may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+--   http://www.latex-project.org/lppl.txt
+-- and version 1.3 or later is part of all distributions of LaTeX
+-- version 2003/12/01 or later.
+--
+-- This work has the LPPL maintenance status "maintained".
+--
+-- The Current Maintainer of this work is Javier Bezos.
+--
+-- The list of derived (unpacked) files belonging to the distribution
+-- and covered by LPPL is defined by the unpacking scripts (with
+-- extension |.ins|) which are part of the distribution.
+--
+-- e.g., Babel.fontmap[1][<prefontid>]=<dirfontid>
+
+Babel.fontmap = Babel.fontmap or {}
+Babel.fontmap[0] = {}      -- l
+Babel.fontmap[1] = {}      -- r
+Babel.fontmap[2] = {}      -- al/an
+
+-- To cancel mirroring. Also OML, OMS, U?
+Babel.symbol_fonts = Babel.symbol_fonts or {}
+Babel.symbol_fonts[font.id('tenln')] = true
+Babel.symbol_fonts[font.id('tenlnw')] = true
+Babel.symbol_fonts[font.id('tencirc')] = true
+Babel.symbol_fonts[font.id('tencircw')] = true
+
+Babel.bidi_enabled = true
+Babel.mirroring_enabled = true
+
+require('babel-data-bidi.lua')
+
+local characters = Babel.characters
+local ranges = Babel.ranges
+
+local DIR = node.id('dir')
+local GLYPH = node.id('glyph')
+
+local function insert_implicit(head, state, outer)
+  local new_state = state
+  if state.sim and state.eim and state.sim ~= state.eim then
+    dir = ((outer == 'r') and 'TLT' or 'TRT') -- i.e., reverse
+    local d = node.new(DIR)
+    d.dir = '+' .. dir
+    node.insert_before(head, state.sim, d)
+    local d = node.new(DIR)
+    d.dir = '-' .. dir
+    node.insert_after(head, state.eim, d)
+  end
+  new_state.sim, new_state.eim = nil, nil
+  return head, new_state
+end
+
+local function insert_numeric(head, state)
+  local new
+  local new_state = state
+  if state.san and state.ean and state.san ~= state.ean then
+    local d = node.new(DIR)
+    d.dir = '+TLT'
+    _, new = node.insert_before(head, state.san, d)
+    if state.san == state.sim then state.sim = new end
+    local d = node.new(DIR)
+    d.dir = '-TLT'
+    _, new = node.insert_after(head, state.ean, d)
+    if state.ean == state.eim then state.eim = new end
+  end
+  new_state.san, new_state.ean = nil, nil
+  return head, new_state
+end
+
+local function glyph_not_symbol_font(node)
+  if node.id == GLYPH then
+    return not Babel.symbol_fonts[node.font]
+  else
+   return false
+  end
+end
+
+-- TODO - \hbox with an explicit dir can lead to wrong results
+-- <R \hbox dir TLT{<R>}> and <L \hbox dir TRT{<L>}>. A small attempt
+-- was made to improve the situation, but the problem is the 3-dir
+-- model in babel/Unicode and the 2-dir model in LuaTeX don't fit
+-- well.
+
+function Babel.bidi(head, ispar, hdir)
+  local d   -- d is used mainly for computations in a loop
+  local prev_d = ''
+  local new_d = false
+
+  local nodes = {}
+  local outer_first = nil
+  local inmath = false
+
+  local glue_d = nil
+  local glue_i = nil
+
+  local has_en = false
+  local first_et = nil
+
+  local has_hyperlink = false
+
+  local ATDIR = Babel.attr_dir
+  local attr_d, temp
+  local locale_d
+
+  local save_outer
+  local locale_d = node.get_attribute(head, ATDIR)
+  if locale_d then
+    locale_d = locale_d & 0x3
+    save_outer = (locale_d == 0 and 'l') or
+                 (locale_d == 1 and 'r') or
+                 (locale_d == 2 and 'al')
+  elseif ispar then            -- Or error? Shouldn't happen
+    save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
+  else                         -- Or error? Shouldn't happen
+    save_outer = ('TRT' == hdir) and 'r' or 'l'
+  end
+    -- when the callback is called, we are just _after_ the box,
+    -- and the textdir is that of the surrounding text
+  -- if not ispar and hdir ~= tex.textdir then
+  --   save_outer = ('TRT' == hdir) and 'r' or 'l'
+  -- end
+  local outer = save_outer
+  local last = outer
+  -- 'al' is only taken into account in the first, current loop
+  if save_outer == 'al' then save_outer = 'r' end
+
+  local fontmap = Babel.fontmap
+
+  for item in node.traverse(head) do
+
+    -- Mask: DxxxPPTT (Done, Pardir, Textdir)
+    locale_d = node.get_attribute(item, ATDIR)
+    texio.write_nl(locale_d or '???')
+    node.set_attribute(item, ATDIR, 0x80)
+
+    -- In what follows, #node is the last (previous) node, because the
+    -- current one is not added until we start processing the neutrals.
+    -- three cases: glyph, dir, otherwise
+    if glyph_not_symbol_font(item)
+       or (item.id == 7 and item.subtype == 2) then
+
+      if locale_d == 0x80 then goto nextnode end
+
+      local d_font = nil
+      local item_r
+      if item.id == 7 and item.subtype == 2 then
+        item_r = item.replace    -- automatic discs have just 1 glyph
+      else
+        item_r = item
+      end
+
+      local chardata = characters[item_r.char]
+      d = chardata and chardata.d or nil
+      if not d or d == 'nsm' then
+        for nn, et in ipairs(ranges) do
+          if item_r.char < et[1] then
+            break
+          elseif item_r.char <= et[2] then
+            if not d then d = et[3]
+            elseif d == 'nsm' then d_font = et[3]
+            end
+            break
+          end
+        end
+      end
+      d = d or 'l'
+
+      -- A short 'pause' in bidi for mapfont
+      -- %%%% TODO. move if fontmap here
+      d_font = d_font or d
+      d_font = (d_font == 'l' and 0) or
+               (d_font == 'nsm' and 0) or
+               (d_font == 'r' and 1) or
+               (d_font == 'al' and 2) or
+               (d_font == 'an' and 2) or nil
+      if d_font and fontmap and fontmap[d_font][item_r.font] then
+        item_r.font = fontmap[d_font][item_r.font]
+      end
+
+      if new_d then
+        table.insert(
+          nodes, {nil, (outer == 'l') and 'l' or 'r', nil, locale_d})
+        if inmath then
+          attr_d = 0
+        else
+          attr_d = locale_d & 0x3
+        end
+        if attr_d == 1 then
+          outer_first = 'r'
+          last = 'r'
+        elseif attr_d == 2 then
+          outer_first = 'r'
+          last = 'al'
+        else
+          outer_first = 'l'
+          last = 'l'
+        end
+        outer = last
+        has_en = false
+        first_et = nil
+        new_d = false
+      end
+
+      if glue_d then
+        if (d == 'l' and 'l' or 'r') ~= glue_d then
+           table.insert(nodes, {glue_i, 'on', nil, locale_d})
+        end
+        glue_d = nil
+        glue_i = nil
+      end
+
+    elseif item.id == DIR then
+      d = nil
+
+      if head ~= item then new_d = true end
+
+    elseif item.id == node.id'glue' and item.subtype == 13 then
+      glue_d = d
+      glue_i = item
+      d = nil
+
+    elseif item.id == node.id'math' then
+      inmath = (item.subtype == 0)
+
+    elseif item.id == 8 and item.subtype == 19 then
+      has_hyperlink = true
+
+    else
+      d = nil
+    end
+
+    -- AL <= EN/ET/ES     -- W2 + W3 + W6
+    if last == 'al' and d == 'en' then
+      d = 'an'           -- W3
+    elseif last == 'al' and (d == 'et' or d == 'es') then
+      d = 'on'           -- W6
+    end
+
+    -- EN + CS/ES + EN     -- W4
+    if d == 'en' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'es' or nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'en' then
+        nodes[#nodes][2] = 'en'
+      end
+    end
+
+    -- AN + CS + AN        -- W4 too, because uax9 mixes both cases
+    if d == 'an' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'an' then
+        nodes[#nodes][2] = 'an'
+      end
+    end
+
+    -- ET/EN               -- W5 + W7->l / W6->on
+    if d == 'et' then
+      first_et = first_et or (#nodes + 1)
+    elseif d == 'en' then
+      has_en = true
+      first_et = first_et or (#nodes + 1)
+    elseif first_et then       -- d may be nil here !
+      if has_en then
+        if last == 'l' then
+          temp = 'l'    -- W7
+        else
+          temp = 'en'   -- W5
+        end
+      else
+        temp = 'on'     -- W6
+      end
+      for e = first_et, #nodes do
+        if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+      end
+      first_et = nil
+      has_en = false
+    end
+
+    -- Force mathdir in math if ON (currently works as expected only
+    -- with 'l')
+
+    if inmath and d == 'on' then
+      d = ('TRT' == tex.mathdir) and 'r' or 'l'
+    end
+
+    if d then
+      if d == 'al' then
+        d = 'r'
+        last = 'al'
+      elseif d == 'l' or d == 'r' then
+        last = d
+      end
+      prev_d = d
+      table.insert(nodes, {item, d, outer_first, locale_d})
+    end
+
+    outer_first = nil
+
+    ::nextnode::
+
+  end -- for each node
+
+  -- TODO -- repeated here in case EN/ET is the last node. Find a
+  -- better way of doing things:
+  if first_et then       -- dir may be nil here !
+    if has_en then
+      if last == 'l' then
+        temp = 'l'    -- W7
+      else
+        temp = 'en'   -- W5
+      end
+    else
+      temp = 'on'     -- W6
+    end
+    for e = first_et, #nodes do
+      if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+    end
+  end
+
+  -- dummy node, to close things
+  table.insert(
+    nodes, {nil, (outer == 'l') and 'l' or 'r', nil, locale_d})
+
+  ---------------  NEUTRAL -----------------
+
+  outer = save_outer
+  last = outer
+
+  local first_on = nil
+
+  for q = 1, #nodes do
+    local item
+
+    local outer_first = nodes[q][3]
+    outer = outer_first or outer
+    last = outer_first or last
+
+    local d = nodes[q][2]
+    if d == 'an' or d == 'en' then d = 'r' end
+    if d == 'cs' or d == 'et' or d == 'es' then d = 'on' end --- W6
+
+    if d == 'on' then
+      first_on = first_on or q
+    elseif first_on then
+      if last == d then
+        temp = d
+      else
+        temp = outer
+      end
+      for r = first_on, q - 1 do
+        nodes[r][2] = temp
+        item = nodes[r][1]    -- MIRRORING
+        if Babel.mirroring_enabled and glyph_not_symbol_font(item)
+             and temp == 'r' and characters[item.char] then
+          local font_mode = ''
+          if item.font > 0 and font.fonts[item.font].properties then
+            font_mode = font.fonts[item.font].properties.mode
+          end
+          if font_mode ~= 'harf' and font_mode ~= 'plug' and
+              (nodes[r][4] or 0) & 0x3 > 0 then
+            item.char = characters[item.char].m or item.char
+          end
+        end
+      end
+      first_on = nil
+    end
+
+    if d == 'r' or d == 'l' then last = d end
+  end
+
+  --------------  IMPLICIT, REORDER ----------------
+
+  outer = save_outer
+  last = outer
+
+  local state = {}
+  state.has_r = false
+
+  for q = 1, #nodes do
+
+    local item = nodes[q][1]
+
+    outer = nodes[q][3] or outer
+
+    local d = nodes[q][2]
+
+    if d == 'nsm' then d = last end             -- W1
+    if d == 'en' then d = 'an' end
+    local isdir = (d == 'r' or d == 'l')
+
+    if outer == 'l' and d == 'an' then
+      state.san = state.san or item
+      state.ean = item
+    elseif state.san then
+      head, state = insert_numeric(head, state)
+    end
+
+    if outer == 'l' then
+      if d == 'an' or d == 'r' then     -- im -> implicit
+        if d == 'r' then state.has_r = true end
+        state.sim = state.sim or item
+        state.eim = item
+      elseif d == 'l' and state.sim and state.has_r then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'l' then
+        state.sim, state.eim, state.has_r = nil, nil, false
+      end
+    else
+      if d == 'an' or d == 'l' then
+        if nodes[q][3] then -- nil except after an explicit dir
+          state.sim = item  -- so we move sim 'inside' the group
+        else
+          state.sim = state.sim or item
+        end
+        state.eim = item
+      elseif d == 'r' and state.sim then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'r' then
+        state.sim, state.eim = nil, nil
+      end
+    end
+
+    if isdir then
+      last = d           -- Don't search back - best save now
+    elseif d == 'on' and state.san  then
+      state.san = state.san or item
+      state.ean = item
+    end
+
+  end
+
+  head = node.prev(head) or head
+  --- FIXES ---
+  if has_hyperlink then
+    local flag, linking = 0, 0
+    for item in node.traverse(head) do
+      if item.id == DIR then
+        if item.dir == '+TRT' or item.dir == '+TLT' then
+          flag = flag + 1
+        elseif item.dir == '-TRT' or item.dir == '-TLT' then
+          flag = flag - 1
+        end
+      elseif item.id == 8 and item.subtype == 19 then
+        linking = flag
+      elseif item.id == 8 and item.subtype == 20 then
+        if linking > 0 then
+          if item.prev.id == DIR and
+              (item.prev.dir == '-TRT' or item.prev.dir == '-TLT') then
+            d = node.new(DIR)
+            d.dir = item.prev.dir
+            node.remove(head, item.prev)
+            node.insert_after(head, item, d)
+          end
+        end
+        linking = 0
+      end
+    end
+  end
+
+  for item in node.traverse_id(10, head) do
+    local p = item
+    local flag = false
+    while p.prev and p.prev.id == 14 do
+      flag = true
+      p = p.prev
+    end
+    if flag then
+      node.insert_before(head, p, node.copy(item))
+      node.remove(head,item)
+    end
+  end
+
+  return head
+end
+-- Make sure anything is marked as 'bidi done' (including nodes inserted
+-- after the babel algorithm). 128 = 1000 0000.
+function Babel.unset_atdir(head)
+  local ATDIR = Babel.attr_dir
+  for item in node.traverse(head) do
+    node.set_attribute(item, ATDIR, 128)
+  end
+  return head
+end


Property changes on: trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-0.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-1.lua
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-1.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-1.lua	2025-03-11 20:05:14 UTC (rev 74566)
@@ -0,0 +1,522 @@
+--
+-- This is file `babel-bidi-basic.lua',
+-- generated with the docstrip utility.
+--
+-- The original source files were:
+--
+-- babel.dtx  (with options: `basic')
+-- 
+--
+-- Copyright (C) 2012-2025 Javier Bezos and Johannes L. Braams.
+-- Copyright (C) 1989-2012 Johannes L. Braams and
+--           any individual authors listed elsewhere in this file.
+-- All rights reserved.
+--
+--
+-- This file is part of the Babel system.
+-- --------------------------------------
+--
+-- It may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+--   http://www.latex-project.org/lppl.txt
+-- and version 1.3 or later is part of all distributions of LaTeX
+-- version 2003/12/01 or later.
+--
+-- This work has the LPPL maintenance status "maintained".
+--
+-- The Current Maintainer of this work is Javier Bezos.
+--
+-- The list of derived (unpacked) files belonging to the distribution
+-- and covered by LPPL is defined by the unpacking scripts (with
+-- extension |.ins|) which are part of the distribution.
+--
+-- e.g., Babel.fontmap[1][<prefontid>]=<dirfontid>
+
+Babel.fontmap = Babel.fontmap or {}
+Babel.fontmap[0] = {}      -- l
+Babel.fontmap[1] = {}      -- r
+Babel.fontmap[2] = {}      -- al/an
+
+-- To cancel mirroring. Also OML, OMS, U?
+Babel.symbol_fonts = Babel.symbol_fonts or {}
+Babel.symbol_fonts[font.id('tenln')] = true
+Babel.symbol_fonts[font.id('tenlnw')] = true
+Babel.symbol_fonts[font.id('tencirc')] = true
+Babel.symbol_fonts[font.id('tencircw')] = true
+
+Babel.bidi_enabled = true
+Babel.mirroring_enabled = true
+
+require('babel-data-bidi.lua')
+
+local characters = Babel.characters
+local ranges = Babel.ranges
+
+local DIR = node.id('dir')
+local GLYPH = node.id('glyph')
+
+local function insert_implicit(head, state, outer)
+  local new_state = state
+  if state.sim and state.eim and state.sim ~= state.eim then
+    dir = ((outer == 'r') and 'TLT' or 'TRT') -- i.e., reverse
+    local d = node.new(DIR)
+    d.dir = '+' .. dir
+    node.insert_before(head, state.sim, d)
+    local d = node.new(DIR)
+    d.dir = '-' .. dir
+    node.insert_after(head, state.eim, d)
+  end
+  new_state.sim, new_state.eim = nil, nil
+  return head, new_state
+end
+
+local function insert_numeric(head, state)
+  local new
+  local new_state = state
+  if state.san and state.ean and state.san ~= state.ean then
+    local d = node.new(DIR)
+    d.dir = '+TLT'
+    _, new = node.insert_before(head, state.san, d)
+    if state.san == state.sim then state.sim = new end
+    local d = node.new(DIR)
+    d.dir = '-TLT'
+    _, new = node.insert_after(head, state.ean, d)
+    if state.ean == state.eim then state.eim = new end
+  end
+  new_state.san, new_state.ean = nil, nil
+  return head, new_state
+end
+
+local function glyph_not_symbol_font(node)
+  if node.id == GLYPH then
+    return not Babel.symbol_fonts[node.font]
+  else
+   return false
+  end
+end
+
+-- TODO - \hbox with an explicit dir can lead to wrong results
+-- <R \hbox dir TLT{<R>}> and <L \hbox dir TRT{<L>}>. A small attempt
+-- was made to improve the situation, but the problem is the 3-dir
+-- model in babel/Unicode and the 2-dir model in LuaTeX don't fit
+-- well.
+
+function Babel.bidi(head, ispar, hdir)
+  local d   -- d is used mainly for computations in a loop
+  local prev_d = ''
+  local new_d = false
+
+  local nodes = {}
+  local outer_first = nil
+  local inmath = false
+
+  local glue_d = nil
+  local glue_i = nil
+
+  local has_en = false
+  local first_et = nil
+
+  local has_hyperlink = false
+
+  local ATDIR = Babel.attr_dir
+  local attr_d, temp
+  local locale_d
+
+  local save_outer
+  local locale_d = node.get_attribute(head, ATDIR)
+  if locale_d then
+    locale_d = locale_d & 0x3
+    save_outer = (locale_d == 0 and 'l') or
+                 (locale_d == 1 and 'r') or
+                 (locale_d == 2 and 'al')
+    texio.write_nl('++ ' .. save_outer)
+  elseif ispar then             -- Or error? Shouldn't happen
+    texio.write_nl('++ P')
+    save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
+  else          
+    texio.write_nl('++ H')      -- Or error? Shouldn't happen
+    save_outer = ('TRT' == hdir) and 'r' or 'l'
+  end
+  -- texio.write('hdir=' .. tex.pardir)
+  texio.write(save_outer)
+    -- when the callback is called, we are just _after_ the box,
+    -- and the textdir is that of the surrounding text
+  -- if not ispar and hdir ~= tex.textdir then
+  --   save_outer = ('TRT' == hdir) and 'r' or 'l'
+  -- end
+  local outer = save_outer
+  local last = outer
+  if head.id == DIR then
+     outer = 'l'
+  end
+  -- 'al' is only taken into account in the first, current loop
+  if save_outer == 'al' then save_outer = 'r' end
+
+  local fontmap = Babel.fontmap
+
+  for item in node.traverse(head) do
+
+    -- Mask: DxxxPPTT (Done, Pardir [0-2], Textdir [0-2])
+    locale_d = node.get_attribute(item, ATDIR)
+    --texio.write_nl('ld=' .. (locale_d or '?'))
+    node.set_attribute(item, ATDIR, 0x80)
+
+    -- In what follows, #node is the last (previous) node, because the
+    -- current one is not added until we start processing the neutrals.
+    -- three cases: glyph, dir, otherwise
+    if glyph_not_symbol_font(item)
+       or (item.id == 7 and item.subtype == 2) then
+
+      if locale_d == 0x80 then goto nextnode end
+
+      local d_font = nil
+      local item_r
+      if item.id == 7 and item.subtype == 2 then
+        item_r = item.replace    -- automatic discs have just 1 glyph
+      else
+        item_r = item
+      end
+
+      local chardata = characters[item_r.char]
+      d = chardata and chardata.d or nil
+      if not d or d == 'nsm' then
+        for nn, et in ipairs(ranges) do
+          if item_r.char < et[1] then
+            break
+          elseif item_r.char <= et[2] then
+            if not d then d = et[3]
+            elseif d == 'nsm' then d_font = et[3]
+            end
+            break
+          end
+        end
+      end
+      d = d or 'l'
+
+      -- A short 'pause' in bidi for mapfont
+      -- %%%% TODO. move if fontmap here
+      d_font = d_font or d
+      d_font = (d_font == 'l' and 0) or
+               (d_font == 'nsm' and 0) or
+               (d_font == 'r' and 1) or
+               (d_font == 'al' and 2) or
+               (d_font == 'an' and 2) or nil
+      if d_font and fontmap and fontmap[d_font][item_r.font] then
+        item_r.font = fontmap[d_font][item_r.font]
+      end
+
+      if new_d then
+        table.insert(
+          nodes, {nil, (outer == 'l') and 'l' or 'r', nil, locale_d})
+        if inmath then
+          attr_d = 0
+        else
+          attr_d = locale_d & 0x3
+        end
+        if attr_d == 1 then
+          outer_first = 'r'
+          last = 'r'
+        elseif attr_d == 2 then
+          outer_first = 'r'
+          last = 'al'
+        else
+          outer_first = 'l'
+          last = 'l'
+        end
+        outer = last
+        has_en = false
+        first_et = nil
+        new_d = false
+      end
+
+      if glue_d then
+        if (d == 'l' and 'l' or 'r') ~= glue_d then
+           table.insert(nodes, {glue_i, 'on', nil, locale_d})
+        end
+        glue_d = nil
+        glue_i = nil
+      end
+
+    elseif item.id == DIR then
+      d = nil
+
+      if head ~= item then new_d = true end
+     -- new_d = true
+
+    elseif item.id == node.id'glue' and item.subtype == 13 then
+      glue_d = d
+      glue_i = item
+      d = nil
+
+    elseif item.id == node.id'math' then
+      inmath = (item.subtype == 0)
+
+    elseif item.id == 8 and item.subtype == 19 then
+      has_hyperlink = true
+
+    else
+      d = nil
+    end
+
+    -- AL <= EN/ET/ES     -- W2 + W3 + W6
+    if last == 'al' and d == 'en' then
+      d = 'an'           -- W3
+    elseif last == 'al' and (d == 'et' or d == 'es') then
+      d = 'on'           -- W6
+    end
+
+    -- EN + CS/ES + EN     -- W4
+    if d == 'en' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'es' or nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'en' then
+        nodes[#nodes][2] = 'en'
+      end
+    end
+
+    -- AN + CS + AN        -- W4 too, because uax9 mixes both cases
+    if d == 'an' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'an' then
+        nodes[#nodes][2] = 'an'
+      end
+    end
+
+    -- ET/EN               -- W5 + W7->l / W6->on
+    if d == 'et' then
+      first_et = first_et or (#nodes + 1)
+    elseif d == 'en' then
+      has_en = true
+      first_et = first_et or (#nodes + 1)
+    elseif first_et then       -- d may be nil here !
+      if has_en then
+        if last == 'l' then
+          temp = 'l'    -- W7
+        else
+          temp = 'en'   -- W5
+        end
+      else
+        temp = 'on'     -- W6
+      end
+      for e = first_et, #nodes do
+        if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+      end
+      first_et = nil
+      has_en = false
+    end
+
+    -- Force mathdir in math if ON (currently works as expected only
+    -- with 'l')
+
+    if inmath and d == 'on' then
+      d = ('TRT' == tex.mathdir) and 'r' or 'l'
+    end
+
+    if d then
+      if d == 'al' then
+        d = 'r'
+        last = 'al'
+      elseif d == 'l' or d == 'r' then
+        last = d
+      end
+      prev_d = d
+      table.insert(nodes, {item, d, outer_first, locale_d})
+    end
+
+    outer_first = nil
+
+    ::nextnode::
+    
+  end -- for each node
+
+  -- TODO -- repeated here in case EN/ET is the last node. Find a
+  -- better way of doing things:
+  if first_et then       -- dir may be nil here !
+    if has_en then
+      if last == 'l' then
+        temp = 'l'    -- W7
+      else
+        temp = 'en'   -- W5
+      end
+    else
+      temp = 'on'     -- W6
+    end
+    for e = first_et, #nodes do
+      if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+    end
+  end
+
+  -- dummy node, to close things
+  table.insert(
+    nodes, {nil, (outer == 'l') and 'l' or 'r', nil, locale_d})
+
+  ---------------  NEUTRAL -----------------
+
+  outer = save_outer
+  last = outer
+
+  local first_on = nil
+
+  for q = 1, #nodes do
+    local item
+
+    local outer_first = nodes[q][3]
+    outer = outer_first or outer
+    last = outer_first or last
+
+    local d = nodes[q][2]
+    if d == 'an' or d == 'en' then d = 'r' end
+    if d == 'cs' or d == 'et' or d == 'es' then d = 'on' end --- W6
+
+    if d == 'on' then
+      first_on = first_on or q
+    elseif first_on then
+      if last == d then
+        temp = d
+      else
+        temp = outer
+      end
+      for r = first_on, q - 1 do
+        nodes[r][2] = temp
+        item = nodes[r][1]    -- MIRRORING
+        if Babel.mirroring_enabled and glyph_not_symbol_font(item)
+             and temp == 'r' and characters[item.char] then
+          local font_mode = ''
+          if item.font > 0 and font.fonts[item.font].properties then
+            font_mode = font.fonts[item.font].properties.mode
+          end
+          texio.write_nl('id=' .. node.get_attribute(
+                 item, Babel.attr_locale) or '?')
+          texio.write(' from= ' .. item.char or '?')
+          texio.write(' dir=' .. ((nodes[r][4] or 3)))
+          texio.write(' nodedir=' .. (nodes[r][2]))
+          if font_mode ~= 'harf' and font_mode ~= 'plug' then
+            -- if Babel.locale_props[tonumber(node.get_attribute(
+                 -- item, Babel.attr_locale))].textdir ~= 'x' then
+              -- item.char = characters[item.char].m or item.char
+            -- end
+            -- if (nodes[r][4] or 0) & 0x3 > 0 then
+              item.char = characters[item.char].m or item.char
+              texio.write(' to= ' .. item.char or '?')
+            -- end
+          end
+        end
+      end
+      first_on = nil
+    end
+
+    if d == 'r' or d == 'l' then last = d end
+  end
+
+  --------------  IMPLICIT, REORDER ----------------
+
+  outer = save_outer
+  last = outer
+
+  local state = {}
+  state.has_r = false
+
+  for q = 1, #nodes do
+
+    local item = nodes[q][1]
+
+    outer = nodes[q][3] or outer
+
+    local d = nodes[q][2]
+
+    if d == 'nsm' then d = last end             -- W1
+    if d == 'en' then d = 'an' end
+    local isdir = (d == 'r' or d == 'l')
+
+    if outer == 'l' and d == 'an' then
+      state.san = state.san or item
+      state.ean = item
+    elseif state.san then
+      head, state = insert_numeric(head, state)
+    end
+
+    if outer == 'l' then
+      if d == 'an' or d == 'r' then     -- im -> implicit
+        if d == 'r' then state.has_r = true end
+        state.sim = state.sim or item
+        state.eim = item
+      elseif d == 'l' and state.sim and state.has_r then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'l' then
+        state.sim, state.eim, state.has_r = nil, nil, false
+      end
+    else
+      if d == 'an' or d == 'l' then
+        if nodes[q][3] then -- nil except after an explicit dir
+          state.sim = item  -- so we move sim 'inside' the group
+        else
+          state.sim = state.sim or item
+        end
+        state.eim = item
+      elseif d == 'r' and state.sim then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'r' then
+        state.sim, state.eim = nil, nil
+      end
+    end
+
+    if isdir then
+      last = d           -- Don't search back - best save now
+    elseif d == 'on' and state.san  then
+      state.san = state.san or item
+      state.ean = item
+    end
+
+  end
+
+  head = node.prev(head) or head
+  --- FIXES ---
+  if has_hyperlink then
+    local flag, linking = 0, 0
+    for item in node.traverse(head) do
+      if item.id == DIR then
+        if item.dir == '+TRT' or item.dir == '+TLT' then
+          flag = flag + 1
+        elseif item.dir == '-TRT' or item.dir == '-TLT' then
+          flag = flag - 1
+        end
+      elseif item.id == 8 and item.subtype == 19 then
+        linking = flag
+      elseif item.id == 8 and item.subtype == 20 then
+        if linking > 0 then
+          if item.prev.id == DIR and
+              (item.prev.dir == '-TRT' or item.prev.dir == '-TLT') then
+            d = node.new(DIR)
+            d.dir = item.prev.dir
+            node.remove(head, item.prev)
+            node.insert_after(head, item, d)
+          end
+        end
+        linking = 0
+      end
+    end
+  end
+
+  for item in node.traverse_id(10, head) do
+    local p = item
+    local flag = false
+    while p.prev and p.prev.id == 14 do
+      flag = true
+      p = p.prev
+    end
+    if flag then
+      node.insert_before(head, p, node.copy(item))
+      node.remove(head,item)
+    end
+  end
+
+  return head
+end
+function Babel.unset_atdir(head)
+  local ATDIR = Babel.attr_dir
+  for item in node.traverse(head) do
+    node.set_attribute(item, ATDIR, 0x80)
+  end
+  return head
+end


Property changes on: trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic-1.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic.lua
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic.lua	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel-bidi-basic.lua	2025-03-11 20:05:14 UTC (rev 74566)
@@ -121,25 +121,23 @@
   local has_hyperlink = false
 
   local ATDIR = Babel.attr_dir
-  local attr_d
+  local attr_d, temp
+  local locale_d
 
   local save_outer
-  local temp = node.get_attribute(head, ATDIR)
-  if temp then
-    temp = temp & 0x3
-    save_outer = (temp == 0 and 'l') or
-                 (temp == 1 and 'r') or
-                 (temp == 2 and 'al')
-  elseif ispar then            -- Or error? Shouldn't happen
+  local locale_d = node.get_attribute(head, ATDIR)
+  if locale_d then
+    locale_d = locale_d & 0x3
+    save_outer = (locale_d == 0 and 'l') or
+                 (locale_d == 1 and 'r') or
+                 (locale_d == 2 and 'al')
+  elseif ispar then       -- Or error? Shouldn't happen
+    -- when the callback is called, we are just _after_ the box,
+    -- and the textdir is that of the surrounding text
     save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
-  else                         -- Or error? Shouldn't happen
+  else                    -- Empty box
     save_outer = ('TRT' == hdir) and 'r' or 'l'
   end
-    -- when the callback is called, we are just _after_ the box,
-    -- and the textdir is that of the surrounding text
-  -- if not ispar and hdir ~= tex.textdir then
-  --   save_outer = ('TRT' == hdir) and 'r' or 'l'
-  -- end
   local outer = save_outer
   local last = outer
   -- 'al' is only taken into account in the first, current loop
@@ -149,14 +147,17 @@
 
   for item in node.traverse(head) do
 
+    -- Mask: DxxxPPTT (Done, Pardir [0-2], Textdir [0-2])
+    locale_d = node.get_attribute(item, ATDIR)
+    node.set_attribute(item, ATDIR, 0x80)
+
     -- In what follows, #node is the last (previous) node, because the
     -- current one is not added until we start processing the neutrals.
-
     -- three cases: glyph, dir, otherwise
     if glyph_not_symbol_font(item)
        or (item.id == 7 and item.subtype == 2) then
 
-      if node.get_attribute(item, ATDIR) == 128 then goto nextnode end
+      if locale_d == 0x80 then goto nextnode end
 
       local d_font = nil
       local item_r
@@ -183,6 +184,7 @@
       d = d or 'l'
 
       -- A short 'pause' in bidi for mapfont
+      -- %%%% TODO. move if fontmap here
       d_font = d_font or d
       d_font = (d_font == 'l' and 0) or
                (d_font == 'nsm' and 0) or
@@ -198,8 +200,7 @@
         if inmath then
           attr_d = 0
         else
-          attr_d = node.get_attribute(item, ATDIR)
-          attr_d = attr_d & 0x3
+          attr_d = locale_d & 0x3
         end
         if attr_d == 1 then
           outer_first = 'r'
@@ -227,9 +228,8 @@
 
     elseif item.id == DIR then
       d = nil
+      new_d = true
 
-      if head ~= item then new_d = true end
-
     elseif item.id == node.id'glue' and item.subtype == 13 then
       glue_d = d
       glue_i = item
@@ -309,7 +309,6 @@
       table.insert(nodes, {item, d, outer_first})
     end
 
-    node.set_attribute(item, ATDIR, 128)
     outer_first = nil
 
     ::nextnode::
@@ -486,12 +485,10 @@
 
   return head
 end
--- Make sure anything is marked as 'bidi done' (including nodes inserted
--- after the babel algorithm). 128 = 1000 0000.
 function Babel.unset_atdir(head)
   local ATDIR = Babel.attr_dir
   for item in node.traverse(head) do
-    node.set_attribute(item, ATDIR, 128)
+    node.set_attribute(item, ATDIR, 0x80)
   end
   return head
 end

Modified: trunk/Master/texmf-dist/tex/generic/babel/babel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel.def	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel.def	2025-03-11 20:05:14 UTC (rev 74566)
@@ -39,7 +39,7 @@
     \wlog{File: #1 #4 #3 <#2>}%
     \let\ProvidesFile\@undefined}
 \fi
-\ProvidesFile{babel.def}[2025/02/14 v25.4 Babel common definitions]
+\ProvidesFile{babel.def}[2025/03/10 v25.5 Babel common definitions]
 \ifx\AtBeginDocument\@undefined
   \def\@empty{}
 \def\loadlocalcfg#1{%
@@ -505,6 +505,13 @@
     \expandafter\bbl at fornext
   \fi}
 \def\bbl at foreach#1{\expandafter\bbl at vforeach\expandafter{#1}}
+\global\let\bbl at done\@empty
+\def\bbl at once#1#2{%
+  \bbl at xin@{,#1,}{,\bbl at done,}%
+  \ifin@\else
+    #2%
+    \xdef\bbl at done{\bbl at done,#1,}%
+  \fi}
 \def\bbl at replace#1#2#3{% in #1 -> repl #2 by #3
   \toks@{}%
   \def\bbl at replace@aux##1#2##2#2{%
@@ -535,7 +542,8 @@
         \def\bbl at tempc{%     Expanded an executed below as 'uplevel'
            \\\makeatletter % "internal" macros with @ are assumed
            \\\scantokens{%
-             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}}%
+             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}%
+             \noexpand\noexpand}%
            \catcode64=\the\catcode64\relax}%  Restore @
       \else
         \let\bbl at tempc\@empty  % Not \relax
@@ -595,8 +603,8 @@
     \toks@\expandafter{\bbl at tempc#3}%
     \expandafter\edef\csname extras\languagename\endcsname{\the\toks@}%
   \fi}
-\def\bbl at version{25.4}
-\def\bbl at date{2025/02/14}
+\def\bbl at version{25.5}
+\def\bbl at date{2025/03/10}
 \ifx\language\@undefined
   \csname newcount\endcsname\language
 \fi
@@ -1515,7 +1523,11 @@
         \bbl at set@user at generic{\expandafter\string\@car#2\@nil}\bbl at tempb
     \fi
     \declare at shorthand{\bbl at tempb}{#2}{#3}}}
-\def\languageshorthands#1{\def\language at group{#1}}
+\def\languageshorthands#1{%
+  \bbl at ifsamestring{none}{#1}{}{%
+    \bbl at once{short-\localename-#1}{%
+      \bbl at info{'\localename' activates '#1' shorthands.\\Reported }}}%
+  \def\language at group{#1}}
 \def\aliasshorthand#1#2{%
   \bbl at ifshorthand{#2}%
     {\expandafter\ifx\csname active at char\string#2\endcsname\relax

Modified: trunk/Master/texmf-dist/tex/generic/babel/babel.sty
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel.sty	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel.sty	2025-03-11 20:05:14 UTC (rev 74566)
@@ -34,7 +34,7 @@
 %%
 \NeedsTeXFormat{LaTeX2e}
 \ProvidesPackage{babel}%
-  [2025/02/14 v25.4
+  [2025/03/10 v25.5
    The multilingual framework for pdfLaTeX, LuaLaTeX and XeLaTeX]
 \@ifpackagewith{babel}{debug}
   {\providecommand\bbl at trace[1]{\message{^^J[ #1 ]}}%
@@ -170,6 +170,13 @@
     \expandafter\bbl at fornext
   \fi}
 \def\bbl at foreach#1{\expandafter\bbl at vforeach\expandafter{#1}}
+\global\let\bbl at done\@empty
+\def\bbl at once#1#2{%
+  \bbl at xin@{,#1,}{,\bbl at done,}%
+  \ifin@\else
+    #2%
+    \xdef\bbl at done{\bbl at done,#1,}%
+  \fi}
 \def\bbl at replace#1#2#3{% in #1 -> repl #2 by #3
   \toks@{}%
   \def\bbl at replace@aux##1#2##2#2{%
@@ -200,7 +207,8 @@
         \def\bbl at tempc{%     Expanded an executed below as 'uplevel'
            \\\makeatletter % "internal" macros with @ are assumed
            \\\scantokens{%
-             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}}%
+             \bbl at tempa\\\@namedef{\bbl at stripslash#1}\bbl at tempb{\bbl at tempe}%
+             \noexpand\noexpand}%
            \catcode64=\the\catcode64\relax}%  Restore @
       \else
         \let\bbl at tempc\@empty  % Not \relax
@@ -466,8 +474,8 @@
       \expandafter\@secondoftwo
     \fi}
 \fi
-\def\bbl at version{25.4}
-\def\bbl at date{2025/02/14}
+\def\bbl at version{25.5}
+\def\bbl at date{2025/03/10}
 \ifx\language\@undefined
   \csname newcount\endcsname\language
 \fi
@@ -1386,7 +1394,11 @@
         \bbl at set@user at generic{\expandafter\string\@car#2\@nil}\bbl at tempb
     \fi
     \declare at shorthand{\bbl at tempb}{#2}{#3}}}
-\def\languageshorthands#1{\def\language at group{#1}}
+\def\languageshorthands#1{%
+  \bbl at ifsamestring{none}{#1}{}{%
+    \bbl at once{short-\localename-#1}{%
+      \bbl at info{'\localename' activates '#1' shorthands.\\Reported }}}%
+  \def\language at group{#1}}
 \def\aliasshorthand#1#2{%
   \bbl at ifshorthand{#2}%
     {\expandafter\ifx\csname active at char\string#2\endcsname\relax
@@ -2941,8 +2953,8 @@
     \global\let\bbl at patchchapter\relax
     \gdef\bbl at chfmt{%
       \bbl at ifunset{bbl@\bbl at chaptype fmt@\languagename}%
-        {\@chapapp\space\thechapter}
-        {\@nameuse{bbl@\bbl at chaptype fmt@\languagename}}}
+        {\@chapapp\space\thechapter}%
+        {\@nameuse{bbl@\bbl at chaptype fmt@\languagename}}}%
     \bbl at add\appendix{\def\bbl at chaptype{appendix}}% Not harmful, I hope
     \bbl at sreplace\ps at headings{\@chapapp\ \thechapter}{\bbl at chfmt}%
     \bbl at sreplace\chaptermark{\@chapapp\ \thechapter}{\bbl at chfmt}%
@@ -2960,8 +2972,8 @@
     \global\let\bbl at patchpart\relax
     \gdef\bbl at partformat{%
       \bbl at ifunset{bbl at partfmt@\languagename}%
-        {\partname\nobreakspace\thepart}
-        {\@nameuse{bbl at partfmt@\languagename}}}
+        {\partname\nobreakspace\thepart}%
+        {\@nameuse{bbl at partfmt@\languagename}}}%
     \bbl at sreplace\@part{\partname\nobreakspace\thepart}{\bbl at partformat}%
     \bbl at toglobal\@part}
 \fi
@@ -3000,6 +3012,11 @@
       {\@nameuse{bbl at date@\languagename @\bbl at calendar}%
          \bbl at they\bbl at them\bbl at thed}%
   \endgroup}
+\def\bbl at printdate#1{%
+  \@ifnextchar[{\bbl at printdate@i{#1}}{\bbl at printdate@i{#1}[]}}
+\def\bbl at printdate@i#1[#2]#3#4#5{%
+  \bbl at usedategrouptrue
+  \@nameuse{bbl at ensure@#1}{\localedate[#2]{#3}{#4}{#5}}}
 \def\bbl at inidate#1.#2.#3.#4\relax#5#6{% TODO - ignore with 'captions'
   \bbl at trim@def\bbl at tempa{#1.#2}%
   \bbl at ifsamestring{\bbl at tempa}{months.wide}%      to savedate
@@ -3026,24 +3043,6 @@
                   {\\\the\year}{\\\the\month}{\\\the\day}}}}%
        \fi}%
       {}}}
-\def\bbl at printdate#1{%
-  \@ifnextchar[{\bbl at printdate@i{#1}}{\bbl at printdate@i{#1}[]}}
-\def\bbl at printdate@i#1[#2]#3#4#5{%
-  \bbl at usedategrouptrue
-  \@nameuse{bbl at ensure@#1}{\localedate[#2]{#3}{#4}{#5}}}
-\AddToHook{begindocument/before}{%
-  \let\bbl at normalsf\normalsfcodes
-  \let\normalsfcodes\relax}
-\AtBeginDocument{%
-  \ifx\bbl at normalsf\@empty
-    \ifnum\sfcode`\.=\@m
-      \let\normalsfcodes\frenchspacing
-    \else
-      \let\normalsfcodes\nonfrenchspacing
-    \fi
-  \else
-    \let\normalsfcodes\bbl at normalsf
-  \fi}
 \let\bbl at calendar\@empty
 \newcommand\babelcalendar[2][\the\year-\the\month-\the\day]{%
   \@nameuse{bbl at ca@#2}#1\@@}
@@ -3087,6 +3086,19 @@
   \bbl at replace@finish at iii\bbl at toreplace}
 \def\bbl at datecntr{\expandafter\bbl at xdatecntr\expandafter}
 \def\bbl at xdatecntr[#1|#2]{\localenumeral{#2}{#1}}
+\AddToHook{begindocument/before}{%
+  \let\bbl at normalsf\normalsfcodes
+  \let\normalsfcodes\relax}
+\AtBeginDocument{%
+  \ifx\bbl at normalsf\@empty
+    \ifnum\sfcode`\.=\@m
+      \let\normalsfcodes\frenchspacing
+    \else
+      \let\normalsfcodes\nonfrenchspacing
+    \fi
+  \else
+    \let\normalsfcodes\bbl at normalsf
+  \fi}
 \bbl at csarg\let{inikv at transforms.prehyphenation}\bbl at inikv
 \bbl at csarg\let{inikv at transforms.posthyphenation}\bbl at inikv
 \def\bbl at transforms@aux#1#2#3#4,#5\relax{%

Modified: trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg	2025-03-11 20:05:14 UTC (rev 74566)
@@ -37,10 +37,10 @@
     \wlog{File: #1 #4 #3 <#2>}%
     \let\ProvidesFile\@undefined}
 \fi
-\ProvidesFile{hyphen.cfg}[2025/02/14 v25.4 Babel hyphens]
+\ProvidesFile{hyphen.cfg}[2025/03/10 v25.5 Babel hyphens]
 \xdef\bbl at format{\jobname}
-\def\bbl at version{25.4}
-\def\bbl at date{2025/02/14}
+\def\bbl at version{25.5}
+\def\bbl at date{2025/03/10}
 \ifx\AtBeginDocument\@undefined
   \def\@empty{}
 \fi

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/cs/babel-cs.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/cs/babel-cs.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/cs/babel-cs.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.4
-date = 2022-07-02
+version = 1.5
+date = 2025-03-04
 name.local = čeština
 name.english = Czech
 name.babel = czech
@@ -183,7 +183,7 @@
 [counters]
 
 [transforms.prehyphenation]
-oneletter.nobreak.1.0 = { |[AIiVvOoUuSsZzKk]()|() }
+oneletter.nobreak.1.0 = { |[AaIiVvOoUuSsZzKk]()|() }
 oneletter.nobreak.1.1 =   { insert, penalty=10000 }
 oneletter.nobreak.1.2 =   {}
 

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1901.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1901.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1901.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.5
-date = 2022-07-02
+version = 1.6
+date = 2025-03-08
 name.local = Deutsch
 name.english = German
 name.babel = german german-traditional
@@ -180,4 +180,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1996.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1996.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-1996.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.5
-date = 2022-07-02
+version = 1.6
+date = 2025-03-08
 name.local = Deutsch
 name.english = German
 name.babel = ngerman german
@@ -179,4 +179,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1901.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1901.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1901.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.5
-date = 2022-07-02
+version = 1.6
+date = 2025-03-08
 name.local = Österreichisches Deutsch
 name.english = Austrian German
 name.babel = austrian-traditional german-austria-traditional german-at-traditional austriangerman-traditional austrian
@@ -185,4 +185,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1996.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1996.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT-1996.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.5
-date = 2022-07-02
+version = 1.6
+date = 2025-03-08
 name.local = Österreichisches Deutsch
 name.english = Austrian German
 name.babel = austrian german-austria german-at austriangerman naustrian
@@ -185,4 +185,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-AT.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.4
-date = 2022-07-02
+version = 1.5
+date = 2025-03-08
 name.local = Österreichisches Deutsch
 name.english = Austrian German
 name.babel = austrian german-austria german-at austriangerman naustrian
@@ -184,4 +184,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1901.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1901.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1901.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.6
-date = 2022-07-02
+version = 1.7
+date = 2025-03-08
 name.local = Schweizer Hochdeutsch
 name.english = Swiss High German
 name.babel = german-switzerland-traditional german-ch-traditional swisshighgerman-traditional
@@ -185,4 +185,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1996.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1996.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH-1996.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.6
-date = 2022-07-02
+version = 1.7
+date = 2025-03-08
 name.local = Schweizer Hochdeutsch
 name.english = Swiss High German
 name.babel = german-switzerland german-ch nswissgerman swisshighgerman
@@ -187,4 +187,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de-CH.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.7
-date = 2022-07-02
+version = 1.8
+date = 2025-03-08
 name.local = Schweizer Hochdeutsch
 name.english = Swiss High German
 name.babel = german-switzerland german-ch swisshighgerman nswissgerman
@@ -184,4 +184,16 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/de/babel-de.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.4
-date = 2022-07-02
+version = 1.5
+date = 2025-03-08
 name.local = Deutsch
 name.english = German
 name.babel = ngerman german
@@ -180,4 +180,17 @@
 plusSign = +
 superscriptingExponent = ·
 
-[counters]
+[transforms.posthyphenation]
+longs.unifraktur.1.0 = { g|?()s()|?[act] }
+longs.unifraktur.1.1 =   {string = ſ }
+longs.unifraktur.2.0 = { [AEOUYaeioyäöüı]|?()s()|?[acteiouyäöüpſs] }
+longs.unifraktur.2.1 =   { string = ſ }
+longs.unifraktur.3.0 = { [bdfhklrſtu]|?()s()|?[acteiouyäöüp] }
+longs.unifraktur.3.1 =   {string = ſ}
+longs.unifraktur.4.0 = { [cjmnpqvw]|?()s()|?[acteiouyäöüpkr] }
+longs.unifraktur.4.1 =   {string = ſ}
+longs.unifraktur.5.0 = { [ßsxz]|?()s()|?[acteiouyäöüpkrbdfghjlmnqvwßſxz] }
+longs.unifraktur.5.1 =   { string = ſ}
+longs.unifraktur.6.0 = { ^{A}*()s() }
+longs.unifraktur.6.1 =   { string = ſ }
+

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/nl/babel-nl.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/nl/babel-nl.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/nl/babel-nl.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.4
-date = 2022-09-24
+version = 1.5
+date = 2025-03-05
 name.local = Nederlands
 name.english = Dutch
 name.babel = dutch
@@ -180,4 +180,9 @@
 plusSign = +
 superscriptingExponent = ×
 
-[counters]
+[transforms.posthyphenation]
+diaeresis.hyphen.1.0 = { [aeiou]|([äëïöü]) }
+diaeresis.hyphen.1.1 =   {}
+diaeresis.hyphen.1.2 =   { remove }
+diaeresis.hyphen.1.3 =   { no = {1}, pre = - , post = {1|äëïöü|aeiou} }
+

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/sk/babel-sk.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/sk/babel-sk.ini	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/sk/babel-sk.ini	2025-03-11 20:05:14 UTC (rev 74566)
@@ -9,8 +9,8 @@
 
 [identification]
 charset = utf8
-version = 1.4
-date = 2022-10-12
+version = 1.5
+date = 2025-03-04
 name.local = slovenčina
 name.english = Slovak
 name.babel = slovak
@@ -183,7 +183,7 @@
 [counters]
 
 [transforms.prehyphenation]
-oneletter.nobreak.1.0 = { |[AIiVvOoUuSsZzKk]()|() }
+oneletter.nobreak.1.0 = { |[AaIiVvOoUuSsZzKk]()|() }
 oneletter.nobreak.1.1 =   { insert, penalty=10000 }
 oneletter.nobreak.1.2 =   {}
 

Modified: trunk/Master/texmf-dist/tex/generic/babel/luababel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/luababel.def	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/luababel.def	2025-03-11 20:05:14 UTC (rev 74566)
@@ -569,8 +569,7 @@
       end
     end,
     'Babel.hyphenate')
-  }
-}
+  }}
 \endgroup
 \def\bbl at provide@intraspace{%
   \bbl at ifunset{bbl at intsp@\languagename}{}%
@@ -753,7 +752,9 @@
         end
       end
 
-      % Tatwil
+      % Tatwil. First create a list of nodes marked with kashida. The
+      % rest of nodes can be ignored. The list of used weigths is build
+      % when transforms with the key kashida= are declared.
       if Babel.kashida_wts then
         local k_wt = node.get_attribute(n, KASHIDA)
         if k_wt > 0 then % todo. parameter for multi inserts
@@ -788,6 +789,11 @@
     end
 
     % == Tatwil ==
+    % Traverse the kashida node list so many times as required, until
+    % the line if filled. The first pass adds a tatweel after each
+    % node with kashida in the line, the second pass adds another one,
+    % and so on. In each pass, add first the kashida with the highest
+    % weight, then with lower weight and so on.
     if #k_list == 0 then goto next_line end
 
     width = node.dimensions(line.head)    % The 'natural' width
@@ -902,11 +908,12 @@
   \bbl at exp{%
     \\\newcommand\<#1default>{}% Just define it
     \\\bbl at add@list\\\bbl at font@fams{#1}%
+    \\\NewHook{#1family}%
     \\\DeclareRobustCommand\<#1family>{%
       \\\not at math@alphabet\<#1family>\relax
       % \\\prepare at family@series at update{#1}\<#1default>% TODO. Fails
       \\\fontfamily\<#1default>%
-      \<ifx>\\\UseHooks\\\@undefined\<else>\\\UseHook{#1family}\<fi>%
+      \\\UseHook{#1family}%
       \\\selectfont}%
     \\\DeclareTextFontCommand{\<text#1>}{\<#1family>}}}
 \def\bbl at nostdfont#1{%
@@ -1596,7 +1603,7 @@
             \let\@eqnnum\bbl at eqnum
           \fi
         \fi}
-      % Hack. YA luatex bug?:
+      % Hack for wrong vertical spacing with \[ \]. YA luatex bug?:
       \expandafter\bbl at sreplace\csname] \endcsname{$$}{\eqno\kern.001pt$$}%
     \else % amstex
       \bbl at exp{% Hack to hide maybe undefined conditionals:
@@ -1741,7 +1748,7 @@
     % TODO - catch non-valid values
   \fi
   % == mapfont ==
-  % For bidi texts, to switch the font based on direction
+  % For bidi texts, to switch the font based on direction. Old.
   \ifx\bbl at KVP@mapfont\@nnil\else
     \bbl at ifsamestring{\bbl at KVP@mapfont}{direction}{}%
       {\bbl at error{unknown-mapfont}{}{}{}}%

Modified: trunk/Master/texmf-dist/tex/generic/babel/nil.ldf
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/nil.ldf	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/nil.ldf	2025-03-11 20:05:14 UTC (rev 74566)
@@ -32,7 +32,7 @@
 %% and covered by LPPL is defined by the unpacking scripts (with
 %% extension |.ins|) which are part of the distribution.
 %%
-\ProvidesLanguage{nil}[2025/02/14 v25.4 Nil language]
+\ProvidesLanguage{nil}[2025/03/10 v25.5 Nil language]
 \LdfInit{nil}{datenil}
 \ifx\l at nil\@undefined
   \newlanguage\l at nil

Added: trunk/Master/texmf-dist/tex/generic/babel/tests-babel-bidi-basic.lua
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/tests-babel-bidi-basic.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/tex/generic/babel/tests-babel-bidi-basic.lua	2025-03-11 20:05:14 UTC (rev 74566)
@@ -0,0 +1,519 @@
+--
+-- This is file `babel-bidi-basic.lua',
+-- generated with the docstrip utility.
+--
+-- The original source files were:
+--
+-- babel.dtx  (with options: `basic')
+-- 
+--
+-- Copyright (C) 2012-2025 Javier Bezos and Johannes L. Braams.
+-- Copyright (C) 1989-2012 Johannes L. Braams and
+--           any individual authors listed elsewhere in this file.
+-- All rights reserved.
+--
+--
+-- This file is part of the Babel system.
+-- --------------------------------------
+--
+-- It may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+--   http://www.latex-project.org/lppl.txt
+-- and version 1.3 or later is part of all distributions of LaTeX
+-- version 2003/12/01 or later.
+--
+-- This work has the LPPL maintenance status "maintained".
+--
+-- The Current Maintainer of this work is Javier Bezos.
+--
+-- The list of derived (unpacked) files belonging to the distribution
+-- and covered by LPPL is defined by the unpacking scripts (with
+-- extension |.ins|) which are part of the distribution.
+--
+-- e.g., Babel.fontmap[1][<prefontid>]=<dirfontid>
+
+Babel.fontmap = Babel.fontmap or {}
+Babel.fontmap[0] = {}      -- l
+Babel.fontmap[1] = {}      -- r
+Babel.fontmap[2] = {}      -- al/an
+
+-- To cancel mirroring. Also OML, OMS, U?
+Babel.symbol_fonts = Babel.symbol_fonts or {}
+Babel.symbol_fonts[font.id('tenln')] = true
+Babel.symbol_fonts[font.id('tenlnw')] = true
+Babel.symbol_fonts[font.id('tencirc')] = true
+Babel.symbol_fonts[font.id('tencircw')] = true
+
+Babel.bidi_enabled = true
+Babel.mirroring_enabled = true
+
+require('babel-data-bidi.lua')
+
+local characters = Babel.characters
+local ranges = Babel.ranges
+
+local DIR = node.id('dir')
+local GLYPH = node.id('glyph')
+
+local function insert_implicit(head, state, outer)
+  local new_state = state
+  if state.sim and state.eim and state.sim ~= state.eim then
+    dir = ((outer == 'r') and 'TLT' or 'TRT') -- i.e., reverse
+    local d = node.new(DIR)
+    d.dir = '+' .. dir
+    node.insert_before(head, state.sim, d)
+    local d = node.new(DIR)
+    d.dir = '-' .. dir
+    node.insert_after(head, state.eim, d)
+  end
+  new_state.sim, new_state.eim = nil, nil
+  return head, new_state
+end
+
+local function insert_numeric(head, state)
+  local new
+  local new_state = state
+  if state.san and state.ean and state.san ~= state.ean then
+    local d = node.new(DIR)
+    d.dir = '+TLT'
+    _, new = node.insert_before(head, state.san, d)
+    if state.san == state.sim then state.sim = new end
+    local d = node.new(DIR)
+    d.dir = '-TLT'
+    _, new = node.insert_after(head, state.ean, d)
+    if state.ean == state.eim then state.eim = new end
+  end
+  new_state.san, new_state.ean = nil, nil
+  return head, new_state
+end
+
+local function glyph_not_symbol_font(node)
+  if node.id == GLYPH then
+    return not Babel.symbol_fonts[node.font]
+  else
+   return false
+  end
+end
+
+-- TODO - \hbox with an explicit dir can lead to wrong results
+-- <R \hbox dir TLT{<R>}> and <L \hbox dir TRT{<L>}>. A small attempt
+-- was made to improve the situation, but the problem is the 3-dir
+-- model in babel/Unicode and the 2-dir model in LuaTeX don't fit
+-- well.
+
+function Babel.bidi(head, ispar, hdir)
+  local d   -- d is used mainly for computations in a loop
+  local prev_d = ''
+  local new_d = false
+
+  local nodes = {}
+  local outer_first = nil
+  local inmath = false
+
+  local glue_d = nil
+  local glue_i = nil
+
+  local has_en = false
+  local first_et = nil
+
+  local has_hyperlink = false
+
+  local ATDIR = Babel.attr_dir
+  local attr_d
+
+  local save_outer
+  local temp = node.get_attribute(head, ATDIR)
+  if temp then
+    temp = temp & 0x3
+    save_outer = (temp == 0 and 'l') or
+                 (temp == 1 and 'r') or
+                 (temp == 2 and 'al')
+  elseif ispar then            -- Or error? Shouldn't happen
+    save_outer = ('TRT' == tex.pardir) and 'r' or 'l'
+  else                         -- Or error? Shouldn't happen
+    save_outer = ('TRT' == hdir) and 'r' or 'l'
+  end
+    -- when the callback is called, we are just _after_ the box,
+    -- and the textdir is that of the surrounding text
+  -- if not ispar and hdir ~= tex.textdir then
+  --   save_outer = ('TRT' == hdir) and 'r' or 'l'
+  -- end
+  local outer = save_outer
+  local last = outer
+  -- 'al' is only taken into account in the first, current loop
+  if save_outer == 'al' then save_outer = 'r' end
+
+  local fontmap = Babel.fontmap
+
+  for item in node.traverse(head) do
+
+    -- In what follows, #node is the last (previous) node, because the
+    -- current one is not added until we start processing the neutrals.
+
+    -- three cases: glyph, dir, otherwise
+    if glyph_not_symbol_font(item)
+       or (item.id == 7 and item.subtype == 2) then
+
+      if node.get_attribute(item, ATDIR) == 128 then goto nextnode end
+
+      local d_font = nil
+      local item_r
+      if item.id == 7 and item.subtype == 2 then
+        item_r = item.replace    -- automatic discs have just 1 glyph
+      else
+        item_r = item
+      end
+
+      local chardata = characters[item_r.char]
+      d = chardata and chardata.d or nil
+      if not d or d == 'nsm' then
+        for nn, et in ipairs(ranges) do
+          if item_r.char < et[1] then
+            break
+          elseif item_r.char <= et[2] then
+            if not d then d = et[3]
+            elseif d == 'nsm' then d_font = et[3]
+            end
+            break
+          end
+        end
+      end
+      d = d or 'l'
+
+      -- A short 'pause' in bidi for mapfont
+      d_font = d_font or d
+      d_font = (d_font == 'l' and 0) or
+               (d_font == 'nsm' and 0) or
+               (d_font == 'r' and 1) or
+               (d_font == 'al' and 2) or
+               (d_font == 'an' and 2) or nil
+      if d_font and fontmap and fontmap[d_font][item_r.font] then
+        item_r.font = fontmap[d_font][item_r.font]
+      end
+
+      if new_d then
+        table.insert(nodes, {nil, (outer == 'l') and 'l' or 'r', nil})
+        if inmath then
+          attr_d = 0
+        else
+          attr_d = node.get_attribute(item, ATDIR)
+          attr_d = attr_d & 0x3
+        end
+        if attr_d == 1 then
+          outer_first = 'r'
+          last = 'r'
+        elseif attr_d == 2 then
+          outer_first = 'r'
+          last = 'al'
+        else
+          outer_first = 'l'
+          last = 'l'
+        end
+        outer = last
+        has_en = false
+        first_et = nil
+        new_d = false
+      end
+
+      if glue_d then
+        if (d == 'l' and 'l' or 'r') ~= glue_d then
+           table.insert(nodes, {glue_i, 'on', nil})
+        end
+        glue_d = nil
+        glue_i = nil
+      end
+
+    elseif item.id == DIR then
+      d = nil
+
+      if head ~= item then new_d = true end
+
+    elseif item.id == node.id'glue' and item.subtype == 13 then
+      glue_d = d
+      glue_i = item
+      d = nil
+
+    elseif item.id == node.id'math' then
+      inmath = (item.subtype == 0)
+
+    elseif item.id == 8 and item.subtype == 19 then
+      has_hyperlink = true
+
+    else
+      d = nil
+    end
+
+    -- AL <= EN/ET/ES     -- W2 + W3 + W6
+    if last == 'al' and d == 'en' then
+      d = 'an'           -- W3
+    elseif last == 'al' and (d == 'et' or d == 'es') then
+      d = 'on'           -- W6
+    end
+
+    -- EN + CS/ES + EN     -- W4
+    if d == 'en' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'es' or nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'en' then
+        nodes[#nodes][2] = 'en'
+      end
+    end
+
+    -- AN + CS + AN        -- W4 too, because uax9 mixes both cases
+    if d == 'an' and #nodes >= 2 then
+      if (nodes[#nodes][2] == 'cs')
+          and nodes[#nodes-1][2] == 'an' then
+        nodes[#nodes][2] = 'an'
+      end
+    end
+
+    -- ET/EN               -- W5 + W7->l / W6->on
+    if d == 'et' then
+      first_et = first_et or (#nodes + 1)
+    elseif d == 'en' then
+      has_en = true
+      first_et = first_et or (#nodes + 1)
+    elseif first_et then       -- d may be nil here !
+      if has_en then
+        if last == 'l' then
+          temp = 'l'    -- W7
+        else
+          temp = 'en'   -- W5
+        end
+      else
+        temp = 'on'     -- W6
+      end
+      for e = first_et, #nodes do
+        if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+      end
+      first_et = nil
+      has_en = false
+    end
+
+    -- Force mathdir in math if ON (currently works as expected only
+    -- with 'l')
+
+    if inmath and d == 'on' then
+      d = ('TRT' == tex.mathdir) and 'r' or 'l'
+    end
+
+    if d then
+      if d == 'al' then
+        d = 'r'
+        last = 'al'
+      elseif d == 'l' or d == 'r' then
+        last = d
+      end
+      prev_d = d
+      table.insert(nodes, {item, d, outer_first})
+    end
+
+    -- node.set_attribute(item, ATDIR, 128)
+    outer_first = nil
+
+    ::nextnode::
+
+  end -- for each node
+
+  -- TODO -- repeated here in case EN/ET is the last node. Find a
+  -- better way of doing things:
+  if first_et then       -- dir may be nil here !
+    if has_en then
+      if last == 'l' then
+        temp = 'l'    -- W7
+      else
+        temp = 'en'   -- W5
+      end
+    else
+      temp = 'on'     -- W6
+    end
+    for e = first_et, #nodes do
+      if glyph_not_symbol_font(nodes[e][1]) then nodes[e][2] = temp end
+    end
+  end
+
+  -- dummy node, to close things
+  table.insert(nodes, {nil, (outer == 'l') and 'l' or 'r', nil})
+
+  ---------------  NEUTRAL -----------------
+
+  outer = save_outer
+  last = outer
+
+  local first_on = nil
+
+  for q = 1, #nodes do
+    local item
+
+    local outer_first = nodes[q][3]
+    outer = outer_first or outer
+    last = outer_first or last
+    
+    texio.write_nl('****')
+    texio.write_nl(outer)
+    if nodes[q][1] then
+      texio.write_nl('char = ' .. nodes[q][1].char)
+    texio.write_nl('dirtext = ' .. (node.get_attribute(nodes[q][1], ATDIR)
+          & 0x3))
+      texio.write(', dir = ' .. nodes[q][2])
+      texio.write(', outer = ' .. (nodes[q][3] or '?'))
+    else
+      texio.write_nl('Ø')
+    end
+    -- outer = 'l'
+    
+    
+    local d = nodes[q][2]
+    if d == 'an' or d == 'en' then d = 'r' end
+    if d == 'cs' or d == 'et' or d == 'es' then d = 'on' end --- W6
+
+    if d == 'on' then
+      first_on = first_on or q
+    elseif first_on then
+      if last == d then
+        temp = d
+      else
+        temp = outer
+      end
+      for r = first_on, q - 1 do
+        nodes[r][2] = temp
+        item = nodes[r][1]    -- MIRRORING
+        if Babel.mirroring_enabled and glyph_not_symbol_font(item)
+             and temp == 'r' and characters[item.char] then
+          texio.write_nl('==== ' .. item.char)
+          texio.write_nl('dir = ' .. node.get_attribute(item, ATDIR))
+          texio.write_nl('dirtext = ' .. (node.get_attribute(item, ATDIR)
+          & 0x3))
+          local font_mode = ''
+          if item.font > 0 and font.fonts[item.font].properties then
+            font_mode = font.fonts[item.font].properties.mode
+          end
+          if font_mode ~= 'harf' and font_mode ~= 'plug' and
+              node.get_attribute(item, ATDIR) & 0x3 ~= 0 then
+            texio.write_nl('change ' .. item.char .. ' -> '
+               .. (characters[item.char].m
+            or item.char))
+            item.char = characters[item.char].m or item.char
+          end
+        end
+      end
+      first_on = nil
+    end
+
+    if d == 'r' or d == 'l' then last = d end
+  end
+
+  --------------  IMPLICIT, REORDER ----------------
+
+  outer = save_outer
+  last = outer
+
+  local state = {}
+  state.has_r = false
+
+  for q = 1, #nodes do
+
+    local item = nodes[q][1]
+
+    outer = nodes[q][3] or outer
+
+    local d = nodes[q][2]
+
+    if d == 'nsm' then d = last end             -- W1
+    if d == 'en' then d = 'an' end
+    local isdir = (d == 'r' or d == 'l')
+
+    if outer == 'l' and d == 'an' then
+      state.san = state.san or item
+      state.ean = item
+    elseif state.san then
+      head, state = insert_numeric(head, state)
+    end
+
+    if outer == 'l' then
+      if d == 'an' or d == 'r' then     -- im -> implicit
+        if d == 'r' then state.has_r = true end
+        state.sim = state.sim or item
+        state.eim = item
+      elseif d == 'l' and state.sim and state.has_r then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'l' then
+        state.sim, state.eim, state.has_r = nil, nil, false
+      end
+    else
+      if d == 'an' or d == 'l' then
+        if nodes[q][3] then -- nil except after an explicit dir
+          state.sim = item  -- so we move sim 'inside' the group
+        else
+          state.sim = state.sim or item
+        end
+        state.eim = item
+      elseif d == 'r' and state.sim then
+        head, state = insert_implicit(head, state, outer)
+      elseif d == 'r' then
+        state.sim, state.eim = nil, nil
+      end
+    end
+
+    if isdir then
+      last = d           -- Don't search back - best save now
+    elseif d == 'on' and state.san  then
+      state.san = state.san or item
+      state.ean = item
+    end
+
+  end
+
+  head = node.prev(head) or head
+  --- FIXES ---
+  if has_hyperlink then
+    local flag, linking = 0, 0
+    for item in node.traverse(head) do
+      if item.id == DIR then
+        if item.dir == '+TRT' or item.dir == '+TLT' then
+          flag = flag + 1
+        elseif item.dir == '-TRT' or item.dir == '-TLT' then
+          flag = flag - 1
+        end
+      elseif item.id == 8 and item.subtype == 19 then
+        linking = flag
+      elseif item.id == 8 and item.subtype == 20 then
+        if linking > 0 then
+          if item.prev.id == DIR and
+              (item.prev.dir == '-TRT' or item.prev.dir == '-TLT') then
+            d = node.new(DIR)
+            d.dir = item.prev.dir
+            node.remove(head, item.prev)
+            node.insert_after(head, item, d)
+          end
+        end
+        linking = 0
+      end
+    end
+  end
+
+  for item in node.traverse_id(10, head) do
+    local p = item
+    local flag = false
+    while p.prev and p.prev.id == 14 do
+      flag = true
+      p = p.prev
+    end
+    if flag then
+      node.insert_before(head, p, node.copy(item))
+      node.remove(head,item)
+    end
+  end
+
+  return head
+end
+-- Make sure anything is marked as 'bidi done' (including nodes inserted
+-- after the babel algorithm). 128 = 1000 0000.
+function Babel.unset_atdir(head)
+  local ATDIR = Babel.attr_dir
+  for item in node.traverse(head) do
+    node.set_attribute(item, ATDIR, 128)
+  end
+  return head
+end


Property changes on: trunk/Master/texmf-dist/tex/generic/babel/tests-babel-bidi-basic.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/tex/generic/babel/txtbabel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/txtbabel.def	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/txtbabel.def	2025-03-11 20:05:14 UTC (rev 74566)
@@ -34,8 +34,6 @@
 %%
 \providecommand\bbl at provide@intraspace{}
 \bbl at trace{Redefinitions for bidi layout}
-\def\bbl at sspre@caption{%  TODO: Unused!
-  \bbl at exp{\everyhbox{\\\bbl at textdir\bbl at cs{wdir@\bbl at main@language}}}}
 \ifx\bbl at opt@layout\@nnil\else % if layout=..
 \def\bbl at startskip{\ifcase\bbl at thepardir\leftskip\else\rightskip\fi}
 \def\bbl at endskip{\ifcase\bbl at thepardir\rightskip\else\leftskip\fi}

Modified: trunk/Master/texmf-dist/tex/generic/babel/xebabel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/xebabel.def	2025-03-11 20:03:36 UTC (rev 74565)
+++ trunk/Master/texmf-dist/tex/generic/babel/xebabel.def	2025-03-11 20:05:14 UTC (rev 74566)
@@ -126,11 +126,12 @@
   \bbl at exp{%
     \\\newcommand\<#1default>{}% Just define it
     \\\bbl at add@list\\\bbl at font@fams{#1}%
+    \\\NewHook{#1family}%
     \\\DeclareRobustCommand\<#1family>{%
       \\\not at math@alphabet\<#1family>\relax
       % \\\prepare at family@series at update{#1}\<#1default>% TODO. Fails
       \\\fontfamily\<#1default>%
-      \<ifx>\\\UseHooks\\\@undefined\<else>\\\UseHook{#1family}\<fi>%
+      \\\UseHook{#1family}%
       \\\selectfont}%
     \\\DeclareTextFontCommand{\<text#1>}{\<#1family>}}}
 \def\bbl at nostdfont#1{%
@@ -365,8 +366,6 @@
     {\bbl at csarg\let{ic@#1@\languagename}\@gobble}}
 \providecommand\bbl at provide@intraspace{}
 \bbl at trace{Redefinitions for bidi layout}
-\def\bbl at sspre@caption{%  TODO: Unused!
-  \bbl at exp{\everyhbox{\\\bbl at textdir\bbl at cs{wdir@\bbl at main@language}}}}
 \ifx\bbl at opt@layout\@nnil\else % if layout=..
 \def\bbl at startskip{\ifcase\bbl at thepardir\leftskip\else\rightskip\fi}
 \def\bbl at endskip{\ifcase\bbl at thepardir\rightskip\else\leftskip\fi}



More information about the tex-live-commits mailing list.