texlive[74879] Master/texmf-dist: l3kernel (8apr25)

commits+karl at tug.org commits+karl at tug.org
Tue Apr 8 22:12:12 CEST 2025


Revision: 74879
          https://tug.org/svn/texlive?view=revision&revision=74879
Author:   karl
Date:     2025-04-08 22:12:11 +0200 (Tue, 08 Apr 2025)
Log Message:
-----------
l3kernel (8apr25)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md
    trunk/Master/texmf-dist/doc/latex/l3kernel/README.md
    trunk/Master/texmf-dist/doc/latex/l3kernel/expl3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3doc.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3docstrip.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news01.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news02.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news03.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news04.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news05.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news06.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news07.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news08.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news09.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news10.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news11.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3news12.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/source3.pdf
    trunk/Master/texmf-dist/doc/latex/l3kernel/source3.tex
    trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex
    trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3.ins
    trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx
    trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx
    trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty
    trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def
    trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls

Added Paths:
-----------
    trunk/Master/texmf-dist/source/latex/l3kernel/l3graphics.dtx

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/CHANGELOG.md	2025-04-08 20:12:11 UTC (rev 74879)
@@ -7,6 +7,33 @@
 
 ## [Unreleased]
 
+## [2025-03-26]
+
+### Added
+- Module `l3graphics`, moved from `l3experimental` to stable status
+
+### Fixed
+- Correct scope for `\prg_gset_conditional:Nnn`
+
+## [2025-03-10]
+
+### Added
+- `\text_words_map_function:nN`
+- Variants `\tl_head:e` and `\tl_tail:e`
+- `l3tl` functions (and variants) which trim spaces from only one side
+  (issue \#1673):
+  - `\tl_trim_left_spaces:n`, `\tl_trim_left_spaces_apply:nN`,
+    `\tl_(g)trim_left_spaces:N`
+  - `\tl_trim_right_spaces:n`, `\tl_trim_right_spaces_apply:nN`,
+    `\tl_(g)trim_right_spaces:N`
+
+### Changed
+- `\fp_set_function:nnn` and `\fp_set_variable:nn` now raise errors on
+  undefined (fp) identifiers (issue \#1700)
+
+### Fixed
+- Avoid low-level error if keys are given without a module (issue \#1254)
+
 ## [2025-01-18]
 
 ### Changed
@@ -330,10 +357,10 @@
 - Switch generally from `x`- to `e`-type variants
 - Convert `\file_if_exist:n(TF)` to expandable status,
   adding predicate version
-- Standardise variants for `\prop_(g)pop:NnN(TF)`
-- Standardise variants for `\prop_(g)put:Nnn`
-- Standardise variants for `\prop_(g)put_if_new:Nnn`
-- Standardise variants for `\prop_(g)remove:Nn`
+- Standardize variants for `\prop_(g)pop:NnN(TF)`
+- Standardize variants for `\prop_(g)put:Nnn`
+- Standardize variants for `\prop_(g)put_if_new:Nnn`
+- Standardize variants for `\prop_(g)remove:Nn`
 
 ### Deprecated
 - `\iow_shipout_x:Nn` in favor of `e`-type naming
@@ -1846,7 +1873,9 @@
 - Step functions have been added for dim variables,
   e.g. `\dim_step_inline:nnnn`
 
-[Unreleased]: https://github.com/latex3/latex3/compare/2025-01-18...HEAD
+[Unreleased]: https://github.com/latex3/latex3/compare/2025-03-26...HEAD
+[2025-03-26]: https://github.com/latex3/latex3/compare/2025-03-10...2025-03-26
+[2025-03-10]: https://github.com/latex3/latex3/compare/2025-01-18...2025-03-10
 [2025-01-18]: https://github.com/latex3/latex3/compare/2025-01-14...2025-01-18
 [2025-01-14]: https://github.com/latex3/latex3/compare/2024-12-25...2025-01-14
 [2024-12-25]: https://github.com/latex3/latex3/compare/2024-12-09...2024-12-25

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/README.md	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/README.md	2025-04-08 20:12:11 UTC (rev 74879)
@@ -1,7 +1,7 @@
 LaTeX3 Programming Conventions
 ==============================
 
-Release 2025-01-18
+Release 2025-03-26
 
 Overview
 --------

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

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/interface3.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -58,7 +58,7 @@
          {latex-team at latex-project.org}%
    }%
 }
-\date{Released 2025-01-18}
+\date{Released 2025-03-26}
 
 \pagenumbering{roman}
 \maketitle

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3prefixes.csv	2025-04-08 20:12:11 UTC (rev 74879)
@@ -30,6 +30,7 @@
 cascade,cascade,F. Pantigny,,,,2020-07-21,2020-07-21,
 catcode,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 cctab,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-28,2012-09-28,
+cdhh,codehigh,Jianrui Lyu,https://github.com/lvjr/codehigh,https://github.com/lvjr/codehigh.git,https://github.com/lvjr/codehigh/issues,2025-02-16,2025-02-16,
 cellprops,cellprops,Julien Rivaud,,,,2018-06-13,2018-06-13,
 chaos,"chaos,schleuderpackung",Marei Peischl,https://ds.ccc.de/,,,2021-05-28,2021-05-28,
 char,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
@@ -249,6 +250,7 @@
 sort,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2017-02-13,
 space,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 spath,spath3,Andrew Stacey,https://github.com/loopspace/spath3,https://github.com/loopspace/spath3.git,https://github.com/loopspace/spath3/issues,2024-07-18,2024-07-18,
+speg,pegmatch,Jianrui Lyu,https://github.com/lvjr/pegmatch,https://github.com/lvjr/pegmatch.git,https://github.com/lvjr/pegmatch/issues,2025-02-16,2025-02-16,
 starray,starray,Alceu Frigeri,https://github.com/alceu-frigeri/starray,https://github.com/alceu-frigeri/starray,https://github.com/alceu-frigeri/starray/issues,2023-05-15,2023-05-15,
 statistics,statistics,Julien Rivaud,https://gitlab.com/frnchfrgg-latex/statistics,https://gitlab.com/frnchfrgg-latex/statistics.git,https://gitlab.com/frnchfrgg-latex/statistics/issues,2018-06-25,2018-06-25,
 stm,lt3-stm,CV Radhakrishnan,http://www.cvr.cc/,,,2014-02-26,2014-02-26,
@@ -275,6 +277,7 @@
 tmpa,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 tmpb,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2018-05-12,2018-05-12,
 token,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2012-09-27,2012-09-27,
+tpeg,pegmatch,Jianrui Lyu,https://github.com/lvjr/pegmatch,https://github.com/lvjr/pegmatch.git,https://github.com/lvjr/pegmatch/issues,2025-02-16,2025-02-16,
 true,l3kernel,The LaTeX Project,https://www.latex-project.org/latex3.html,https://github.com/latex3/latex3.git,https://github.com/latex3/latex3/issues,2021-04-23,2021-04-23,
 twmk,menukeys,Tobias Weh,https://github.com/tweh/menukeys,git@github.com:tweh/menukeys.git,https://github.com/tweh/menukeys/issues,2020-10-31,2020-10-31,“classic” L2 package using only some expl3 code
 ufcombo,combofont,Ulrike Fischer,https://github.com/u-fischer/combofont,https://github.com/u-fischer/combofont,https://github.com/u-fischer/combofont/issues,2020-04-24,2020-04-24,

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

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3styleguide.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -32,7 +32,7 @@
         {latex-team at latex-project.org}%
     }%
 }
-\date{Released 2025-01-18}
+\date{Released 2025-03-26}
 
 \begin{document}
 

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3syntax-changes.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -32,7 +32,7 @@
         {latex-team at latex-project.org}%
     }%
 }
-\date{Released 2025-01-18}
+\date{Released 2025-03-26}
 
 \newcommand{\TF}{\textit{(TF)}}
 

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/l3term-glossary.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -32,7 +32,7 @@
         {latex-team at latex-project.org}%
     }%
 }
-\date{Released 2025-01-18}
+\date{Released 2025-03-26}
 
 \newcommand{\TF}{\textit{(TF)}}
 

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

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/source3.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/source3.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/source3.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -57,7 +57,7 @@
          {latex-team at latex-project.org}%
    }%
 }
-\date{Released 2025-01-18}
+\date{Released 2025-03-26}
 
 \pagenumbering{roman}
 \maketitle

Modified: trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/doc/latex/l3kernel/source3body.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -637,6 +637,7 @@
 \DocInput{l3box.dtx}
 \DocInput{l3coffins.dtx}
 \DocInput{l3color.dtx}
+\DocInput{l3graphics.dtx}
 \DocInput{l3pdf.dtx}
 
 % implementation part only

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/expl3.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -24,7 +24,7 @@
 %
 %<*driver|generic|package|2ekernel>
 %</driver|generic|package|2ekernel>
-\def\ExplFileDate{2025-01-18}%
+\def\ExplFileDate{2025-03-26}%
 %<*driver>
 \documentclass[full]{l3doc}
 \usepackage{graphicx}
@@ -51,7 +51,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -254,7 +254,7 @@
 % \begin{quote}
 %   \meta{module} and \meta{description}
 % \end{quote}
-% these both give information about the command.
+% and these two both give information about the command.
 %
 % A \emph{module} is a collection of closely related functions and
 % variables. Typical module names include~|int| for integer parameters
@@ -543,9 +543,8 @@
 %     documented in \href{interface3.pdf}{interface3.pdf}%^^A
 %     \footnote{If a primitive offers a functionality not yet in the
 %       kernel, programmers and users are encouraged to write to the
-%       \texttt{LaTeX-L} mailing list
-%       (\url{mailto:LATEX-L at listserv.uni-heidelberg.de}) describing
-%       their use-case and intended behaviour, so that a possible
+%       team describing
+%       their use-case and intended behavior, so that a possible
 %       interface can be discussed.  Temporarily, while an interface is
 %       not provided, programmers may use the procedure described in the
 %       \href{l3styleguide.pdf}{l3styleguide.pdf}.}.
@@ -896,8 +895,7 @@
 %   \item Use long, descriptive names for functions and variables,
 %     and for auxiliary functions use the parent function name plus
 %     |aux|, |auxi|, |auxii| and so on.
-%   \item If in doubt, ask the team via the LaTeX-L list: someone will
-%     soon get back to you!
+%   \item If in doubt, ask the team: someone will soon get back to you!
 % \end{itemize}
 %
 % \section{Load-time options for \pkg{expl3}}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3.ins
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3.ins	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3.ins	2025-04-08 20:12:11 UTC (rev 74879)
@@ -105,6 +105,7 @@
         \from{l3regex.dtx}      {package}
         \from{l3box.dtx}        {package}
         \from{l3color.dtx}      {package}
+        \from{l3graphics.dtx}   {package}
         \from{l3pdf.dtx}        {package,tex}
         \from{l3coffins.dtx}    {package}
         \from{l3luatex.dtx}     {package,tex}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3basics.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -2014,7 +2014,7 @@
 \cs_gset_protected:Npn \prg_set_conditional:Nnn
   { \@@_generate_conditional_count:NNNnn \cs_set:Npn e }
 \cs_gset_protected:Npn \prg_gset_conditional:Nnn
-  { \@@_generate_conditional_count:NNNnn \cs_set:Npn e }
+  { \@@_generate_conditional_count:NNNnn \cs_gset:Npn e }
 \cs_gset_protected:Npn \prg_new_conditional:Nnn
   { \@@_generate_conditional_count:NNNnn \cs_new:Npn e }
 \cs_gset_protected:Npn \prg_set_protected_conditional:Nnn

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3bitset.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3bootstrap.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3box.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3cctab.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3clist.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -2185,9 +2185,9 @@
 % \begin{macro}{\@@_rand_item:nn}
 %   The |N|-type function is not implemented through the |n|-type
 %   function for efficiency: for instance comma-list variables do not
-%   require space-trimming of their items.  Even testing for emptyness
+%   require space-trimming of their items.  Even testing for emptiness
 %   of an |n|-type comma-list is slow, so we count items first and use
-%   that both for the emptyness test and the pseudo-random integer.
+%   that both for the emptiness test and the pseudo-random integer.
 %   Importantly, \cs{clist_item:Nn} and \cs{clist_item:nn} only evaluate
 %   their argument once.
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3coffins.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3color.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -2440,14 +2440,14 @@
 %  \begin{macro}[EXP]{\@@_model_devicen_mix:nw}
 % \begin{macro}{\@@_model_devicen_init:nnn}
 % \begin{macro}{\@@_model_devicen_init:nnnn}
-% \begin{macro}{\@@_model_devicen_tranform:w}
+% \begin{macro}{\@@_model_devicen_transform:w}
 % \begin{macro}
 %   {
-%     \@@_model_devicen_tranform_1:nnnnn ,
-%     \@@_model_devicen_tranform_3:nnnnn ,
-%     \@@_model_devicen_tranform_4:nnnnn ,
+%     \@@_model_devicen_transform_1:nnnnn ,
+%     \@@_model_devicen_transform_3:nnnnn ,
+%     \@@_model_devicen_transform_4:nnnnn ,
 %   }
-% \begin{macro}{\@@_model_devicen_tranform:nnn}
+% \begin{macro}{\@@_model_devicen_transform:nnn}
 % \begin{macro}[EXP]{\@@_model_devicen_colorant:n}
 % \begin{macro}{\@@_model_devicen_convert:nnn}
 % \begin{macro}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3debug.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -69,7 +69,7 @@
 %
 % Standard file identification.
 %    \begin{macrocode}
-\ProvidesExplFile{l3debug.def}{2025-01-18}{}{L3 Debugging support}
+\ProvidesExplFile{l3debug.def}{2025-03-26}{}{L3 Debugging support}
 %    \end{macrocode}
 %
 % \begin{variable}{\s_@@_stop}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3deprecation.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3doc.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -71,7 +71,7 @@
 % This isn't included in the typeset documentation because it's a bit
 % ugly:
 %<*class>
-\ProvidesExplClass{l3doc}{2025-01-18}{}
+\ProvidesExplClass{l3doc}{2025-03-26}{}
   {L3 Experimental documentation class}
 %</class>
 % \fi
@@ -84,7 +84,7 @@
 %    require you to do updates, if the class changes.}}
 %
 % \author{\Team}
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 % \maketitle
 % \tableofcontents
 %
@@ -4716,7 +4716,7 @@
 %    \begin{macrocode}
 \tl_const:Nn \Team
   {
-    The~\LaTeX~Project~team\thanks
+    The~\LaTeX{}~Project~team\thanks
       {\url{https://www.latex-project.org/latex3/}}
   }
 %    \end{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3docstrip.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -63,7 +63,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3expan.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3file.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -4010,7 +4010,7 @@
 % \begin{variable}{\c_sys_platform_str}
 %   Detecting the platform on \LuaTeX{} is easy: for other engines, we use
 %   the fact that the two common cases have special null files. It is possible
-%   to probe further (see package \pkg{platform}), but that requires shell
+%   to probe further (see package \pkg{ifplatform}), but that requires shell
 %   escape and seems unlikely to be useful. This is set up here as it requires
 %   file searching.
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3flag.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-assign.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 % \maketitle
 %
 % \begin{documentation}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-aux.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -161,16 +161,16 @@
 %     \toprule
 %     \multicolumn{1}{c}{Representation} & Meaning \\
 %     \midrule
-%     0 0 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Positive zero.      \\
-%     0 2 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Negative zero.      \\
-%     1 0 \Arg{exponent} \Arg{X_1} \Arg{X_2} \Arg{X_3} \Arg{X_4} \texttt{;}
+%     0 0 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Positive zero.      \\
+%     0 2 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Negative zero.      \\
+%     1 0 \Arg{exponent} \Arg{X_1} \Arg{X_2} \Arg{X_3} \Arg{X_4} \cs{@@_sep:}
 %                                   & Positive floating point. \\
-%     1 2 \Arg{exponent} \Arg{X_1} \Arg{X_2} \Arg{X_3} \Arg{X_4} \texttt{;}
+%     1 2 \Arg{exponent} \Arg{X_1} \Arg{X_2} \Arg{X_3} \Arg{X_4} \cs{@@_sep:}
 %                                   & Negative floating point. \\
-%     2 0 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Positive infinity.  \\
-%     2 2 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Negative infinity.  \\
-%     3 1 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Quiet \texttt{nan}.        \\
-%     3 1 \cs[no-index]{s_@@_\ldots}  \texttt{;} & Signalling \texttt{nan}.   \\
+%     2 0 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Positive infinity.  \\
+%     2 2 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Negative infinity.  \\
+%     3 1 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Quiet \texttt{nan}.        \\
+%     3 1 \cs[no-index]{s_@@_\ldots} \cs{@@_sep:} & Signalling \texttt{nan}.   \\
 %     \bottomrule
 %   \end{tabular}
 % \end{table}
@@ -731,9 +731,9 @@
 % following trick splits it into two blocks of $4$ digits, padding
 % with zeros on the left.
 % \begin{verbatim}
-%   \cs_new:Npn \pack:NNNNNw #1 #2#3#4#5 #6; { {#2#3#4#5} {#6} }
-%   \exp_after:wN \pack:NNNNNw
-%     \__fp_int_value:w \__fp_int_eval:w 1 0000 0000 + #1 ;
+%   \cs_new:Npn \__fp_pack:NNNNNw #1 #2#3#4#5 #6 \__fp_sep: { {#2#3#4#5} {#6} }
+%   \exp_after:wN \__fp_pack:NNNNNw
+%     \__fp_int_value:w \__fp_int_eval:w 1 0000 0000 + #1 \__fp_sep:
 % \end{verbatim}
 % The idea is that adding $10^8$ to the number ensures that it has
 % exactly $9$ digits, and can then easily find which digits correspond
@@ -742,7 +742,7 @@
 % \TeX{}'s integers). This method is very heavily relied upon in
 % \texttt{l3fp-basics}.
 %
-% More specifically, the auxiliary inserts |+ #1#2#3#4#5 ; {#6}|, which
+% More specifically, the auxiliary inserts |+ #1#2#3#4#5| \cs{@@_sep:} |{#6}|, which
 % allows us to compute several blocks of $4$ digits in a nested manner,
 % performing carries on the fly.  Say we want to compute $1\,2345 \times
 % 6677\,8899$.  With simplified names, we would do
@@ -749,12 +749,12 @@
 % \begin{verbatim}
 %   \exp_after:wN \post_processing:w
 %   \__fp_int_value:w \__fp_int_eval:w - 5 0000
-%     \exp_after:wN \pack:NNNNNw
+%     \exp_after:wN \__fp_pack:NNNNNw
 %     \__fp_int_value:w \__fp_int_eval:w 4 9995 0000
 %       + 12345 * 6677
-%       \exp_after:wN \pack:NNNNNw
+%       \exp_after:wN \__fp_pack:NNNNNw
 %       \__fp_int_value:w \__fp_int_eval:w 5 0000 0000
-%         + 12345 * 8899 ;
+%         + 12345 * 8899 \__fp_sep:
 % \end{verbatim}
 % The \cs{exp_after:wN} triggers \cs{int_value:w} \cs{@@_int_eval:w}, which
 % starts a first computation, whose initial value is $- 5\,0000$ (the

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-basics.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-convert.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-expo.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -494,7 +494,7 @@
 %         + T \left(\frac{1}{7} + T \left( \frac{1}{9} + \cdots
 %           \right)\right)\right)\right)\right)
 %   \]
-%   The process looks as follows
+%   The process looks as follows (\cs{@@_sep:} represented by |;|)
 %   \begin{verbatim}
 %     \loop 5; A;
 %     \div_int 5; 1.0; \add A; \mul T; {\loop \eval 5-2;}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-extended.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-functions.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -159,7 +159,7 @@
 %     \cs{fp_set_function:nnn} \Arg{identifier}
 %       \Arg{comma-list of variables} \Arg{expression}
 %   \end{syntax}
-%   Defines the \meta{identifier} to stand for a function which expects
+%   Sets the \meta{identifier} to stand for a function which expects
 %   some arguments defined by the \meta{comma-list of variables}, and
 %   evaluates to the \meta{expression}.
 %    \begin{macrocode}
@@ -174,7 +174,10 @@
       { \msg_error:nnn { fp } { id-invalid } {#2} }
       {
         \cs_if_exist:cF { @@_parse_word_#2:N }
-          { \@@_function_set_parsing:Nn \cs_set_eq:NN {#2} }
+          {
+            \msg_error:nnn {fp} { id-undefined } {#2}
+            \@@_function_set_parsing:Nn \cs_set_eq:NN {#2}
+          }
         \group_begin:
           \int_zero:N \l_@@_function_arg_int
           \exp_args:No \clist_map_inline:nn { \tl_to_str:n {#3} }

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-logic.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-parse.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -202,17 +202,17 @@
 % number, and a semicolon in the input stream.  Once |\operand:w| is
 % done expanding, we obtain essentially
 % \begin{syntax}
-%   \cs{exp_after:wN} |\add:ww| \cs{int_value:w} 12345 ;
-%   \cs{exp:w} \cs{exp_end:} 333444 ;
+%   \cs{exp_after:wN} |\add:ww| \cs{int_value:w} 12345 \cs{@@_sep:}
+%   \cs{exp:w} \cs{exp_end:} 333444 \cs{@@_sep:}
 % \end{syntax}
 % where in fact \cs{exp_after:wN} has already been expanded,
 % \cs{int_value:w} has already seen |12345|, and
 % \cs{exp:w} is still looking for a number.  It finds
 % \cs{exp_end:}, hence expands to nothing.  Now, \cs{int_value:w} sees
-% the \texttt{;}, which cannot be part of a number.  The expansion
+% the \cs{@@_sep:}, which cannot be part of a number.  The expansion
 % stops, and we are left with
 % \begin{syntax}
-%   |\add:ww| 12345 ; 333444 ;
+%   |\add:ww| 12345 \cs{@@_sep:} 333444 \cs{@@_sep:}
 % \end{syntax}
 % which can safely perform the addition by grabbing two arguments
 % delimited by~\cs{@@_sep:}.
@@ -1447,8 +1447,8 @@
 %   small or large).  It should appear in an integer expression.  This
 %   function reads digits one by one, until reaching a non-digit, and
 %   adds~$1$ to the integer expression for each digit.  If all digits
-%   found are~$0$, the function ends the expression by |;0|,
-%   otherwise by |;1|.  This is done by switching the loop to
+%   found are~$0$, the function ends the expression by \cs{@@_sep:}|0|,
+%   otherwise by \cs{@@_sep:}|1|.  This is done by switching the loop to
 %   |round_up| at the first non-zero digit, thus we avoid to test
 %   whether digits are~$0$ or not once we see a first non-zero digit.
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-random.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -525,7 +525,8 @@
 %   rather than $R$ to avoid overflow.
 %
 %   Then we have \cs{@@_randint_wide_aux:w} \meta{X_1}\cs{@@_sep:}\meta{X_0}\cs{@@_sep:}
-%   \meta{Y_1}\cs{@@_sep:}\meta{Y_0}\cs{@@_sep:} \meta{R_2}\cs{@@_sep:}\meta{R_1}\cs{@@_sep:}\meta{R_0}|;.|
+%   \meta{Y_1}\cs{@@_sep:}\meta{Y_0}\cs{@@_sep:}
+%   \meta{R_2}\cs{@@_sep:}\meta{R_1}\cs{@@_sep:}\meta{R_0}\cs{@@_sep:}|.|
 %   and we apply the algorithm described earlier.
 %    \begin{macrocode}
 \cs_new:Npn \__kernel_randint:nn #1#2

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-round.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -144,7 +144,8 @@
 %   \item \cs{@@_round:NNN} \meta{sign} \meta{digit_1} \meta{digit_2}
 %     can expand to |0\exp_stop_f:| or |1\exp_stop_f:|.
 %   \item \cs{@@_round_s:NNNw} \meta{sign} \meta{digit_1} \meta{digit_2}
-%     \meta{more digits}\cs{@@_sep:} can expand to |0\exp_stop_f:;| or |1\exp_stop_f:;|.
+%     \meta{more digits}\cs{@@_sep:} can expand to
+%     |0\exp_stop_f:|\cs{@@_sep:} or |1\exp_stop_f:|\cs{@@_sep:}.
 %   \item \cs{@@_round_neg:NNN} \meta{sign} \meta{digit_1} \meta{digit_2}
 %     can expand to |0\exp_stop_f:| or |1\exp_stop_f:|.
 % \end{itemize}
@@ -270,9 +271,10 @@
 %     \cs{@@_round_s:NNNw} \meta{final sign} \meta{digit} \meta{more digits} \cs{@@_sep:}
 %   \end{syntax}
 %   Similar to \cs{@@_round:NNN}, but with an extra semicolon, this
-%   function expands to |0\exp_stop_f:;| if rounding $\meta{final sign}
-%   \meta{digit}.\meta{more digits}$ to an integer truncates, and to
-%   |1\exp_stop_f:;| otherwise.  The \meta{more digits} part must be a digit,
+%   function expands to |0\exp_stop_f:|\cs{@@_sep:} if rounding
+%   $\meta{final sign}\meta{digit}.\meta{more digits}$ to an integer
+%   truncates, and to |1\exp_stop_f:|\cs{@@_sep:} otherwise.
+%   The \meta{more digits} part must be a digit,
 %   followed by something that does not overflow a \cs{int_use:N}
 %   \cs{@@_int_eval:w} construction.  The only relevant information about
 %   this piece is whether it is zero or not.

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-symbolic.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -1,6 +1,6 @@
 % \iffalse
 %
-%% File l3fp-symbolic.dtx (C) Copyright 2012-2015,2017,2018,2020,2021,2023 The LaTeX Project
+%% File l3fp-symbolic.dtx (C) Copyright 2012-2025 The LaTeX Project
 %
 % It may be distributed and/or modified under the conditions of the
 % LaTeX Project Public License (LPPL), either version 1.3c of this
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -115,20 +115,20 @@
 %   \__fp_types_binary:Nww \__fp_+_o:ww ,
 %   {
 %     \s__fp_symbolic \__fp_symbolic_chk:w
-%       \__fp_variable_o:w a , { } ;
+%       \__fp_variable_o:w a , { } \__fp_sep:
 %     \s__fp_symbolic \__fp_symbolic_chk:w
 %       \__fp_types_binary:Nww \__fp_*_o:ww ,
 %       {
 %         \s__fp_symbolic \__fp_symbolic_chk:w
-%           \__fp_variable_o:w b , { } ;
+%           \__fp_variable_o:w b , { } \__fp_sep:
 %         \s__fp_symbolic \__fp_symbolic_chk:w
 %           \__fp_types_unary:NNw \__fp_sin_o:w \use_i:nn ,
 %           {
 %             \s__fp_symbolic \__fp_symbolic_chk:w
-%               \__fp_variable_o:w c , { } ;
-%           } ;
-%       } ;
-%   } ;
+%               \__fp_variable_o:w c , { } \__fp_sep:
+%           } \__fp_sep:
+%       } \__fp_sep:
+%   } \__fp_sep:
 % \end{verbatim}
 %
 % \begin{variable}{\s_@@_symbolic}
@@ -299,10 +299,12 @@
 %     \@@_symbolic_cot_o:w      ,
 %     \@@_symbolic_csc_o:w      ,
 %     \@@_symbolic_exp_o:w      ,
+%     \@@_symbolic_fact_o:w     ,
 %     \@@_symbolic_ln_o:w       ,
 %     \@@_symbolic_not_o:w      ,
 %     \@@_symbolic_sec_o:w      ,
 %     \@@_symbolic_set_sign_o:w ,
+%     \@@_symbolic_sign_o:w     ,
 %     \@@_symbolic_sin_o:w      ,
 %     \@@_symbolic_tan_o:w      ,
 %   }
@@ -309,8 +311,8 @@
 %    \begin{macrocode}
 \tl_map_inline:nn
   {
-    {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {exp} {ln}
-    {not} {sec} {set_sign} {sin} {sqrt} {tan}
+    {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {exp} {fact} {ln}
+    {not} {sec} {set_sign} {sin} {sign} {sqrt} {tan}
   }
   {
     \cs_new:cpe { @@_symbolic_#1_o:w }
@@ -586,12 +588,12 @@
       { \msg_error:nnn { fp } { id-invalid } {#1} }
       {
         \cs_if_exist:cT { @@_parse_word_#1:N }
-        {
-          \msg_error:nnn
-            { fp } { id-already-defined } {#1}
-          \cs_undefine:c { @@_parse_word_#1:N }
-          \cs_set_eq:cN { l_@@_variable_#1_fp } \tex_undefined:D
-        }
+          {
+            \msg_error:nnn
+              { fp } { id-already-defined } {#1}
+            \cs_undefine:c { @@_parse_word_#1:N }
+            \cs_set_eq:cN { l_@@_variable_#1_fp } \tex_undefined:D
+          }
       \@@_variable_set_parsing:Nn \cs_gset_eq:NN {#1}
       }
   }
@@ -602,8 +604,9 @@
 % \begin{variable}{\l_@@_symbolic_flag}
 % \begin{macro}{\fp_set_variable:nn, \@@_set_variable:nn}
 %   Refuse invalid identifiers.  If the variable does not exist yet,
-%   define it just as in \cs{fp_new_variable:n} (but without unnecessary
-%   checks).  Then evaluate~|#2|.  If the result contains the
+%   raise an error and define it just as in \cs{fp_new_variable:n}
+%   (but without unnecessary checks).
+%   Then evaluate~|#2|.  If the result contains the
 %   identifier~|#1|, we would later get a loop in cases such as
 %   \begin{quote}
 %     \cs{fp_set_variable:nn} |{A}| |{A}|\\
@@ -625,7 +628,11 @@
     \@@_id_if_invalid:nTF {#1}
       { \msg_error:nnn { fp } { id-invalid } {#1} }
       {
-        \@@_variable_set_parsing:Nn \cs_set_eq:NN {#1}
+        \cs_if_exist:cF { @@_parse_word_#1:N }
+          {
+            \msg_error:nnn {fp} { id-undefined } {#1}
+            \@@_variable_set_parsing:Nn \cs_set_eq:NN {#1}
+          }
         \fp_set:Nn \l_@@_symbolic_fp {#2}
         \cs_set_nopar:cpn { l_@@_variable_#1_fp }
           { \flag_ensure_raised:N \l_@@_symbolic_flag \c_nan_fp }
@@ -659,6 +666,12 @@
     LaTeX~has~been~asked~to~create~a~new~floating~point~identifier~'#1'~
     but~this~name~has~already~been~used~elsewhere.
   }
+\msg_new:nnnn { fp } { id-undefined }
+  { Floating~point~identifier~'#1'~is~undefined. }
+  {
+    LaTeX~has~been~asked~to~set~a~floating~point~identifier~'#1'~
+    but~this~name~has~not~been~declared.
+  }
 \msg_new:nnnn { fp } { id-used-elsewhere }
   { Floating~point~identifier~'#1'~already~used~for~something~else. }
   {

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-traps.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 % \maketitle
 %
 % \begin{documentation}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-trig.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %          {latex-team at latex-project.org}^^A
 %    }^^A
 % }
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp-types.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -42,7 +42,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fp.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -49,7 +49,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -694,6 +694,14 @@
 % be set with \cs{fp_set_variable:nn} to arbitrary floating point
 % expressions including other variables.
 %
+% At present, the following operations and functions are \emph{not} supported
+% \begin{itemize}
+%  \item infix binary comparisons like |>|, |=|, |<|
+%  \item infix ternary operator like |?:|
+%  \item prefix variable-ary functions like |round| and friends,
+%    |min|, |max|, |atan|, |acot|, |atand|, |acotd|
+% \end{itemize}
+%
 % \begin{function}[added = 2023-10-19]{\fp_new_variable:n}
 %   \begin{syntax}
 %     \cs{fp_new_variable:n} \Arg{identifier}
@@ -714,10 +722,12 @@
 %   \begin{syntax}
 %     \cs{fp_set_variable:nn} \Arg{identifier} \Arg{fp expr}
 %   \end{syntax}
-%   Defines the \meta{identifier} to stand in any further expression for
+%   Sets the \meta{identifier} to stand in any further expression for
 %   the result of evaluating the \meta{floating point expression} as
-%   much as possible.  The result may contain other variables, which are
-%   then replaced by their values if they have any.  For instance,
+%   much as possible.  The \meta{identifier} must be declared by
+%   \cs{fp_new_function:n} first.  The result may contain other
+%   variables, which are then replaced by their values if they have any.
+%   For instance,
 %   \begin{quote}\let\obeyedline=\newline\obeylines^^A
 %     \cs{fp_new_variable:n} |{ A }|
 %     \cs{fp_new_variable:n} |{ B }|
@@ -775,10 +785,11 @@
 %   \begin{syntax}
 %     \cs{fp_set_function:nnn} \Arg{identifier} \Arg{vars} \Arg{fp expr}
 %   \end{syntax}
-%   Defines the \meta{identifier} to stand in any further expression for
+%   Sets the \meta{identifier} to stand in any further expression for
 %   the result of evaluating the \meta{floating point expression}, with
 %   the \meta{identifier} accepting the \meta{vars} (a non-empty
-%   comma-separated list).
+%   comma-separated list). The \meta{identifier} must be declared by
+%   \cs{fp_new_function:n} first.
 %   The result may contain other functions, which are
 %   then replaced by their results if they have any.  For instance,
 %   \begin{quote}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3fparray.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Added: trunk/Master/texmf-dist/source/latex/l3kernel/l3graphics.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3graphics.dtx	                        (rev 0)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3graphics.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -0,0 +1,786 @@
+% \iffalse meta-comment
+%
+%% File: l3graphics.dtx
+%
+% Copyright (C) 2017-2025 The LaTeX Project
+%
+% It may be distributed and/or modified under the conditions of the
+% LaTeX Project Public License (LPPL), either version 1.3c of this
+% license or (at your option) any later version.  The latest version
+% of this license is in the file
+%
+%    http://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3kernel bundle" (The Work in LPPL)
+% and all files in that bundle must be distributed together.
+%
+% -----------------------------------------------------------------------
+%
+% The development version of the bundle can be found at
+%
+%    https://github.com/latex3/latex3
+%
+% for those people who are interested.
+%
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+  \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+%   The \pkg{l3graphics} module\\ Graphics inclusion support^^A
+% }
+%
+% \author{^^A
+%  The \LaTeX{} Project\thanks
+%    {^^A
+%      E-mail:
+%        \href{mailto:latex-team at latex-project.org}
+%          {latex-team at latex-project.org}^^A
+%    }^^A
+% }
+%
+% \date{Released 2025-03-26}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Graphics keys}
+%
+% Inclusion of graphic files requires a range of low-level data be passed to
+% the backend. This is set up using a small number of key--value settings,
+% which are stored in the |graphics| tree.
+%
+% \begin{variable}{decodearray}
+%   Array to decode color in bitmap graphic: when non-empty, this should
+%   be in the form of one, two or three pairs of real numbers in the range
+%   $[0,1]$, separated by spaces.
+% \end{variable}
+%
+% \begin{variable}{draft}
+%   Switch to enable draft mode: graphics are read but not included when this is
+%   true.
+% \end{variable}
+%
+% \begin{variable}{interpolate}
+%   Switch which indicates whether interpolation should be applied to bitmap
+%   graphic files.
+% \end{variable}
+%
+% \begin{variable}{page}
+%   The page to extract from a multi-page graphic file: used for |.pdf| files
+%   which may contain multiple pages.
+% \end{variable}
+%
+% \begin{variable}{pdf-attr}
+%   Additional PDF-focussed attributes: available to allow control of
+%   extended |.pdf| structures beyond those needed for graphic inclusion.
+%   Due to backend restrictions, this key is only functional with direct
+%   PDF mode (pdf\TeX{} and Lua\TeX{}).
+% \end{variable}
+%
+% \begin{variable}{pagebox}
+%   The nature of the page box setting used to determine the bounding box of
+%   material: used for |.pdf| files which feature multiple page box
+%   specifications. A choice from |art|, |bleed|, |crop|, |media|, |trim|.
+%   The standard setting is |crop|.
+% \end{variable}
+%
+% \begin{variable}{type}
+%   The type of graphic file being included: if this key is not set, the
+%   \emph{type} is determined from the file extension.
+% \end{variable}
+%
+% \section{Including graphics}
+%
+% \begin{function}[added = 2025-03-14]
+%   {\graphics_include:nn, \graphics_include:nV}
+%   \begin{syntax}
+%     \cs{graphics_include:nn} \Arg{keys} \Arg{file}
+%   \end{syntax}
+%   Horizontal-mode command which includes the \meta{file} as a graphic
+%   at the current location. The file \meta{type} may be given as one of the
+%   \meta{keys}, or will otherwise be determined from file extension. The
+%   \meta{keys} is used to pass settings as detailed above.
+% \end{function}
+%
+% \begin{variable}[added = 2025-03-14]{\l_graphics_ext_type_prop}
+%   Defines mapping between file extensions and file types; where there is
+%   no entry for an extension, the type is assumed to be the extension
+%   with the leading |.| removed. Entries should be made in lower case, and
+%   the key should be an extension including the leading |.|, for example
+%   \begin{verbatim}
+%     \prop_put:Nnn \l_graphics_ext_type_prop { .ps } { eps }
+%   \end{verbatim}
+% \end{variable}
+%
+% \begin{variable}[added = 2025-03-14]{\l_graphics_search_ext_seq}
+%   Extensions to use for graphic searching when the given \meta{file} name is not
+%   found by \cs{graphics_get_full_name:nN}.
+% \end{variable}
+%
+% \begin{variable}[added = 2025-03-14]{\l_graphics_search_path_seq}
+%   Each entry is the path to a directory which should be searched when
+%   seeking a graphic file. Each path can be relative or absolute, and should
+%   not include the trailing slash. The entries are not expanded when
+%   used so may contain active characters but should not feature any
+%   variable content. Spaces need not be quoted.
+% \end{variable}
+%
+% \section{Utility functions}
+%
+% \begin{function}[noTF, added = 2025-03-14]{\graphics_get_full_name:nN}
+%   \begin{syntax}
+%     \cs{graphics_get_full_name:nN} \Arg{file} \meta{tl~var}
+%     \cs{graphics_get_full_name:nNTF} \Arg{file} \meta{tl~var} \Arg{true code} \Arg{false code}
+%   \end{syntax}
+%   Searches for \meta{file} first as given and then using the extensions
+%   listed in \cs{l_graphics_search_ext_seq}. The search path used will be
+%   the entries of \cs{l_graphics_search_path_seq}. If found, the full file
+%   name including any path and extension will be returned in the
+%   \meta{tl~var}. In the non-branching version, the \meta{tl var} will be
+%   set to \cs{q_no_value} in the case that the graphics is not found.
+% \end{function}
+%
+% \begin{function}[added = 2025-03-14]{\graphics_get_pagecount:nN}
+%   \begin{syntax}
+%     \cs{graphics_get_pagecount:nn} \Arg{file} \meta{tl~var}
+%   \end{syntax}
+%   Reads the graphics \meta{file} and extracts the number of pages, which
+%   are stored in the \meta{tl~var}.
+% \end{function}
+%
+% \section{Showing and logging included graphics}
+%
+% \begin{function}[added = 2025-03-14]
+%   {\graphics_show_list:, \graphics_log_list:}
+%   \begin{syntax}
+%     \cs{graphics_show_list:}
+%     \cs{graphics_log_list:}
+%   \end{syntax}
+%   These functions list all graphic files loaded in a similar manner to
+%   \cs{file_show_list:} and \cs{file_log_list:}. While
+%   \cs{graphics_show_list:} displays the list in the terminal,
+%   \cs{graphics_log_list:} outputs it to the log file only. In both cases, only
+%   graphics loaded by \pkg{l3graphics} are listed.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3graphics} implementation}
+%
+%    \begin{macrocode}
+%<*package>
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%<@@=graphics>
+%    \end{macrocode}
+%
+% Transitional support.
+%    \begin{macrocode}
+\cs_if_exist:NT \@expl at finalise@setup@@@@
+  {
+    \tl_gput_right:Nn \@expl at finalise@setup@@@@
+      { \declare at file@substitution { l3graphics.sty } { null.tex } }
+  }
+%    \end{macrocode}
+%
+% \begin{variable}{\l_@@_internal_dim, \l_@@_internal_ior, \l_@@_internal_tl}
+%   Scratch space.
+%    \begin{macrocode}
+\dim_new:N \l_@@_internal_dim
+\ior_new:N \l_@@_internal_ior
+\tl_new:N  \l_@@_internal_tl
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\s_@@_stop}
+%   Internal scan marks.
+%    \begin{macrocode}
+\scan_new:N \s_@@_stop
+%    \end{macrocode}
+% \end{variable}
+%
+% \subsection{Graphics keys}
+%
+% \begin{macro}
+%   {
+%     \l_@@_decodearray_str  ,
+%     \l__@@_draft_bool      ,
+%     \l_@@_interpolate_bool ,
+%     \l_@@_page_int         ,
+%     \l_@@_pagebox_tl       ,
+%     \l_@@_pdf_str          ,
+%     \l_@@_type_str
+%   }
+%   Keys which control features of graphics. The standard value of |pagebox|
+%   set up here should match the default for the backends themselves: in
+%   the absence of any other setting the |crop| should be used. Note that
+%   the variable \cs{l_@@_pagebox_str} can be empty internally, as backends
+%   which do not support |pagebox| are set up to clear it entirely. The
+%   store for |pagebox| is a |tl| as that makes extracting the data
+%   easier for some backends.
+%    \begin{macrocode}
+\tl_new:N \l_@@_pagebox_tl
+\keys_define:nn { graphics }
+  {
+    decodearray .str_set:N =
+      \l_@@_decodearray_str ,
+    draft .bool_set:N =
+      \l_@@_draft_bool ,
+    interpolate .bool_set:N =
+      \l_@@_interpolate_bool ,
+    pagebox .choices:nn =
+      { art , bleed , crop , media , trim }
+      {
+        \tl_set:Ne \l_@@_pagebox_tl
+          { \l_keys_choice_tl box }
+      } ,
+    pagebox .initial:n =
+      crop ,
+    page .int_set:N =
+      \l_@@_page_int ,
+    pdf-attr .str_set:N =
+      \l_@@_pdf_str ,
+    type . str_set:N =
+      \l_@@_type_str
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \subsection{Obtaining bounding box data}
+%
+% \begin{variable}
+%   {
+%     \l_@@_llx_dim , \l_@@_lly_dim,
+%     \l_@@_urx_dim , \l_@@_ury_dim
+%   }
+%   Storage for the return of bounding box.
+%    \begin{macrocode}
+\dim_new:N \l_@@_llx_dim
+\dim_new:N \l_@@_lly_dim
+\dim_new:N \l_@@_urx_dim
+\dim_new:N \l_@@_ury_dim
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_bb_save:n, \@@_bb_save:e}
+% \begin{macro}{\@@_bb_restore:nF, \@@_bb_restore:eF}
+%   Caching graphic bounding boxes is sensible, and these functions are needed both
+%   here and for drive-specific work. So they are made available as documented
+%   functions. To save on registers, the \enquote{origin} is only saved if it is
+%   not at zero.
+%     \begin{macrocode}
+\cs_new_protected:Npn \@@_bb_save:n #1
+  {
+    \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
+      { \msg_error:nnn { graphic } { bb-already-cached } {#1} }
+      {
+        \dim_compare:nNnF \l_@@_llx_dim = { 0pt }
+          { \dim_const:cn { c_@@_ #1 _llx_dim } { \l_@@_llx_dim } }
+        \dim_compare:nNnF \l_@@_lly_dim = { 0pt }
+          { \dim_const:cn { c_@@_ #1 _lly_dim } { \l_@@_lly_dim } }
+        \dim_const:cn { c_@@_ #1 _urx_dim } { \l_@@_urx_dim }
+        \dim_const:cn { c_@@_ #1 _ury_dim } { \l_@@_ury_dim }
+      }
+  }
+\cs_generate_variant:Nn \@@_bb_save:n { e }
+\cs_new_protected:Npn \@@_bb_restore:nF #1#2
+  {
+    \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
+      {
+        \dim_set_eq:Nc \l_@@_urx_dim { c_@@_ #1 _urx_dim }
+        \dim_set_eq:Nc \l_@@_ury_dim { c_@@_ #1 _ury_dim }
+        \dim_if_exist:cTF { c_@@_ #1 _llx_dim }
+          { \dim_set_eq:Nc \l_@@_llx_dim { c_@@_ #1 _llx_dim } }
+          { \dim_zero:N \l_@@_llx_dim }
+        \dim_if_exist:cTF { c_@@_ #1 _lly_dim }
+          { \dim_set_eq:Nc \l_@@_lly_dim { c_@@_ #1 _lly_dim } }
+          { \dim_zero:N \l_@@_lly_dim }
+      }
+      {#2}
+  }
+\cs_generate_variant:Nn \@@_bb_restore:nF { e }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_extract_bb:n, \@@_read_bb:n}
+% \begin{macro}{\@@_extract_bb_auix:nn, \@@_extract_bb_auix:Vn}
+% \begin{macro}{\@@_extract_bb_auxii:nnn}
+% \begin{macro}{\@@_extract_bb_auxiii:nnnn, \@@_extract_bb_auxiii:Vnnn}
+% \begin{macro}{\@@_extract_bb_auxiv:nnn}
+% \begin{macro}{\@@_read_bb_auxi:nnnn, \@@_read_bb_auxii:Vnnn}
+% \begin{macro}
+%   {
+%     \@@_read_bb_auxii:w ,
+%     \@@_read_bb_auxiv:w ,
+%     \@@_read_bb_auxv:w
+%   }
+%  Extracting the bounding box from an |.eps| or |.bb| file is done by
+%  reading each line and searching for the marker text |%%BoundingBox:|.
+%  The data is read as a string with a mapping over
+%  the lines: as there is a colon involved, a little bit of work is needed to
+%  get the matching correct. The same approach covers cases where the bounding
+%  box has to be calculated by |extractbb|, with just the initial phase
+%  different.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_extract_bb:n #1
+  {
+    \int_compare:nNnTF \l_@@_page_int > 0
+      { \@@_extract_bb_auxi:Vn \l_@@_page_int {#1} }
+      { \@@_extract_bb_auxii:nnn {#1} { } { } }
+  }
+\cs_new_protected:Npn \@@_extract_bb_auxi:nn #1#2
+  { \@@_extract_bb_auxii:nnn {#2} { :P #1 } { -p~#1~ } }
+\cs_generate_variant:Nn \@@_extract_bb_auxi:nn { Vn }
+\cs_new_protected:Npn \@@_extract_bb_auxii:nnn #1#2#3
+  {
+    \tl_if_empty:NTF \l_@@_pagebox_tl
+      { \@@_extract_bb_auxiv:nnn }
+      { \@@_extract_bb_auxiii:Vnnn \l_@@_pagebox_tl }
+      {#1} {#2} {#3}
+  }
+\cs_new_protected:Npn \@@_extract_bb_auxiii:nnnn #1#2#3#4
+  { \@@_extract_bb_auxiv:nnn {#2} { : #1 #3 } { #4 -B~#1~ } }
+\cs_generate_variant:Nn \@@_extract_bb_auxiii:nnnn { V }
+\cs_new_protected:Npn \@@_extract_bb_auxiv:nnn #1#2#3
+  {
+    \@@_read_bb_auxi:nnnn {#1} {#2}
+      { \ior_shell_open:Nn \l_@@_internal_ior { extractbb~#3-O~#1 } }
+      { pipe-failed }
+  }
+\cs_new_protected:Npn \@@_read_bb:n #1
+  {
+    \@@_read_bb_auxi:nnnn {#1} { }
+      { \ior_open:Nn \l_@@_internal_ior {#1} }
+      { graphic-not-found }
+  }
+%   \end{macrocode}
+%  Before any real searching, a check to see if there are cached values
+%  available. The name of each graphic will be unique and so it's sensible to
+%  store the bounding box data in \TeX{}: this avoids multiple file operations.
+%  As bounding boxes here start away from the lower-left origin, we need to
+%  store all four values (contrast with for example the \texttt{pdfmode}
+%  driver). Here |#2| is a potential page identifier: used to allow caching of
+%  individual pages in a multi-page document. Caching is applied to the
+%  upper-right position in all cases, but as the lower-left will often be
+%  $(0,0)$ it is only cached if required.
+%   \begin{macrocode}
+\cs_new_protected:Npn \@@_read_bb_auxi:nnnn #1#2#3#4
+  {
+    \@@_bb_restore:nF {#1#2}
+      { \@@_read_bb_auxii:nnnn {#3} {#4} {#1} {#2} }
+  }
+\cs_new_protected:Npe \@@_read_bb_auxii:nnnn #1#2#3#4
+  {
+    #1
+    \exp_not:N \ior_if_eof:NTF \exp_not:N \l_@@_internal_ior
+      { \msg_error:nnn { graphics } {#2} {#3} }
+      {
+        \ior_str_map_inline:Nn \exp_not:N \l_@@_internal_ior
+          {
+            \exp_not:N \@@_read_bb_auxiii:w
+              ##1 ~ \c_colon_str \s_@@_stop
+          }
+        \@@_bb_save:n {#3#4}
+      }
+    \ior_close:N \exp_not:N \l_@@_internal_ior
+  }
+\use:e
+  {
+    \cs_new_protected:Npn \exp_not:N \@@_read_bb_auxiii:w
+      #1 \c_colon_str #2 \s_@@_stop
+      {
+        \exp_not:N \str_if_eq:nnT
+          { \c_percent_str \c_percent_str BoundingBox }
+          {#1}
+          { \exp_not:N \@@_read_bb_auxiv:w #2 ( ) \s_@@_stop }
+      }
+  }
+%    \end{macrocode}
+%   If the bounding box is |atend|, just ignore the current one and keep going.
+%   Otherwise, we need to allow for tabs and multiple spaces (as the line has
+%   been read as a string). The easiest way to deal with that is to scan the
+%   tokens: at this stage the line fragment should be just numbers and
+%   whitespace. \TeX{} will then tidy up for us, with just a leading space to
+%   worry about: that is taken out by the |\use:n| here.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_read_bb_auxiv:w #1 ( #2 ) #3 \s_@@_stop
+  {
+    \str_if_eq:nnF {#2} { atend }
+      {
+        \tl_set_rescan:Nne \l_@@_internal_tl
+          {
+            \char_set_catcode_space:n {  9 }
+            \char_set_catcode_space:n { 32 }
+          }
+          { \use:n #1 }
+        \exp_after:wN \@@_read_bb_auxv:w \l_@@_internal_tl \s_@@_stop
+      }
+  }
+%    \end{macrocode}
+%   A trailing space was deliberately added earlier so we know that the final
+%   data point is terminated by a space.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_read_bb_auxv:w #1~#2~#3~#4~#5 \s_@@_stop
+  {
+    \dim_set:Nn \l_@@_llx_dim { #1 bp }
+    \dim_set:Nn \l_@@_lly_dim { #2 bp }
+    \dim_set:Nn \l_@@_urx_dim { #3 bp }
+    \dim_set:Nn \l_@@_ury_dim { #4 bp }
+    \ior_map_break:
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_final_name_str, \l_@@_full_name_str}
+%   The full name is as you'd expect the name including path and extension.
+%   The final name here reflects any conversions carried out by the backend,
+%   for example if an |.eps| is converted to |.pdf|.
+%    \begin{macrocode}
+\str_new:N \l_@@_final_name_str
+\str_new:N \l_@@_full_name_str
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_internal_box}
+%    \begin{macrocode}
+\box_new:N \l_@@_internal_box
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_dir_str \l_@@_name_str \l_@@_ext_str}
+%    \begin{macrocode}
+\str_new:N \l_@@_dir_str
+\str_new:N \l_@@_name_str
+\str_new:N \l_@@_ext_str
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_search_path_seq}
+%    \begin{macrocode}
+\seq_new:N \l_graphics_search_path_seq
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_search_ext_seq}
+%   Used to specify fall-back extensions: actually set on a per-backend basis.
+%    \begin{macrocode}
+\seq_new:N \l_graphics_search_ext_seq
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_ext_type_prop}
+%   Mapping between extensions and types for non-standard mappings
+%    \begin{macrocode}
+\prop_new:N \l_graphics_ext_type_prop
+\prop_put:Nnn \l_graphics_ext_type_prop { .ps } { eps }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_record_seq}
+%   A list of graphic files used.
+%    \begin{macrocode}
+\seq_new:N \g_@@_record_seq
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\graphics_include:nn, \graphics_include:nV}
+% \begin{macro}{\@@_include_search:n}
+% \begin{macro}{\@@_include:}
+% \begin{macro}
+%   {
+%     \@@_include_auxi:n, \@@_include_auxi:e, \@@_include_auxii:n,
+%     \@@_include_auxiii:n, \@@_include_auxiv:n
+%   }
+%   Actually including an graphic is relatively straight-forward: most of the
+%   work is done by the backend. We only have to deal with making sure the
+%   box has no apparent depth. Where the first given name is not found, we
+%   search based on extension only if the \meta{type} was not given. The one
+%   wrinkle is that we may have found a \texttt{.tex} file matching the file
+%   name stem: that's not what we want, so we have to filter out.
+%    \begin{macrocode}
+\cs_new_protected:Npn \graphics_include:nn #1#2
+  {
+    \group_begin:
+      \keys_set:nn { graphics } {#1}
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#2} \l_@@_full_name_str
+        {
+          \str_if_eq:eeTF { \l_@@_full_name_str } { #2 .tex }
+            { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
+            { \@@_include: }
+        }
+        { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
+    \group_end:
+  }
+\cs_generate_variant:Nn \graphics_include:nn { nV }
+\cs_new_protected:Npn \@@_include:
+  {
+    \str_if_empty:NTF \l_@@_type_str
+      {
+        \file_parse_full_name:VNNN \l_@@_full_name_str
+          \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
+        \@@_include_auxi:e
+          {
+            \exp_args:Ne \str_tail:n
+              { \str_casefold:V \l_@@_ext_str }
+          }
+      }
+      { \@@_include_auxi:e { \l_@@_type_str } }
+  }
+\cs_new_protected:Npn \@@_include_auxi:n #1
+  {
+    \prop_get:NnNF \l_graphics_ext_type_prop { .#1 } \l_@@_internal_tl
+      { \tl_set:Nn \l_@@_internal_tl {#1} }
+    \exp_args:NV \@@_include_auxii:n \l_@@_internal_tl
+  }
+\cs_generate_variant:Nn \@@_include_auxi:n { e }
+\cs_new_protected:Npn \@@_include_auxii:n #1
+  {
+    \mode_leave_vertical:
+    \cs_if_exist:cTF { @@_backend_include_ #1 :n }
+      {
+        \tl_set_eq:NN \l_@@_final_name_str \l_@@_full_name_str
+        \str_set:Ne \l_@@_full_name_str
+          { \exp_args:NV \__kernel_file_name_quote:n \l_@@_full_name_str }
+        \exp_args:NnV \use:c { @@_backend_getbb_ #1 :n }
+          \l_@@_full_name_str
+        \seq_gput_right:NV \g_@@_record_seq \l_@@_final_name_str
+        \clist_if_exist:NT \@filelist
+          { \exp_args:NV \@addtofilelist \l_@@_final_name_str }
+        \bool_if:NTF \l_@@_draft_bool
+          { \@@_include_auxiii:n }
+          { \@@_include_auxiv:n }
+            {#1}
+      }
+      { \msg_error:nnn { graphics } { unsupported-graphic-type } {#1} }
+  }
+\cs_new_protected:Npn \@@_include_auxiii:n #1
+  {
+    \hbox_to_wd:nn { \l_@@_urx_dim - \l_@@_llx_dim }
+      {
+        \tex_vrule:D
+        \tex_hss:D
+        \vbox_to_ht:nn
+          { \l_@@_ury_dim - \l_@@_lly_dim }
+          {
+            \tex_hrule:D width
+              \dim_eval:n { \l_@@_urx_dim - \l_@@_llx_dim }
+            \tex_vss:D
+            \hbox_to_wd:nn
+              { \l_@@_urx_dim - \l_@@_llx_dim }
+              {
+                \ttfamily
+                \tex_hss:D \l_@@_full_name_str \tex_hss:D
+              }
+            \tex_vss:D
+            \tex_hrule:D
+          }
+        \tex_hss:D
+        \tex_vrule:D
+      }
+  }
+\cs_new_protected:Npn \@@_include_auxiv:n #1
+  {
+    \hbox_set:Nn \l_@@_internal_box
+      {
+        \exp_args:NnV \use:c { @@_backend_include_ #1 :n }
+          \l_@@_full_name_str
+      }
+    \box_set_dp:Nn \l_@@_internal_box { 0pt }
+    \box_set_ht:Nn \l_@@_internal_box
+      { \l_@@_ury_dim - \l_@@_lly_dim }
+    \box_set_wd:Nn \l_@@_internal_box
+      { \l_@@_urx_dim - \l_@@_llx_dim }
+    \box_use_drop:N \l_@@_internal_box
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\graphics_show_list:, \graphics_log_list:, \@@_list:N}
+% \begin{macro}[EXP]{\@@_list_aux:n}
+%   A function to list all graphic files used.
+%    \begin{macrocode}
+\cs_new_protected:Npn \graphics_show_list: { \@@_list:N \msg_show:nneeee }
+\cs_new_protected:Npn \graphics_log_list: { \@@_list:N \msg_log:nneeee }
+\cs_new_protected:Npn \@@_list:N #1
+  {
+    \seq_remove_duplicates:N \g_@@_record_seq
+    #1 { kernel } { file-list }
+      { \seq_map_function:NN \g_@@_record_seq \@@_list_aux:n }
+        { } { } { }
+  }
+\cs_new:Npn \@@_list_aux:n #1 { \iow_newline: #1 }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Utility functions}
+%
+% \begin{macro}{\graphics_get_full_name:nN}
+% \begin{macro}[TF]{\graphics_get_full_name:nN}
+% \begin{macro}{\@@_get_full_name:n}
+%   As well as searching by path, etc., there is a need here to check that
+%   we do not trip over |foo.bar| if |.bar| is not a known extension for
+%   the current backend.
+%    \begin{macrocode}
+\cs_new_protected:Npn \graphics_get_full_name:nN #1#2
+  {
+    \graphics_get_full_name:nNF {#1} #2
+      { \tl_set:Nn #2 { \q_no_value } }
+  }
+\prg_new_protected_conditional:Npnn \graphics_get_full_name:nN #1#2
+  { T , F , TF }
+  {
+    \group_begin:
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#1} \l_@@_full_name_str
+        {
+          \str_if_eq:eeTF { \l_@@_full_name_str } { #1 .tex }
+            { \@@_get_full_name:n {#1} }
+            {
+              \file_parse_full_name:VNNN \l_@@_full_name_str
+                \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
+              \seq_map_inline:Nn \l_graphics_search_ext_seq
+                {
+                  \str_if_eq:nVT {##1} \l_@@_ext_str
+                    { \seq_map_break:n { \use_none:nn } }
+                }
+                  \@@_get_full_name:n {#1}
+            }
+        }
+        { \@@_get_full_name:n {#1} }
+    \exp_args:NNNV \group_end:
+    \tl_set:Nn #2 \l_@@_full_name_str
+    \tl_if_empty:NTF #2
+      { \prg_return_false: }
+      { \prg_return_true: }
+  }
+\cs_new_protected:Npn \@@_get_full_name:n #1
+  {
+    \str_clear:N \l_@@_full_name_str
+    \seq_map_inline:Nn \l_graphics_search_ext_seq
+      {
+        \file_get_full_name:nNT { #1 ##1 } \l_@@_full_name_str
+          { \seq_map_break:n { \use_none:nn } }
+      }
+    \use:n
+      { \str_clear:N \l_@@_full_name_str }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\graphics_get_pagecount:nN}
+% \begin{macro}{\@@_get_pagecount:n}
+% \begin{macro}{\@@_get_pagecount:nw}
+%   A generic function to read the number of pages in a graphic file. This is
+%   used by all of the backend where there is not a dedicated primitive. The
+%   plan is essentially the same as reading the bounding box. To avoid multiple
+%   calls, the value is cached either here or in the backend.
+%    \begin{macrocode}
+\cs_new_protected:Npn \graphics_get_pagecount:nN #1#2
+  {
+    \group_begin:
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#1} \l_@@_full_name_str
+        {
+          \int_if_exist:cF { c_@@_ \l_@@_full_name_str _pages_int }
+            {
+              \exp_args:NV \@@_backend_get_pagecount:n
+                \l_@@_full_name_str
+            }
+          \tl_set:Nv #2 { c_@@_ \l_@@_full_name_str _pages_int }
+        }
+        {
+          \tl_set:Nn #2 { 0 }
+          \msg_error:nnn { graphics } { graphic-not-found } {#1}
+        }
+    \exp_args:NNNV \group_end:
+    \tl_set:Nn #2 #2
+  }
+\cs_new_protected:Npe \@@_get_pagecount:n #1
+  {
+    \exp_not:N \ior_shell_open:Nn \exp_not:N \l_@@_internal_ior
+      { extractbb~-O~#1 }
+    \exp_not:N \ior_if_eof:NTF \exp_not:N \l_@@_internal_ior
+      { \msg_error:nnn { graphics } { pipe-failed } }
+      {
+        \ior_str_map_inline:Nn \exp_not:N \l_@@_internal_ior
+          {
+            \exp_not:N \@@_get_pagecount:nw {#1}
+              ##1 ~ \c_colon_str \c_colon_str \s_@@_stop
+          }
+        \exp_not:N \int_if_exist:cF { c_@@_ #1 _pages_int }
+          { \int_const:cn { c_@@_ #1 _pages_int } { 1 } }
+      }
+    \ior_close:N \exp_not:N \l_@@_internal_ior
+  }
+\use:e
+  {
+    \cs_new_protected:Npn \exp_not:N \@@_get_pagecount:nw
+      #1#2 \c_colon_str #3 \c_colon_str #4 \s_@@_stop
+      {
+        \exp_not:N \str_if_eq:nnT
+          { \c_percent_str \c_percent_str Pages }
+          {#2}
+          {
+            \int_const:cn { c_@@_ #1 _pages_int } {#3}
+            \exp_not:N \ior_map_break:
+          }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro} 
+%
+% \subsection{Messages}
+%
+%    \begin{macrocode}
+\msg_new:nnnn { graphics } { graphic-not-found }
+  { Image~file~'#1'~not~found. }
+  {
+    LaTeX~tried~to~open~graphic~file~'#1',~
+    but~the~file~could~not~be~read.
+  }
+\msg_new:nnnn { graphics } { pipe-failed }
+  { Cannot~run~piped~system~commands. }
+  {
+    LaTeX~tried~to~call~a~system~process~but~this~was~not~possible.\\
+    Try~the~"--shell-escape"~(or~"--enable-pipes")~option.
+  }
+\msg_new:nnnn { graphics } { unsupported-graphic-type }
+  { Image~type~'#1'~not~supported~by~current~driver. }
+  {
+    LaTeX~was~asked~to~include~an~graphic~of~type~'#1',~
+    but~this~is~not~supported~by~the~current~driver~(production~route).
+  }
+%    \end{macrocode}
+%
+%    \begin{macrocode}
+%</package>
+%    \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex


Property changes on: trunk/Master/texmf-dist/source/latex/l3kernel/l3graphics.dtx
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3int.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3intarray.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3kernel-functions.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -177,6 +177,22 @@
 %   and |#3| and |#4| empty.
 % \end{function}
 %
+% \begin{function}[EXP]
+%   {
+%     \__kernel_codepoint_to_grapheme_class:n,
+%     \__kernel_codepoint_to_wordbreak_class:n
+%   }
+%   \begin{syntax}
+%     \cs{__kernel_codepoint_to_grapheme_class:n} \Arg{codepoint}
+%     \cs{__kernel_codepoint_to_wordbreak_class:n} \Arg{codepoint}
+%   \end{syntax}
+%   Expands to the Unicode properties |Grapheme_Cluster_Break| and
+%   |Word_Break|, respectively, of the \meta{codepoint}. Here,
+%   |Grapheme_Cluster_Break| and |Word_Break| are strings matching
+%   exactly the descriptors given in
+%   \url{https://www.unicode.org/reports/tr29/}.
+% \end{function}
+%
 % \begin{function}{\__kernel_cs_parm_from_arg_count:nnF}
 %   \begin{syntax}
 %     \cs{__kernel_cs_parm_from_arg_count:nnF} \Arg{follow-on} \Arg{args} \Arg{false code}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3keys.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -2365,6 +2365,7 @@
 % \begin{macro}{\@@_usage:n}
 % \begin{macro}{\@@_usage:NN}
 % \begin{macro}{\@@_usage:w}
+% \begin{macro}{\@@_usage_aux:w}
 %   Save the relevant data.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_usage:n #1
@@ -2405,7 +2406,11 @@
     \prop_get:NVNF #1 \l_@@_module_str \l_@@_tmpa_tl
       { \tl_clear:N \l_@@_tmpa_tl }
     \tl_set:Ne \l_@@_tmpb_tl
-      { \exp_after:wN \@@_usage:w \l_keys_path_str \s_@@_stop }
+      {
+        \exp_after:wN \exp_after:wN \exp_after:wN \@@_usage:w \exp_after:wN 
+          \l_keys_path_str \exp_after:wN / \exp_after:wN \s_@@_stop
+          \exp_after:wN { \l_keys_path_str }
+      }
     \bool_if:NTF #2
       { \clist_put_right:NV \l_@@_tmpa_tl \l_@@_tmpb_tl }
       { \clist_remove_all:NV \l_@@_tmpa_tl \l_@@_tmpb_tl }
@@ -2412,11 +2417,18 @@
     \prop_put:NVV #1 \l_@@_module_str
       \l_@@_tmpa_tl
   }
-\cs_new:Npn \@@_usage:w #1 / #2 \s_@@_stop {#2}
+\cs_new:Npn \@@_usage:w #1 / #2 \s_@@_stop #3
+  {
+    \tl_if_blank:nTF {#2}
+     {#1}
+     { \@@_usage_aux:w #3 \s_@@_stop }
+  }
+\cs_new:Npn \@@_usage_aux:w #1 / #2 \s_@@_stop {#2}
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}{\@@_variable_set:NnnN, \@@_variable_set:cnnN}
 % \begin{macro}{\@@_variable_set_required:NnnN, \@@_variable_set_required:cnnN}
@@ -3539,35 +3551,59 @@
 % \end{macro}
 %
 % \begin{macro}[EXP,pTF]{\keys_if_exist:nn}
+% \begin{macro}[EXP]{\@@_if_exist:nn, \@@_if_exist:ee}
 %   A utility for others to see if a key exists.
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \keys_if_exist:nn #1#2 { p , T , F , TF }
   {
+    \@@_if_exist:ee
+      { \@@_trim_spaces:n {#1} }
+      { \@@_trim_spaces:n {#2} }
+  }
+\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
+\cs_new:Npn \@@_if_exist:nn #1#2
+  {
     \cs_if_exist:cTF
-      { \c_@@_code_root_str \@@_trim_spaces:n { #1 / #2 } }
+      { \c_@@_code_root_str #1 \tl_if_blank:nF {#1} { / } #2 }
       { \prg_return_true: }
       { \prg_return_false: }
   }
-\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
+\cs_generate_variant:Nn \@@_if_exist:nn { ee }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}[EXP,pTF]{\keys_if_choice_exist:nnn}
+% \begin{macro}[EXP]{\@@_if_exist:nnn, \@@_if_exist:eee}
 %   Just an alternative view on \cs{keys_if_exist:nnTF}.
 %    \begin{macrocode}
 \prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3
   { p , T , F , TF }
   {
+    \@@_if_exist:eee
+      { \@@_trim_spaces:n {#1} }
+      { \@@_trim_spaces:n {#2} }
+      { \@@_trim_spaces:n {#3} }
+  }
+\cs_new:Npn \@@_if_exist:nnn #1#2#3
+  {
     \cs_if_exist:cTF
-      { \c_@@_code_root_str \@@_trim_spaces:n { #1 / #2 / #3 } }
+      {
+        \c_@@_code_root_str
+          #1 \tl_if_blank:nF {#1} { / }
+          #2 \tl_if_blank:nF {#2} { / }
+          #3
+      }
       { \prg_return_true: }
       { \prg_return_false: }
   }
+\cs_generate_variant:Nn \@@_if_exist:nnn { eee }
 %    \end{macrocode}
 % \end{macro}
+% \end{macro}
 %
 % \begin{macro}{\keys_show:nn, \keys_log:nn}
-% \begin{macro}{\@@_show:Nnn}
+% \begin{macro}{\@@_show:Nnn, \@@_show_aux:Nnn, , \@@_show_aux:Nee}
 % \begin{macro}{\@@_show:n}
 % \begin{macro}{\@@_show:w}
 % \begin{macro}{\@@_show:Nw}
@@ -3579,8 +3615,15 @@
   { \@@_show:Nnn \msg_log:nneeee }
 \cs_new_protected:Npn \@@_show:Nnn #1#2#3
   {
+    \@@_show_aux:Nee
+      #1
+      { \@@_trim_spaces:n {#2} }
+      { \@@_trim_spaces:n {#3} }
+  }
+\cs_new_protected:Npn \@@_show_aux:Nnn #1#2#3
+  {
     #1 { keys } { show-key }
-      { \@@_trim_spaces:n { #2 / #3 } }
+      { #2 \tl_if_blank:nF {#2} { / } #3 }
       {
         \keys_if_exist:nnT {#2} {#3}
           {
@@ -3591,7 +3634,7 @@
                     \exp_args:Nc \cs_replacement_spec:N
                     {
                       \c_@@_code_root_str
-                      \@@_trim_spaces:n { #2 / #3 }
+                      #2 \tl_if_blank:nF {#2} { / } #3
                     }
                   }
               }
@@ -3599,6 +3642,7 @@
       }
       { } { }
   }
+\cs_generate_variant:Nn \@@_show_aux:Nnn { Nee }
 \cs_new:Npe \@@_show:n #1
   {
     \exp_not:N \@@_show:w

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3legacy.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3luatex.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3msg.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3names.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -584,6 +584,7 @@
   \@@_primitive:NN \pdfpageref            \tex_pdfpageref:D
   \@@_primitive:NN \pdfpageresources      \tex_pdfpageresources:D
   \@@_primitive:NN \pdfpagesattr          \tex_pdfpagesattr:D
+  \@@_primitive:NN \pdfptexuseunderscore  \tex_pdfptexuseunderscore:D
   \@@_primitive:NN \pdfrefobj             \tex_pdfrefobj:D
   \@@_primitive:NN \pdfrefxform           \tex_pdfrefxform:D
   \@@_primitive:NN \pdfrefximage          \tex_pdfrefximage:D
@@ -896,6 +897,7 @@
   \@@_primitive:NN \mathdisplayskipmode   \tex_mathdisplayskipmode:D
   \@@_primitive:NN \matheqdirmode         \tex_matheqdirmode:D
   \@@_primitive:NN \matheqnogapstep       \tex_matheqnogapstep:D
+  \@@_primitive:NN \mathemptydisplaymode  \tex_mathemptydisplaymode:D
   \@@_primitive:NN \mathflattenmode       \tex_mathflattenmode:D
   \@@_primitive:NN \mathitalicsmode       \tex_mathitalicsmode:D
   \@@_primitive:NN \mathnolimitsmode      \tex_mathnolimitsmode:D
@@ -1262,6 +1264,7 @@
 %    \end{macrocode}
 % Newer cross-engine primitives.
 %    \begin{macrocode}
+  \@@_primitive:NN \ignoreprimitiveerror  \tex_ignoreprimitiveerror:D
   \@@_primitive:NN \partokencontext       \tex_partokencontext:D
   \@@_primitive:NN \partokenname          \tex_partokenname:D
   \@@_primitive:NN \showstream            \tex_showstream:D

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3pdf.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3prg.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3prop.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3quark.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3regex.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -769,7 +769,7 @@
 %   Searches for the \meta{regex} in the contents of the
 %   \meta{tl~var} and replaces the first match with the
 %   \meta{replacement}. In the \meta{replacement},
-%   |\0| represents the full match, |\1| represent the contents of the
+%   |\0| represents the full match, |\1| represents the contents of the
 %   first capturing group, |\2| of the second, \emph{etc.}
 %   The result is assigned locally to \meta{tl~var}.
 % \end{function}
@@ -786,7 +786,7 @@
 %   Replaces all occurrences of the \meta{regex} in the
 %   contents of the \meta{tl~var}
 %   by the \meta{replacement}, where |\0| represents
-%   the full match, |\1| represent the contents of the first capturing
+%   the full match, |\1| represents the contents of the first capturing
 %   group, |\2| of the second, \emph{etc.} Every match is treated
 %   independently, and matches cannot overlap.  The result is assigned
 %   locally to \meta{tl~var}.

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3seq.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3skip.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3sort.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3str-convert.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3str.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -289,7 +289,7 @@
 % \begin{function}[added = 2013-07-24, updated = 2022-03-21, EXP, noTF]
 %   {
 %     \str_case:nn, \str_case:Vn, \str_case:Nn, \str_case:on, \str_case:en,
-%     \str_case:nV, \str_case:nv
+%     \str_case:nV, \str_case:nv, \str_case:ne
 %   }
 %   \begin{syntax}
 %     \cs{str_case:nnTF} \Arg{test string} \\
@@ -1253,7 +1253,8 @@
 %
 % \begin{macro}[EXP, noTF]
 %   {
-%     \str_case:nn, \str_case:Vn, \str_case:Nn, \str_case:on, \str_case:en, \str_case:nV, \str_case:nv,
+%     \str_case:nn, \str_case:Vn, \str_case:Nn, \str_case:on, \str_case:en,
+%     \str_case:nV, \str_case:nv, \str_case:ne,
 %     \str_case_e:nn, \str_case_e:en
 %   }
 % \begin{macro}[EXP]{\@@_case:nnTF, \@@_case_e:nnTF}
@@ -1289,9 +1290,9 @@
   }
 \cs_new:Npn \@@_case:nnTF #1#2#3#4
   { \@@_case:nw {#1} #2 {#1} { } \s_@@_mark {#3} \s_@@_mark {#4} \s_@@_stop }
-\cs_generate_variant:Nn \str_case:nn   { V , o , e , nV , nv }
+\cs_generate_variant:Nn \str_case:nn   { V , o , e , nV , nv , ne }
 \prg_generate_conditional_variant:Nnn \str_case:nn
-  { V , o , e , nV , nv } { T , F , TF }
+  { V , o , e , nV , nv , ne } { T , F , TF }
 \cs_new_eq:NN \str_case:Nn   \str_case:Vn
 \cs_new_eq:NN \str_case:NnT  \str_case:VnT
 \cs_new_eq:NN \str_case:NnF  \str_case:VnF

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3sys.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -1097,7 +1097,7 @@
 %
 % \begin{macro}[EXP]{\sys_timer:, \@@_elapsedtime:}
 % \begin{macro}[EXP, pTF]{\sys_if_timer_exist:}
-%   In \LuaTeX{}, create a pseudo-primitve, otherwise try to
+%   In \LuaTeX{}, create a pseudo-primitive, otherwise try to
 %   locate the real primitive.  The elapsed time will be
 %   available if this succeeds.
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-case.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-map.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -65,150 +65,100 @@
 %
 % \subsection{Mapping to text}
 %
-% \begin{macro}[EXP]{\text_map_function:nN}
-% \begin{macro}[EXP]{\@@_map_function:nN}
-% \begin{macro}[EXP]{\@@_map_loop:Nnw}
-% \begin{macro}[EXP]{\@@_map_group:Nnn}
-% \begin{macro}[EXP]{\@@_map_space:Nnw}
-% \begin{macro}[EXP]{\@@_map_N_type:NnN}
-% \begin{macro}[EXP]{\@@_map_codepoint:Nnn}
-% \begin{macro}[EXP]{\@@_map_CR:Nnw}
-% \begin{macro}[EXP]{\@@_map_CR:NnN}
+% \subsubsection{Common code}
+%
+% \begin{macro}[EXP]{\@@_map_function:nnN, \@@_map_function:enN}
+% \begin{macro}[EXP]{\@@_map_loop:Nnnw}
+% \begin{macro}[EXP]{\@@_map_group:Nnnn}
+% \begin{macro}[EXP]{\@@_map_space:Nnnw}
+% \begin{macro}[EXP]{\@@_map_N_type:NnnN}
+% \begin{macro}[EXP]{\@@_map_codepoint:Nnnn}
+% \begin{macro}[EXP]{\@@_map_CR:Nnnw}
+% \begin{macro}[EXP]{\@@_map_CR:NnnN}
 % \begin{macro}[EXP]{\@@_map_class:Nnnn}
-% \begin{macro}[EXP]{\@@_map_class:nNnnn}
-% \begin{macro}[EXP]{\@@_map_class_loop:Nnnnw}
-% \begin{macro}[EXP]{\@@_map_class_end:nw}
-% \begin{macro}[EXP]
-%   {
-%     \@@_map_Control:Nnn     ,
-%     \@@_map_Extend:Nnn      ,
-%     \@@_map_SpacingMark:Nnn ,
-%     \@@_map_Prepend:Nnn     ,
-%     \@@_map_Prepend_aux:Nnn
-%   }
-% \begin{macro}[EXP]{\@@_map_Prepend:nNnn}
-% \begin{macro}[EXP]{\@@_map_Prepend_loop:Nnnw}
-% \begin{macro}[EXP]
-%   {
-%     \@@_map_not_Control:Nnn     ,
-%     \@@_map_not_Extend:Nnn      ,
-%     \@@_map_not_SpacingMark:Nnn ,
-%     \@@_map_not_Prepend:Nnn     ,
-%     \@@_map_not_L:Nnn           ,
-%     \@@_map_not_LV:Nnn          ,
-%     \@@_map_not_V:Nnn           ,
-%     \@@_map_not_LVT:Nnn         ,
-%     \@@_map_not_T:Nnn
-%   }
-% \begin{macro}[EXP]
-%   {
-%     \@@_map_L:Nnn   ,
-%     \@@_map_LV:Nnn  ,
-%     \@@_map_V:Nnn   ,
-%     \@@_map_LVT:Nnn ,
-%     \@@_map_T:Nnn
-%   }
-% \begin{macro}[EXP]{\@@_map_hangul:Nnnw}
-% \begin{macro}[EXP]{\@@_map_hangul:NnnN}
-% \begin{macro}[EXP]{\@@_map_hangul:Nnnn}
-% \begin{macro}[EXP]{\@@_map_hangul_aux:Nnnnw}
-% \begin{macro}[EXP]{\@@_map_hangul:nNnnnw}
-% \begin{macro}[EXP]{\@@_map_hangul_loop:Nnnnnw}
-% \begin{macro}[EXP]{\@@_map_hangul_next:Nnnn}
-% \begin{macro}[EXP]{\@@_map_hangul_end:nw}
-% \begin{macro}[EXP]
-%   {
-%     \@@_map_hangul_L:Nnn   ,
-%     \@@_map_hangul_LV:Nnn  ,
-%     \@@_map_hangul_V:Nnn   ,
-%     \@@_map_hangul_LVT:Nnn ,
-%     \@@_map_hangul_T:Nnn
-%   }
-% \begin{macro}[EXP]
-%   {\@@_map_Regional_Indicator:Nnn, \@@_map_Regional_Indicator_aux:Nnn}
-% \begin{macro}[EXP]{\@@_map_lookahead:NnNw}
-% \begin{macro}[EXP]{\@@_map_lookahead:NnNN}
+% \begin{macro}[EXP]{\@@_map_class:Nnnnn}
+% \begin{macro}[EXP]{\@@_map_lookahead:Nnnnnw}
+% \begin{macro}[EXP]{\@@_map_lookahead:NnnnnN}
+% \begin{macro}[TF,EXP]{\@@_map_if_ignorable:n}
 % \begin{macro}[EXP]{\@@_map_output:Nn}
 % \begin{macro}[EXP]{\text_map_break:}
 % \begin{macro}[EXP]{\text_map_break:n}
-%   The standard lead-off for an action loop.
+%   Mapping to text all works the same way: using standard \enquote{action}
+%   loop on expanded text. There are different ways to determine the boundary
+%   conditions for breaking: to avoid duplication, the common ideas are covered
+%   here with the specifics split out. In all cases, anything which is not a
+%   character token is treated as a boundary.
 %    \begin{macrocode}
-\cs_new:Npn \text_map_function:nN #1#2
-  { \exp_args:Ne \@@_map_function:nN { \text_expand:n {#1} } #2 }
-\cs_new:Npn \@@_map_function:nN #1#2
+\cs_new:Npn \@@_map_function:nnN #1#2#3
   {
-    \@@_map_loop:Nnw #2 { } #1
+    \@@_map_loop:Nnnw #3 {#2} { } #1
       \q_@@_recursion_tail \q_@@_recursion_stop
     \prg_break_point:Nn \text_map_break: { }
   }
-%    \end{macrocode}
-%  The standard set up for an \enquote{action} loop. Groups are handled by
-%  recursion, spaces are treated similarly: both count as grapheme boundaries.
-%  For \texttt{N}-type tokens, we filter out control sequences (again
-%  a boundary), then move on to further analysis.
-%    \begin{macrocode}
-\cs_new:Npn \@@_map_loop:Nnw #1#2#3 \q_@@_recursion_stop
+\cs_generate_variant:Nn \@@_map_function:nnN { e }
+\cs_new:Npn \@@_map_loop:Nnnw #1#2#3#4 \q_@@_recursion_stop
   {
-    \tl_if_head_is_N_type:nTF {#3}
-      { \@@_map_N_type:NnN }
+    \tl_if_head_is_N_type:nTF {#4}
+      { \@@_map_N_type:NnnN }
       {
-        \tl_if_head_is_group:nTF {#3}
-          { \@@_map_group:Nnn }
-          { \@@_map_space:Nnw }
+        \tl_if_head_is_group:nTF {#4}
+          { \@@_map_group:Nnnn }
+          { \@@_map_space:Nnnw }
       }
-    #1 {#2} #3 \q_@@_recursion_stop
+    #1 {#2} {#3} #4 \q_@@_recursion_stop
   }
-\cs_new:Npn \@@_map_group:Nnn #1#2#3
+\cs_new:Npn \@@_map_group:Nnnn #1#2#3#4
   {
-    \@@_map_output:Nn #1 {#2}
+    \@@_map_output:Nn #1 {#3}
     {
-      \@@_map_loop:Nnw #1 { } #2
+      \@@_map_loop:Nnnw #1 {#2} { } #4
         \q_@@_recursion_tail \q_@@_recursion_stop
       \prg_break_point:Nn \text_map_break: { }
     }
-    \@@_map_loop:Nnw #1 { }
+    \@@_map_loop:Nnnw #1 {#2} { }
   }
 \use:e
-  { \cs_new:Npn \exp_not:N \@@_map_space:Nnw #1#2 \c_space_tl }
+  { \cs_new:Npn \exp_not:N \@@_map_space:Nnnw #1#2#3 \c_space_tl }
   {
-    \@@_map_output:Nn #1 {#2}
+    \@@_map_output:Nn #1 {#3}
     #1 { ~ }
-    \@@_map_loop:Nnw #1 { }
+    \@@_map_loop:Nnnw #1 {#2} { }
   }
-\cs_new:Npn \@@_map_N_type:NnN #1#2#3
+\cs_new:Npn \@@_map_N_type:NnnN #1#2#3#4
   {
-    \@@_if_q_recursion_tail_stop_do:Nn #3
+    \@@_if_q_recursion_tail_stop_do:Nn #4
       {
-        \@@_map_output:Nn #1 {#2}
+        \@@_map_output:Nn #1 {#3}
         \text_map_break:
       }
-    \token_if_cs:NTF #3
+    \token_if_cs:NTF #4
       {
-        \@@_map_output:Nn #1 {#2}
-        #1 {#3}
-        \@@_map_loop:Nnw #1 { }
+        \@@_map_output:Nn #1 {#3}
+        #1 {#4}
+        \@@_map_loop:Nnnw #1 {#2} { }
       }
       {
         \@@_codepoint_process:nN
-          { \@@_map_codepoint:Nnn #1 {#2} } #3
+          { \@@_map_codepoint:Nnnn #1 {#2} {#3} } #4
       }
   }
 %    \end{macrocode}
 %  We pull out a few special cases here. Carriage returns case needs a bit of
 %  context handling so has an auxiliary. Codepoint U+200D is the zero-width
-%  joiner, which has no context to concern us: just don't break.
+%  joiner, which has no context to concern us: just don't break. (These special
+%  cases apply to all forms of text mapping.)
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_codepoint:Nnn #1#2#3
+\cs_new:Npn \@@_map_codepoint:Nnnn #1#2#3#4
   {
-    \@@_codepoint_compare:nNnTF {#3} = { "0D }
+    \@@_codepoint_compare:nNnTF {#4} = { "000D }
       {
-        \@@_map_output:Nn #1 {#2}
-        \@@_map_CR:Nnw #1 {#3}
+        \@@_map_output:Nn #1 {#3}
+        \@@_map_CR:Nnnw #1 {#2} {#4}
       }
       {
-        \@@_codepoint_compare:nNnTF {#3} = { "200D }
-          { \@@_map_loop:Nnw #1 {#2#3} }
-          { \@@_map_class:Nnnn #1 {#2} {#3} { Control } }
+        \@@_codepoint_compare:nNnTF {#4} = { "200D }
+          { \@@_map_loop:Nnnw #1 {#2} {#3#4} }
+          { \@@_map_class:Nnnn #1 {#2} {#3} {#4} }
       }
   }
 %    \end{macrocode}
@@ -215,157 +165,264 @@
 %   A carriage return is a boundary unless it is immediately followed by
 %   a line feed, in which case that pair is a boundary.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_CR:Nnw #1#2#3 \q_@@_recursion_stop
+\cs_new:Npn \@@_map_CR:Nnnw #1#2#3#4 \q_@@_recursion_stop
   {
-    \tl_if_head_is_N_type:nTF {#3}
-      { \@@_map_CR:NnN #1 {#2} }
+    \tl_if_head_is_N_type:nTF {#4}
+      { \@@_map_CR:NnnN #1 {#2} {#3} }
       {
-        #1 {#2}
-        \@@_map_loop:Nnw #1 { }
+        #1 {#3}
+        \@@_map_loop:Nnnw #1 {#2} { }
       }
-        #3 \q_@@_recursion_stop
+        #4 \q_@@_recursion_stop
   }
-\cs_new:Npn \@@_map_CR:NnN #1#2#3
+\cs_new:Npn \@@_map_CR:NnnN #1#2#3#4
   {
-    \@@_if_q_recursion_tail_stop_do:Nn #3
+    \@@_if_q_recursion_tail_stop_do:Nn #4
       {
-        #1 {#2}
+        #1 {#3}
         \text_map_break:
       }
     \bool_lazy_and:nnTF
-      { ! \token_if_cs_p:N #3 }
-      { \int_compare_p:nNn { `#3 } = { "0A } }
+      { ! \token_if_cs_p:N #4 }
+      { \int_compare_p:nNn { `#4 } = { "000A } }
       {
-        \@@_map_output:Nn #1 {#2#3}
-        \@@_map_loop:Nnw #1 { }
+        \@@_map_output:Nn #1 {#3#4}
+        \@@_map_loop:Nnnw #1 {#2} { }
       }
-      { \@@_map_loop:Nnw #1 { } #3 }
+      { \@@_map_loop:Nnnw #1 {#2} { } #3 }
   }
 %    \end{macrocode}
 %   There are various classes of character, and we deal with them all in
 %   the same general way. We need to example the relevant list of codepoints:
-%   if we get a hit, then we do whatever the relevant action is. Otherwise
-%   we loop, but only if the current codepoint could still match: the
-%   loop stops early otherwise and we move forward.
+%   if we get a hit, then we do whatever the relevant action is. To keep names
+%   short and to allow code sharing, we have two ways of naming the functions:
+%   most class names are unique, so it's only where we see the same name used
+%   in both break classes that we need more control.
 %    \begin{macrocode}
 \cs_new:Npn \@@_map_class:Nnnn #1#2#3#4
   {
-    \exp_args:Nv \@@_map_class:nNnnn { c_@@_grapheme_ #4 _clist }
-      #1 {#2} {#3} {#4}
+    \exp_args:Nnnne \@@_map_class:Nnnnn #1 {#2} {#3} {#4}
+      {
+        \use:c { __kernel_codepoint_to_ #2 _class:n }
+          { \@@_codepoint_from_chars:Nw #4 }
+      }
   }
-\cs_new:Npn \@@_map_class:nNnnn #1#2#3#4#5
+\cs_new:Npn \@@_map_class:Nnnnn #1#2#3#4#5
   {
-    \@@_map_class_loop:Nnnnw #2 {#3} {#4} {#5}
-      #1 , \q_@@_recursion_tail .. , \q_@@_recursion_stop
+    \cs_if_exist_use:cF { @@_map_ #5 :Nnnn }
+      { \@@_map_Other:Nnnn }
+        #1 {#2} {#3} {#4}
   }
-\cs_new:Npn \@@_map_class_loop:Nnnnw #1#2#3#4 #5 .. #6 ,
+%    \end{macrocode}
+%   A generic loop-ahead setup: we need to handle both the previously collected
+%   tokens and any \enquote{conditional} ones. The latter occur when looking
+%   ahead for word-breaking: these \emph{may} be combined with the collected
+%   tokens, but if we hit the end-of-loop, need to be output separately.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_lookahead:Nnnnnw #1#2#3#4#5#6 \q_@@_recursion_stop
   {
-    \@@_if_q_recursion_tail_stop_do:nn {#5}
-      { \use:c { @@_map_not_ #4 :Nnn } #1 {#2} {#3} }
-    \@@_codepoint_compare:nNnTF {#3} < { "#5 }
+    \tl_if_head_is_N_type:nTF {#6}
+      { \@@_map_lookahead:NnnnnN #1 {#2} {#3} {#4} {#5} }
+      { \@@_map_loop:Nnnw #1 {#2} {#3} #4 }
+    #6 \q_@@_recursion_stop
+  }
+\cs_new:Npn \@@_map_lookahead:NnnnnN #1#2#3#4#5#6
+  {
+    \@@_if_q_recursion_tail_stop_do:Nn #6
       {
-        \@@_map_class_end:nw
-          { \use:c { @@_map_not_ #4 :Nnn } #1 {#2} {#3} }
+        #1 {#3}
+        \tl_if_blank:nF {#4} { #1 {#4} }
       }
+    \token_if_cs:NTF #6
       {
-        \@@_codepoint_compare:nNnTF {#3} > { "#6 }
-          { \@@_map_class_loop:Nnnnw #1 {#2} {#3} {#4} }
-          {
-            \@@_map_class_end:nw
-              { \use:c { @@_map_ #4 :Nnn } #1 {#2} {#3} }
-          }
+        #1 {#3}
+        \@@_map_loop:Nnnw #1 {#2} { } #4
       }
+      { \@@_codepoint_process:nN { #5 #1 {#2} {#3} {#4} } }
+        #6
   }
-\cs_new:Npn \@@_map_class_end:nw #1#2 \q_@@_recursion_stop {#1}
 %    \end{macrocode}
-%   Break before \emph{and} after.
+%   To deal with \enquote{ignored} characters for word break mapping: needed
+%   for generic |Regional_Indicator| function, so set up here.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_Control:Nnn #1#2#3
+\prg_new_conditional:Npnn \@@_map_if_ignorable:n #1 { TF }
   {
-    \@@_map_output:Nn #1 {#2}
-    \@@_map_output:Nn #1 {#3}
-    \@@_map_loop:Nnw #1 { }
+    \str_case:nnTF {#1}
+      {
+        { Extend }       { }
+        { Format }       { }
+        { ZWJ }          { }
+      }
+      \prg_return_true:
+      \prg_return_false:
   }
 %    \end{macrocode}
-%   Keep collecting.
+%   For the end of the process.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_Extend:Nnn #1#2#3
-  { \@@_map_loop:Nnw #1 {#2#3} }
-\cs_new_eq:NN \@@_map_SpacingMark:Nnn \@@_map_Extend:Nnn
+\cs_new:Npn \@@_map_output:Nn #1#2
+  { \tl_if_blank:nF {#2} { #1 {#2} } }
+\cs_new:Npn \text_map_break:
+  { \prg_map_break:Nn \text_map_break: { } }
+\cs_new:Npn \text_map_break:n
+  { \prg_map_break:Nn \text_map_break: }
 %    \end{macrocode}
-%   Outputting anything earlier, the combine with what follows. The only
-%   exclusions are control characters.
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+%   {
+%     \@@_map_Control:Nnnn            ,
+%     \@@_map_Newline:Nnnn            ,
+%     \@@_map_Extend:Nnnn             ,
+%     \@@_map_Format:Nnnn             ,
+%     \@@_map_SpacingMark:Nnnn        ,
+%     \@@_map_Other:Nnnn              ,
+%     \@@_map_Regional_Indicator:Nnnn
+%   }
+% \begin{macro}[EXP]{\@@_map_Regional_Indicator_aux:Nnnnn}
+%   A small number of classes appear in both forms of breaking and have the
+%   same behavior. For |Control| and |Newline|, we set up here as they are the
+%   same outcome. We have the same story for |Format|, which is functionally
+%   the same as |Newline|.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_Prepend:Nnn #1#2#3
+\cs_new:Npn \@@_map_Control:Nnnn #1#2#3#4
   {
-    \@@_map_output:Nn #1 {#2}
-    \@@_map_lookahead:NnNw #1 {#3} \@@_map_Prepend_aux:Nnn
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_output:Nn #1 {#4}
+    \@@_map_loop:Nnnw #1 {#2} { }
   }
-\cs_new:Npn \@@_map_Prepend_aux:Nnn #1#2#3
+\cs_new_eq:NN \@@_map_Newline:Nnnn \@@_map_Control:Nnnn
+\cs_new:Npn \@@_map_Extend:Nnnn #1#2#3#4
+  { \@@_map_loop:Nnnw #1 {#2} {#3#4} }
+\cs_new_eq:NN \@@_map_Format:Nnnn \@@_map_Extend:Nnnn
+\cs_new_eq:NN \@@_map_SpacingMark:Nnnn \@@_map_Extend:Nnnn
+\cs_new:Npn \@@_map_Other:Nnnn #1#2#3#4
   {
-    \bool_lazy_or:nnTF
-      { \@@_codepoint_compare_p:nNn {#3} = { "0A } }
-      { \@@_codepoint_compare_p:nNn {#3} = { "0D } }
-      {
-        #1 {#2}
-        \@@_map_loop:Nnw #1 {#3}
-      }
-      {
-        \exp_args:NV \@@_map_Prepend:nNnn
-          \c_@@_grapheme_Control_clist
-          #1 {#2} {#3}
-      }
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_loop:Nnnw #1 {#2} {#4}
   }
-\cs_new:Npn \@@_map_Prepend:nNnn #1#2#3#4
+%    \end{macrocode}
+%  The Regional Indicator rule means looking ahead and dealing with the
+%  case where there are two in a row. So we use a look ahead to pick them
+%  off. As there is only one range the values are hard-coded. For
+%  word breaking, we also need to allow for the various extenders.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_Regional_Indicator:Nnnn #1#2#3#4
   {
-    \@@_map_Prepend_loop:Nnnw #2 {#3} {#4}
-      #1 , \q_@@_recursion_tail .. , \q_@@_recursion_stop
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_lookahead:Nnnnnw #1 {#2} {#4} { }
+      \@@_map_Regional_Indicator_aux:Nnnnn
   }
-\cs_new:Npn \@@_map_Prepend_loop:Nnnw #1#2#3 #4 .. #5 ,
+\cs_new:Npn \@@_map_Regional_Indicator_aux:Nnnnn #1#2#3#4#5
   {
-    \@@_if_q_recursion_tail_stop_do:nn {#4}
-      { \@@_map_loop:Nnw #1 {#2#3} }
-    \@@_codepoint_compare:nNnTF {#3} < { "#4 }
+    \bool_lazy_or:nnTF
+      { \@@_codepoint_compare_p:nNn {#5} < { "1F1E6 } }
+      { \@@_codepoint_compare_p:nNn {#5} > { "1F1FF } }
       {
-        \@@_map_class_end:nw
-          { \@@_map_loop:Nnw #1 {#2#3} }
-      }
-      {
-        \@@_codepoint_compare:nNnTF {#3} > { "#5 }
-          { \@@_map_Prepend_loop:Nnnw #1 {#2} {#3} }
+        \str_if_eq:nnTF {#2} { wordbreak }
           {
-            \@@_map_class_end:nw
-              { \@@_map_loop:Nnw #1 {#2} #3 }
+            \exp_args:Ne \@@_map_if_ignorable:nTF
+              {
+                \__kernel_codepoint_to_grapheme_class:n
+                  { \@@_codepoint_from_chars:Nw #5 }
+              }
+              {
+                \@@_map_lookahead:Nnnnnw #1 {#2} {#3#5} { }
+                  \@@_map_Regional_Indicator_aux:Nnnnn
+              }
+              { \@@_map_loop:Nnnw #1 {#2} {#3} #5 }
           }
+          { \@@_map_loop:Nnnw #1 {#2} {#3} #5 }
       }
+      { \@@_map_loop:Nnnw #1 {#2} {#3#5} }
   }
 %    \end{macrocode}
-%   Dealing with end-of-class is done such that we can be flexible.
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Grapheme mapping}
+%
+% \begin{macro}[EXP]{\text_map_function:nN}
+% \begin{macro}[EXP]{\@@_map_Prepend:Nnnn}
+% \begin{macro}[EXP]{\@@_map_Prepend_aux:Nnnnn}
+% \begin{macro}[EXP]{\@@_map_Prepend:Nnn}
+% \begin{macro}[EXP]
+%   {
+%     \@@_map_L:Nnnn   ,
+%     \@@_map_LV:Nnnn  ,
+%     \@@_map_V:Nnnn   ,
+%     \@@_map_LVT:Nnnn ,
+%     \@@_map_T:Nnnn
+%   }
+% \begin{macro}[EXP]{\@@_map_hangul:Nnnw}
+% \begin{macro}[EXP]{\@@_map_hangul:NnnN}
+% \begin{macro}[EXP]{\@@_map_hangul:Nnnn}
+% \begin{macro}[EXP]{\@@_map_hangul_aux:Nnnnw}
+% \begin{macro}[EXP]{\@@_map_hangul:Nnnnw}
+% \begin{macro}[EXP]{\@@_map_hangul_next:Nnnnn}
+% \begin{macro}[EXP]{\@@_map_hangul_end:nw}
+% \begin{macro}[EXP]
+%   {
+%     \@@_map_hangul_L:Nnn   ,
+%     \@@_map_hangul_LV:Nnn  ,
+%     \@@_map_hangul_V:Nnn   ,
+%     \@@_map_hangul_LVT:Nnn ,
+%     \@@_map_hangul_T:Nnn
+%   }
+%   The standard lead-off for an action loop.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_not_Control:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { Extend } }
-\cs_new:Npn \@@_map_not_Extend:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { SpacingMark } }
-\cs_new:Npn \@@_map_not_SpacingMark:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { Prepend } }
-\cs_new:Npn \@@_map_not_Prepend:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { L } }
-\cs_new:Npn \@@_map_not_L:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { LV } }
-\cs_new:Npn \@@_map_not_LV:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { V } }
-\cs_new:Npn \@@_map_not_V:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { LVT } }
-\cs_new:Npn \@@_map_not_LVT:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { T } }
-\cs_new:Npn \@@_map_not_T:Nnn #1#2#3
-  { \@@_map_class:Nnnn #1 {#2} {#3} { Regional_Indicator } }
-\cs_new:Npn \@@_map_not_Regional_Indicator:Nnn #1#2#3
+\cs_new:Npn \text_map_function:nN #1#2
   {
-    \@@_map_output:Nn #1 {#2}
-    \@@_map_loop:Nnw #1 {#3}
+    \@@_map_function:enN { \text_expand:n {#1} }
+      { grapheme } #2
   }
 %    \end{macrocode}
+%   Outputting anything earlier, the combine with what follows. The only
+%   exclusions are control characters.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_Prepend:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_lookahead:Nnnnnw #1 { grapheme } {#4} { }
+      \@@_map_Prepend_aux:Nnnnn
+  }
+\cs_new:Npn \@@_map_Prepend_aux:Nnnnn #1#2#3#4#5
+  {
+    \bool_lazy_or:nnTF
+      { \@@_codepoint_compare_p:nNn {#5} = { "000A } }
+      { \@@_codepoint_compare_p:nNn {#5} = { "000D } }
+      {
+        #1 {#3}
+        \@@_map_loop:Nnnw #1 { grapheme } {#5}
+      }
+      { \@@_map_Prepend:Nnn #1 {#3} {#5} }
+  }
+\cs_new:Npn \@@_map_Prepend:Nnn #1#2#3
+  {
+    \str_if_eq:eeTF
+      { Control }
+      {
+        \__kernel_codepoint_to_grapheme_class:n
+          { \@@_codepoint_from_chars:Nw #3 }
+      }
+      { \@@_map_loop:Nnnw #1 { grapheme } {#2} #3 }
+      { \@@_map_loop:Nnnw #1 { grapheme } {#2#3} }
+  }
+%    \end{macrocode}
 %   Hangul needs additional treatment. First we have to deal with
 %   the start-of-Hangul position: output what we had up to now, then
 %   move the specialist handler. The idea here is to pick off the
@@ -374,26 +431,26 @@
 %   Other than that, we just keep building up the Hangul codepoints
 %   using a dedicated version of the loop from above.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_L:Nnn #1#2#3
+\cs_new:Npn \@@_map_L:Nnnn #1#2#3#4
   {
-    \@@_map_output:Nn #1 {#2}
+    \@@_map_output:Nn #1 {#3}
     \@@_map_hangul:Nnnw
-      #1 {#3} { L ; V ; LV ; LVT }
+      #1 {#4} { L ; V ; LV ; LVT }
   }
-\cs_new:Npn \@@_map_LV:Nnn #1#2#3
+\cs_new:Npn \@@_map_LV:Nnnn #1#2#3#4
   {
-    \@@_map_output:Nn #1 {#2}
+    \@@_map_output:Nn #1 {#3}
     \@@_map_hangul:Nnnw
-      #1 {#3} { V ; T }
+      #1 {#4} { V ; T }
   }
-\cs_new_eq:NN \@@_map_V:Nnn \@@_map_LV:Nnn
-\cs_new:Npn \@@_map_LVT:Nnn #1#2#3
+\cs_new_eq:NN \@@_map_V:Nnnn \@@_map_LV:Nnnn
+\cs_new:Npn \@@_map_LVT:Nnnn #1#2#3#4
   {
-    \@@_map_output:Nn #1 {#2}
+    \@@_map_output:Nn #1 {#3}
     \@@_map_hangul:Nnnw
-      #1 {#3} { T }
+      #1 {#4} { T }
   }
-\cs_new_eq:NN \@@_map_T:Nnn \@@_map_LVT:Nnn
+\cs_new_eq:NN \@@_map_T:Nnnn \@@_map_LVT:Nnnn
 \cs_new:Npn \@@_map_hangul:Nnnw #1#2#3#4 \q_@@_recursion_stop
   {
     \tl_if_head_is_N_type:nTF {#4}
@@ -400,7 +457,7 @@
       { \@@_map_hangul:NnnN #1 {#2} {#3} }
       {
         #1 {#2}
-        \@@_map_loop:Nnw #1 { }
+        \@@_map_loop:Nnnw #1 { grapheme } { }
       }
     #4 \q_@@_recursion_stop
   }
@@ -414,7 +471,7 @@
     \token_if_cs:NTF #4
       {
         #1 {#2}
-        \@@_map_loop:Nnw #1 { }
+        \@@_map_loop:Nnnw #1 { grapheme } { }
       }
       {
         \@@_codepoint_process:nN
@@ -421,43 +478,31 @@
           { \@@_map_hangul:Nnnn #1 {#2} {#3} } #4
       }
   }
+\exp_args_generate:n { Nnne }
 \cs_new:Npn \@@_map_hangul:Nnnn #1#2#3#4
   {
-    \@@_map_hangul_aux:Nnnw #1 {#2} {#4}
+    \exp_args:NNnne \@@_map_hangul_aux:Nnnnw #1 {#2} {#4}
+      {
+        \__kernel_codepoint_to_grapheme_class:n
+          { \@@_codepoint_from_chars:Nw #4 }
+      }
       #3 ; \q_recursion_tail ; \q_recursion_stop
   }
-\cs_new:Npn \@@_map_hangul_aux:Nnnw #1#2#3#4 ;
+\cs_new:Npn \@@_map_hangul_aux:Nnnnw #1#2#3#4#5 ;
   {
-    \quark_if_recursion_tail_stop_do:nn {#4}
-      { \@@_map_loop:Nnw #1 {#2} #3 }
-    \exp_args:Nv \@@_map_hangul:nNnnnw { c_@@_grapheme_ #4 _clist }
-      #1 {#2} {#3} {#4}
+    \quark_if_recursion_tail_stop_do:nn {#5}
+      { \@@_map_loop:Nnnw #1 { grapheme } {#2} #3 }
+    \@@_map_hangul:Nnnnnw #1 {#2} {#3} {#4} {#5}
   }
-\cs_new:Npn \@@_map_hangul:nNnnnw #1#2#3#4#5#6  \q_recursion_stop
+\cs_generate_variant:Nn \@@_map_hangul_aux:Nnnnw { Nnne }
+\cs_new:Npn \@@_map_hangul:Nnnnnw #1#2#3#4#5#6 \q_recursion_stop
   {
-    \@@_map_hangul_loop:Nnnnnw #2 {#3} {#4} {#5} {#6}
-      #1 , \q_@@_recursion_tail .. , \q_@@_recursion_stop
+    \str_if_eq:nnTF {#4} {#5}
+      { \use:c { @@_map_hangul_ #5 :Nnn } #1 {#2} {#3} }
+      { \@@_map_hangul_next:Nnnnn #1 {#2} {#3} {#4} {#6} }
   }
-\cs_new:Npn \@@_map_hangul_loop:Nnnnnw #1#2#3#4#5 #6 .. #7 ,
-  {
-    \@@_if_q_recursion_tail_stop_do:nn {#6}
-      { \@@_map_hangul_next:Nnnn #1 {#2} {#3} {#5} }
-    \@@_codepoint_compare:nNnTF {#3} < { "#6 }
-      {
-        \@@_map_hangul_end:nw
-          { \@@_map_hangul_next:Nnnn #1 {#2} {#3} {#5} }
-      }
-      {
-        \@@_codepoint_compare:nNnTF {#3} > { "#7 }
-          { \@@_map_hangul_loop:Nnnnnw #1 {#2} {#3} {#4} {#5} }
-          {
-            \@@_map_hangul_end:nw
-              { \use:c { @@_map_hangul_ #4 :Nnn } #1 {#2} {#3} }
-          }
-      }
-  }
-\cs_new:Npn \@@_map_hangul_next:Nnnn #1#2#3#4
-  { \@@_map_hangul_aux:Nnnw #1 {#2} {#3} #4 \q_recursion_stop }
+\cs_new:Npn \@@_map_hangul_next:Nnnnn #1#2#3#4#5
+  { \@@_map_hangul_aux:Nnnnw #1 {#2} {#3} {#4} #5 \q_recursion_stop }
 \cs_new:Npn \@@_map_hangul_end:nw #1#2 \q_@@_recursion_stop {#1}
 \cs_new:Npn \@@_map_hangul_L:Nnn #1#2#3
   {
@@ -477,56 +522,235 @@
   }
 \cs_new_eq:NN \@@_map_hangul_T:Nnn \@@_map_hangul_LVT:Nnn
 %    \end{macrocode}
-%  The Regional Indicator rule means looking ahead and dealing with the
-%  case where there are two in a row. So we use a look ahead to pick them
-%  off. As there is only one range the values are hard-coded.
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Word break mapping}
+%
+% \begin{macro}[EXP]{\text_map_function:nN}
+% \begin{macro}[EXP]{\@@_map_collect:Nnnnn}
+% \begin{macro}[EXP]{\@@_map_collect_auxi:nnnNnnnn}
+% \begin{macro}[EXP]{\@@_map_collect_auxii:nNnnnnn}
+% \begin{macro}[EXP]{\@@_map_collect_auxiii:n}
+% \begin{macro}[EXP]{\@@_map_collect_auxiv:nnNnnnn}
+% \begin{macro}[EXP]{\@@_map_collect_auxv:nNnnnnn}
+% \begin{macro}[EXP]
+%   {
+%     \@@_map_ALetter:Nnnn       ,
+%     \@@_map_Hebrew_Letter:Nnnn ,
+%     \@@_map_Katakana:Nnnn      ,
+%     \@@_map_Numeric:Nnnn       ,
+%     \@@_map_WSegSpace:Nnnn     ,
+%     \@@_map_ExtendNumLet:Nnnn
+%   }
+% \begin{macro}[EXP]{\@@_map_ExtendNumLet_auxi::Nnnnn}
+% \begin{macro}[EXP]{\@@_map_ExtendNumLet_auxii:nNnn}
+%   The standard lead-off for an action loop.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_Regional_Indicator:Nnn #1#2#3
+\cs_new:Npn \text_words_map_function:nN #1#2
   {
-    \@@_map_output:Nn #1 {#2}
-    \@@_map_lookahead:NnNw #1 {#3} \@@_map_Regional_Indicator_aux:Nnn
+    \@@_map_function:enN { \text_expand:n {#1} }
+      { wordbreak } #2
   }
-\cs_new:Npn \@@_map_Regional_Indicator_aux:Nnn #1#2#3
+%    \end{macrocode}
+%   The main rule for word breaking is that characters bind to following
+%   ones, potentially either allowing for \emph{or} totally ignoring
+%   intervening ones. For each class, we are passed a list of classes that
+%   bind and ones that we should allow in between. In all cases, the classes
+%   |Extend|, |Format| and |ZWJ| need to be entirely ignored: they are hard
+%   coded and handled separately from the in-between ones. Notice that we use
+%   \cs{str_case:nnTF} to make our boolean here: that way, all that needs to be
+%   passed internally are lists of classes.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_collect:Nnnnn #1#2#3#4#5
   {
-    \bool_lazy_or:nnTF
-      { \@@_codepoint_compare_p:nNn {#3} < { "1F1E6 } }
-      { \@@_codepoint_compare_p:nNn {#3} > { "1F1FF } }
+    \@@_map_lookahead:Nnnnnw #1 { wordbreak } {#2} { }
+      { \@@_map_collect_auxi:nnnNnnnn {#3} {#4} {#5} }
+  }
+\cs_new:Npn \@@_map_collect_auxi:nnnNnnnn #1#2#3#4#5#6#7#8
+  {
+    \exp_args:Ne \@@_map_collect_auxii:nNnnnnn
       {
-        \@@_map_loop:Nnw #1 {#2} #3
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \@@_codepoint_from_chars:Nw #8 }
       }
-      { \@@_map_loop:Nnw #1 {#2#3} }
+	  #4 {#6} {#1} {#2} {#3} {#8}
   }
 %    \end{macrocode}
-%   A generic loop-ahead setup.
+%   We now need to deal with the three possible positive outcomes of examining
+%   the next character. The first is that we have found one of the binding
+%   characters that ends the current cycle: we then pass on to the appropriate
+%   function.  Second, we have the ignored characters: if we find these, we
+%   loop back around. Finally, we look at the \enquote{in-between} characters:
+%   if one is found, we need a further look ahead to reach a decision. Rather
+%   than have extra complexity in the setup, we have a hard-coded skipping of
+%   |ExtendNumLet| for |WSegSpace| (as |ExtendNumLet| only applies to
+%   |ALetter|, |Hebrew_Letter|, |Numeric| and |Katakana|).
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_lookahead:NnNw #1#2#3#4 \q_@@_recursion_stop
+\cs_new:Npn \@@_map_collect_auxii:nNnnnnn #1#2#3#4#5#6#7
   {
-    \tl_if_head_is_N_type:nTF {#4}
-      { \@@_map_lookahead:NnNN #1 {#2} #3 }
-      { \@@_map_loop:Nnw #1 {#2} }
-    #4 \q_@@_recursion_stop
+    \str_case:neTF {#1}
+      {
+        \tl_map_function:eN
+          {
+            #4
+            \str_if_eq:nnF {#4} { { WSegSpace } } { { ExtendNumLet } }
+          }
+          \@@_map_collect_auxiii:n
+      }
+      {
+        \cs_if_exist_use:cF { @@_map_ #1 :Nnnn }
+          { \@@_map_Other:Nnnn }
+            #2 { wordbreak } { } {#3#7}
+      }
+      {
+        \@@_map_if_ignorable:nTF {#1}
+          { \@@_map_collect:Nnnnn #2 {#3#7} {#4} {#5} {#6} }
+          {
+            \str_case:neTF {#1}
+              { \tl_map_function:nN {#5} \@@_map_collect_auxiii:n }
+              {
+                \@@_map_lookahead:Nnnnnw #2 { wordbreak } {#3} {#7}
+                  { \@@_map_collect_auxiv:nnNnnnn {#5} {#6} }
+              }
+              {
+                \@@_map_output:Nn #2 {#3}
+                \@@_map_loop:Nnnw #2 { wordbreak } { } #7
+              }
+          }
+      }
   }
-\cs_new:Npn \@@_map_lookahead:NnNN #1#2#3#4
+\cs_new:Npn \@@_map_collect_auxiii:n #1
+  { \exp_not:n { {#1} { } } }
+%    \end{macrocode}
+%   We are now have a character which \emph{may} bind to the previous one if
+%   the next character is of the correct class also. So we carry forward the
+%   collected material and the conditional character, then look ahead again.
+%   If successful, combine together and move on using the new class, otherwise
+%   output and restart where we were.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_collect_auxiv:nnNnnnn #1#2#3#4#5#6#7
   {
-    \@@_if_q_recursion_tail_stop_do:Nn #4 { #1 {#2} }
-    \token_if_cs:NTF #4
+    \exp_args:Ne \@@_map_collect_auxv:nNnnnnn
       {
-        #1 {#2}
-        \@@_map_loop:Nnw #1 { }
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \@@_codepoint_from_chars:Nw #7 }
       }
-      { \@@_codepoint_process:nN { #3 #1 {#2} } }
-        #4
+	  #3 {#5} {#6} {#1} {#2} {#7}
   }
+\cs_new:Npn \@@_map_collect_auxv:nNnnnnn #1#2#3#4#5#6#7
+  {
+    \str_case:neTF {#1}
+      { \tl_map_function:nN {#6} \@@_map_collect_auxiii:n }
+      { \use:c { @@_map_ #1 :Nnnn } #2 { wordbreak } { } {#3#4#7} }
+      {
+        \@@_map_if_ignorable:nTF {#1}
+          {
+            \@@_map_lookahead:Nnnnnw #2 { wordbreak } {#3} {#4#7}
+              { \@@_map_collect_auxiv:nnNnnnn {#5} {#6} }
+          }
+          {
+            \@@_map_output:Nn #2 {#3}
+            \@@_map_loop:Nnnw #2 { wordbreak } { } #4#7
+          }
+      }
+  }
 %    \end{macrocode}
-%   For the end of the process.
+%   Use the generic collector.
 %    \begin{macrocode}
-\cs_new:Npn \@@_map_output:Nn #1#2
-  { \tl_if_blank:nF {#2} { #1 {#2} } }
-\cs_new:Npn \text_map_break:
-  { \prg_map_break:Nn \text_map_break: { } }
-\cs_new:Npn \text_map_break:n
-  { \prg_map_break:Nn \text_map_break: }
+\cs_new:Npn \@@_map_ALetter:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } }
+      { { MidLetter } { MidNumLet } { Single_Quote } }
+      { { ALetter } { Hebrew_Letter } }
+  }
+\cs_new:Npn \@@_map_Hebrew_Letter:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } { Single_Quote } }
+      { { MidLetter } { MidNumLet } { Double_Quote } }
+      { { Hebrew_Letter } }
+  }
+\cs_new:Npn \@@_map_Katakana:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_collect:Nnnnn #1 {#4} { { Katakana } } { } { }
+  }
+\cs_new:Npn \@@_map_Numeric:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } }
+      { { MidNum } { MidNumLet } { Single_Quote } }
+      { { Numeric } }
+  }
+\cs_new:Npn \@@_map_WSegSpace:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_collect:Nnnnn #1 {#4} { { WSegSpace } } { } { }
+  }
 %    \end{macrocode}
+%   We should only get here in the case we have a \enquote{dangling} extender.
+%   If so, look ahead for characters to bind to, then for the set of three
+%   that we need to skip over.
+%    \begin{macrocode}
+\cs_new:Npn \@@_map_ExtendNumLet:Nnnn #1#2#3#4
+  {
+    \@@_map_output:Nn #1 {#3}
+    \@@_map_lookahead:Nnnnnw #1 { wordbreak } {#4} { }
+      \@@_map_ExtendNumLet_auxi:Nnnnn
+  }
+\cs_new:Npn \@@_map_ExtendNumLet_auxi:Nnnnn #1#2#3#4#5
+  {
+    \exp_args:Ne \@@_map_ExtendNumLet_auxii:nNnn
+      {
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \@@_codepoint_from_chars:Nw #5 }
+      }
+      #1 {#3} {#5}
+  }
+\cs_new:Npn \@@_map_ExtendNumLet_auxii:nNnn #1#2#3#4
+  {
+    \str_case:nnTF {#1}
+      {
+        { ALetter }       { }
+        { Hebrew_Letter } { }
+        { Numeric }       { }
+        { Katakana }      { }
+        { ExtendNumLet }  { }
+      }
+      {
+        \cs_if_exist_use:cF { @@_map_ #1 :Nnnn } % TEMP?
+          { \@@_map_Other:Nnnn }
+            #2 { wordbreak } { } {#3#4}
+      }
+      {
+        \@@_map_if_ignorable:nTF {#1}
+          {
+            \@@_map_lookahead:Nnnnnw #2 { wordbreak } {#3#4} { }
+              \@@_map_ExtendNumLet_auxi:Nnnnn
+          }
+          {
+            \@@_map_output:Nn #2 {#3}
+            \@@_map_loop:Nnnw #2 { wordbreak } { } #4
+          }
+      }
+  }
+%    \end{macrocode}
 % \end{macro}
 % \end{macro}
 % \end{macro}
@@ -537,31 +761,10 @@
 % \end{macro}
 % \end{macro}
 % \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
-% \end{macro}
 %
-% \begin{macro}{\text_map_inline:nn}
+% \subsection{Inline mappings}
+%
+% \begin{macro}{\text_map_inline:nn, \text_words_map_inline:nn}
 %   The standard non-expandable inline version.
 %    \begin{macrocode}
 \cs_new_protected:Npn \text_map_inline:nn #1#2
@@ -574,6 +777,16 @@
     \prg_break_point:Nn \text_map_break:
       { \int_gdecr:N \g__kernel_prg_map_int }
   }
+\cs_new_protected:Npn \text_words_map_inline:nn #1#2
+  {
+    \int_gincr:N \g__kernel_prg_map_int
+    \cs_gset_protected:cpn
+      { @@_map_ \int_use:N \g__kernel_prg_map_int :w } ##1 {#2}
+    \exp_args:Nnc \text_words_map_function:nN {#1}
+      { @@_map_ \int_use:N \g__kernel_prg_map_int :w }
+    \prg_break_point:Nn \text_map_break:
+      { \int_gdecr:N \g__kernel_prg_map_int }
+  }
 %    \end{macrocode}
 % \end{macro}
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text-purify.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3text.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -296,26 +296,28 @@
 %   \texttt{true}.
 % \end{variable}
 %
-% \section{Mapping to graphemes}
+% \section{Mapping to text}
 %
 % Grapheme splitting is implemented using the algorithm described in Unicode
 % Standard Annex \#29. This includes support for extended grapheme clusters.
-% Text starting with a line feed or carriage return character will drop this
-% due to standard \TeX{} processing. At present extended pictograms are
-% not supported: these may be added in a future release.
+% Leading line feeds or carriage returns will be dropped due to standard
+% \TeX{} processing. At present extended pictograms are
+% not supported: these may be added in a future release. Some aspects of Indic
+% grapheme breaking, introduced in Unicode~15, are also currently absent.
 %
 % \begin{function}[rEXP, added = 2022-08-04]{\text_map_function:nN}
 %   \begin{syntax}
 %     \cs{text_map_function:nN} \Arg{text} \meta{function}
 %   \end{syntax}
-%   Takes user input \meta{text} and expands as described for
-%   \cs{text_expand:n}, then maps over the \emph{graphemes} within the
+%   This takes the user input in \meta{text} and expands it as with
+%   \cs{text_expand:n}; it then maps over the \emph{graphemes} within the
 %   result, passing each grapheme to the \meta{function}.
 %   Broadly a grapheme is a \enquote{user perceived character}:
 %   the Unicode Consortium describe the decomposition of input to
 %   graphemes in depth, and the approach used here implements that
-%   algorithm. The \meta{function} should accept one argument as \meta{balanced
-%   text}: this may be comprise codepoints or may be a control sequence.
+%   algorithm. The \meta{function} should accept one argument as
+%   \meta{balanced text}: this may comprise codepoints or it may be a
+%   control sequence.
 %   With $8$-bit engines, the codepoint(s) themselves may of course be
 %   made up of multiple bytes: the mapping will pass the correct codepoints
 %   independent of the engine in use.
@@ -326,15 +328,15 @@
 %   \begin{syntax}
 %     \cs{text_map_inline:nn} \Arg{text} \Arg{inline function}
 %   \end{syntax}
-%   Takes user input \meta{text} and expands as described for
-%   \cs{text_expand:n}, then maps over the \emph{graphemes} within the
+%   This takes the user input in \meta{text} and expands it as with
+%   \cs{text_expand:n}; it then maps over the \emph{graphemes} within the
 %   result, passing each grapheme to the \meta{inline function}.
 %   Broadly a grapheme is a \enquote{user perceived character}:
 %   the Unicode Consortium describe the decomposition of input to
 %   graphemes in depth, and the approach used here implements that
 %   algorithm. The \meta{inline function} should consist of code which
-%   receives the grapheme as \meta{balanced
-%   text}: this may be comprise codepoints or may be a control sequence.
+%   receives the grapheme as \meta{balanced text}: this may comprise
+%   codepoints or it may be a control sequence.
 %   With $8$-bit engines, the codepoint(s) themselves may of course be
 %   made up of multiple bytes: the mapping will pass the correct codepoints
 %   independent of the engine in use.
@@ -341,6 +343,46 @@
 %   See also \cs{text_map_function:nN}.
 % \end{function}
 %
+% Word breaking is implemented using the standard algorithm described in Unicode
+% Standard Annex \#29. Leading line feeds or carriage returns will be dropped due
+% to standard \TeX{} processing. Spaces are always
+% considered a break even if immediately followed by an extending character.
+% At present extended pictograms are not supported: these may be added in a future
+% release. Language-based tailoring may be added in a future release: at present
+% the algorithm is exactly that described in the annex.
+%
+% \begin{function}[rEXP, added = 2025-02-12]{\text_words_map_function:nN}
+%   \begin{syntax}
+%     \cs{text_words_map_function:nN} \Arg{text} \meta{function}
+%   \end{syntax}
+%   This takes the user input in \meta{text} and expands it as with
+%   \cs{text_expand:n}; it then maps over the \emph{words} within the
+%   result, passing each word to the \meta{function}. Word boundaries are
+%   determined using the standard algorithm described by the Unicode
+%   Consortium. The \meta{function} should accept one argument as
+%   \meta{balanced text}: this may comprise codepoints or it may be a
+%   control sequence. With $8$-bit engines, the codepoint(s) themselves
+%   may of course be made up of multiple bytes: the mapping will pass the
+%   correct codepoints independent of the engine in use.
+%   See also \cs{text_words_map_inline:nn}.
+% \end{function} 
+%
+% \begin{function}[rEXP, added = 2025-02-12]{\text_words_map_inline:nn}
+%   \begin{syntax}
+%     \cs{text_words_map_inline:nn} \Arg{text} \Arg{inline function}
+%   \end{syntax}
+%   This takes the user input in \meta{text} and expands it as with
+%   \cs{text_expand:n}; it then maps over the \emph{words} within the
+%   result, passing each word to the \meta{function}. Word boundaries are
+%   determined using the standard algorithm described by the Unicode
+%   Consortium. The \meta{inline function} should consist of code that will
+%   accept one argument as \meta{balanced text}: this may comprise codepoints
+%   or it may be a control sequence. With $8$-bit engines, the codepoint(s)
+%   themselves may of course be made up of multiple bytes: the mapping will
+%   pass the correct codepoints independent of the engine in use.
+%   See also \cs{text_words_map_function:nN}.
+% \end{function} 
+%
 % \begin{function}[rEXP, added = 2022-08-04]
 %   {\text_map_break:, \text_map_break:n}
 %   \begin{syntax}
@@ -347,7 +389,8 @@
 %     \cs{text_map_break:}
 %     \cs{text_map_break:n} \Arg{code}
 %   \end{syntax}
-%   Used to terminate a \cs[no-index]{text_map_\ldots} function before all
+%   Used to terminate a \cs[no-index]{text_map_\ldots} or
+%   \cs[no-index]{text_words_map_\ldots} function before all
 %   entries in the \meta{text} have been processed. This
 %   normally takes place within a conditional statement.
 % \end{function}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-analysis.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -44,7 +44,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl-build.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3tl.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -655,8 +655,7 @@
 % \begin{function}[added = 2011-07-09, updated = 2012-06-25, EXP]
 %   {
 %     \tl_trim_spaces:n, \tl_trim_spaces:V, \tl_trim_spaces:v,
-%       \tl_trim_spaces:e,
-%     \tl_trim_spaces:o
+%     \tl_trim_spaces:e, \tl_trim_spaces:o,
 %   }
 %   \begin{syntax}
 %     \cs{tl_trim_spaces:n} \Arg{token list}
@@ -672,6 +671,28 @@
 %   \end{texnote}
 % \end{function}
 %
+% \begin{function}[added = 2025-02-02, EXP]
+%   {
+%     \tl_trim_left_spaces:n, \tl_trim_left_spaces:V, \tl_trim_left_spaces:v,
+%     \tl_trim_left_spaces:e, \tl_trim_left_spaces:o,
+%     \tl_trim_right_spaces:n, \tl_trim_right_spaces:V, \tl_trim_right_spaces:v,
+%     \tl_trim_right_spaces:e, \tl_trim_right_spaces:o
+%   }
+%   \begin{syntax}
+%     \cs{tl_trim_left_spaces:n} \Arg{token list}
+%   \end{syntax}
+%   Analogue of \cs{tl_trim_spaces:n} which removes any leading \emph{or}
+%   trailing explicit space characters
+%   (explicit tokens with character code~$32$ and category code~$10$)
+%   from the \meta{token list} and leaves the result in the input
+%   stream.
+%   \begin{texnote}
+%     The result is returned within \tn{unexpanded}, which means that the token
+%     list does not expand further when appearing in an \texttt{e}-type
+%     or \texttt{x}-type argument expansion.
+%   \end{texnote}
+% \end{function}
+%
 % \begin{function}[added = 2018-04-12, EXP]
 %   {\tl_trim_spaces_apply:nN, \tl_trim_spaces_apply:oN}
 %   \begin{syntax}
@@ -683,6 +704,21 @@
 %   \texttt{n}-type argument.
 % \end{function}
 %
+% \begin{function}[added = 2025-02-02, EXP]
+%   {
+%     \tl_trim_left_spaces_apply:nN, \tl_trim_left_spaces_apply:oN,
+%     \tl_trim_right_spaces_apply:nN, \tl_trim_right_spaces_apply:oN
+%   }
+%   \begin{syntax}
+%     \cs{tl_trim_left_spaces_apply:nN} \Arg{token list} \meta{function}
+%   \end{syntax}
+%   Analogue of \cs{tl_trim_spaces_apply:nN} which removes any leading
+%   \emph{or} trailing explicit space characters (explicit
+%   tokens with character code~$32$ and category code~$10$) from the
+%   \meta{token list} and passes the result to the \meta{function} as an
+%   \texttt{n}-type argument.
+% \end{function}
+%
 % \begin{function}[added = 2011-07-09]
 %   {
 %     \tl_trim_spaces:N,  \tl_trim_spaces:c,
@@ -696,6 +732,22 @@
 %   character code~$32$ and category code~$10$) from its contents.
 % \end{function}
 %
+% \begin{function}[added = 2025-02-02]
+%   {
+%     \tl_trim_left_spaces:N, \tl_trim_left_spaces:c,
+%     \tl_trim_right_spaces:N, \tl_trim_right_spaces:c,
+%     \tl_gtrim_left_spaces:N, \tl_gtrim_left_spaces:c,
+%     \tl_gtrim_right_spaces:N, \tl_gtrim_right_spaces:c
+%   }
+%   \begin{syntax}
+%     \cs{tl_trim_left_spaces:N} \meta{tl~var}
+%   \end{syntax}
+%   Analogue of \cs{tl_trim_spaces:N} which sets the \meta{tl~var} to
+%   contain the result of removing any leading
+%   \emph{or} trailing explicit space characters (explicit tokens with
+%   character code~$32$ and category code~$10$) from its contents.
+% \end{function}
+%
 % \subsection{Viewing token lists}
 %
 % \begin{function}[updated = 2021-04-29]{\tl_show:N, \tl_show:c}
@@ -757,7 +809,8 @@
 %   \texttt{n}-type arguments. See also \cs{tl_map_function:nN}.
 % \end{function}
 %
-% \begin{function}[updated = 2012-06-29, rEXP]{\tl_map_function:nN}
+% \begin{function}[updated = 2012-06-29, rEXP]
+%   {\tl_map_function:nN, \tl_map_function:eN}
 %   \begin{syntax}
 %     \cs{tl_map_function:nN} \Arg{token list} \meta{function}
 %   \end{syntax}
@@ -890,7 +943,7 @@
 % text or single normal token) in a token list, or the remaining tokens.
 %
 % \begin{function}[updated = 2012-09-09, EXP]
-%   {\tl_head:N, \tl_head:n, \tl_head:V, \tl_head:v, \tl_head:f}
+%   {\tl_head:N, \tl_head:n, \tl_head:V, \tl_head:v, \tl_head:f, \tl_head:e}
 %   \begin{syntax}
 %     \cs{tl_head:n} \Arg{token list}
 %   \end{syntax}
@@ -942,7 +995,7 @@
 % \end{function}
 %
 % \begin{function}[updated = 2012-09-01, EXP]
-%   {\tl_tail:N, \tl_tail:n, \tl_tail:V, \tl_tail:v, \tl_tail:f}
+%   {\tl_tail:N, \tl_tail:n, \tl_tail:V, \tl_tail:v, \tl_tail:f, \tl_tail:e}
 %   \begin{syntax}
 %     \cs{tl_tail:n} \Arg{token list}
 %   \end{syntax}
@@ -1194,7 +1247,7 @@
 %   Searches for the \meta{regular expression} in the contents of the
 %   \meta{tl~var} and replaces the first match with the
 %   \meta{replacement}. In the \meta{replacement},
-%   |\0| represents the full match, |\1| represent the contents of the
+%   |\0| represents the full match, |\1| represents the contents of the
 %   first capturing group, |\2| of the second, \emph{etc.}
 %   Theses are alternative names for \cs{regex_replace_once:nnN} and friends,
 %   with arguments re-ordered for \meta{tl~var} setting;
@@ -2195,7 +2248,7 @@
 %   as our~\meta{A}.  The argument~|#2| only serves to collect~|?|
 %   characters for~|#1|.  Note that the order of the tests means that
 %   the first two are done every time, which is wasteful (for instance,
-%   we repeatedly test for the emptyness of~|#6|).  However, this is
+%   we repeatedly test for the emptiness of~|#6|).  However, this is
 %   rare enough not to matter.  Finally, choose~\meta{B} to be
 %   \cs{q_@@_nil} or~\cs{q_@@_stop} such that it is not equal to~|#6|.
 %
@@ -2493,7 +2546,7 @@
 %   \TeX{} skips spaces when reading a non-delimited arguments. Thus,
 %   a \meta{token list} is blank if and only if \cs{use_none:n}
 %   \meta{token list} |?| is empty after one expansion.  The auxiliary
-%   \cs{@@_if_empty_if:o} is a fast emptyness test, converting its
+%   \cs{@@_if_empty_if:o} is a fast emptiness test, converting its
 %   argument to a string (after one expansion) and using the test
 %   \cs{if:w} \cs{scan_stop:} |...| \cs{scan_stop:}.
 %    \begin{macrocode}
@@ -2638,7 +2691,7 @@
 %   starts with |-NoValue-|, while the second argument is empty if |##1|
 %   is exactly |-NoValue-| or if it has a question mark just following
 %   |-NoValue-|.  In this second case, however, the material after the
-%   first |?!| remains and makes the emptyness test return
+%   first |?!| remains and makes the emptiness test return
 %   \texttt{false}.
 %    \begin{macrocode}
 \cs_set_protected:Npn \@@_tmp:w #1
@@ -2756,7 +2809,8 @@
 %
 % \begin{macro}
 %   {
-%     \tl_map_function:nN, \tl_map_function:NN, \tl_map_function:cN,
+%     \tl_map_function:nN, \tl_map_function:eN,
+%     \tl_map_function:NN, \tl_map_function:cN,
 %     \@@_map_function:Nnnnnnnnn, \@@_map_function_end:w,
 %     \@@_use_none_delimit_by_s_stop:w
 %   }
@@ -2779,6 +2833,7 @@
       \s_@@_stop \s_@@_stop \s_@@_stop \s_@@_stop
     \prg_break_point:Nn \tl_map_break: { }
   }
+\cs_generate_variant:Nn \tl_map_function:nN { e }
 \cs_new:Npn \tl_map_function:NN
   { \exp_args:No \tl_map_function:nN }
 \cs_generate_variant:Nn \tl_map_function:NN { c }
@@ -3014,18 +3069,30 @@
 % \begin{macro}
 %   {
 %     \tl_trim_spaces:n, \tl_trim_spaces:V, \tl_trim_spaces:v,
-%       \tl_trim_spaces:e,
-%     \tl_trim_spaces:o, 
+%     \tl_trim_spaces:e, \tl_trim_spaces:o,
+%     \tl_trim_left_spaces:n, \tl_trim_left_spaces:V, \tl_trim_left_spaces:v,
+%     \tl_trim_left_spaces:e, \tl_trim_left_spaces:o,
+%     \tl_trim_right_spaces:n, \tl_trim_right_spaces:V, \tl_trim_right_spaces:v,
+%     \tl_trim_right_spaces:e, \tl_trim_right_spaces:o
 %   }
-% \begin{macro}{\tl_trim_spaces_apply:nN, \tl_trim_spaces_apply:oN}
 % \begin{macro}
 %   {
+%     \tl_trim_spaces_apply:nN, \tl_trim_spaces_apply:oN,
+%     \tl_trim_left_spaces_apply:nN, \tl_trim_left_spaces_apply:oN,
+%     \tl_trim_right_spaces_apply:nN, \tl_trim_right_spaces_apply:oN
+%   }
+% \begin{macro}
+%   {
 %     \tl_trim_spaces:N, \tl_trim_spaces:c,
-%     \tl_gtrim_spaces:N, \tl_gtrim_spaces:c
+%     \tl_trim_left_spaces:N, \tl_trim_left_spaces:c,
+%     \tl_trim_right_spaces:N, \tl_trim_right_spaces:c,
+%     \tl_gtrim_spaces:N, \tl_gtrim_spaces:c,
+%     \tl_gtrim_left_spaces:N, \tl_gtrim_left_spaces:c,
+%     \tl_gtrim_right_spaces:N, \tl_gtrim_right_spaces:c
 %   }
 %   Trimming spaces from around the input is deferred to an internal
-%   function whose first argument is the token list to trim, augmented
-%   by an initial \cs{@@_trim_mark:}, and whose second argument is a
+%   function whose first argument is the token list to trim,
+%   and whose second argument is a
 %   \meta{continuation}, which receives as a braced argument
 %   \cs{@@_trim_mark:} \meta{trimmed token list}.  The control sequence
 %   \cs{@@_trim_mark:} expands to nothing in a single expansion.  In the case
@@ -3033,31 +3100,56 @@
 %   continuation, so that space trimming behaves correctly within an
 %   \texttt{e}-type or \texttt{x}-type expansion.
 %    \begin{macrocode}
-\cs_new:Npn \tl_trim_spaces:n #1
-  {
-    \@@_trim_spaces:nn
-      { \@@_trim_mark: #1 }
-      { \__kernel_exp_not:w \exp_after:wN }
-  }
-\cs_generate_variant:Nn \tl_trim_spaces:n { V , v , e , o }
+\cs_new:Npn \tl_trim_spaces:n
+  { \@@_trim_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_new:Npn \tl_trim_left_spaces:n
+  { \@@_trim_left_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_new:Npn \tl_trim_right_spaces:n
+  { \@@_trim_right_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_generate_variant:Nn \tl_trim_spaces:n       { V , v , e , o }
+\cs_generate_variant:Nn \tl_trim_left_spaces:n  { V , v , e , o }
+\cs_generate_variant:Nn \tl_trim_right_spaces:n { V , v , e , o }
 \cs_new:Npn \tl_trim_spaces_apply:nN #1#2
-  { \@@_trim_spaces:nn { \@@_trim_mark: #1 } { \exp_args:No #2 } }
+  { \@@_trim_spaces:nn { \exp_args:No #2 } { #1 } }
+\cs_new:Npn \tl_trim_left_spaces_apply:nN #1#2
+  { \@@_trim_left_spaces:nn { \exp_args:No #2 } { #1 } }
+\cs_new:Npn \tl_trim_right_spaces_apply:nN #1#2
+  { \@@_trim_right_spaces:nn { \exp_args:No #2 } { #1 } }
 \cs_generate_variant:Nn \tl_trim_spaces_apply:nN { o }
+\cs_generate_variant:Nn \tl_trim_left_spaces_apply:nN { o }
+\cs_generate_variant:Nn \tl_trim_right_spaces_apply:nN { o }
 \cs_new_protected:Npn \tl_trim_spaces:N #1
   { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_trim_left_spaces:N #1
+  { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_left_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_trim_right_spaces:N #1
+  { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_right_spaces:n {#1} } }
 \cs_new_protected:Npn \tl_gtrim_spaces:N #1
   { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_gtrim_left_spaces:N #1
+  { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_left_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_gtrim_right_spaces:N #1
+  { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_right_spaces:n {#1} } }
 \cs_generate_variant:Nn \tl_trim_spaces:N  { c }
+\cs_generate_variant:Nn \tl_trim_left_spaces:N  { c }
+\cs_generate_variant:Nn \tl_trim_right_spaces:N  { c }
 \cs_generate_variant:Nn \tl_gtrim_spaces:N { c }
+\cs_generate_variant:Nn \tl_gtrim_left_spaces:N { c }
+\cs_generate_variant:Nn \tl_gtrim_right_spaces:N { c }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 %
-% \begin{macro}{\@@_trim_spaces:nn}
 % \begin{macro}
 %   {
+%      \@@_trim_spaces:nn,
+%      \@@_trim_left_spaces:nn, \@@_trim_right_spaces:nn
+%   }
+% \begin{macro}
+%   {
 %     \@@_trim_spaces_auxi:w, \@@_trim_spaces_auxii:w,
-%     \@@_trim_spaces_auxiii:w, \@@_trim_spaces_auxiv:w
+%     \@@_trim_spaces_auxiii:w, \@@_trim_spaces_auxiv:w,
+%     \@@_trim_spaces_auxv:w
 %   }
 % \begin{macro}{\@@_trim_mark:}
 %   Trimming spaces from around the input is done using delimited
@@ -3069,50 +3161,72 @@
 %   list: then |##1| is the token list and |##3| is
 %   \cs{@@_trim_spaces_auxii:w}. This hands the relevant tokens to the
 %   loop \cs{@@_trim_spaces_auxiii:w}, responsible for trimming
-%   trailing spaces. The end is reached when \verb*+ + \cs{s_@@_nil}
+%   trailing spaces. The end is reached when \verb*+ +\cs{s_@@_nil}
 %   matches the one present in the definition of \cs{tl_trim_spaces:n}.
 %   Then \cs{@@_trim_spaces_auxiv:w} puts the token list into a group,
 %   with a lingering \cs{@@_trim_mark:} at the start (which will expand to
 %   nothing in one step of expansion), and feeds this to the
 %   \meta{continuation}.
+%
+%   Trimming just leading spaces (see \cs{@@_trim_left_spaces:nn}) also
+%   starts with \cs{@@_trim_spaces_auxi:w}, but when it loops until
+%   \cs{@@_trim_mark:}\verb*+ + matches the end of the token list, |##1|
+%   is the token list \emph{but} |##3| is \cs{@@_trim_spaces_auxv:w}.
+%   Like \cs{@@_trim_spaces_auxiv:w}, this directly hands the relevant
+%   tokens to the exit \meta{continuation}.
+%   Trimming just trailing spaces (see \cs{@@_trim_right_spaces:nn})
+%   starts with the (second) loop \cs{@@_trim_spaces_auxiii:w}.
 %    \begin{macrocode}
 \cs_set_protected:Npn \@@_tmp:w #1
   {
-    \cs_new:Npn \@@_trim_spaces:nn ##1
+    \cs_new:Npn \@@_trim_spaces:nn ##1##2
       {
         \@@_trim_spaces_auxi:w
-          ##1
-          \s_@@_nil
-          \@@_trim_mark: #1 { }
+          \@@_trim_mark: ##2 \s_@@_nil
+          \@@_trim_mark: \@@_trim_spaces_auxi:w
+          \@@_trim_mark: #1
           \@@_trim_mark: \@@_trim_spaces_auxii:w
-          \@@_trim_spaces_auxiii:w
-          #1 \s_@@_nil
-          \@@_trim_spaces_auxiv:w
-        \s_@@_stop
+        {##1}
       }
-    \cs_new:Npn
-        \@@_trim_spaces_auxi:w ##1 \@@_trim_mark: #1 ##2 \@@_trim_mark: ##3
+    \cs_new:Npn \@@_trim_left_spaces:nn ##1##2
       {
-        ##3
         \@@_trim_spaces_auxi:w
-        \@@_trim_mark:
-        ##2
-        \@@_trim_mark: #1 {##1}
+          \@@_trim_mark: ##2 \s_@@_nil
+          \@@_trim_mark: \@@_trim_spaces_auxi:w
+          \@@_trim_mark: #1
+          \@@_trim_mark: \@@_trim_spaces_auxv:w
+        {##1}
       }
+    \cs_new:Npn \@@_trim_right_spaces:nn ##1##2
+      {
+        \@@_trim_spaces_auxiii:w
+          \@@_trim_mark: ##2 \s_@@_nil \@@_trim_spaces_auxiii:w
+          #1 \s_@@_nil \@@_trim_spaces_auxiv:w
+        {##1}
+      }
+    \cs_new:Npn \@@_trim_spaces_auxi:w
+        ##1 \@@_trim_mark: #1 ##2 \@@_trim_mark: ##3
+      { ##3 ##1 \@@_trim_mark: ##2 \@@_trim_mark: \@@_trim_spaces_auxi:w }
     \cs_new:Npn \@@_trim_spaces_auxii:w
-        \@@_trim_spaces_auxi:w \@@_trim_mark: \@@_trim_mark: ##1
+        \@@_trim_mark: ##1 \@@_trim_mark: ##2 \@@_trim_spaces_auxi:w
+        \@@_trim_mark: \@@_trim_mark: \@@_trim_spaces_auxi:w
       {
         \@@_trim_spaces_auxiii:w
-        ##1
+          \@@_trim_mark: ##1 \@@_trim_spaces_auxiii:w
+          #1 \s_@@_nil \@@_trim_spaces_auxiv:w
       }
     \cs_new:Npn \@@_trim_spaces_auxiii:w ##1 #1 \s_@@_nil ##2
-      {
+      { ##2 ##1 \s_@@_nil \@@_trim_spaces_auxiii:w }
+    \cs_new:Npn \@@_trim_spaces_auxiv:w
+        ##1 \s_@@_nil
+        \@@_trim_spaces_auxiii:w \s_@@_nil \@@_trim_spaces_auxiii:w
         ##2
+      { ##2 {##1} }
+    \cs_new:Npn \@@_trim_spaces_auxv:w
         ##1 \s_@@_nil
-        \@@_trim_spaces_auxiii:w
-      }
-    \cs_new:Npn \@@_trim_spaces_auxiv:w ##1 \s_@@_nil ##2 \s_@@_stop ##3
-      { ##3 { ##1 } }
+        \@@_trim_mark: \@@_trim_spaces_auxi:w \@@_trim_mark:
+        \@@_trim_mark: \@@_trim_spaces_auxi:w ##2
+      { ##2 {##1} }
     \cs_new:Npn \@@_trim_mark: {}
   }
 \@@_tmp:w { ~ }
@@ -3129,10 +3243,10 @@
 %
 % \subsection{The first token from a token list}
 %
-% \begin{macro}{\tl_head:N, \tl_head:n, \tl_head:V, \tl_head:v, \tl_head:f}
+% \begin{macro}{\tl_head:N, \tl_head:n, \tl_head:V, \tl_head:v, \tl_head:f, \tl_head:e}
 % \begin{macro}{\@@_head_auxi:nw, \@@_head_auxii:n}
 % \begin{macro}{\tl_head:w,\@@_tl_head:w}
-% \begin{macro}{\tl_tail:N, \tl_tail:n, \tl_tail:V, \tl_tail:v, \tl_tail:f}
+% \begin{macro}{\tl_tail:N, \tl_tail:n, \tl_tail:V, \tl_tail:v, \tl_tail:f, \tl_tail:e}
 %   Finding the head of a token list expandably always strips braces, which
 %   is fine as this is consistent with for example mapping over a list. The
 %   empty brace groups in \cs{tl_head:n} ensure that a blank argument gives an
@@ -3152,7 +3266,7 @@
     \__kernel_exp_not:w {#1}
     \exp_after:wN \use_none:n \exp_after:wN { \if_false: } \fi:
   }
-\cs_generate_variant:Nn \tl_head:n { V , v , f }
+\cs_generate_variant:Nn \tl_head:n { V , v , f , e }
 \cs_new:Npn \tl_head:w #1#2 \q_stop {#1}
 \cs_new:Npn \@@_tl_head:w #1#2 \s_@@_stop {#1}
 \cs_new:Npn \tl_head:N { \exp_args:No \tl_head:n }
@@ -3179,7 +3293,7 @@
         { { } }
         { \exp_after:wN { \use_none:n #1 } }
   }
-\cs_generate_variant:Nn \tl_tail:n { V , v , f }
+\cs_generate_variant:Nn \tl_tail:n { V , v , f , e }
 \cs_new:Npn \tl_tail:N { \exp_args:No \tl_tail:n }
 %    \end{macrocode}
 % \end{macro}

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3token.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %

Modified: trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/source/latex/l3kernel/l3unicode.dtx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -43,7 +43,7 @@
 %    }^^A
 % }
 %
-% \date{Released 2025-01-18}
+% \date{Released 2025-03-26}
 %
 % \maketitle
 %
@@ -485,38 +485,42 @@
 %
 % Notice that in the standard \pkg{expl3} way we are indexes position not
 % offset: that does mean a little work later.
+%
+% For integer values, everything is done using token lists to avoid loosing
+% registers or making global names.
 %    \begin{macrocode}
 \group_begin:
   \clist_map_inline:nn
-    { category , uppercase , lowercase }
+    { category , grapheme , lowercase , uppercase , wordbreak }
     {
       \cs_set_nopar:cpn { l_@@_ #1 _block_clist } { }
       \cs_set_nopar:cpn { l_@@_ #1 _block_tl } { 1 }
       \cs_set_nopar:cpn { l_@@_ #1 _pos_tl } { 0 }
+      \cs_set_nopar:cpn { l_@@_ #1 _next_tl } { 0 }
       \intarray_new:cn { g_@@_ #1 _index_intarray }
         { \int_div_truncate:nn { "110000 } \c_@@_block_size_int }
     }
 %    \end{macrocode}
-%  We need an integer value when matching the current block to those we have
-%  already seen, and a way to track codepoints for handling ranges. Again,
-%  we avoid using up registers or creating global names.
+% We also need to track the matched block: this is used dynamically so we
+% only require one variable.
 %    \begin{macrocode}
-  \cs_set_nopar:Npn \l_@@_next_codepoint_fint_tl { 0 }
   \cs_set_nopar:Npn \l_@@_matched_block_tl { 0 }
 %    \end{macrocode}
-% For Unicode general category, there needs to be numerical representation of
-% each possible value. As we need to go from string to number here, but the
-% other way elsewhere, we set up fast mappings both ways, but one set local
-% and the other as constants.
+% For Unicode general category and the various breaking properties, there needs
+% to be numerical representation of each possible value. As we need to go from
+% string to number here, but the other way elsewhere, we set up fast mappings
+% both ways, but one set local and the other as constants.
 %    \begin{macrocode}
-  \cs_set_protected:Npn \@@_data_auxi:w #1#2
+  \cs_set_nopar:Npn \l_@@_uppercase_default_tl { 0 }
+  \cs_set_nopar:Npn \l_@@_lowercase_default_tl { 0 }
+  \cs_set_protected:Npn \@@_data_auxi:w #1#2#3
     {
-      \quark_if_recursion_tail_stop:n {#2}
-      \cs_set_nopar:cpn { l_@@_category_ #2 _tl } {#1}
-      \str_const:cn { c_@@_category_ \tex_romannumeral:D #1 _str } {#2}
-      \exp_args:Ne \@@_data_auxi:w { \int_eval:n { #1 + 1 } }
+      \quark_if_recursion_tail_stop:n {#3}
+      \cs_set_nopar:cpn { l_@@_ #2 _ #3 _tl } {#1}
+      \str_const:cn { c_@@_ #2 _ \tex_romannumeral:D #1 _str } {#3}
+      \exp_args:Ne \@@_data_auxi:w { \int_eval:n { #1 + 1 } } {#2}
     }
-  \@@_data_auxi:w { 1 }
+  \@@_data_auxi:w { 1 } { category }
     { Lu } { Ll } { Lt } { Lm } { Lo }
     { Mn } { Me } { Mc }
     { Nd } { Nl } { No }
@@ -526,7 +530,88 @@
     { Sm } { Sc } { Sk } { So }
     \q_recursion_tail
     \q_recursion_stop
+  \cs_set_eq:NN \l_@@_category_default_tl \l_@@_category_Cn_tl
+  \@@_data_auxi:w { 1 } { grapheme }
+    { Control }
+    { CR } { LF } { ZWJ }
+    { Extend }
+    { L } { LV } { LVT } { T } { V }
+    { Prepend }
+    { Regional_Indicator }
+    { SpacingMark }
+    { Other }
+    \q_recursion_tail
+    \q_recursion_stop
+  \cs_set_eq:NN \l_@@_grapheme_default_tl \l_@@_grapheme_Other_tl
+  \@@_data_auxi:w { 1 } { wordbreak }
+    { Double_Quote } { Single_Quote }
+    { CR } { LF } { Newline }
+    { WSegSpace } { ZWJ }
+    { Extend } { ExtendNumLet }
+    { Regional_Indicator }
+    { Format }
+    { Katakana }
+    { ALetter } { MidLetter } { Hebrew_Letter }
+    { Numeric }{ MidNum } { MidNumLet }
+    { Other }
+    \q_recursion_tail
+    \q_recursion_stop
+  \cs_set_eq:NN \l_@@_wordbreak_default_tl \l_@@_wordbreak_Other_tl
 %    \end{macrocode}
+%  For the the property fields, we need to use a two-step procedure
+%  as the file is ordered by class not codepoint. First, we parse the content
+%  into the hash table locally: there are around $1400$ codepoints to handle,
+%  which is workable. Reading this is quite easy: we store any end-of-range
+%  codepoint and the class that applies. Conversion to a two-stage table
+%  is deferred to later.
+%    \begin{macrocode}
+  \cs_set_protected:Npn \@@_data_auxi:w #1 ;~ #2 ~ #3 \q_stop
+    { \@@_data_auxii:w #1 .. \q_stop {#2} }
+  \cs_set_protected:Npn \@@_data_auxii:w #1 .. #2 \q_stop
+    { \@@_data_auxiii:w #1 ~ .. #2 ~ \q_stop }
+  \cs_set_protected:Npn \@@_data_auxiii:w #1 ~ #2 .. #3 ~ #4 \q_stop #5#6
+    {
+      \cs_set_nopar:cpe { l_@@_ #6 _ \tex_romannumeral:D "#1 _tl }
+        {
+          {#3}
+          { \use:c { l_@@_ #6 _ #5 _tl } }
+        }
+    }
+  \cs_set_protected:Npn \@@_data_auxvi:w #1#2
+    {
+      \ior_open:Nn \g_@@_data_ior {#1}
+      \ior_str_map_inline:Nn \g_@@_data_ior
+        {
+          \str_if_eq:eeF { \tl_head:w ##1 \c_hash_str \q_stop } { \c_hash_str }
+            {
+              \tl_if_blank:nF {##1}
+                { \@@_data_auxi:w ##1 \q_stop {#2} }
+            }
+        }
+      \ior_close:N \g_@@_data_ior
+    }
+  \@@_data_auxvi:w { GraphemeBreakProperty.txt } { grapheme }
+  \@@_data_auxvi:w { WordBreakProperty.txt } { wordbreak }
+%    \end{macrocode}
+%  The set up for adding property data is now sorted.
+%    \begin{macrocode}
+  \cs_set_protected:Npn \@@_data_property:nnnn #1#2#3#4
+    {
+      \int_compare:nNnT {#3} > { \use:c { l_@@_ #4 _next_tl } }
+        {
+          \@@_range:nnv {#3} {#4}
+            { l_@@_ #4 _default_tl }
+        }
+      \@@_add:nn {#4} {#2}
+      \tl_set:ce { l_@@_ #4 _next_tl } { \int_eval:n { #3 + 1 } }
+      \tl_if_blank:nF {#1}
+        {
+          \@@_range:nnn {"#1} {#4} {#2}
+          \@@_add:nn {#4} {#2}
+          \tl_set:ce { l_@@_ #4 _next_tl } { \int_eval:n { "#1 + 1 } }
+        }
+    }
+%    \end{macrocode}
 % Parse the main Unicode data file and pull out the NFD and case changing
 % data. The NFD data is stored on using the hash table approach and can yield
 % a predictable number of codepoints: one or two. We also need the case data,
@@ -593,7 +678,7 @@
 %    \begin{macrocode}
   \cs_set_protected:Npn \@@_data_auxiv:w #1 ; #2 ; #3 ; #4 ; #5 ; #6 ;
     {
-      \int_compare:nNnT {"#1} > \l_@@_next_codepoint_fint_tl
+      \int_compare:nNnT {"#1} > \l_@@_category_next_tl
         {
           \@@_data_auxv:nnnnw {#1} {#3} {#4} {#5}
             #2 Last> \q_stop
@@ -607,8 +692,26 @@
             { c_@@_titlecase_ \codepoint_str_generate:n {"#1} _tl }
             { {"#6} { } { } }
         }
-      \tl_set:Ne \l_@@_next_codepoint_fint_tl
+      \@@_data_auxvi:nn { grapheme } {"#1}
+      \@@_data_auxvi:nn { wordbreak } {"#1}
+%    \end{macrocode}
+% To deal with the property data, we need to recover stored information and
+% build the table. Generally we can assume that all codepoints in these
+% files are in |UnicodeData.txt|, but there is one place
+% a bit more work is needed. For Hangul syllables, the main file lists a range
+% but within that we have different classifications for breaking: that needs to
+% be handled with a second loop. The values for this range are hard-coded,
+% skipping the end-of-range as that will be mopped up by the main loop.
+%    \begin{macrocode}
+      \int_compare:nNnT {"#1} = { "AC00 }
+        {
+          \int_step_inline:nnn { "AC01 } { "D7A2 }
+            { \@@_data_auxvi:nn { grapheme } {##1} }
+        }
+      \tl_set:Ne \l_@@_category_next_tl
         { \int_eval:n { "#1 + 1 } }
+      \tl_set_eq:NN \l_@@_lowercase_next_tl \l_@@_category_next_tl
+      \tl_set_eq:NN \l_@@_uppercase_next_tl \l_@@_category_next_tl
     }
   \cs_set_protected:Npn \@@_add:nn #1#2
     {
@@ -622,30 +725,47 @@
 %  The general category for unassigned characters is \texttt{Cn}, so we
 %  find the correct value once and then use that.
 %    \begin{macrocode}
-  \cs_set_protected:Npe \@@_data_auxv:nnnnw #1#2#3#4#5 Last> #6 \q_stop
+  \cs_set_protected:Npn \@@_data_auxv:nnnnw #1#2#3#4#5 Last> #6 \q_stop
     {
-      \exp_not:N \tl_if_blank:nTF {#6}
+      \tl_if_blank:nTF {#6}
         {
-          \exp_not:N \@@_range:nnn {#1} { category }
-            { \exp_not:V \l_@@_category_Cn_tl }
-          \exp_not:N \@@_range:nnn {#1} { uppercase } { 0 }
-          \exp_not:N \@@_range:nnn {#1} { lowercase } { 0 }
+          \@@_range:nno {"#1} { category }
+            \l_@@_category_default_tl
+          \@@_range:nnn {"#1} { uppercase } { 0 }
+          \@@_range:nnn {"#1} { lowercase } { 0 }
         }
         {
-          \exp_not:N \@@_range:nnn {#1} { category } {#2}
-          \exp_not:N \@@_range:nnn {#1} { uppercase } {#3}
-          \exp_not:N \@@_range:nnn {#1} { lowercase } {#4}
+          \@@_range:nnn {"#1} { category } {#2}
+          \@@_range:nnn {"#1} { uppercase } {#3}
+          \@@_range:nnn {"#1} { lowercase } {#4}
         }      
     }
 %    \end{macrocode}
+%  Recover and process grapheme data.
+%    \begin{macrocode}
+  \cs_set_protected:Npn \@@_data_auxvi:nn #1#2
+    {
+      \cs_if_exist:cT
+        { l_@@_ #1 _ \tex_romannumeral:D #2 _tl }
+        {
+          \exp_after:wN \exp_after:wN \exp_after:wN \@@_data_property:nnnn
+            \cs:w
+              l_@@_ #1 _ \tex_romannumeral:D #2 _tl
+                \cs_end: {#2} {#1}
+        }
+    }
+%    \end{macrocode}
 %  Calculated the length of the range and the space remaining in the current
 %  block.
 %    \begin{macrocode}
-  \cs_set_protected:Npn \@@_range:nnn #1
+  \cs_set_protected:Npn \@@_range:nnn #1#2
     {
       \exp_args:Nf \@@_range_aux:nnn
-        { \int_eval:n { "#1 - \l_@@_next_codepoint_fint_tl } }
+        { \int_eval:n { #1 - \use:c { l_@@_ #2 _next_tl } } }
+        {#2}
     }
+  \cs_set_protected:Npn \@@_range:nno { \exp_args:Nnno \@@_range:nnn }
+  \cs_set_protected:Npn \@@_range:nnv { \exp_args:Nnnv \@@_range:nnn }
   \cs_set_protected:Npn \@@_range_aux:nnn #1#2
     {
       \exp_args:Nf \@@_range:nnnn
@@ -666,7 +786,7 @@
 %   single codepoint, so the middle step is optimised.
 %    \begin{macrocode}
   \cs_set_protected:Npn \@@_range:nnnn #1#2#3#4
-    {
+    {  
       \prg_replicate:nn {#1}
         { \clist_put_right:cn { l_@@_ #3 _block_clist } {#4} }
     \int_compare:nNnT { \clist_count:c { l_@@_ #3 _block_clist } }
@@ -730,7 +850,7 @@
               \l_@@_matched_block_tl
           }
         \tl_set:ce { l_@@_ #1 _pos_tl }
-          { \int_eval:n { \tl_use:c { l_@@_ #1 _pos_tl } + #2 } } 
+          { \int_eval:n { \tl_use:c { l_@@_ #1 _pos_tl } + #2 } }
       \clist_clear:c { l_@@_ #1 _block_clist }
     }
 %    \end{macrocode}
@@ -738,15 +858,16 @@
 % of the block comma-lists into one large second-stage table with offsets.
 % As we use an index not an offset, there is a little back-and-forward to do.
 %    \begin{macrocode}
-  \cs_set_protected:Npn \@@_finalise_blocks:
+  \cs_set_protected:Npn \@@_finalise_blocks:n #1
     {
-      \clist_map_inline:nn { category , uppercase , lowercase }
+      \clist_map_inline:nn {#1}
         {
-          \@@_range:nnn { 110000 } {##1} { 0 }
-          \@@_finalise_blocks:n {##1}
+          \exp_args:Nnnv \@@_range:nnn { "110000 } {##1}
+            { l_@@_ ##1 _default_tl }
+          \@@_finalise_blocks_aux:n {##1}
         }
     }
-  \cs_set_protected:Npn \@@_finalise_blocks:n #1
+  \cs_set_protected:Npn \@@_finalise_blocks_aux:n #1
     {
       \cs_gset_eq:cc { c_@@_ #1 _index_intarray } { g_@@_ #1 _index_intarray }
       \cs_undefine:c { g_@@_ #1 _index_intarray }
@@ -783,17 +904,22 @@
 %  a token list with spaces retained.
 %    \begin{macrocode}
   \ior_open:Nn \g_@@_data_ior { UnicodeData.txt }
-  \group_begin:
-    \char_set_catcode_space:n { `\  }%
-    \ior_map_variable:NNn \g_@@_data_ior \l_@@_tmpa_tl
-      {%
-        \if_meaning:w \l_@@_tmpa_tl \c_space_tl
-          \exp_after:wN \ior_map_break:
-        \fi:
-        \exp_after:wN \@@_data_auxi:w \l_@@_tmpa_tl \q_stop
-      }%
-    \@@_finalise_blocks:
-  \group_end:
+  \char_set_catcode_space:n { `\  }%
+  \ior_map_variable:NNn \g_@@_data_ior \l_@@_tmpa_tl
+    {%
+      \if_meaning:w \l_@@_tmpa_tl \c_space_tl
+        \exp_after:wN \ior_map_break:
+      \fi:
+      \exp_after:wN \@@_data_auxi:w \l_@@_tmpa_tl \q_stop
+    }%
+  \char_set_catcode_ignore:n { `\  }%
+%    \end{macrocode}
+%  Blocks need to be finalised once they have been read as this relies on
+%  knowing the last real codepoint. So the tables for |UnicodeData.txt|
+%  are closed out before reading additional files.
+%    \begin{macrocode}
+  \@@_finalise_blocks:n
+    { category , grapheme , lowercase , uppercase , wordbreak }
 \group_end:
 %    \end{macrocode}
 %
@@ -961,6 +1087,35 @@
 % \end{macro}
 % \end{macro}
 %
+% \begin{macro}[EXP]
+%   {
+%     \__kernel_codepoint_to_grapheme_class:n,
+%     \__kernel_codepoint_to_wordbreak_class:n
+%   }
+%   Get the value and convert back to the string: not currently
+%   public but otherwise very similar to \cs{codepoint_to_category:n}.
+%    \begin{macrocode}
+\cs_new:Npn \__kernel_codepoint_to_grapheme_class:n #1
+  {
+    \cs:w
+      c_@@_grapheme_
+      \tex_romannumeral:D 
+        \__kernel_codepoint_data:nn { grapheme } {#1}
+      _str
+    \cs_end:
+  }
+\cs_new:Npn \__kernel_codepoint_to_wordbreak_class:n #1
+  {
+    \cs:w
+      c_@@_wordbreak_
+      \tex_romannumeral:D 
+        \__kernel_codepoint_data:nn { wordbreak } {#1}
+      _str
+    \cs_end:
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{macro}[EXP]{\@@_nfd:n}
 % \begin{macro}[EXP]{\@@_nfd:nn}
 %   A simple interface.
@@ -978,54 +1133,6 @@
 % \end{macro}
 %
 %    \begin{macrocode}
-%<@@=text>
-%    \end{macrocode}
-%
-%  Read the Unicode grapheme data. This is quite easy to handle and we only need
-%  codepoints, not characters, so there is no need to worry about the engine in use.
-%  As reading as a string is most convenient, we have to do some work to remove
-%  spaces: the hardest part of the entire process!
-%    \begin{macrocode}
-\ior_new:N \g_@@_data_ior
-\group_begin:
-  \ior_open:Nn \g_@@_data_ior { GraphemeBreakProperty.txt }
-  \cs_set_nopar:Npn \l_@@_tmpa_str { }
-  \cs_set_nopar:Npn \l_@@_tmpb_str { }
-  \cs_set_protected:Npn \@@_data_auxi:w #1 ;~ #2 ~ #3 \q_stop
-    {
-      \str_if_eq:VnF \l_@@_tmpb_str {#2}
-        {
-          \str_if_empty:NF \l_@@_tmpb_str
-            {
-              \clist_const:ce { c_@@_grapheme_ \l_@@_tmpb_str _clist }
-                { \exp_after:wN \use_none:n \l_@@_tmpa_str }
-              \cs_set_nopar:Npn \l_@@_tmpa_str { }
-            }
-          \cs_set_nopar:Npn \l_@@_tmpb_str {#2}
-        }
-      \@@_data_auxii:w #1 .. #1 .. #1 \q_stop
-    }
-  \cs_set_protected:Npn \@@_data_auxii:w #1 .. #2 .. #3 \q_stop
-    {
-      \cs_set_nopar:Npe \l_@@_tmpa_str
-        {
-          \l_@@_tmpa_str ,
-          \tl_trim_spaces:n {#1} .. \tl_trim_spaces:n {#2}
-        }
-    }
-  \ior_str_map_inline:Nn \g_@@_data_ior
-    {
-      \str_if_eq:eeF { \tl_head:w #1 \c_hash_str \q_stop } { \c_hash_str }
-        {
-          \tl_if_blank:nF {#1}
-            { \@@_data_auxi:w #1 \q_stop }
-        }
-    }
-  \ior_close:N \g_@@_data_ior
-\group_end:    
-%    \end{macrocode}
-%
-%    \begin{macrocode}
 %</package>
 %    \end{macrocode}
 %

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-code.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -51,6 +51,7 @@
 %% l3regex.dtx  (with options: `package')
 %% l3box.dtx  (with options: `package')
 %% l3color.dtx  (with options: `package')
+%% l3graphics.dtx  (with options: `package')
 %% l3pdf.dtx  (with options: `package,tex')
 %% l3coffins.dtx  (with options: `package')
 %% l3luatex.dtx  (with options: `package,tex')
@@ -75,7 +76,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2025-01-18}%
+\def\ExplFileDate{2025-03-26}%
 \begingroup
   \def\next{\endgroup}%
   \expandafter\ifx\csname PackageError\endcsname\relax
@@ -720,6 +721,7 @@
   \__kernel_primitive:NN \pdfpageref            \tex_pdfpageref:D
   \__kernel_primitive:NN \pdfpageresources      \tex_pdfpageresources:D
   \__kernel_primitive:NN \pdfpagesattr          \tex_pdfpagesattr:D
+  \__kernel_primitive:NN \pdfptexuseunderscore  \tex_pdfptexuseunderscore:D
   \__kernel_primitive:NN \pdfrefobj             \tex_pdfrefobj:D
   \__kernel_primitive:NN \pdfrefxform           \tex_pdfrefxform:D
   \__kernel_primitive:NN \pdfrefximage          \tex_pdfrefximage:D
@@ -986,6 +988,7 @@
   \__kernel_primitive:NN \mathdisplayskipmode   \tex_mathdisplayskipmode:D
   \__kernel_primitive:NN \matheqdirmode         \tex_matheqdirmode:D
   \__kernel_primitive:NN \matheqnogapstep       \tex_matheqnogapstep:D
+  \__kernel_primitive:NN \mathemptydisplaymode  \tex_mathemptydisplaymode:D
   \__kernel_primitive:NN \mathflattenmode       \tex_mathflattenmode:D
   \__kernel_primitive:NN \mathitalicsmode       \tex_mathitalicsmode:D
   \__kernel_primitive:NN \mathnolimitsmode      \tex_mathnolimitsmode:D
@@ -1326,6 +1329,7 @@
   \__kernel_primitive:NN \omathchardef          \tex_omathchardef:D
   \__kernel_primitive:NN \omathcode             \tex_omathcode:D
   \__kernel_primitive:NN \oradical              \tex_oradical:D
+  \__kernel_primitive:NN \ignoreprimitiveerror  \tex_ignoreprimitiveerror:D
   \__kernel_primitive:NN \partokencontext       \tex_partokencontext:D
   \__kernel_primitive:NN \partokenname          \tex_partokenname:D
   \__kernel_primitive:NN \showstream            \tex_showstream:D
@@ -1670,7 +1674,7 @@
 \cs_gset_protected:Npn \prg_set_conditional:Nnn
   { \__prg_generate_conditional_count:NNNnn \cs_set:Npn e }
 \cs_gset_protected:Npn \prg_gset_conditional:Nnn
-  { \__prg_generate_conditional_count:NNNnn \cs_set:Npn e }
+  { \__prg_generate_conditional_count:NNNnn \cs_gset:Npn e }
 \cs_gset_protected:Npn \prg_new_conditional:Nnn
   { \__prg_generate_conditional_count:NNNnn \cs_new:Npn e }
 \cs_gset_protected:Npn \prg_set_protected_conditional:Nnn
@@ -4078,6 +4082,7 @@
       \s__tl_stop \s__tl_stop \s__tl_stop \s__tl_stop
     \prg_break_point:Nn \tl_map_break: { }
   }
+\cs_generate_variant:Nn \tl_map_function:nN { e }
 \cs_new:Npn \tl_map_function:NN
   { \exp_args:No \tl_map_function:nN }
 \cs_generate_variant:Nn \tl_map_function:NN { c }
@@ -4201,59 +4206,92 @@
   }
 \cs_new:Npn \__tl_reverse_items:wn #1 \s__tl_stop #2
   { \__kernel_exp_not:w \exp_after:wN { \use_none:nn #2 } }
-\cs_new:Npn \tl_trim_spaces:n #1
-  {
-    \__tl_trim_spaces:nn
-      { \__tl_trim_mark: #1 }
-      { \__kernel_exp_not:w \exp_after:wN }
-  }
-\cs_generate_variant:Nn \tl_trim_spaces:n { V , v , e , o }
+\cs_new:Npn \tl_trim_spaces:n
+  { \__tl_trim_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_new:Npn \tl_trim_left_spaces:n
+  { \__tl_trim_left_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_new:Npn \tl_trim_right_spaces:n
+  { \__tl_trim_right_spaces:nn { \__kernel_exp_not:w \exp_after:wN } }
+\cs_generate_variant:Nn \tl_trim_spaces:n       { V , v , e , o }
+\cs_generate_variant:Nn \tl_trim_left_spaces:n  { V , v , e , o }
+\cs_generate_variant:Nn \tl_trim_right_spaces:n { V , v , e , o }
 \cs_new:Npn \tl_trim_spaces_apply:nN #1#2
-  { \__tl_trim_spaces:nn { \__tl_trim_mark: #1 } { \exp_args:No #2 } }
+  { \__tl_trim_spaces:nn { \exp_args:No #2 } { #1 } }
+\cs_new:Npn \tl_trim_left_spaces_apply:nN #1#2
+  { \__tl_trim_left_spaces:nn { \exp_args:No #2 } { #1 } }
+\cs_new:Npn \tl_trim_right_spaces_apply:nN #1#2
+  { \__tl_trim_right_spaces:nn { \exp_args:No #2 } { #1 } }
 \cs_generate_variant:Nn \tl_trim_spaces_apply:nN { o }
+\cs_generate_variant:Nn \tl_trim_left_spaces_apply:nN { o }
+\cs_generate_variant:Nn \tl_trim_right_spaces_apply:nN { o }
 \cs_new_protected:Npn \tl_trim_spaces:N #1
   { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_trim_left_spaces:N #1
+  { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_left_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_trim_right_spaces:N #1
+  { \__kernel_tl_set:Nx #1 { \exp_args:No \tl_trim_right_spaces:n {#1} } }
 \cs_new_protected:Npn \tl_gtrim_spaces:N #1
   { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_gtrim_left_spaces:N #1
+  { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_left_spaces:n {#1} } }
+\cs_new_protected:Npn \tl_gtrim_right_spaces:N #1
+  { \__kernel_tl_gset:Nx #1 { \exp_args:No \tl_trim_right_spaces:n {#1} } }
 \cs_generate_variant:Nn \tl_trim_spaces:N  { c }
+\cs_generate_variant:Nn \tl_trim_left_spaces:N  { c }
+\cs_generate_variant:Nn \tl_trim_right_spaces:N  { c }
 \cs_generate_variant:Nn \tl_gtrim_spaces:N { c }
+\cs_generate_variant:Nn \tl_gtrim_left_spaces:N { c }
+\cs_generate_variant:Nn \tl_gtrim_right_spaces:N { c }
 \cs_set_protected:Npn \__tl_tmp:w #1
   {
-    \cs_new:Npn \__tl_trim_spaces:nn ##1
+    \cs_new:Npn \__tl_trim_spaces:nn ##1##2
       {
         \__tl_trim_spaces_auxi:w
-          ##1
-          \s__tl_nil
-          \__tl_trim_mark: #1 { }
+          \__tl_trim_mark: ##2 \s__tl_nil
+          \__tl_trim_mark: \__tl_trim_spaces_auxi:w
+          \__tl_trim_mark: #1
           \__tl_trim_mark: \__tl_trim_spaces_auxii:w
-          \__tl_trim_spaces_auxiii:w
-          #1 \s__tl_nil
-          \__tl_trim_spaces_auxiv:w
-        \s__tl_stop
+        {##1}
       }
-    \cs_new:Npn
-        \__tl_trim_spaces_auxi:w ##1 \__tl_trim_mark: #1 ##2 \__tl_trim_mark: ##3
+    \cs_new:Npn \__tl_trim_left_spaces:nn ##1##2
       {
-        ##3
         \__tl_trim_spaces_auxi:w
-        \__tl_trim_mark:
-        ##2
-        \__tl_trim_mark: #1 {##1}
+          \__tl_trim_mark: ##2 \s__tl_nil
+          \__tl_trim_mark: \__tl_trim_spaces_auxi:w
+          \__tl_trim_mark: #1
+          \__tl_trim_mark: \__tl_trim_spaces_auxv:w
+        {##1}
       }
+    \cs_new:Npn \__tl_trim_right_spaces:nn ##1##2
+      {
+        \__tl_trim_spaces_auxiii:w
+          \__tl_trim_mark: ##2 \s__tl_nil \__tl_trim_spaces_auxiii:w
+          #1 \s__tl_nil \__tl_trim_spaces_auxiv:w
+        {##1}
+      }
+    \cs_new:Npn \__tl_trim_spaces_auxi:w
+        ##1 \__tl_trim_mark: #1 ##2 \__tl_trim_mark: ##3
+      { ##3 ##1 \__tl_trim_mark: ##2 \__tl_trim_mark: \__tl_trim_spaces_auxi:w }
     \cs_new:Npn \__tl_trim_spaces_auxii:w
-        \__tl_trim_spaces_auxi:w \__tl_trim_mark: \__tl_trim_mark: ##1
+        \__tl_trim_mark: ##1 \__tl_trim_mark: ##2 \__tl_trim_spaces_auxi:w
+        \__tl_trim_mark: \__tl_trim_mark: \__tl_trim_spaces_auxi:w
       {
         \__tl_trim_spaces_auxiii:w
-        ##1
+          \__tl_trim_mark: ##1 \__tl_trim_spaces_auxiii:w
+          #1 \s__tl_nil \__tl_trim_spaces_auxiv:w
       }
     \cs_new:Npn \__tl_trim_spaces_auxiii:w ##1 #1 \s__tl_nil ##2
-      {
+      { ##2 ##1 \s__tl_nil \__tl_trim_spaces_auxiii:w }
+    \cs_new:Npn \__tl_trim_spaces_auxiv:w
+        ##1 \s__tl_nil
+        \__tl_trim_spaces_auxiii:w \s__tl_nil \__tl_trim_spaces_auxiii:w
         ##2
+      { ##2 {##1} }
+    \cs_new:Npn \__tl_trim_spaces_auxv:w
         ##1 \s__tl_nil
-        \__tl_trim_spaces_auxiii:w
-      }
-    \cs_new:Npn \__tl_trim_spaces_auxiv:w ##1 \s__tl_nil ##2 \s__tl_stop ##3
-      { ##3 { ##1 } }
+        \__tl_trim_mark: \__tl_trim_spaces_auxi:w \__tl_trim_mark:
+        \__tl_trim_mark: \__tl_trim_spaces_auxi:w ##2
+      { ##2 {##1} }
     \cs_new:Npn \__tl_trim_mark: {}
   }
 \__tl_tmp:w { ~ }
@@ -4267,7 +4305,7 @@
     \__kernel_exp_not:w {#1}
     \exp_after:wN \use_none:n \exp_after:wN { \if_false: } \fi:
   }
-\cs_generate_variant:Nn \tl_head:n { V , v , f }
+\cs_generate_variant:Nn \tl_head:n { V , v , f , e }
 \cs_new:Npn \tl_head:w #1#2 \q_stop {#1}
 \cs_new:Npn \__tl_tl_head:w #1#2 \s__tl_stop {#1}
 \cs_new:Npn \tl_head:N { \exp_args:No \tl_head:n }
@@ -4278,7 +4316,7 @@
         { { } }
         { \exp_after:wN { \use_none:n #1 } }
   }
-\cs_generate_variant:Nn \tl_tail:n { V , v , f }
+\cs_generate_variant:Nn \tl_tail:n { V , v , f , e }
 \cs_new:Npn \tl_tail:N { \exp_args:No \tl_tail:n }
 \prg_new_conditional:Npnn \tl_if_head_eq_charcode:nN #1#2 { p , T , F , TF }
   {
@@ -4987,9 +5025,9 @@
   }
 \cs_new:Npn \__str_case:nnTF #1#2#3#4
   { \__str_case:nw {#1} #2 {#1} { } \s__str_mark {#3} \s__str_mark {#4} \s__str_stop }
-\cs_generate_variant:Nn \str_case:nn   { V , o , e , nV , nv }
+\cs_generate_variant:Nn \str_case:nn   { V , o , e , nV , nv , ne }
 \prg_generate_conditional_variant:Nnn \str_case:nn
-  { V , o , e , nV , nv } { T , F , TF }
+  { V , o , e , nV , nv , ne } { T , F , TF }
 \cs_new_eq:NN \str_case:Nn   \str_case:Vn
 \cs_new_eq:NN \str_case:NnT  \str_case:VnT
 \cs_new_eq:NN \str_case:NnF  \str_case:VnF
@@ -14315,7 +14353,11 @@
     \prop_get:NVNF #1 \l__keys_module_str \l__keys_tmpa_tl
       { \tl_clear:N \l__keys_tmpa_tl }
     \tl_set:Ne \l__keys_tmpb_tl
-      { \exp_after:wN \__keys_usage:w \l_keys_path_str \s__keys_stop }
+      {
+        \exp_after:wN \exp_after:wN \exp_after:wN \__keys_usage:w \exp_after:wN
+          \l_keys_path_str \exp_after:wN / \exp_after:wN \s__keys_stop
+          \exp_after:wN { \l_keys_path_str }
+      }
     \bool_if:NTF #2
       { \clist_put_right:NV \l__keys_tmpa_tl \l__keys_tmpb_tl }
       { \clist_remove_all:NV \l__keys_tmpa_tl \l__keys_tmpb_tl }
@@ -14322,7 +14364,13 @@
     \prop_put:NVV #1 \l__keys_module_str
       \l__keys_tmpa_tl
   }
-\cs_new:Npn \__keys_usage:w #1 / #2 \s__keys_stop {#2}
+\cs_new:Npn \__keys_usage:w #1 / #2 \s__keys_stop #3
+  {
+    \tl_if_blank:nTF {#2}
+     {#1}
+     { \__keys_usage_aux:w #3 \s__keys_stop }
+  }
+\cs_new:Npn \__keys_usage_aux:w #1 / #2 \s__keys_stop {#2}
 \cs_new_protected:Npn \__keys_variable_set:NnnN #1#2#3#4
   {
     \use:c { #2_if_exist:NF } #1 { \use:c { #2 _new:N } #1 }
@@ -14977,20 +15025,40 @@
   }
 \prg_new_conditional:Npnn \keys_if_exist:nn #1#2 { p , T , F , TF }
   {
+    \__keys_if_exist:ee
+      { \__keys_trim_spaces:n {#1} }
+      { \__keys_trim_spaces:n {#2} }
+  }
+\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
+\cs_new:Npn \__keys_if_exist:nn #1#2
+  {
     \cs_if_exist:cTF
-      { \c__keys_code_root_str \__keys_trim_spaces:n { #1 / #2 } }
+      { \c__keys_code_root_str #1 \tl_if_blank:nF {#1} { / } #2 }
       { \prg_return_true: }
       { \prg_return_false: }
   }
-\prg_generate_conditional_variant:Nnn \keys_if_exist:nn { ne } { p , T , F , TF }
+\cs_generate_variant:Nn \__keys_if_exist:nn { ee }
 \prg_new_conditional:Npnn \keys_if_choice_exist:nnn #1#2#3
   { p , T , F , TF }
   {
+    \__keys_if_exist:eee
+      { \__keys_trim_spaces:n {#1} }
+      { \__keys_trim_spaces:n {#2} }
+      { \__keys_trim_spaces:n {#3} }
+  }
+\cs_new:Npn \__keys_if_exist:nnn #1#2#3
+  {
     \cs_if_exist:cTF
-      { \c__keys_code_root_str \__keys_trim_spaces:n { #1 / #2 / #3 } }
+      {
+        \c__keys_code_root_str
+          #1 \tl_if_blank:nF {#1} { / }
+          #2 \tl_if_blank:nF {#2} { / }
+          #3
+      }
       { \prg_return_true: }
       { \prg_return_false: }
   }
+\cs_generate_variant:Nn \__keys_if_exist:nnn { eee }
 \cs_new_protected:Npn \keys_show:nn
   { \__keys_show:Nnn \msg_show:nneeee }
 \cs_new_protected:Npn \keys_log:nn
@@ -14997,8 +15065,15 @@
   { \__keys_show:Nnn \msg_log:nneeee }
 \cs_new_protected:Npn \__keys_show:Nnn #1#2#3
   {
+    \__keys_show_aux:Nee
+      #1
+      { \__keys_trim_spaces:n {#2} }
+      { \__keys_trim_spaces:n {#3} }
+  }
+\cs_new_protected:Npn \__keys_show_aux:Nnn #1#2#3
+  {
     #1 { keys } { show-key }
-      { \__keys_trim_spaces:n { #2 / #3 } }
+      { #2 \tl_if_blank:nF {#2} { / } #3 }
       {
         \keys_if_exist:nnT {#2} {#3}
           {
@@ -15009,7 +15084,7 @@
                     \exp_args:Nc \cs_replacement_spec:N
                     {
                       \c__keys_code_root_str
-                      \__keys_trim_spaces:n { #2 / #3 }
+                      #2 \tl_if_blank:nF {#2} { / } #3
                     }
                   }
               }
@@ -15017,6 +15092,7 @@
       }
       { } { }
   }
+\cs_generate_variant:Nn \__keys_show_aux:Nnn { Nee }
 \cs_new:Npe \__keys_show:n #1
   {
     \exp_not:N \__keys_show:w
@@ -21744,7 +21820,7 @@
       { \__fp_invalid_operation_o:Nww #2 }
       {#1}
   }
-%% File l3fp-symbolic.dtx (C) Copyright 2012-2015,2017,2018,2020,2021,2023 The LaTeX Project
+%% File l3fp-symbolic.dtx (C) Copyright 2012-2025 The LaTeX Project
 \fp_new:N \l__fp_symbolic_fp
 \scan_new:N \s__fp_symbolic
 \cs_new_protected:Npn \__fp_symbolic_chk:w #1,#2#3\__fp_sep:
@@ -21821,8 +21897,8 @@
   }
 \tl_map_inline:nn
   {
-    {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {exp} {ln}
-    {not} {sec} {set_sign} {sin} {sqrt} {tan}
+    {acos} {acsc} {asec} {asin} {cos} {cot} {csc} {exp} {fact} {ln}
+    {not} {sec} {set_sign} {sin} {sign} {sqrt} {tan}
   }
   {
     \cs_new:cpe { __fp_symbolic_#1_o:w }
@@ -22008,12 +22084,12 @@
       { \msg_error:nnn { fp } { id-invalid } {#1} }
       {
         \cs_if_exist:cT { __fp_parse_word_#1:N }
-        {
-          \msg_error:nnn
-            { fp } { id-already-defined } {#1}
-          \cs_undefine:c { __fp_parse_word_#1:N }
-          \cs_set_eq:cN { l__fp_variable_#1_fp } \tex_undefined:D
-        }
+          {
+            \msg_error:nnn
+              { fp } { id-already-defined } {#1}
+            \cs_undefine:c { __fp_parse_word_#1:N }
+            \cs_set_eq:cN { l__fp_variable_#1_fp } \tex_undefined:D
+          }
       \__fp_variable_set_parsing:Nn \cs_gset_eq:NN {#1}
       }
   }
@@ -22027,7 +22103,11 @@
     \__fp_id_if_invalid:nTF {#1}
       { \msg_error:nnn { fp } { id-invalid } {#1} }
       {
-        \__fp_variable_set_parsing:Nn \cs_set_eq:NN {#1}
+        \cs_if_exist:cF { __fp_parse_word_#1:N }
+          {
+            \msg_error:nnn {fp} { id-undefined } {#1}
+            \__fp_variable_set_parsing:Nn \cs_set_eq:NN {#1}
+          }
         \fp_set:Nn \l__fp_symbolic_fp {#2}
         \cs_set_nopar:cpn { l__fp_variable_#1_fp }
           { \flag_ensure_raised:N \l__fp_symbolic_flag \c_nan_fp }
@@ -22054,6 +22134,12 @@
     LaTeX~has~been~asked~to~create~a~new~floating~point~identifier~'#1'~
     but~this~name~has~already~been~used~elsewhere.
   }
+\msg_new:nnnn { fp } { id-undefined }
+  { Floating~point~identifier~'#1'~is~undefined. }
+  {
+    LaTeX~has~been~asked~to~set~a~floating~point~identifier~'#1'~
+    but~this~name~has~not~been~declared.
+  }
 \msg_new:nnnn { fp } { id-used-elsewhere }
   { Floating~point~identifier~'#1'~already~used~for~something~else. }
   {
@@ -22142,7 +22228,10 @@
       { \msg_error:nnn { fp } { id-invalid } {#2} }
       {
         \cs_if_exist:cF { __fp_parse_word_#2:N }
-          { \__fp_function_set_parsing:Nn \cs_set_eq:NN {#2} }
+          {
+            \msg_error:nnn {fp} { id-undefined } {#2}
+            \__fp_function_set_parsing:Nn \cs_set_eq:NN {#2}
+          }
         \group_begin:
           \int_zero:N \l__fp_function_arg_int
           \exp_args:No \clist_map_inline:nn { \tl_to_str:n {#3} }
@@ -32048,6 +32137,385 @@
       { is~undefined. }
       { has~the~properties: #2 }
   }
+%% File: l3graphics.dtx
+\cs_if_exist:NT \@expl at finalise@setup@@
+  {
+    \tl_gput_right:Nn \@expl at finalise@setup@@
+      { \declare at file@substitution { l3graphics.sty } { null.tex } }
+  }
+\dim_new:N \l__graphics_internal_dim
+\ior_new:N \l__graphics_internal_ior
+\tl_new:N  \l__graphics_internal_tl
+\scan_new:N \s__graphics_stop
+\tl_new:N \l__graphics_pagebox_tl
+\keys_define:nn { graphics }
+  {
+    decodearray .str_set:N =
+      \l__graphics_decodearray_str ,
+    draft .bool_set:N =
+      \l__graphics_draft_bool ,
+    interpolate .bool_set:N =
+      \l__graphics_interpolate_bool ,
+    pagebox .choices:nn =
+      { art , bleed , crop , media , trim }
+      {
+        \tl_set:Ne \l__graphics_pagebox_tl
+          { \l_keys_choice_tl box }
+      } ,
+    pagebox .initial:n =
+      crop ,
+    page .int_set:N =
+      \l__graphics_page_int ,
+    pdf-attr .str_set:N =
+      \l__graphics_pdf_str ,
+    type . str_set:N =
+      \l__graphics_type_str
+  }
+\dim_new:N \l__graphics_llx_dim
+\dim_new:N \l__graphics_lly_dim
+\dim_new:N \l__graphics_urx_dim
+\dim_new:N \l__graphics_ury_dim
+\cs_new_protected:Npn \__graphics_bb_save:n #1
+  {
+    \dim_if_exist:cTF { c__graphics_ #1 _urx_dim }
+      { \msg_error:nnn { graphic } { bb-already-cached } {#1} }
+      {
+        \dim_compare:nNnF \l__graphics_llx_dim = { 0pt }
+          { \dim_const:cn { c__graphics_ #1 _llx_dim } { \l__graphics_llx_dim } }
+        \dim_compare:nNnF \l__graphics_lly_dim = { 0pt }
+          { \dim_const:cn { c__graphics_ #1 _lly_dim } { \l__graphics_lly_dim } }
+        \dim_const:cn { c__graphics_ #1 _urx_dim } { \l__graphics_urx_dim }
+        \dim_const:cn { c__graphics_ #1 _ury_dim } { \l__graphics_ury_dim }
+      }
+  }
+\cs_generate_variant:Nn \__graphics_bb_save:n { e }
+\cs_new_protected:Npn \__graphics_bb_restore:nF #1#2
+  {
+    \dim_if_exist:cTF { c__graphics_ #1 _urx_dim }
+      {
+        \dim_set_eq:Nc \l__graphics_urx_dim { c__graphics_ #1 _urx_dim }
+        \dim_set_eq:Nc \l__graphics_ury_dim { c__graphics_ #1 _ury_dim }
+        \dim_if_exist:cTF { c__graphics_ #1 _llx_dim }
+          { \dim_set_eq:Nc \l__graphics_llx_dim { c__graphics_ #1 _llx_dim } }
+          { \dim_zero:N \l__graphics_llx_dim }
+        \dim_if_exist:cTF { c__graphics_ #1 _lly_dim }
+          { \dim_set_eq:Nc \l__graphics_lly_dim { c__graphics_ #1 _lly_dim } }
+          { \dim_zero:N \l__graphics_lly_dim }
+      }
+      {#2}
+  }
+\cs_generate_variant:Nn \__graphics_bb_restore:nF { e }
+\cs_new_protected:Npn \__graphics_extract_bb:n #1
+  {
+    \int_compare:nNnTF \l__graphics_page_int > 0
+      { \__graphics_extract_bb_auxi:Vn \l__graphics_page_int {#1} }
+      { \__graphics_extract_bb_auxii:nnn {#1} { } { } }
+  }
+\cs_new_protected:Npn \__graphics_extract_bb_auxi:nn #1#2
+  { \__graphics_extract_bb_auxii:nnn {#2} { :P #1 } { -p~#1~ } }
+\cs_generate_variant:Nn \__graphics_extract_bb_auxi:nn { Vn }
+\cs_new_protected:Npn \__graphics_extract_bb_auxii:nnn #1#2#3
+  {
+    \tl_if_empty:NTF \l__graphics_pagebox_tl
+      { \__graphics_extract_bb_auxiv:nnn }
+      { \__graphics_extract_bb_auxiii:Vnnn \l__graphics_pagebox_tl }
+      {#1} {#2} {#3}
+  }
+\cs_new_protected:Npn \__graphics_extract_bb_auxiii:nnnn #1#2#3#4
+  { \__graphics_extract_bb_auxiv:nnn {#2} { : #1 #3 } { #4 -B~#1~ } }
+\cs_generate_variant:Nn \__graphics_extract_bb_auxiii:nnnn { V }
+\cs_new_protected:Npn \__graphics_extract_bb_auxiv:nnn #1#2#3
+  {
+    \__graphics_read_bb_auxi:nnnn {#1} {#2}
+      { \ior_shell_open:Nn \l__graphics_internal_ior { extractbb~#3-O~#1 } }
+      { pipe-failed }
+  }
+\cs_new_protected:Npn \__graphics_read_bb:n #1
+  {
+    \__graphics_read_bb_auxi:nnnn {#1} { }
+      { \ior_open:Nn \l__graphics_internal_ior {#1} }
+      { graphic-not-found }
+  }
+\cs_new_protected:Npn \__graphics_read_bb_auxi:nnnn #1#2#3#4
+  {
+    \__graphics_bb_restore:nF {#1#2}
+      { \__graphics_read_bb_auxii:nnnn {#3} {#4} {#1} {#2} }
+  }
+\cs_new_protected:Npe \__graphics_read_bb_auxii:nnnn #1#2#3#4
+  {
+    #1
+    \exp_not:N \ior_if_eof:NTF \exp_not:N \l__graphics_internal_ior
+      { \msg_error:nnn { graphics } {#2} {#3} }
+      {
+        \ior_str_map_inline:Nn \exp_not:N \l__graphics_internal_ior
+          {
+            \exp_not:N \__graphics_read_bb_auxiii:w
+              ##1 ~ \c_colon_str \s__graphics_stop
+          }
+        \__graphics_bb_save:n {#3#4}
+      }
+    \ior_close:N \exp_not:N \l__graphics_internal_ior
+  }
+\use:e
+  {
+    \cs_new_protected:Npn \exp_not:N \__graphics_read_bb_auxiii:w
+      #1 \c_colon_str #2 \s__graphics_stop
+      {
+        \exp_not:N \str_if_eq:nnT
+          { \c_percent_str \c_percent_str BoundingBox }
+          {#1}
+          { \exp_not:N \__graphics_read_bb_auxiv:w #2 ( ) \s__graphics_stop }
+      }
+  }
+\cs_new_protected:Npn \__graphics_read_bb_auxiv:w #1 ( #2 ) #3 \s__graphics_stop
+  {
+    \str_if_eq:nnF {#2} { atend }
+      {
+        \tl_set_rescan:Nne \l__graphics_internal_tl
+          {
+            \char_set_catcode_space:n {  9 }
+            \char_set_catcode_space:n { 32 }
+          }
+          { \use:n #1 }
+        \exp_after:wN \__graphics_read_bb_auxv:w \l__graphics_internal_tl \s__graphics_stop
+      }
+  }
+\cs_new_protected:Npn \__graphics_read_bb_auxv:w #1~#2~#3~#4~#5 \s__graphics_stop
+  {
+    \dim_set:Nn \l__graphics_llx_dim { #1 bp }
+    \dim_set:Nn \l__graphics_lly_dim { #2 bp }
+    \dim_set:Nn \l__graphics_urx_dim { #3 bp }
+    \dim_set:Nn \l__graphics_ury_dim { #4 bp }
+    \ior_map_break:
+  }
+\str_new:N \l__graphics_final_name_str
+\str_new:N \l__graphics_full_name_str
+\box_new:N \l__graphics_internal_box
+\str_new:N \l__graphics_dir_str
+\str_new:N \l__graphics_name_str
+\str_new:N \l__graphics_ext_str
+\seq_new:N \l_graphics_search_path_seq
+\seq_new:N \l_graphics_search_ext_seq
+\prop_new:N \l_graphics_ext_type_prop
+\prop_put:Nnn \l_graphics_ext_type_prop { .ps } { eps }
+\seq_new:N \g__graphics_record_seq
+\cs_new_protected:Npn \graphics_include:nn #1#2
+  {
+    \group_begin:
+      \keys_set:nn { graphics } {#1}
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#2} \l__graphics_full_name_str
+        {
+          \str_if_eq:eeTF { \l__graphics_full_name_str } { #2 .tex }
+            { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
+            { \__graphics_include: }
+        }
+        { \msg_error:nnn { graphics } { graphic-not-found } {#2} }
+    \group_end:
+  }
+\cs_generate_variant:Nn \graphics_include:nn { nV }
+\cs_new_protected:Npn \__graphics_include:
+  {
+    \str_if_empty:NTF \l__graphics_type_str
+      {
+        \file_parse_full_name:VNNN \l__graphics_full_name_str
+          \l__graphics_dir_str \l__graphics_name_str \l__graphics_ext_str
+        \__graphics_include_auxi:e
+          {
+            \exp_args:Ne \str_tail:n
+              { \str_casefold:V \l__graphics_ext_str }
+          }
+      }
+      { \__graphics_include_auxi:e { \l__graphics_type_str } }
+  }
+\cs_new_protected:Npn \__graphics_include_auxi:n #1
+  {
+    \prop_get:NnNF \l_graphics_ext_type_prop { .#1 } \l__graphics_internal_tl
+      { \tl_set:Nn \l__graphics_internal_tl {#1} }
+    \exp_args:NV \__graphics_include_auxii:n \l__graphics_internal_tl
+  }
+\cs_generate_variant:Nn \__graphics_include_auxi:n { e }
+\cs_new_protected:Npn \__graphics_include_auxii:n #1
+  {
+    \mode_leave_vertical:
+    \cs_if_exist:cTF { __graphics_backend_include_ #1 :n }
+      {
+        \tl_set_eq:NN \l__graphics_final_name_str \l__graphics_full_name_str
+        \str_set:Ne \l__graphics_full_name_str
+          { \exp_args:NV \__kernel_file_name_quote:n \l__graphics_full_name_str }
+        \exp_args:NnV \use:c { __graphics_backend_getbb_ #1 :n }
+          \l__graphics_full_name_str
+        \seq_gput_right:NV \g__graphics_record_seq \l__graphics_final_name_str
+        \clist_if_exist:NT \@filelist
+          { \exp_args:NV \@addtofilelist \l__graphics_final_name_str }
+        \bool_if:NTF \l__graphics_draft_bool
+          { \__graphics_include_auxiii:n }
+          { \__graphics_include_auxiv:n }
+            {#1}
+      }
+      { \msg_error:nnn { graphics } { unsupported-graphic-type } {#1} }
+  }
+\cs_new_protected:Npn \__graphics_include_auxiii:n #1
+  {
+    \hbox_to_wd:nn { \l__graphics_urx_dim - \l__graphics_llx_dim }
+      {
+        \tex_vrule:D
+        \tex_hss:D
+        \vbox_to_ht:nn
+          { \l__graphics_ury_dim - \l__graphics_lly_dim }
+          {
+            \tex_hrule:D width
+              \dim_eval:n { \l__graphics_urx_dim - \l__graphics_llx_dim }
+            \tex_vss:D
+            \hbox_to_wd:nn
+              { \l__graphics_urx_dim - \l__graphics_llx_dim }
+              {
+                \ttfamily
+                \tex_hss:D \l__graphics_full_name_str \tex_hss:D
+              }
+            \tex_vss:D
+            \tex_hrule:D
+          }
+        \tex_hss:D
+        \tex_vrule:D
+      }
+  }
+\cs_new_protected:Npn \__graphics_include_auxiv:n #1
+  {
+    \hbox_set:Nn \l__graphics_internal_box
+      {
+        \exp_args:NnV \use:c { __graphics_backend_include_ #1 :n }
+          \l__graphics_full_name_str
+      }
+    \box_set_dp:Nn \l__graphics_internal_box { 0pt }
+    \box_set_ht:Nn \l__graphics_internal_box
+      { \l__graphics_ury_dim - \l__graphics_lly_dim }
+    \box_set_wd:Nn \l__graphics_internal_box
+      { \l__graphics_urx_dim - \l__graphics_llx_dim }
+    \box_use_drop:N \l__graphics_internal_box
+  }
+\cs_new_protected:Npn \graphics_show_list: { \__graphics_list:N \msg_show:nneeee }
+\cs_new_protected:Npn \graphics_log_list: { \__graphics_list:N \msg_log:nneeee }
+\cs_new_protected:Npn \__graphics_list:N #1
+  {
+    \seq_remove_duplicates:N \g__graphics_record_seq
+    #1 { kernel } { file-list }
+      { \seq_map_function:NN \g__graphics_record_seq \__graphics_list_aux:n }
+        { } { } { }
+  }
+\cs_new:Npn \__graphics_list_aux:n #1 { \iow_newline: #1 }
+\cs_new_protected:Npn \graphics_get_full_name:nN #1#2
+  {
+    \graphics_get_full_name:nNF {#1} #2
+      { \tl_set:Nn #2 { \q_no_value } }
+  }
+\prg_new_protected_conditional:Npnn \graphics_get_full_name:nN #1#2
+  { T , F , TF }
+  {
+    \group_begin:
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#1} \l__graphics_full_name_str
+        {
+          \str_if_eq:eeTF { \l__graphics_full_name_str } { #1 .tex }
+            { \__graphics_get_full_name:n {#1} }
+            {
+              \file_parse_full_name:VNNN \l__graphics_full_name_str
+                \l__graphics_dir_str \l__graphics_name_str \l__graphics_ext_str
+              \seq_map_inline:Nn \l_graphics_search_ext_seq
+                {
+                  \str_if_eq:nVT {##1} \l__graphics_ext_str
+                    { \seq_map_break:n { \use_none:nn } }
+                }
+                  \__graphics_get_full_name:n {#1}
+            }
+        }
+        { \__graphics_get_full_name:n {#1} }
+    \exp_args:NNNV \group_end:
+    \tl_set:Nn #2 \l__graphics_full_name_str
+    \tl_if_empty:NTF #2
+      { \prg_return_false: }
+      { \prg_return_true: }
+  }
+\cs_new_protected:Npn \__graphics_get_full_name:n #1
+  {
+    \str_clear:N \l__graphics_full_name_str
+    \seq_map_inline:Nn \l_graphics_search_ext_seq
+      {
+        \file_get_full_name:nNT { #1 ##1 } \l__graphics_full_name_str
+          { \seq_map_break:n { \use_none:nn } }
+      }
+    \use:n
+      { \str_clear:N \l__graphics_full_name_str }
+  }
+\cs_new_protected:Npn \graphics_get_pagecount:nN #1#2
+  {
+    \group_begin:
+      \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+      \file_get_full_name:nNTF {#1} \l__graphics_full_name_str
+        {
+          \int_if_exist:cF { c__graphics_ \l__graphics_full_name_str _pages_int }
+            {
+              \exp_args:NV \__graphics_backend_get_pagecount:n
+                \l__graphics_full_name_str
+            }
+          \tl_set:Nv #2 { c__graphics_ \l__graphics_full_name_str _pages_int }
+        }
+        {
+          \tl_set:Nn #2 { 0 }
+          \msg_error:nnn { graphics } { graphic-not-found } {#1}
+        }
+    \exp_args:NNNV \group_end:
+    \tl_set:Nn #2 #2
+  }
+\cs_new_protected:Npe \__graphics_get_pagecount:n #1
+  {
+    \exp_not:N \ior_shell_open:Nn \exp_not:N \l__graphics_internal_ior
+      { extractbb~-O~#1 }
+    \exp_not:N \ior_if_eof:NTF \exp_not:N \l__graphics_internal_ior
+      { \msg_error:nnn { graphics } { pipe-failed } }
+      {
+        \ior_str_map_inline:Nn \exp_not:N \l__graphics_internal_ior
+          {
+            \exp_not:N \__graphics_get_pagecount:nw {#1}
+              ##1 ~ \c_colon_str \c_colon_str \s__graphics_stop
+          }
+        \exp_not:N \int_if_exist:cF { c__graphics_ #1 _pages_int }
+          { \int_const:cn { c__graphics_ #1 _pages_int } { 1 } }
+      }
+    \ior_close:N \exp_not:N \l__graphics_internal_ior
+  }
+\use:e
+  {
+    \cs_new_protected:Npn \exp_not:N \__graphics_get_pagecount:nw
+      #1#2 \c_colon_str #3 \c_colon_str #4 \s__graphics_stop
+      {
+        \exp_not:N \str_if_eq:nnT
+          { \c_percent_str \c_percent_str Pages }
+          {#2}
+          {
+            \int_const:cn { c__graphics_ #1 _pages_int } {#3}
+            \exp_not:N \ior_map_break:
+          }
+      }
+  }
+\msg_new:nnnn { graphics } { graphic-not-found }
+  { Image~file~'#1'~not~found. }
+  {
+    LaTeX~tried~to~open~graphic~file~'#1',~
+    but~the~file~could~not~be~read.
+  }
+\msg_new:nnnn { graphics } { pipe-failed }
+  { Cannot~run~piped~system~commands. }
+  {
+    LaTeX~tried~to~call~a~system~process~but~this~was~not~possible.\\
+    Try~the~"--shell-escape"~(or~"--enable-pipes")~option.
+  }
+\msg_new:nnnn { graphics } { unsupported-graphic-type }
+  { Image~type~'#1'~not~supported~by~current~driver. }
+  {
+    LaTeX~was~asked~to~include~an~graphic~of~type~'#1',~
+    but~this~is~not~supported~by~the~current~driver~(production~route).
+  }
 %% File: l3pdf.dtx
 \scan_new:N \s__pdf_stop
 \bool_new:N \g__pdf_init_bool
@@ -33832,24 +34300,26 @@
 \ior_new:N \g__codepoint_data_ior
 \group_begin:
   \clist_map_inline:nn
-    { category , uppercase , lowercase }
+    { category , grapheme , lowercase , uppercase , wordbreak }
     {
       \cs_set_nopar:cpn { l__codepoint_ #1 _block_clist } { }
       \cs_set_nopar:cpn { l__codepoint_ #1 _block_tl } { 1 }
       \cs_set_nopar:cpn { l__codepoint_ #1 _pos_tl } { 0 }
+      \cs_set_nopar:cpn { l__codepoint_ #1 _next_tl } { 0 }
       \intarray_new:cn { g__codepoint_ #1 _index_intarray }
         { \int_div_truncate:nn { "110000 } \c__codepoint_block_size_int }
     }
-  \cs_set_nopar:Npn \l__codepoint_next_codepoint_fint_tl { 0 }
   \cs_set_nopar:Npn \l__codepoint_matched_block_tl { 0 }
-  \cs_set_protected:Npn \__codepoint_data_auxi:w #1#2
+  \cs_set_nopar:Npn \l__codepoint_uppercase_default_tl { 0 }
+  \cs_set_nopar:Npn \l__codepoint_lowercase_default_tl { 0 }
+  \cs_set_protected:Npn \__codepoint_data_auxi:w #1#2#3
     {
-      \quark_if_recursion_tail_stop:n {#2}
-      \cs_set_nopar:cpn { l__codepoint_category_ #2 _tl } {#1}
-      \str_const:cn { c__codepoint_category_ \tex_romannumeral:D #1 _str } {#2}
-      \exp_args:Ne \__codepoint_data_auxi:w { \int_eval:n { #1 + 1 } }
+      \quark_if_recursion_tail_stop:n {#3}
+      \cs_set_nopar:cpn { l__codepoint_ #2 _ #3 _tl } {#1}
+      \str_const:cn { c__codepoint_ #2 _ \tex_romannumeral:D #1 _str } {#3}
+      \exp_args:Ne \__codepoint_data_auxi:w { \int_eval:n { #1 + 1 } } {#2}
     }
-  \__codepoint_data_auxi:w { 1 }
+  \__codepoint_data_auxi:w { 1 } { category }
     { Lu } { Ll } { Lt } { Lm } { Lo }
     { Mn } { Me } { Mc }
     { Nd } { Nl } { No }
@@ -33859,6 +34329,76 @@
     { Sm } { Sc } { Sk } { So }
     \q_recursion_tail
     \q_recursion_stop
+  \cs_set_eq:NN \l__codepoint_category_default_tl \l__codepoint_category_Cn_tl
+  \__codepoint_data_auxi:w { 1 } { grapheme }
+    { Control }
+    { CR } { LF } { ZWJ }
+    { Extend }
+    { L } { LV } { LVT } { T } { V }
+    { Prepend }
+    { Regional_Indicator }
+    { SpacingMark }
+    { Other }
+    \q_recursion_tail
+    \q_recursion_stop
+  \cs_set_eq:NN \l__codepoint_grapheme_default_tl \l__codepoint_grapheme_Other_tl
+  \__codepoint_data_auxi:w { 1 } { wordbreak }
+    { Double_Quote } { Single_Quote }
+    { CR } { LF } { Newline }
+    { WSegSpace } { ZWJ }
+    { Extend } { ExtendNumLet }
+    { Regional_Indicator }
+    { Format }
+    { Katakana }
+    { ALetter } { MidLetter } { Hebrew_Letter }
+    { Numeric }{ MidNum } { MidNumLet }
+    { Other }
+    \q_recursion_tail
+    \q_recursion_stop
+  \cs_set_eq:NN \l__codepoint_wordbreak_default_tl \l__codepoint_wordbreak_Other_tl
+  \cs_set_protected:Npn \__codepoint_data_auxi:w #1 ;~ #2 ~ #3 \q_stop
+    { \__codepoint_data_auxii:w #1 .. \q_stop {#2} }
+  \cs_set_protected:Npn \__codepoint_data_auxii:w #1 .. #2 \q_stop
+    { \__codepoint_data_auxiii:w #1 ~ .. #2 ~ \q_stop }
+  \cs_set_protected:Npn \__codepoint_data_auxiii:w #1 ~ #2 .. #3 ~ #4 \q_stop #5#6
+    {
+      \cs_set_nopar:cpe { l__codepoint_ #6 _ \tex_romannumeral:D "#1 _tl }
+        {
+          {#3}
+          { \use:c { l__codepoint_ #6 _ #5 _tl } }
+        }
+    }
+  \cs_set_protected:Npn \__codepoint_data_auxvi:w #1#2
+    {
+      \ior_open:Nn \g__codepoint_data_ior {#1}
+      \ior_str_map_inline:Nn \g__codepoint_data_ior
+        {
+          \str_if_eq:eeF { \tl_head:w ##1 \c_hash_str \q_stop } { \c_hash_str }
+            {
+              \tl_if_blank:nF {##1}
+                { \__codepoint_data_auxi:w ##1 \q_stop {#2} }
+            }
+        }
+      \ior_close:N \g__codepoint_data_ior
+    }
+  \__codepoint_data_auxvi:w { GraphemeBreakProperty.txt } { grapheme }
+  \__codepoint_data_auxvi:w { WordBreakProperty.txt } { wordbreak }
+  \cs_set_protected:Npn \__codepoint_data_property:nnnn #1#2#3#4
+    {
+      \int_compare:nNnT {#3} > { \use:c { l__codepoint_ #4 _next_tl } }
+        {
+          \__codepoint_range:nnv {#3} {#4}
+            { l__codepoint_ #4 _default_tl }
+        }
+      \__codepoint_add:nn {#4} {#2}
+      \tl_set:ce { l__codepoint_ #4 _next_tl } { \int_eval:n { #3 + 1 } }
+      \tl_if_blank:nF {#1}
+        {
+          \__codepoint_range:nnn {"#1} {#4} {#2}
+          \__codepoint_add:nn {#4} {#2}
+          \tl_set:ce { l__codepoint_ #4 _next_tl } { \int_eval:n { "#1 + 1 } }
+        }
+    }
   \cs_set_protected:Npn \__codepoint_data_auxi:w
     #1 ; #2 ; #3 ; #4 ; #5 ; #6 ; #7 ; #8 ; #9 ;
     {
@@ -33901,7 +34441,7 @@
     }
   \cs_set_protected:Npn \__codepoint_data_auxiv:w #1 ; #2 ; #3 ; #4 ; #5 ; #6 ;
     {
-      \int_compare:nNnT {"#1} > \l__codepoint_next_codepoint_fint_tl
+      \int_compare:nNnT {"#1} > \l__codepoint_category_next_tl
         {
           \__codepoint_data_auxv:nnnnw {#1} {#3} {#4} {#5}
             #2 Last> \q_stop
@@ -33915,8 +34455,17 @@
             { c__codepoint_titlecase_ \codepoint_str_generate:n {"#1} _tl }
             { {"#6} { } { } }
         }
-      \tl_set:Ne \l__codepoint_next_codepoint_fint_tl
+      \__codepoint_data_auxvi:nn { grapheme } {"#1}
+      \__codepoint_data_auxvi:nn { wordbreak } {"#1}
+      \int_compare:nNnT {"#1} = { "AC00 }
+        {
+          \int_step_inline:nnn { "AC01 } { "D7A2 }
+            { \__codepoint_data_auxvi:nn { grapheme } {##1} }
+        }
+      \tl_set:Ne \l__codepoint_category_next_tl
         { \int_eval:n { "#1 + 1 } }
+      \tl_set_eq:NN \l__codepoint_lowercase_next_tl \l__codepoint_category_next_tl
+      \tl_set_eq:NN \l__codepoint_uppercase_next_tl \l__codepoint_category_next_tl
     }
   \cs_set_protected:Npn \__codepoint_add:nn #1#2
     {
@@ -33925,26 +34474,40 @@
         = \c__codepoint_block_size_int
         { \__codepoint_save_blocks:nn {#1} { 1 } }
     }
-  \cs_set_protected:Npe \__codepoint_data_auxv:nnnnw #1#2#3#4#5 Last> #6 \q_stop
+  \cs_set_protected:Npn \__codepoint_data_auxv:nnnnw #1#2#3#4#5 Last> #6 \q_stop
     {
-      \exp_not:N \tl_if_blank:nTF {#6}
+      \tl_if_blank:nTF {#6}
         {
-          \exp_not:N \__codepoint_range:nnn {#1} { category }
-            { \exp_not:V \l__codepoint_category_Cn_tl }
-          \exp_not:N \__codepoint_range:nnn {#1} { uppercase } { 0 }
-          \exp_not:N \__codepoint_range:nnn {#1} { lowercase } { 0 }
+          \__codepoint_range:nno {"#1} { category }
+            \l__codepoint_category_default_tl
+          \__codepoint_range:nnn {"#1} { uppercase } { 0 }
+          \__codepoint_range:nnn {"#1} { lowercase } { 0 }
         }
         {
-          \exp_not:N \__codepoint_range:nnn {#1} { category } {#2}
-          \exp_not:N \__codepoint_range:nnn {#1} { uppercase } {#3}
-          \exp_not:N \__codepoint_range:nnn {#1} { lowercase } {#4}
+          \__codepoint_range:nnn {"#1} { category } {#2}
+          \__codepoint_range:nnn {"#1} { uppercase } {#3}
+          \__codepoint_range:nnn {"#1} { lowercase } {#4}
         }
     }
-  \cs_set_protected:Npn \__codepoint_range:nnn #1
+  \cs_set_protected:Npn \__codepoint_data_auxvi:nn #1#2
     {
+      \cs_if_exist:cT
+        { l__codepoint_ #1 _ \tex_romannumeral:D #2 _tl }
+        {
+          \exp_after:wN \exp_after:wN \exp_after:wN \__codepoint_data_property:nnnn
+            \cs:w
+              l__codepoint_ #1 _ \tex_romannumeral:D #2 _tl
+                \cs_end: {#2} {#1}
+        }
+    }
+  \cs_set_protected:Npn \__codepoint_range:nnn #1#2
+    {
       \exp_args:Nf \__codepoint_range_aux:nnn
-        { \int_eval:n { "#1 - \l__codepoint_next_codepoint_fint_tl } }
+        { \int_eval:n { #1 - \use:c { l__codepoint_ #2 _next_tl } } }
+        {#2}
     }
+  \cs_set_protected:Npn \__codepoint_range:nno { \exp_args:Nnno \__codepoint_range:nnn }
+  \cs_set_protected:Npn \__codepoint_range:nnv { \exp_args:Nnnv \__codepoint_range:nnn }
   \cs_set_protected:Npn \__codepoint_range_aux:nnn #1#2
     {
       \exp_args:Nf \__codepoint_range:nnnn
@@ -34014,15 +34577,16 @@
           { \int_eval:n { \tl_use:c { l__codepoint_ #1 _pos_tl } + #2 } }
       \clist_clear:c { l__codepoint_ #1 _block_clist }
     }
-  \cs_set_protected:Npn \__codepoint_finalise_blocks:
+  \cs_set_protected:Npn \__codepoint_finalise_blocks:n #1
     {
-      \clist_map_inline:nn { category , uppercase , lowercase }
+      \clist_map_inline:nn {#1}
         {
-          \__codepoint_range:nnn { 110000 } {##1} { 0 }
-          \__codepoint_finalise_blocks:n {##1}
+          \exp_args:Nnnv \__codepoint_range:nnn { "110000 } {##1}
+            { l__codepoint_ ##1 _default_tl }
+          \__codepoint_finalise_blocks_aux:n {##1}
         }
     }
-  \cs_set_protected:Npn \__codepoint_finalise_blocks:n #1
+  \cs_set_protected:Npn \__codepoint_finalise_blocks_aux:n #1
     {
       \cs_gset_eq:cc { c__codepoint_ #1 _index_intarray } { g__codepoint_ #1 _index_intarray }
       \cs_undefine:c { g__codepoint_ #1 _index_intarray }
@@ -34055,17 +34619,17 @@
         { \int_eval:n { #1 + 1 } } {#2} {#3}
     }
   \ior_open:Nn \g__codepoint_data_ior { UnicodeData.txt }
-  \group_begin:
-    \char_set_catcode_space:n { `\  }%
-    \ior_map_variable:NNn \g__codepoint_data_ior \l__codepoint_tmpa_tl
-      {%
-        \if_meaning:w \l__codepoint_tmpa_tl \c_space_tl
-          \exp_after:wN \ior_map_break:
-        \fi:
-        \exp_after:wN \__codepoint_data_auxi:w \l__codepoint_tmpa_tl \q_stop
-      }%
-    \__codepoint_finalise_blocks:
-  \group_end:
+  \char_set_catcode_space:n { `\  }%
+  \ior_map_variable:NNn \g__codepoint_data_ior \l__codepoint_tmpa_tl
+    {%
+      \if_meaning:w \l__codepoint_tmpa_tl \c_space_tl
+        \exp_after:wN \ior_map_break:
+      \fi:
+      \exp_after:wN \__codepoint_data_auxi:w \l__codepoint_tmpa_tl \q_stop
+    }%
+  \char_set_catcode_ignore:n { `\  }%
+  \__codepoint_finalise_blocks:n
+    { category , grapheme , lowercase , uppercase , wordbreak }
 \group_end:
 \cs_new:Npn \__kernel_codepoint_data:nn #1#2
   {
@@ -34185,6 +34749,24 @@
     { }
     { }
   }
+\cs_new:Npn \__kernel_codepoint_to_grapheme_class:n #1
+  {
+    \cs:w
+      c__codepoint_grapheme_
+      \tex_romannumeral:D
+        \__kernel_codepoint_data:nn { grapheme } {#1}
+      _str
+    \cs_end:
+  }
+\cs_new:Npn \__kernel_codepoint_to_wordbreak_class:n #1
+  {
+    \cs:w
+      c__codepoint_wordbreak_
+      \tex_romannumeral:D
+        \__kernel_codepoint_data:nn { wordbreak } {#1}
+      _str
+    \cs_end:
+  }
 \cs_new:Npn \__codepoint_nfd:n #1
   { \exp_args:Ne \__codepoint_nfd:nn { \codepoint_str_generate:n {#1} } {#1} }
 \cs_new:Npn \__codepoint_nfd:nn #1#2
@@ -34193,43 +34775,6 @@
       { \tl_use:c { c__codepoint_nfd_ #1 _tl } }
       { {#2} { } }
   }
-\ior_new:N \g__text_data_ior
-\group_begin:
-  \ior_open:Nn \g__text_data_ior { GraphemeBreakProperty.txt }
-  \cs_set_nopar:Npn \l__text_tmpa_str { }
-  \cs_set_nopar:Npn \l__text_tmpb_str { }
-  \cs_set_protected:Npn \__text_data_auxi:w #1 ;~ #2 ~ #3 \q_stop
-    {
-      \str_if_eq:VnF \l__text_tmpb_str {#2}
-        {
-          \str_if_empty:NF \l__text_tmpb_str
-            {
-              \clist_const:ce { c__text_grapheme_ \l__text_tmpb_str _clist }
-                { \exp_after:wN \use_none:n \l__text_tmpa_str }
-              \cs_set_nopar:Npn \l__text_tmpa_str { }
-            }
-          \cs_set_nopar:Npn \l__text_tmpb_str {#2}
-        }
-      \__text_data_auxii:w #1 .. #1 .. #1 \q_stop
-    }
-  \cs_set_protected:Npn \__text_data_auxii:w #1 .. #2 .. #3 \q_stop
-    {
-      \cs_set_nopar:Npe \l__text_tmpa_str
-        {
-          \l__text_tmpa_str ,
-          \tl_trim_spaces:n {#1} .. \tl_trim_spaces:n {#2}
-        }
-    }
-  \ior_str_map_inline:Nn \g__text_data_ior
-    {
-      \str_if_eq:eeF { \tl_head:w #1 \c_hash_str \q_stop } { \c_hash_str }
-        {
-          \tl_if_blank:nF {#1}
-            { \__text_data_auxi:w #1 \q_stop }
-        }
-    }
-  \ior_close:N \g__text_data_ior
-\group_end:
 %% File: l3text.dtx
 \cs_generate_variant:Nn \tl_if_head_eq_meaning_p:nN { o }
 \scan_new:N \s__text_stop
@@ -36540,223 +37085,249 @@
     \text_declare_uppercase_mapping:nn { "01F0 } { \v { J } }
   }
 %% File: l3text-map.dtx
-\cs_new:Npn \text_map_function:nN #1#2
-  { \exp_args:Ne \__text_map_function:nN { \text_expand:n {#1} } #2 }
-\cs_new:Npn \__text_map_function:nN #1#2
+\cs_new:Npn \__text_map_function:nnN #1#2#3
   {
-    \__text_map_loop:Nnw #2 { } #1
+    \__text_map_loop:Nnnw #3 {#2} { } #1
       \q__text_recursion_tail \q__text_recursion_stop
     \prg_break_point:Nn \text_map_break: { }
   }
-\cs_new:Npn \__text_map_loop:Nnw #1#2#3 \q__text_recursion_stop
+\cs_generate_variant:Nn \__text_map_function:nnN { e }
+\cs_new:Npn \__text_map_loop:Nnnw #1#2#3#4 \q__text_recursion_stop
   {
-    \tl_if_head_is_N_type:nTF {#3}
-      { \__text_map_N_type:NnN }
+    \tl_if_head_is_N_type:nTF {#4}
+      { \__text_map_N_type:NnnN }
       {
-        \tl_if_head_is_group:nTF {#3}
-          { \__text_map_group:Nnn }
-          { \__text_map_space:Nnw }
+        \tl_if_head_is_group:nTF {#4}
+          { \__text_map_group:Nnnn }
+          { \__text_map_space:Nnnw }
       }
-    #1 {#2} #3 \q__text_recursion_stop
+    #1 {#2} {#3} #4 \q__text_recursion_stop
   }
-\cs_new:Npn \__text_map_group:Nnn #1#2#3
+\cs_new:Npn \__text_map_group:Nnnn #1#2#3#4
   {
-    \__text_map_output:Nn #1 {#2}
+    \__text_map_output:Nn #1 {#3}
     {
-      \__text_map_loop:Nnw #1 { } #2
+      \__text_map_loop:Nnnw #1 {#2} { } #4
         \q__text_recursion_tail \q__text_recursion_stop
       \prg_break_point:Nn \text_map_break: { }
     }
-    \__text_map_loop:Nnw #1 { }
+    \__text_map_loop:Nnnw #1 {#2} { }
   }
 \use:e
-  { \cs_new:Npn \exp_not:N \__text_map_space:Nnw #1#2 \c_space_tl }
+  { \cs_new:Npn \exp_not:N \__text_map_space:Nnnw #1#2#3 \c_space_tl }
   {
-    \__text_map_output:Nn #1 {#2}
+    \__text_map_output:Nn #1 {#3}
     #1 { ~ }
-    \__text_map_loop:Nnw #1 { }
+    \__text_map_loop:Nnnw #1 {#2} { }
   }
-\cs_new:Npn \__text_map_N_type:NnN #1#2#3
+\cs_new:Npn \__text_map_N_type:NnnN #1#2#3#4
   {
-    \__text_if_q_recursion_tail_stop_do:Nn #3
+    \__text_if_q_recursion_tail_stop_do:Nn #4
       {
-        \__text_map_output:Nn #1 {#2}
+        \__text_map_output:Nn #1 {#3}
         \text_map_break:
       }
-    \token_if_cs:NTF #3
+    \token_if_cs:NTF #4
       {
-        \__text_map_output:Nn #1 {#2}
-        #1 {#3}
-        \__text_map_loop:Nnw #1 { }
+        \__text_map_output:Nn #1 {#3}
+        #1 {#4}
+        \__text_map_loop:Nnnw #1 {#2} { }
       }
       {
         \__text_codepoint_process:nN
-          { \__text_map_codepoint:Nnn #1 {#2} } #3
+          { \__text_map_codepoint:Nnnn #1 {#2} {#3} } #4
       }
   }
-\cs_new:Npn \__text_map_codepoint:Nnn #1#2#3
+\cs_new:Npn \__text_map_codepoint:Nnnn #1#2#3#4
   {
-    \__text_codepoint_compare:nNnTF {#3} = { "0D }
+    \__text_codepoint_compare:nNnTF {#4} = { "000D }
       {
-        \__text_map_output:Nn #1 {#2}
-        \__text_map_CR:Nnw #1 {#3}
+        \__text_map_output:Nn #1 {#3}
+        \__text_map_CR:Nnnw #1 {#2} {#4}
       }
       {
-        \__text_codepoint_compare:nNnTF {#3} = { "200D }
-          { \__text_map_loop:Nnw #1 {#2#3} }
-          { \__text_map_class:Nnnn #1 {#2} {#3} { Control } }
+        \__text_codepoint_compare:nNnTF {#4} = { "200D }
+          { \__text_map_loop:Nnnw #1 {#2} {#3#4} }
+          { \__text_map_class:Nnnn #1 {#2} {#3} {#4} }
       }
   }
-\cs_new:Npn \__text_map_CR:Nnw #1#2#3 \q__text_recursion_stop
+\cs_new:Npn \__text_map_CR:Nnnw #1#2#3#4 \q__text_recursion_stop
   {
-    \tl_if_head_is_N_type:nTF {#3}
-      { \__text_map_CR:NnN #1 {#2} }
+    \tl_if_head_is_N_type:nTF {#4}
+      { \__text_map_CR:NnnN #1 {#2} {#3} }
       {
-        #1 {#2}
-        \__text_map_loop:Nnw #1 { }
+        #1 {#3}
+        \__text_map_loop:Nnnw #1 {#2} { }
       }
-        #3 \q__text_recursion_stop
+        #4 \q__text_recursion_stop
   }
-\cs_new:Npn \__text_map_CR:NnN #1#2#3
+\cs_new:Npn \__text_map_CR:NnnN #1#2#3#4
   {
-    \__text_if_q_recursion_tail_stop_do:Nn #3
+    \__text_if_q_recursion_tail_stop_do:Nn #4
       {
-        #1 {#2}
+        #1 {#3}
         \text_map_break:
       }
     \bool_lazy_and:nnTF
-      { ! \token_if_cs_p:N #3 }
-      { \int_compare_p:nNn { `#3 } = { "0A } }
+      { ! \token_if_cs_p:N #4 }
+      { \int_compare_p:nNn { `#4 } = { "000A } }
       {
-        \__text_map_output:Nn #1 {#2#3}
-        \__text_map_loop:Nnw #1 { }
+        \__text_map_output:Nn #1 {#3#4}
+        \__text_map_loop:Nnnw #1 {#2} { }
       }
-      { \__text_map_loop:Nnw #1 { } #3 }
+      { \__text_map_loop:Nnnw #1 {#2} { } #3 }
   }
 \cs_new:Npn \__text_map_class:Nnnn #1#2#3#4
   {
-    \exp_args:Nv \__text_map_class:nNnnn { c__text_grapheme_ #4 _clist }
-      #1 {#2} {#3} {#4}
+    \exp_args:Nnnne \__text_map_class:Nnnnn #1 {#2} {#3} {#4}
+      {
+        \use:c { __kernel_codepoint_to_ #2 _class:n }
+          { \__text_codepoint_from_chars:Nw #4 }
+      }
   }
-\cs_new:Npn \__text_map_class:nNnnn #1#2#3#4#5
+\cs_new:Npn \__text_map_class:Nnnnn #1#2#3#4#5
   {
-    \__text_map_class_loop:Nnnnw #2 {#3} {#4} {#5}
-      #1 , \q__text_recursion_tail .. , \q__text_recursion_stop
+    \cs_if_exist_use:cF { __text_map_ #5 :Nnnn }
+      { \__text_map_Other:Nnnn }
+        #1 {#2} {#3} {#4}
   }
-\cs_new:Npn \__text_map_class_loop:Nnnnw #1#2#3#4 #5 .. #6 ,
+\cs_new:Npn \__text_map_lookahead:Nnnnnw #1#2#3#4#5#6 \q__text_recursion_stop
   {
-    \__text_if_q_recursion_tail_stop_do:nn {#5}
-      { \use:c { __text_map_not_ #4 :Nnn } #1 {#2} {#3} }
-    \__text_codepoint_compare:nNnTF {#3} < { "#5 }
+    \tl_if_head_is_N_type:nTF {#6}
+      { \__text_map_lookahead:NnnnnN #1 {#2} {#3} {#4} {#5} }
+      { \__text_map_loop:Nnnw #1 {#2} {#3} #4 }
+    #6 \q__text_recursion_stop
+  }
+\cs_new:Npn \__text_map_lookahead:NnnnnN #1#2#3#4#5#6
+  {
+    \__text_if_q_recursion_tail_stop_do:Nn #6
       {
-        \__text_map_class_end:nw
-          { \use:c { __text_map_not_ #4 :Nnn } #1 {#2} {#3} }
+        #1 {#3}
+        \tl_if_blank:nF {#4} { #1 {#4} }
       }
+    \token_if_cs:NTF #6
       {
-        \__text_codepoint_compare:nNnTF {#3} > { "#6 }
-          { \__text_map_class_loop:Nnnnw #1 {#2} {#3} {#4} }
-          {
-            \__text_map_class_end:nw
-              { \use:c { __text_map_ #4 :Nnn } #1 {#2} {#3} }
-          }
+        #1 {#3}
+        \__text_map_loop:Nnnw #1 {#2} { } #4
       }
+      { \__text_codepoint_process:nN { #5 #1 {#2} {#3} {#4} } }
+        #6
   }
-\cs_new:Npn \__text_map_class_end:nw #1#2 \q__text_recursion_stop {#1}
-\cs_new:Npn \__text_map_Control:Nnn #1#2#3
+\prg_new_conditional:Npnn \__text_map_if_ignorable:n #1 { TF }
   {
-    \__text_map_output:Nn #1 {#2}
+    \str_case:nnTF {#1}
+      {
+        { Extend }       { }
+        { Format }       { }
+        { ZWJ }          { }
+      }
+      \prg_return_true:
+      \prg_return_false:
+  }
+\cs_new:Npn \__text_map_output:Nn #1#2
+  { \tl_if_blank:nF {#2} { #1 {#2} } }
+\cs_new:Npn \text_map_break:
+  { \prg_map_break:Nn \text_map_break: { } }
+\cs_new:Npn \text_map_break:n
+  { \prg_map_break:Nn \text_map_break: }
+\cs_new:Npn \__text_map_Control:Nnnn #1#2#3#4
+  {
     \__text_map_output:Nn #1 {#3}
-    \__text_map_loop:Nnw #1 { }
+    \__text_map_output:Nn #1 {#4}
+    \__text_map_loop:Nnnw #1 {#2} { }
   }
-\cs_new:Npn \__text_map_Extend:Nnn #1#2#3
-  { \__text_map_loop:Nnw #1 {#2#3} }
-\cs_new_eq:NN \__text_map_SpacingMark:Nnn \__text_map_Extend:Nnn
-\cs_new:Npn \__text_map_Prepend:Nnn #1#2#3
+\cs_new_eq:NN \__text_map_Newline:Nnnn \__text_map_Control:Nnnn
+\cs_new:Npn \__text_map_Extend:Nnnn #1#2#3#4
+  { \__text_map_loop:Nnnw #1 {#2} {#3#4} }
+\cs_new_eq:NN \__text_map_Format:Nnnn \__text_map_Extend:Nnnn
+\cs_new_eq:NN \__text_map_SpacingMark:Nnnn \__text_map_Extend:Nnnn
+\cs_new:Npn \__text_map_Other:Nnnn #1#2#3#4
   {
-    \__text_map_output:Nn #1 {#2}
-    \__text_map_lookahead:NnNw #1 {#3} \__text_map_Prepend_aux:Nnn
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_loop:Nnnw #1 {#2} {#4}
   }
-\cs_new:Npn \__text_map_Prepend_aux:Nnn #1#2#3
+\cs_new:Npn \__text_map_Regional_Indicator:Nnnn #1#2#3#4
   {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_lookahead:Nnnnnw #1 {#2} {#4} { }
+      \__text_map_Regional_Indicator_aux:Nnnnn
+  }
+\cs_new:Npn \__text_map_Regional_Indicator_aux:Nnnnn #1#2#3#4#5
+  {
     \bool_lazy_or:nnTF
-      { \__text_codepoint_compare_p:nNn {#3} = { "0A } }
-      { \__text_codepoint_compare_p:nNn {#3} = { "0D } }
+      { \__text_codepoint_compare_p:nNn {#5} < { "1F1E6 } }
+      { \__text_codepoint_compare_p:nNn {#5} > { "1F1FF } }
       {
-        #1 {#2}
-        \__text_map_loop:Nnw #1 {#3}
+        \str_if_eq:nnTF {#2} { wordbreak }
+          {
+            \exp_args:Ne \__text_map_if_ignorable:nTF
+              {
+                \__kernel_codepoint_to_grapheme_class:n
+                  { \__text_codepoint_from_chars:Nw #5 }
+              }
+              {
+                \__text_map_lookahead:Nnnnnw #1 {#2} {#3#5} { }
+                  \__text_map_Regional_Indicator_aux:Nnnnn
+              }
+              { \__text_map_loop:Nnnw #1 {#2} {#3} #5 }
+          }
+          { \__text_map_loop:Nnnw #1 {#2} {#3} #5 }
       }
-      {
-        \exp_args:NV \__text_map_Prepend:nNnn
-          \c__text_grapheme_Control_clist
-          #1 {#2} {#3}
-      }
+      { \__text_map_loop:Nnnw #1 {#2} {#3#5} }
   }
-\cs_new:Npn \__text_map_Prepend:nNnn #1#2#3#4
+\cs_new:Npn \text_map_function:nN #1#2
   {
-    \__text_map_Prepend_loop:Nnnw #2 {#3} {#4}
-      #1 , \q__text_recursion_tail .. , \q__text_recursion_stop
+    \__text_map_function:enN { \text_expand:n {#1} }
+      { grapheme } #2
   }
-\cs_new:Npn \__text_map_Prepend_loop:Nnnw #1#2#3 #4 .. #5 ,
+\cs_new:Npn \__text_map_Prepend:Nnnn #1#2#3#4
   {
-    \__text_if_q_recursion_tail_stop_do:nn {#4}
-      { \__text_map_loop:Nnw #1 {#2#3} }
-    \__text_codepoint_compare:nNnTF {#3} < { "#4 }
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_lookahead:Nnnnnw #1 { grapheme } {#4} { }
+      \__text_map_Prepend_aux:Nnnnn
+  }
+\cs_new:Npn \__text_map_Prepend_aux:Nnnnn #1#2#3#4#5
+  {
+    \bool_lazy_or:nnTF
+      { \__text_codepoint_compare_p:nNn {#5} = { "000A } }
+      { \__text_codepoint_compare_p:nNn {#5} = { "000D } }
       {
-        \__text_map_class_end:nw
-          { \__text_map_loop:Nnw #1 {#2#3} }
+        #1 {#3}
+        \__text_map_loop:Nnnw #1 { grapheme } {#5}
       }
+      { \__text_map_Prepend:Nnn #1 {#3} {#5} }
+  }
+\cs_new:Npn \__text_map_Prepend:Nnn #1#2#3
+  {
+    \str_if_eq:eeTF
+      { Control }
       {
-        \__text_codepoint_compare:nNnTF {#3} > { "#5 }
-          { \__text_map_Prepend_loop:Nnnw #1 {#2} {#3} }
-          {
-            \__text_map_class_end:nw
-              { \__text_map_loop:Nnw #1 {#2} #3 }
-          }
+        \__kernel_codepoint_to_grapheme_class:n
+          { \__text_codepoint_from_chars:Nw #3 }
       }
+      { \__text_map_loop:Nnnw #1 { grapheme } {#2} #3 }
+      { \__text_map_loop:Nnnw #1 { grapheme } {#2#3} }
   }
-\cs_new:Npn \__text_map_not_Control:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { Extend } }
-\cs_new:Npn \__text_map_not_Extend:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { SpacingMark } }
-\cs_new:Npn \__text_map_not_SpacingMark:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { Prepend } }
-\cs_new:Npn \__text_map_not_Prepend:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { L } }
-\cs_new:Npn \__text_map_not_L:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { LV } }
-\cs_new:Npn \__text_map_not_LV:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { V } }
-\cs_new:Npn \__text_map_not_V:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { LVT } }
-\cs_new:Npn \__text_map_not_LVT:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { T } }
-\cs_new:Npn \__text_map_not_T:Nnn #1#2#3
-  { \__text_map_class:Nnnn #1 {#2} {#3} { Regional_Indicator } }
-\cs_new:Npn \__text_map_not_Regional_Indicator:Nnn #1#2#3
+\cs_new:Npn \__text_map_L:Nnnn #1#2#3#4
   {
-    \__text_map_output:Nn #1 {#2}
-    \__text_map_loop:Nnw #1 {#3}
-  }
-\cs_new:Npn \__text_map_L:Nnn #1#2#3
-  {
-    \__text_map_output:Nn #1 {#2}
+    \__text_map_output:Nn #1 {#3}
     \__text_map_hangul:Nnnw
-      #1 {#3} { L ; V ; LV ; LVT }
+      #1 {#4} { L ; V ; LV ; LVT }
   }
-\cs_new:Npn \__text_map_LV:Nnn #1#2#3
+\cs_new:Npn \__text_map_LV:Nnnn #1#2#3#4
   {
-    \__text_map_output:Nn #1 {#2}
+    \__text_map_output:Nn #1 {#3}
     \__text_map_hangul:Nnnw
-      #1 {#3} { V ; T }
+      #1 {#4} { V ; T }
   }
-\cs_new_eq:NN \__text_map_V:Nnn \__text_map_LV:Nnn
-\cs_new:Npn \__text_map_LVT:Nnn #1#2#3
+\cs_new_eq:NN \__text_map_V:Nnnn \__text_map_LV:Nnnn
+\cs_new:Npn \__text_map_LVT:Nnnn #1#2#3#4
   {
-    \__text_map_output:Nn #1 {#2}
+    \__text_map_output:Nn #1 {#3}
     \__text_map_hangul:Nnnw
-      #1 {#3} { T }
+      #1 {#4} { T }
   }
-\cs_new_eq:NN \__text_map_T:Nnn \__text_map_LVT:Nnn
+\cs_new_eq:NN \__text_map_T:Nnnn \__text_map_LVT:Nnnn
 \cs_new:Npn \__text_map_hangul:Nnnw #1#2#3#4 \q__text_recursion_stop
   {
     \tl_if_head_is_N_type:nTF {#4}
@@ -36763,7 +37334,7 @@
       { \__text_map_hangul:NnnN #1 {#2} {#3} }
       {
         #1 {#2}
-        \__text_map_loop:Nnw #1 { }
+        \__text_map_loop:Nnnw #1 { grapheme } { }
       }
     #4 \q__text_recursion_stop
   }
@@ -36777,7 +37348,7 @@
     \token_if_cs:NTF #4
       {
         #1 {#2}
-        \__text_map_loop:Nnw #1 { }
+        \__text_map_loop:Nnnw #1 { grapheme } { }
       }
       {
         \__text_codepoint_process:nN
@@ -36784,43 +37355,31 @@
           { \__text_map_hangul:Nnnn #1 {#2} {#3} } #4
       }
   }
+\exp_args_generate:n { Nnne }
 \cs_new:Npn \__text_map_hangul:Nnnn #1#2#3#4
   {
-    \__text_map_hangul_aux:Nnnw #1 {#2} {#4}
+    \exp_args:NNnne \__text_map_hangul_aux:Nnnnw #1 {#2} {#4}
+      {
+        \__kernel_codepoint_to_grapheme_class:n
+          { \__text_codepoint_from_chars:Nw #4 }
+      }
       #3 ; \q_recursion_tail ; \q_recursion_stop
   }
-\cs_new:Npn \__text_map_hangul_aux:Nnnw #1#2#3#4 ;
+\cs_new:Npn \__text_map_hangul_aux:Nnnnw #1#2#3#4#5 ;
   {
-    \quark_if_recursion_tail_stop_do:nn {#4}
-      { \__text_map_loop:Nnw #1 {#2} #3 }
-    \exp_args:Nv \__text_map_hangul:nNnnnw { c__text_grapheme_ #4 _clist }
-      #1 {#2} {#3} {#4}
+    \quark_if_recursion_tail_stop_do:nn {#5}
+      { \__text_map_loop:Nnnw #1 { grapheme } {#2} #3 }
+    \__text_map_hangul:Nnnnnw #1 {#2} {#3} {#4} {#5}
   }
-\cs_new:Npn \__text_map_hangul:nNnnnw #1#2#3#4#5#6  \q_recursion_stop
+\cs_generate_variant:Nn \__text_map_hangul_aux:Nnnnw { Nnne }
+\cs_new:Npn \__text_map_hangul:Nnnnnw #1#2#3#4#5#6 \q_recursion_stop
   {
-    \__text_map_hangul_loop:Nnnnnw #2 {#3} {#4} {#5} {#6}
-      #1 , \q__text_recursion_tail .. , \q__text_recursion_stop
+    \str_if_eq:nnTF {#4} {#5}
+      { \use:c { __text_map_hangul_ #5 :Nnn } #1 {#2} {#3} }
+      { \__text_map_hangul_next:Nnnnn #1 {#2} {#3} {#4} {#6} }
   }
-\cs_new:Npn \__text_map_hangul_loop:Nnnnnw #1#2#3#4#5 #6 .. #7 ,
-  {
-    \__text_if_q_recursion_tail_stop_do:nn {#6}
-      { \__text_map_hangul_next:Nnnn #1 {#2} {#3} {#5} }
-    \__text_codepoint_compare:nNnTF {#3} < { "#6 }
-      {
-        \__text_map_hangul_end:nw
-          { \__text_map_hangul_next:Nnnn #1 {#2} {#3} {#5} }
-      }
-      {
-        \__text_codepoint_compare:nNnTF {#3} > { "#7 }
-          { \__text_map_hangul_loop:Nnnnnw #1 {#2} {#3} {#4} {#5} }
-          {
-            \__text_map_hangul_end:nw
-              { \use:c { __text_map_hangul_ #4 :Nnn } #1 {#2} {#3} }
-          }
-      }
-  }
-\cs_new:Npn \__text_map_hangul_next:Nnnn #1#2#3#4
-  { \__text_map_hangul_aux:Nnnw #1 {#2} {#3} #4 \q_recursion_stop }
+\cs_new:Npn \__text_map_hangul_next:Nnnnn #1#2#3#4#5
+  { \__text_map_hangul_aux:Nnnnw #1 {#2} {#3} {#4} #5 \q_recursion_stop }
 \cs_new:Npn \__text_map_hangul_end:nw #1#2 \q__text_recursion_stop {#1}
 \cs_new:Npn \__text_map_hangul_L:Nnn #1#2#3
   {
@@ -36839,45 +37398,162 @@
       #1 {#2#3} { T }
   }
 \cs_new_eq:NN \__text_map_hangul_T:Nnn \__text_map_hangul_LVT:Nnn
-\cs_new:Npn \__text_map_Regional_Indicator:Nnn #1#2#3
+\cs_new:Npn \text_words_map_function:nN #1#2
   {
-    \__text_map_output:Nn #1 {#2}
-    \__text_map_lookahead:NnNw #1 {#3} \__text_map_Regional_Indicator_aux:Nnn
+    \__text_map_function:enN { \text_expand:n {#1} }
+      { wordbreak } #2
   }
-\cs_new:Npn \__text_map_Regional_Indicator_aux:Nnn #1#2#3
+\cs_new:Npn \__text_map_collect:Nnnnn #1#2#3#4#5
   {
-    \bool_lazy_or:nnTF
-      { \__text_codepoint_compare_p:nNn {#3} < { "1F1E6 } }
-      { \__text_codepoint_compare_p:nNn {#3} > { "1F1FF } }
+    \__text_map_lookahead:Nnnnnw #1 { wordbreak } {#2} { }
+      { \__text_map_collect_auxi:nnnNnnnn {#3} {#4} {#5} }
+  }
+\cs_new:Npn \__text_map_collect_auxi:nnnNnnnn #1#2#3#4#5#6#7#8
+  {
+    \exp_args:Ne \__text_map_collect_auxii:nNnnnnn
       {
-        \__text_map_loop:Nnw #1 {#2} #3
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \__text_codepoint_from_chars:Nw #8 }
       }
-      { \__text_map_loop:Nnw #1 {#2#3} }
+  #4 {#6} {#1} {#2} {#3} {#8}
   }
-\cs_new:Npn \__text_map_lookahead:NnNw #1#2#3#4 \q__text_recursion_stop
+\cs_new:Npn \__text_map_collect_auxii:nNnnnnn #1#2#3#4#5#6#7
   {
-    \tl_if_head_is_N_type:nTF {#4}
-      { \__text_map_lookahead:NnNN #1 {#2} #3 }
-      { \__text_map_loop:Nnw #1 {#2} }
-    #4 \q__text_recursion_stop
+    \str_case:neTF {#1}
+      {
+        \tl_map_function:eN
+          {
+            #4
+            \str_if_eq:nnF {#4} { { WSegSpace } } { { ExtendNumLet } }
+          }
+          \__text_map_collect_auxiii:n
+      }
+      {
+        \cs_if_exist_use:cF { __text_map_ #1 :Nnnn }
+          { \__text_map_Other:Nnnn }
+            #2 { wordbreak } { } {#3#7}
+      }
+      {
+        \__text_map_if_ignorable:nTF {#1}
+          { \__text_map_collect:Nnnnn #2 {#3#7} {#4} {#5} {#6} }
+          {
+            \str_case:neTF {#1}
+              { \tl_map_function:nN {#5} \__text_map_collect_auxiii:n }
+              {
+                \__text_map_lookahead:Nnnnnw #2 { wordbreak } {#3} {#7}
+                  { \__text_map_collect_auxiv:nnNnnnn {#5} {#6} }
+              }
+              {
+                \__text_map_output:Nn #2 {#3}
+                \__text_map_loop:Nnnw #2 { wordbreak } { } #7
+              }
+          }
+      }
   }
-\cs_new:Npn \__text_map_lookahead:NnNN #1#2#3#4
+\cs_new:Npn \__text_map_collect_auxiii:n #1
+  { \exp_not:n { {#1} { } } }
+\cs_new:Npn \__text_map_collect_auxiv:nnNnnnn #1#2#3#4#5#6#7
   {
-    \__text_if_q_recursion_tail_stop_do:Nn #4 { #1 {#2} }
-    \token_if_cs:NTF #4
+    \exp_args:Ne \__text_map_collect_auxv:nNnnnnn
       {
-        #1 {#2}
-        \__text_map_loop:Nnw #1 { }
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \__text_codepoint_from_chars:Nw #7 }
       }
-      { \__text_codepoint_process:nN { #3 #1 {#2} } }
-        #4
+  #3 {#5} {#6} {#1} {#2} {#7}
   }
-\cs_new:Npn \__text_map_output:Nn #1#2
-  { \tl_if_blank:nF {#2} { #1 {#2} } }
-\cs_new:Npn \text_map_break:
-  { \prg_map_break:Nn \text_map_break: { } }
-\cs_new:Npn \text_map_break:n
-  { \prg_map_break:Nn \text_map_break: }
+\cs_new:Npn \__text_map_collect_auxv:nNnnnnn #1#2#3#4#5#6#7
+  {
+    \str_case:neTF {#1}
+      { \tl_map_function:nN {#6} \__text_map_collect_auxiii:n }
+      { \use:c { __text_map_ #1 :Nnnn } #2 { wordbreak } { } {#3#4#7} }
+      {
+        \__text_map_if_ignorable:nTF {#1}
+          {
+            \__text_map_lookahead:Nnnnnw #2 { wordbreak } {#3} {#4#7}
+              { \__text_map_collect_auxiv:nnNnnnn {#5} {#6} }
+          }
+          {
+            \__text_map_output:Nn #2 {#3}
+            \__text_map_loop:Nnnw #2 { wordbreak } { } #4#7
+          }
+      }
+  }
+\cs_new:Npn \__text_map_ALetter:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } }
+      { { MidLetter } { MidNumLet } { Single_Quote } }
+      { { ALetter } { Hebrew_Letter } }
+  }
+\cs_new:Npn \__text_map_Hebrew_Letter:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } { Single_Quote } }
+      { { MidLetter } { MidNumLet } { Double_Quote } }
+      { { Hebrew_Letter } }
+  }
+\cs_new:Npn \__text_map_Katakana:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_collect:Nnnnn #1 {#4} { { Katakana } } { } { }
+  }
+\cs_new:Npn \__text_map_Numeric:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_collect:Nnnnn #1 {#4}
+      { { ALetter } { Hebrew_Letter } { Numeric } }
+      { { MidNum } { MidNumLet } { Single_Quote } }
+      { { Numeric } }
+  }
+\cs_new:Npn \__text_map_WSegSpace:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_collect:Nnnnn #1 {#4} { { WSegSpace } } { } { }
+  }
+\cs_new:Npn \__text_map_ExtendNumLet:Nnnn #1#2#3#4
+  {
+    \__text_map_output:Nn #1 {#3}
+    \__text_map_lookahead:Nnnnnw #1 { wordbreak } {#4} { }
+      \__text_map_ExtendNumLet_auxi:Nnnnn
+  }
+\cs_new:Npn \__text_map_ExtendNumLet_auxi:Nnnnn #1#2#3#4#5
+  {
+    \exp_args:Ne \__text_map_ExtendNumLet_auxii:nNnn
+      {
+        \__kernel_codepoint_to_wordbreak_class:n
+          { \__text_codepoint_from_chars:Nw #5 }
+      }
+      #1 {#3} {#5}
+  }
+\cs_new:Npn \__text_map_ExtendNumLet_auxii:nNnn #1#2#3#4
+  {
+    \str_case:nnTF {#1}
+      {
+        { ALetter }       { }
+        { Hebrew_Letter } { }
+        { Numeric }       { }
+        { Katakana }      { }
+        { ExtendNumLet }  { }
+      }
+      {
+        \cs_if_exist_use:cF { __text_map_ #1 :Nnnn } % TEMP?
+          { \__text_map_Other:Nnnn }
+            #2 { wordbreak } { } {#3#4}
+      }
+      {
+        \__text_map_if_ignorable:nTF {#1}
+          {
+            \__text_map_lookahead:Nnnnnw #2 { wordbreak } {#3#4} { }
+              \__text_map_ExtendNumLet_auxi:Nnnnn
+          }
+          {
+            \__text_map_output:Nn #2 {#3}
+            \__text_map_loop:Nnnw #2 { wordbreak } { } #4
+          }
+      }
+  }
 \cs_new_protected:Npn \text_map_inline:nn #1#2
   {
     \int_gincr:N \g__kernel_prg_map_int
@@ -36888,6 +37564,16 @@
     \prg_break_point:Nn \text_map_break:
       { \int_gdecr:N \g__kernel_prg_map_int }
   }
+\cs_new_protected:Npn \text_words_map_inline:nn #1#2
+  {
+    \int_gincr:N \g__kernel_prg_map_int
+    \cs_gset_protected:cpn
+      { __text_map_ \int_use:N \g__kernel_prg_map_int :w } ##1 {#2}
+    \exp_args:Nnc \text_words_map_function:nN {#1}
+      { __text_map_ \int_use:N \g__kernel_prg_map_int :w }
+    \prg_break_point:Nn \text_map_break:
+      { \int_gdecr:N \g__kernel_prg_map_int }
+  }
 %% File: l3text-purify.dtx
 \__kernel_quark_new_test:N \__text_if_recursion_tail_stop:N
 \cs_new:Npn \text_purify:n #1

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3-generic.tex	2025-04-08 20:12:11 UTC (rev 74879)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2025-01-18}%
+\def\ExplFileDate{2025-03-26}%
 \let\ExplLoaderFileDate\ExplFileDate
 \begingroup
   \catcode`\_=11

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.ltx	2025-04-08 20:12:11 UTC (rev 74879)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2025-01-18}%
+\def\ExplFileDate{2025-03-26}%
 \let\ExplLoaderFileDate\ExplFileDate
 \begingroup
   \catcode`\_=11

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/expl3.sty	2025-04-08 20:12:11 UTC (rev 74879)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: expl3.dtx
-\def\ExplFileDate{2025-01-18}%
+\def\ExplFileDate{2025-03-26}%
 \let\ExplLoaderFileDate\ExplFileDate
 \ProvidesPackage{expl3}
   [%

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/l3debug.def	2025-04-08 20:12:11 UTC (rev 74879)
@@ -19,7 +19,7 @@
 %% and all files in that bundle must be distributed together.
 %% 
 %% File: l3debug.dtx
-\ProvidesExplFile{l3debug.def}{2025-01-18}{}{L3 Debugging support}
+\ProvidesExplFile{l3debug.def}{2025-03-26}{}{L3 Debugging support}
 \scan_new:N \s__debug_stop
 \cs_new:Npn \__debug_use_i_delimit_by_s_stop:nw #1 #2 \s__debug_stop {#1}
 \quark_new:N \q__debug_recursion_tail

Modified: trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls
===================================================================
--- trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls	2025-04-08 20:11:39 UTC (rev 74878)
+++ trunk/Master/texmf-dist/tex/latex/l3kernel/l3doc.cls	2025-04-08 20:12:11 UTC (rev 74879)
@@ -20,7 +20,7 @@
 %% 
 %% File: l3doc.dtx
 \RequirePackage{calc}
-\ProvidesExplClass{l3doc}{2025-01-18}{}
+\ProvidesExplClass{l3doc}{2025-03-26}{}
   {L3 Experimental documentation class}
 \clist_new:N \g_docinput_clist
 \seq_new:N \g_doc_functions_seq



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