summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3experimental
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/l3experimental
Initial commit
Diffstat (limited to 'macros/latex/contrib/l3experimental')
-rw-r--r--macros/latex/contrib/l3experimental/CHANGELOG.md130
-rw-r--r--macros/latex/contrib/l3experimental/README.md147
-rw-r--r--macros/latex/contrib/l3experimental/l3benchmark.dtx584
-rw-r--r--macros/latex/contrib/l3experimental/l3benchmark.ins58
-rw-r--r--macros/latex/contrib/l3experimental/l3benchmark.pdfbin0 -> 314549 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3cctab.dtx422
-rw-r--r--macros/latex/contrib/l3experimental/l3cctab.ins62
-rw-r--r--macros/latex/contrib/l3experimental/l3cctab.pdfbin0 -> 330803 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3color.dtx902
-rw-r--r--macros/latex/contrib/l3experimental/l3color.ins58
-rw-r--r--macros/latex/contrib/l3experimental/l3color.pdfbin0 -> 350093 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-boxes.dtx155
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-code.pdfbin0 -> 613079 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-code.tex64
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-layers.dtx216
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-paths.dtx1037
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-points.dtx971
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-scopes.dtx270
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-softpath.dtx615
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-state.dtx205
-rw-r--r--macros/latex/contrib/l3experimental/l3draw-transforms.dtx339
-rw-r--r--macros/latex/contrib/l3experimental/l3draw.dtx1166
-rw-r--r--macros/latex/contrib/l3experimental/l3draw.ins70
-rw-r--r--macros/latex/contrib/l3experimental/l3draw.pdfbin0 -> 491444 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3galley.dtx2662
-rw-r--r--macros/latex/contrib/l3experimental/l3galley.pdfbin0 -> 386124 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3graphics.dtx716
-rw-r--r--macros/latex/contrib/l3experimental/l3graphics.ins56
-rw-r--r--macros/latex/contrib/l3experimental/l3graphics.pdfbin0 -> 363470 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3pdf-code.pdfbin0 -> 276751 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3pdf-code.tex56
-rw-r--r--macros/latex/contrib/l3experimental/l3pdf.dtx337
-rw-r--r--macros/latex/contrib/l3experimental/l3pdf.ins60
-rw-r--r--macros/latex/contrib/l3experimental/l3pdf.pdfbin0 -> 350290 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3str-format.dtx982
-rw-r--r--macros/latex/contrib/l3experimental/l3str-format.pdfbin0 -> 345219 bytes
-rw-r--r--macros/latex/contrib/l3experimental/l3str.ins58
-rw-r--r--macros/latex/contrib/l3experimental/l3sys-shell.dtx372
-rw-r--r--macros/latex/contrib/l3experimental/l3sys-shell.ins62
-rw-r--r--macros/latex/contrib/l3experimental/l3sys-shell.pdfbin0 -> 312113 bytes
-rw-r--r--macros/latex/contrib/l3experimental/xcoffins.dtx938
-rw-r--r--macros/latex/contrib/l3experimental/xcoffins.ins58
-rw-r--r--macros/latex/contrib/l3experimental/xcoffins.pdfbin0 -> 373079 bytes
-rw-r--r--macros/latex/contrib/l3experimental/xgalley.dtx1559
-rw-r--r--macros/latex/contrib/l3experimental/xgalley.ins59
-rw-r--r--macros/latex/contrib/l3experimental/xgalley.pdfbin0 -> 286254 bytes
46 files changed, 15446 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3experimental/CHANGELOG.md b/macros/latex/contrib/l3experimental/CHANGELOG.md
new file mode 100644
index 0000000000..d2288ef1b9
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/CHANGELOG.md
@@ -0,0 +1,130 @@
+# Changelog
+All notable changes to the `l3experimental` bundle since the start of 2018
+will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+this project uses date-based 'snapshot' version identifiers.
+
+## [Unreleased]
+
+## [2019-08-25]
+
+### Changed
+
+- `\draw_unit_vector:n` returns a vertical vector when the length is
+ zero (see #609)
+- Collect `pwd` data with no `\endlinechar` (see #613)
+- Default precision in `\fp_format:nn` when no style is specified
+
+### Fixed
+
+- Corrected behaviour of catcode tables (see #610)
+
+## [2019-07-01]
+
+### Added
+
+- New module `l3pdf`
+
+### Changed
+
+- Re-order arguments for polar points (`l3draw`)
+
+### Removed
+
+- `l3str-convert` module: moved to `l3kernel` (`expl3` core)
+
+## [2019-05-28]
+
+### Added
+
+- New `l3graphics` module
+
+### Fixed
+
+- Missing `\scan_stop:` in benchmark code (fixes #577)
+
+## [2019-05-03]
+
+## Fixed
+
+- Clipping of paths by `l3draw`
+
+## [2019-03-05]
+
+### Added
+
+- Support for drawing layers
+
+### Changed
+
+- Update `l3draw` transformation names
+
+## [2019-01-28]
+
+### Changed
+
+- Track `expl3` changes
+
+## [2018-10-31]
+
+### Added
+
+- New module `l3cctab`
+
+## [2018-10-26]
+
+### Added
+
+- New module `l3benchmark`
+
+## [2018-08-24]
+
+### Fixed
+
+- Actually distribute `l3sys-shell`
+
+## [2018-08-23]
+
+### Added
+- `CHANGELOG.md` (fixes #460)
+- `l3sys-shell` for experimental shell functions
+
+## [2018-05-12]
+
+### Changed
+- Track changes in primitive naming in `l3kernel`
+
+## [2018-04-30]
+
+### Changed
+- Switch to ISO date format
+- Improve cross-module use of internal functions
+
+## [2018-03-05]
+
+### Added
+- Several new functions added to `l3draw`
+
+## [2018-02-21]
+
+### Added
+- New `l3color` module using `xcolor`-like expression syntax
+- New `l3draw` module, based on `pgf` layer of the TikZ system
+
+[Unreleased]: https://github.com/latex3/latex3/compare/2019-08-25...HEAD
+[2019-08-25]: https://github.com/latex3/latex3/compare/2019-07-01...2019-08-25
+[2019-07-01]: https://github.com/latex3/latex3/compare/2019-05-28...2019-07-01
+[2019-05-28]: https://github.com/latex3/latex3/compare/2019-05-03...2019-05-28
+[2019-05-03]: https://github.com/latex3/latex3/compare/2019-03-05...2019-05-03
+[2019-03-05]: https://github.com/latex3/latex3/compare/2019-01-28...2019-03-05
+[2019-01-28]: https://github.com/latex3/latex3/compare/2018-10-31...2019-01-28
+[2018-10-31]: https://github.com/latex3/latex3/compare/2018-10-26...2018-10-31
+[2018-10-26]: https://github.com/latex3/latex3/compare/2018-10-17...2018-10-26
+[2018-10-17]: https://github.com/latex3/latex3/compare/2018-08-24...2018-10-17
+[2018-08-24]: https://github.com/latex3/latex3/compare/2018-08-23...2018-08-24
+[2018-08-23]: https://github.com/latex3/latex3/compare/2018-05-12...2018-08-23
+[2018-05-12]: https://github.com/latex3/latex3/compare/2018-04-30...2018-05-12
+[2018-04-30]: https://github.com/latex3/latex3/compare/2018-03-05...2018-04-30
+[2018-03-05]: https://github.com/latex3/latex3/compare/2018-02-21...2018-03-05
+[2018-02-21]: https://github.com/latex3/latex3/compare/2017-12-16...2018-02-21
diff --git a/macros/latex/contrib/l3experimental/README.md b/macros/latex/contrib/l3experimental/README.md
new file mode 100644
index 0000000000..7892d19f5e
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/README.md
@@ -0,0 +1,147 @@
+Experimental LaTeX3 Concepts
+============================
+
+Release 2019-08-25
+
+Overview
+--------
+
+The `l3experimental` packages are a collection of experimental implementations
+for aspects of the LaTeX3 kernel, dealing with higher-level ideas such as the
+Designer Interface. Some of them work as stand alone packages, providing new
+functionality, and can be used on top of LaTeX2e with no changes to the
+existing kernel. Others go further, and redefine LaTeX2e internals to provide
+better methods for managing certain constructs. The packages in the collection
+are under active development and the interfaces may change.
+
+All of the material in the collection requires the LaTeX3 base layer package
+[`l3kernel`](http://ctan.org/pkg/l3kernel). The two packages must be installed
+in matching versions: if you update `l3experimental`, make sure that `l3kernel` is
+updated at the same time.
+
+Currently included in the CTAN release of l3experimental are the following
+bundles:
+* `l3benchmark`
+* `l3cctab`
+* `l3color`
+* `l3draw`
+* `l3graphics`
+* `l3pdf`
+* `l3str`
+* `l3sys-shell`
+* `xcoffins`
+* `xgalley`
+
+`l3benchmark`
+-------------
+
+This module provides support for benchmarking the performance of code.
+
+`l3cctab`
+---------
+
+This module provides support for saving and restoring category codes
+_en masse_ in a table. This is supported for the 8-bit range for all engines,
+and for the entire Unicode range with LuaTeX.
+
+`l3color`
+---------
+
+This module provides support for setting colors using a range of color models.
+It also allows the construction of 'color expressions', in which multiple colors
+are mixed together at the macro level. There is also support for spot colors:
+the latter is highly experimental.
+
+`l3draw`
+--------
+
+This module provides a code-level interface for constructing drawings. The
+interfaces are heavily inspired by the `pgf` layer of the widely-used
+TikZ system.
+
+`l3graphics`
+-------------
+
+This module provides interfaces for the inclusion of graphics files
+in documents, similar to the `graphics` package.
+
+`l3pdf`
+-------
+
+This module provides support for core PDF concepts such compression, objects,
+PDF version and so on.
+
+`l3str`
+-------
+
+A 'string' in TeX terms is a token list in which all of the tokens have
+category code 12 ('other'), with the exception of spaces which have the
+category code 10 ('space'). The `l3strf-format` module provides methods
+for formatting such strings.
+
+`l3sys-shell`
+-------------
+
+This module provides abstractions for common shell functions, e.g. file
+deletion and copying.
+
+`xcoffins`
+----------
+
+A _coffin_ is a 'box with handles': a data structure which comprises
+both a TeX box and associated information to allow controlled typesetting.
+The `xcoffins` package provides a high-level interface for manipulating
+coffins. This is supported by the lower-level `l3coffins` package, which
+provides the data structure.
+
+`xgalley`
+---------
+
+In LaTeX3 terminology a galley is a rectangular area which receives
+text and other material filling it from top. The vertically extend of
+a galley is normally not restricted: instead certain chunks are taken
+off the top of an already partially filled galley to form columns or
+similar areas on a page. This process is typically asynchronous but
+there are ways to control or change its behaviour. The xgalley module
+provides a mechanism for filling galleys and controlling the spacing,
+hyphenation and justification within them.
+
+Discussion
+----------
+
+Discussion concerning the approach, suggestions for improvements,
+changes, additions, _etc._ should be addressed to the list
+[LaTeX-L](https://listserv.uni-heidelberg.de/cgi-bin/wa?A0=LATEX-L).
+
+You can subscribe to this list by sending mail to
+
+ listserv@urz.uni-heidelberg.de
+
+with the body containing
+
+ subscribe LATEX-L <Your-First-Name> <Your-Second-Name>
+
+Issues
+------
+
+The issue tracker for LaTeX3 is currently located
+[on GitHub](https://github.com/latex3/latex3/issues).
+
+Please report specific issues with LaTeX3 code there; more general
+discussion should be directed to the [LaTeX-L list](#Discussion).
+
+The LaTeX3 Project
+------------------
+
+Development of LaTeX3 is carried out by
+[The LaTeX3 Project](https://www.latex-project.org/latex3/).
+
+The development team can be contacted
+by e-mail: <latex-team@latex-project.org>; for general LaTeX3 discussion
+the [LaTeX-L list](#Discussion) should be used.
+
+-----
+
+<p>Copyright (C) 1998-2011,2015-2019 The LaTeX3 Project <br />
+<a href="http://latex-project.org/">http://latex-project.org/</a> <br />
+All rights reserved.</p>
diff --git a/macros/latex/contrib/l3experimental/l3benchmark.dtx b/macros/latex/contrib/l3experimental/l3benchmark.dtx
new file mode 100644
index 0000000000..c7b172488a
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3benchmark.dtx
@@ -0,0 +1,584 @@
+% \iffalse meta-comment
+%
+%% File: l3benchmark.dtx
+%
+% Copyright (C) 2011,2012,2014-2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3benchmark} package\\ Experimental benchmarking^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Additions to \pkg{l3sys}: elapsed time}
+%
+% \begin{function}{\sys_gzero_timer:}
+% \begin{syntax}
+% \cs{sys_gzero_timer:}
+% \end{syntax}
+% Resets the timer to zero.
+% \end{function}
+%
+% \begin{function}[EXP]{\sys_timer:}
+% \begin{syntax}
+% \cs{sys_timer:}
+% \end{syntax}
+% Expands to the current value of the engine's timer clock, a
+% non-negative integer. In engines without clock support this expands
+% to $0$ after an error. In \LuaTeX{} only the CPU time is measured,
+% while in other engines real time is measured (including time waiting
+% for user input).
+% \end{function}
+%
+% \section{Benchmark}
+%
+% \begin{variable}{\g_benchmark_duration_target_fp}
+% This variable (default value: $1$) controls roughly for how long
+% \cs{benchmark:n} will repeat code to more accurately benchmark it.
+% The actual duration of one call to \cs{benchmark:n} typically lasts
+% between half and twice \cs{g_benchmark_duration_target_fp} seconds,
+% unless of course running the code only once already lasts longer
+% than this.
+% \end{variable}
+%
+% \begin{function}{\benchmark_once:n}
+% \begin{syntax}
+% \cs{benchmark_once:n} \Arg{code}
+% \end{syntax}
+% Prints to the terminal the time taken by \TeX{} to run the
+% \meta{code}, and an estimated number of elementary operations. The
+% \meta{code} is run only once so the time may be quite inaccurate for
+% fast code.
+% \end{function}
+%
+% \begin{function}{\benchmark:n}
+% \begin{syntax}
+% \cs{benchmark:n} \Arg{code}
+% \end{syntax}
+% Prints to the terminal the time taken by \TeX{} to run the
+% \meta{code}, and an estimated number of elementary operations. The
+% \meta{code} may be run many times and not within a group, thus code
+% with side-effects may cause problems.
+% \end{function}
+%
+% \begin{function}{\benchmark_tic:, \benchmark_toc:}
+% \begin{syntax}
+% \cs{benchmark_tic:} \meta{slow code} \cs{benchmark_toc:}
+% \end{syntax}
+% When it is not possible to run \cs{benchmark:n} (e.g., the code is
+% part of the execution of a package which cannot be looped) the
+% tic/toc commands can be used instead to time between two points in
+% the code. When executed, \cs{benchmark_tic:} will print a line to the
+% terminal, and \cs{benchmark_toc:} will print a matching line with a
+% time to indicate the duration between them in seconds.
+% These commands can be nested.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3benchmark} implementation}
+%
+% Our working unit is the scaled second, namely $2^{-16}$ seconds.
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3benchmark}{2019-08-25}{}
+ {L3 Experimental benchmarking}
+%</package>
+% \end{macrocode}
+%
+% \subsection{Additions to \pkg{l3sys}: elapsed time}
+%
+% \begin{macrocode}
+%<@@=sys>
+% \end{macrocode}
+%
+% \begin{macro}[EXP]{\sys_timer:}
+% \begin{macro}[EXP,pTF]{\sys_if_timer_exist:}
+% In \LuaTeX{}, use emulation (see \pkg{l3luatex}),
+% otherwise try to locate the primitive. The
+% elapsed time will be available if this succeeds.
+% \begin{macrocode}
+\sys_if_engine_luatex:TF
+ {
+ \cs_new:Npn \sys_timer:
+ { \lua_now:n { l3kernel.elapsedtime() } }
+ }
+ {
+ \cs_if_exist:NT \tex_elapsedtime:D
+ {
+ \cs_new:Npn \sys_timer:
+ { \int_value:w \tex_elapsedtime:D }
+ }
+ }
+\@@_const:nn { sys_if_timer_exist }
+ { \cs_if_exist_p:N \sys_timer: }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\sys_gzero_timer:, \@@_timer_fp:}
+% \begin{variable}{\g_@@_timer_base_fp}
+% Three case, and in each case we define \cs{sys_gzero_timer:}.
+% \begin{macrocode}
+\cs_if_exist:NTF \sys_timer:
+ {
+ \sys_if_engine_luatex:TF
+ {
+ \cs_new_protected:Npn \sys_gzero_timer:
+ { \lua_now:n { l3kernel.resettimer() } }
+ }
+ { \cs_new_protected:Npn \sys_gzero_timer: { \tex_resettimer:D } }
+ }
+% \end{macrocode}
+% If the elapsed time cannot be accessed then we define user
+% commands to produce errors.
+% \begin{macrocode}
+ {
+ \__kernel_msg_new:nnnn { kernel } { no-elapsed-time }
+ { No~clock~detected~for~#1. }
+ { The~current~engine~provides~no~way~to~access~the~system~time. }
+ \cs_new_protected:Npn \sys_gzero_timer:
+ {
+ \__kernel_msg_error:nnn { kernel } { no-elapsed-time }
+ { \sys_gzero_timer: }
+ }
+ \cs_new:Npn \sys_timer:
+ {
+ \int_value:w
+ \__kernel_msg_expandable_error:nnn { kernel } { no-elapsed-time }
+ { \sys_timer: }
+ \c_zero_int
+ }
+ }
+% \end{macrocode}
+% \end{variable}
+% \end{macro}
+%
+% \subsection{Benchmarking code}
+%
+% \begin{macrocode}
+%<@@=benchmark>
+% \end{macrocode}
+%
+% \begin{variable}{\g_benchmark_duration_target_fp}
+% The benchmark is constrained to take roughly (from half to twice)
+% \cs{g_benchmark_duration_target_fp} seconds, unless one iteration of
+% the code takes longer.
+% \begin{macrocode}
+\fp_new:N \g_benchmark_duration_target_fp
+\fp_gset:Nn \g_benchmark_duration_target_fp { 1 }
+% \end{macrocode}
+% \end{variable}
+%
+% Having access to the system time is essential.
+% \begin{macrocode}
+\sys_if_timer_exist:F
+ {
+ \fp_gset:Nn \g_benchmark_duration_target_fp { nan }
+ \cs_new_protected:Npn \benchmark_once:n #1
+ { \msg_error:nn { benchmark } { no-time } }
+ \cs_new_eq:NN \benchmark:n \benchmark_once:n
+ \cs_new_protected:Npn \benchmark_tic:
+ { \msg_error:nn { benchmark } { no-time } }
+ \cs_new_eq:NN \benchmark_toc: \benchmark_tic:
+ \msg_new:nnnn { benchmark } { no-time }
+ { The~l3benchmark~package~failed~to~access~a~clock. }
+ { The~current~engine~provides~no~way~to~access~the~system~time. }
+ \msg_critical:nn { benchmark } { no-time }
+ }
+% \end{macrocode}
+%
+% \subsubsection{Raw measurement}
+%
+% \begin{variable}{\g_@@_nesting_int}
+% \begin{macro}{\@@_raw:nN, \@@_raw_aux:N, \@@_raw_end:N}
+% Store in the given integer variable the time it took to perform a given
+% piece of code, in scaled seconds. We call \cs{sys_timer:} as
+% close before and after the code as possible. We store the
+% intermediate result in a new integer when \cs{@@_raw:nN} is
+% nested.
+% \begin{macrocode}
+\int_new:N \g_@@_nesting_int
+\cs_new_protected:Npn \@@_raw:nN #1
+ {
+ \int_gincr:N \g_@@_nesting_int
+ \exp_args:Nc \@@_raw_aux:N
+ { g_@@_ \int_use:N \g_@@_nesting_int _int }
+ \@@_raw_aux:
+ #1
+ \@@_raw_end:N
+ }
+\cs_new_protected:Npn \@@_raw_aux:N #1
+ {
+ \int_gzero_new:N #1
+ \cs_gset_protected:Npn \@@_raw_aux: { \int_gset:Nn #1 { \sys_timer: } }
+ }
+\cs_new_protected:Npn \@@_raw_end:N #1
+ {
+ \int_gset:Nn #1
+ {
+ \sys_timer: -
+ \int_use:c { g_@@_ \int_use:N \g_@@_nesting_int _int }
+ }
+ \int_gdecr:N \g_@@_nesting_int
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{variable}
+%
+% \begin{macro}{\@@_raw_replicate:nnN, \@@_tmp:w}
+% Here, we wish to measure the time it takes for the piece of code
+% |#2| to be run |#1| times, and store the result in the
+% integer~|#3|.
+%
+% If the number of copies required is large, defining \cs{@@_tmp:w}
+% would exhaust \TeX{}'s main memory. In that case, we replicate
+% $|#1|/5000$ times the given code before passing it to the main call
+% to \cs{@@_tmp:w}. Of course the division rounds to an integer, so
+% that step introduces a relative error of order at most
+% $5000/500000$, less than many other sources of variability.
+%
+% We subtract the time for another call to \cs{@@_tmp:w}, with the
+% same arguments (to capture the time it takes to read the argument)
+% but empty expansion.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tmp:w ?
+\cs_new_protected:Npn \@@_raw_replicate:nnN #1
+ {
+ \int_compare:nNnTF {#1} > { 500000 }
+ { \@@_raw_replicate_large:nnN {#1} }
+ { \@@_raw_replicate_small:nnN {#1} }
+ }
+\cs_new_protected:Npn \@@_raw_replicate_large:nnN #1#2
+ {
+ \cs_set:Npx \@@_tmp:w ##1 { \prg_replicate:nn { 5000 } {##1} }
+ \exp_args:Nno \@@_raw_replicate:nnN { #1 / 5000 }
+ { \@@_tmp:w {#2} }
+ }
+\cs_new_protected:Npn \@@_raw_replicate_small:nnN #1#2
+ {
+ \cs_set:Npx \@@_tmp:w ##1##2 { \prg_replicate:nn {#1} {##1} }
+ \@@_raw:nN { \@@_tmp:w {#2} { } } \g_@@_time_int
+ \exp_args:No \@@_raw_replicate_aux:nnN
+ { \int_use:N \g_@@_time_int } {#2}
+ }
+\cs_new_protected:Npn \@@_raw_replicate_aux:nnN #1#2#3
+ {
+ \@@_raw:nN { \@@_tmp:w { } {#2} } \g_@@_time_int
+ \int_gset:Nn #3 { #1 - \g_@@_time_int }
+ \cs_set_eq:NN \@@_tmp:w \prg_do_nothing:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Main benchmarking}
+%
+% \begin{variable}{\g_@@_time_fp, \g_@@_ops_fp}
+% Functions such as \cs{benchmark:n} store the measured time in
+% \cs{g_@@_time_fp} (in seconds) and the estimated number of
+% operations in \cs{g_@@_ops_fp}.
+% \begin{macrocode}
+\fp_new:N \g_@@_time_fp
+\fp_new:N \g_@@_ops_fp
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_duration_int}
+% A conversion of \cs{g_benchmark_duration_target_fp} seconds into scaled seconds.
+% \begin{macrocode}
+\int_new:N \g_@@_duration_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_time_int, \g_@@_time_a_int, \g_@@_time_b_int, \g_@@_time_c_int, \g_@@_time_d_int}
+% These variables hold the time for running a piece of code, as an
+% integer in scaled seconds.
+% \begin{macrocode}
+\int_new:N \g_@@_time_int
+\int_new:N \g_@@_time_a_int
+\int_new:N \g_@@_time_b_int
+\int_new:N \g_@@_time_c_int
+\int_new:N \g_@@_time_d_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_repeat_int}
+% Holds the number of times that the piece of code was
+% repeated when timing.
+% \begin{macrocode}
+\int_new:N \g_@@_repeat_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_code_tl}
+% Holds the piece of code to repeat.
+% \begin{macrocode}
+\tl_new:N \g_@@_code_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\benchmark_once:n}
+% Convert the raw time from scaled seconds to seconds, and convert to
+% a number of operations. It is important to measure the elementary
+% operation before running the user code because both measurements use
+% the same temporary variables.
+% \begin{macrocode}
+\cs_new_protected:Npn \benchmark_once:n #1
+ {
+ \@@_measure_op:
+ \@@_raw:nN {#1} \g_@@_time_int
+ \fp_gset:Nn \g_@@_time_fp { \g_@@_time_int / 65536 }
+ \@@_display:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\benchmark:n}
+% After setting up some variables the work is done by \cs{@@_aux:}.
+% \begin{macrocode}
+\cs_new_protected:Npn \benchmark:n #1
+ {
+ \@@_measure_op:
+ \tl_gset:Nn \g_@@_code_tl {#1}
+ \@@_aux:
+ \@@_display:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_aux:}
+% The main timing function. First time the user code once. If that
+% took more than half the allotted time (\cs{g_@@_duration_int}) we're
+% done. If that took much less, repeatedly quadruple the number of
+% copies until it takes a reasonable amount of time. Once we reach a
+% reasonable time (or we risk an overflow), compute a number of times
+% that can fit in one quarter of the allotted time and measure that
+% four times. To save time we reuse the result of the first pass if
+% \cs{g_@@_repeat_int} is one. Once we have four results, find the
+% smallest, divided by $65536$ and by the number of repetitions, and
+% display that.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_aux:
+ {
+ \int_gset:Nn \g_@@_repeat_int { 1 }
+ \@@_raw:nN { \g_@@_code_tl } \g_@@_time_int
+ \int_compare:nNnF \g_@@_time_int < { \g_@@_duration_int / 2 }
+ { \prg_break: }
+ \bool_until_do:nn
+ {
+ \int_compare_p:nNn \g_@@_time_int > { \g_@@_duration_int / 32 }
+ || \int_compare_p:nNn \g_@@_repeat_int > { \c_max_int / 4 }
+ }
+ {
+ \int_gset:Nn \g_@@_repeat_int { 4 * \g_@@_repeat_int }
+ \@@_run:N \g_@@_time_int
+ }
+ \int_gset:Nn \g_@@_repeat_int
+ {
+ \fp_to_int:n
+ {
+ max ( 1 , min ( \c_max_int ,
+ \g_@@_duration_int * \g_@@_repeat_int /
+ \int_eval:n { 4 * \g_@@_time_int } ) )
+ }
+ }
+ \int_compare:nNnTF \g_@@_repeat_int = 1
+ { \int_gset_eq:NN \g_@@_time_a_int \g_@@_time_int }
+ { \@@_run:N \g_@@_time_a_int }
+ \@@_run:N \g_@@_time_b_int
+ \@@_run:N \g_@@_time_c_int
+ \@@_run:N \g_@@_time_d_int
+ \int_gset:Nn \g_@@_time_int
+ {
+ \int_min:nn
+ { \int_min:nn \g_@@_time_a_int \g_@@_time_b_int }
+ { \int_min:nn \g_@@_time_c_int \g_@@_time_d_int }
+ }
+ \prg_break_point:
+ \int_compare:nNnT \g_@@_time_int < 3 { \int_gzero:N \g_@@_time_int }
+ \fp_gset:Nn \g_@@_time_fp
+ { \g_@@_time_int / \g_@@_repeat_int / 65536 }
+ }
+\cs_new_protected:Npn \@@_run:N
+ { \exp_args:NNo \@@_raw_replicate:nnN \g_@@_repeat_int { \g_@@_code_tl } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Display}
+%
+% \begin{variable}{\g_@@_one_op_fp}
+% Time for one operation.
+% \begin{macrocode}
+\fp_new:N \g_@@_one_op_fp
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_measure_op:}
+% Measure one arbitrary single operation (which we put in \cs{g_@@_code_tl}).
+% This uses a common auxiliary \cs{@@_aux:} with the main benchmark function.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_measure_op:
+ {
+ \int_gset:Nn \g_@@_duration_int
+ { \fp_to_int:n { 65536 * \g_benchmark_duration_target_fp } / 4 }
+ \tl_gset:Nn \g_@@_code_tl
+ { \int_gadd:Nn \g_@@_duration_int { 0 } }
+ \@@_aux:
+ \fp_gset:Nn \g_@@_one_op_fp { max(\g_@@_time_fp, 1e-16) }
+ \int_gset:Nn \g_@@_duration_int
+ { \fp_to_int:n { 65536 * \g_benchmark_duration_target_fp } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_fp_to_tl:N, \@@_fp_to_tl_aux:nN}
+% Similar to \cs{fp_to_tl:N} but rounds to $3$ significant digits and
+% uses scientific notation starting from |1e3|.
+% \begin{macrocode}
+\cs_new:Npn \@@_fp_to_tl:N #1
+ {
+ \fp_compare:nTF { abs(#1) < 1000 }
+ { \fp_to_tl:n { round(#1, 2 - logb(#1)) } }
+ {
+ \exp_args:Nf \@@_fp_to_tl_aux:nN
+ { \fp_to_int:n { logb(#1) } } #1
+ }
+ }
+\cs_new:Npn \@@_fp_to_tl_aux:nN #1#2
+ { \fp_to_tl:n { round(#2 * 1e-#1, 2) } e#1 }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_display:}
+% Function to display the time that was measured and the estimated
+% number of operations.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_display:
+ {
+ \fp_gset:Nn \g_@@_ops_fp { \g_@@_time_fp / \g_@@_one_op_fp }
+ \iow_term:x
+ {
+ \@@_fp_to_tl:N \g_@@_time_fp \c_space_tl seconds \c_space_tl
+ ( \@@_fp_to_tl:N \g_@@_ops_fp \c_space_tl ops)
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Benchmark tic toc}
+%
+% \begin{variable}{\g_@@_tictoc_int, \g_@@_tictoc_seq, \l_@@_tictoc_pop_tl}
+% \begin{macrocode}
+\int_new:N \g_@@_tictoc_int
+\seq_new:N \g_@@_tictoc_seq
+\tl_new:N \l_@@_tictoc_pop_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}[EXP]{\@@_tictoc_prefix:}
+% We include the package name in analogy with continuation lines of
+% error/warning messages.
+% \begin{macrocode}
+\cs_new:Npn \@@_tictoc_prefix:
+ {
+ (l3benchmark) \c_space_tl
+ + \prg_replicate:nn { \g_@@_tictoc_int } { -+ } \c_space_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\benchmark_tic:}
+% \begin{macrocode}
+\cs_new_protected:Npn \benchmark_tic:
+ {
+ \iow_term:x { \@@_tictoc_prefix: TIC }
+ \exp_args:NNf \seq_gput_right:Nn \g_@@_tictoc_seq { \sys_timer: }
+ \int_gincr:N \g_@@_tictoc_int
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\benchmark_toc:, \@@_toc:}
+% \begin{macrocode}
+\cs_new:Npn \benchmark_toc:
+ {
+ \seq_gpop_right:NNTF \g_@@_tictoc_seq \l_@@_tictoc_pop_tl
+ { \@@_toc: }
+ { \msg_error:nn { benchmark } { toc-first } }
+ }
+\cs_new_protected:Npn \@@_toc:
+ {
+ \int_gdecr:N \g_@@_tictoc_int
+ \fp_gset:Nn \g_@@_time_fp
+ { ( \sys_timer: - \l_@@_tictoc_pop_tl ) / 65536 }
+ \iow_term:x
+ {
+ \@@_tictoc_prefix:
+ TOC: \c_space_tl
+ \@@_fp_to_tl:N \g_@@_time_fp \c_space_tl s
+ }
+ }
+\msg_new:nnn { benchmark } { toc-first }
+ {
+ \token_to_str:N \benchmark_toc: \c_space_tl without~
+ \token_to_str:N \benchmark_tic: \c_space_tl !
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3benchmark.ins b/macros/latex/contrib/l3experimental/l3benchmark.ins
new file mode 100644
index 0000000000..d925b01b14
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3benchmark.ins
@@ -0,0 +1,58 @@
+\iffalse meta-comment
+
+File: l3benchmark.ins
+
+Copyright (C) 2011,2018,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3benchmark.sty}{\from{l3benchmark.dtx}{package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3benchmark.pdf b/macros/latex/contrib/l3experimental/l3benchmark.pdf
new file mode 100644
index 0000000000..99eeeb6285
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3benchmark.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3cctab.dtx b/macros/latex/contrib/l3experimental/l3cctab.dtx
new file mode 100644
index 0000000000..85a029b9bf
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3cctab.dtx
@@ -0,0 +1,422 @@
+% \iffalse meta-comment
+%
+%% File: l3cctab.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3cctab} package\\ Experimental category code tables^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{\pkg{l3cctab} documentation}
+%
+% A category code table enables rapid switching of all category codes in
+% one operation. For \LuaTeX{}, this is possible over the entire Unicode
+% range. For other engines, only the $8$-bit range ($0$-$255$) is covered by
+% such tables.
+%
+% \begin{function}{\cctab_new:N}
+% \begin{syntax}
+% \cs{cctab_new:N} \meta{category code table}
+% \end{syntax}
+% Creates a new category code table, initially with the codes as
+% used by \IniTeX{}.
+% \end{function}
+%
+% \begin{function}{\cctab_const:Nn}
+% \begin{syntax}
+% \cs{cctab_const:Nn} \meta{category code table} \Arg{category code set up}
+% \end{syntax}
+% Creates a new category code table and applies the
+% \meta{category code set up} on top of prevailing settings, then saves
+% as a constant table.
+% \end{function}
+%
+% \begin{function}{\cctab_gset:Nn}
+% \begin{syntax}
+% \cs{cctab_gset:Nn} \meta{category code table} \Arg{category code set up}
+% \end{syntax}
+% Sets the \meta{category code table} to apply the category codes
+% which apply when the prevailing régime is modified by the
+% \meta{category code set up}. Thus within a standard code block
+% the starting point will be the code applied by \cs{c_code_cctab}.
+% The assignment of the table is global: the underlying primitive does
+% not respect grouping.
+% \end{function}
+%
+% \begin{function}{\cctab_begin:N}
+% \begin{syntax}
+% \cs{cctab_begin:N} \meta{category code table}
+% \end{syntax}
+% Switches the category codes in force to those stored in the
+% \meta{category code table}. The prevailing codes before the
+% function is called are added to a stack, for use with
+% \cs{cctab_end:}.
+% \end{function}
+%
+% \begin{function}{\cctab_end:}
+% \begin{syntax}
+% \cs{cctab_end:}
+% \end{syntax}
+% Ends the scope of a \meta{category code table} started using
+% \cs{cctab_begin:N}, retuning the codes to those in force before the
+% matching \cs{cctab_begin:N} was used.
+% \end{function}
+%
+% \begin{variable}{\c_code_cctab}
+% Category code table for the code environment. This does not include
+% setting the behaviour of the line-end character, which is only
+% altered by \cs{ExplSyntaxOn}.
+% \end{variable}
+%
+% \begin{variable}{\c_document_cctab}
+% Category code table for a standard \LaTeX{} document. This does not
+% include setting the behaviour of the line-end character, which is
+% only altered by \cs{ExplSyntaxOff}.
+% \end{variable}
+%
+% \begin{variable}{\c_initex_cctab}
+% Category code table as set up by \IniTeX{}.
+% \end{variable}
+%
+% \begin{variable}{\c_other_cctab}
+% Category code table where all characters have category code $12$
+% (other).
+% \end{variable}
+%
+% \begin{variable}{\c_str_cctab}
+% Category code table where all characters have category code $12$
+% (other) with the exception of spaces, which have category code
+% $10$ (space).
+% \end{variable}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3cctab} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=cctab>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3cctab}{2019-08-25}{}
+ {L3 Experimental category code tables}
+%</package>
+% \end{macrocode}
+%
+% \begin{variable}{\g_@@_allocate_int}
+% \begin{variable}{\g_@@_stack_int}
+% \begin{variable}{\g_@@_stack_seq}
+% To allocate category code tables, both the read-only and stack
+% tables need to be followed. There is also a sequence stack for the
+% dynamic tables themselves.
+% \begin{macrocode}
+\int_new:N \g_@@_allocate_int
+\int_gset:Nn \g_@@_allocate_int { -1 }
+\int_new:N \g_@@_stack_int
+\seq_new:N \g_@@_stack_seq
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_tmp_tl}
+% Scratch space.
+% \begin{macrocode}
+\tl_new:N \l_@@_tmp_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\cctab_new:N}
+% \begin{macro}{\cctab_begin:N}
+% \begin{macro}{\cctab_end:}
+% \begin{macro}{\cctab_gset:Nn}
+% As \LuaTeX{} offers engine support for category code tables, and this is
+% entirely lacking from the other engines, we need two complementary
+% approaches here. Rather than intermix them, we split the set up based on
+% engine. (Some future \XeTeX{} may add support, at which point the
+% conditional here would be subtly different.)
+%
+% First, the \LuaTeX{} case.
+% \begin{macrocode}
+\sys_if_engine_luatex:TF
+ {
+% \end{macrocode}
+% Creating a new category code table is done slightly differently
+% from other registers. Low-numbered tables are more efficiently-stored
+% than high-numbered ones. There is also a need to have a stack of
+% flexible tables as well as the set of read-only ones. To satisfy both
+% of these requirements, odd numbered tables are used for read-only
+% tables, and even ones for the stack. Here, therefore, the odd numbers
+% are allocated.
+% \begin{macrocode}
+ \cs_new_protected:Npn \cctab_new:N #1
+ {
+ \__kernel_chk_if_free_cs:N #1
+%<*initex>
+ \int_gadd:Nn \g_@@_allocate_int { 2 }
+ \int_compare:nNnTF
+ \g_@@_allocate_int < { \c_max_register_int + 1 }
+ {
+ \tex_global:D \tex_chardef:D #1 \g_@@_allocate_int
+ \tex_initcatcodetable:D #1
+ }
+ {
+ \__kernel_msg_fatal:nnx
+ { kernel } { out-of-registers } { cctab }
+ }
+%</initex>
+%<*package>
+ \newcatcodetable #1
+%</package>
+ }
+% \end{macrocode}
+% The aim here is to ensure that the saved tables are read-only. This is
+% done by using a stack of tables which are not read only, and actually
+% having them as \enquote{in use} copies.
+% \begin{macrocode}
+ \cs_new_protected:Npn \cctab_begin:N #1
+ {
+ \seq_gpush:Nx \g_@@_stack_seq { \tex_the:D \tex_catcodetable:D }
+ \tex_catcodetable:D #1
+ \int_gadd:Nn \g_@@_stack_int { 2 }
+ \int_compare:nNnT \g_@@_stack_int > \c_max_register_int
+ { \__kernel_msg_fatal:nn { kernel } { cctab-stack-full } }
+ \tex_savecatcodetable:D \g_@@_stack_int
+ \tex_catcodetable:D \g_@@_stack_int
+ }
+ \cs_new_protected:Npn \cctab_end:
+ {
+ \int_gsub:Nn \g_@@_stack_int { 2 }
+ \seq_if_empty:NTF \g_@@_stack_seq
+ { \tl_set:Nn \l_@@_tmp_tl { 0 } }
+ { \seq_gpop:NN \g_@@_stack_seq \l_@@_tmp_tl }
+ \tex_catcodetable:D \l_@@_tmp_tl \scan_stop:
+ }
+% \end{macrocode}
+% Category code tables are always global, so only one version is needed.
+% The set up here is simple, and means that at the point of use there is
+% no need to worry about escaping category codes.
+% \begin{macrocode}
+ \cs_new_protected:Npn \cctab_gset:Nn #1#2
+ {
+ \group_begin:
+ #2
+ \tex_savecatcodetable:D #1
+ \group_end:
+ }
+ }
+% \end{macrocode}
+% Now the case for other engines. Here, we use an integer array for each
+% table. The index base is out-by-one, so we have an internal function to
+% handle that. The rest of the approach here is pretty simple: use a stack
+% of tables, and save to them at each |begin|. Unlike the \LuaTeX{} case,
+% we can't accidentally alter a saved table, which makes life a little
+% easier.
+% \begin{macrocode}
+ {
+ \cs_new_protected:Npn \@@_gstore:Nnn #1#2#3
+ {
+ \intarray_gset:cnn
+ { g_@@_ \int_use:N #1 _cctab } { #2 + 1 } {#3}
+ }
+% \end{macrocode}
+% Following the \LuaTeX{} pattern, a new table starts with \IniTeX{} codes.
+% \begin{macrocode}
+ \cs_new_protected:Npn \cctab_new:N #1
+ {
+ \int_gadd:Nn \g_@@_allocate_int { 2 }
+ \int_const:Nn #1 { \g_@@_allocate_int }
+ \intarray_new:cn { g_@@_ \int_use:N #1 _cctab } { 256 }
+ \int_step_inline:nn { 256 }
+ {
+ \intarray_gset:cnn
+ { g_@@_ \int_use:N #1 _cctab } {##1} { 12 }
+ }
+ \@@_gstore:Nnn #1 { 0 } { 9 }
+ \@@_gstore:Nnn #1 { 13 } { 5 }
+ \@@_gstore:Nnn #1 { 32 } { 10 }
+ \@@_gstore:Nnn #1 { 37 } { 14 }
+ \int_step_inline:nnn { 64 } { 89 }
+ { \@@_gstore:Nnn #1 {##1} { 11 } }
+ \@@_gstore:Nnn #1 { 92 } { 0 }
+ \int_step_inline:nnn { 97 } { 122 }
+ { \@@_gstore:Nnn #1 {##1} { 11 } }
+ \@@_gstore:Nnn #1 { 127 } { 15 }
+ }
+ \cs_new_protected:Npn \cctab_begin:N #1
+ {
+ \int_gadd:Nn \g_@@_stack_int { 2 }
+ \int_compare:nNnT \g_@@_stack_int > \c_max_register_int
+ { \__kernel_msg_fatal:nn { kernel } { cctab-stack-full } }
+ \cs_if_exist:cF { g_@@_ \int_use:N \g_@@_stack_int _cctab }
+ {
+ \intarray_new:cn
+ { g_@@_ \int_use:N \g_@@_stack_int _cctab }
+ { 256 }
+ }
+ \int_step_inline:nn { 256 }
+ {
+ \intarray_gset:cnn
+ { g_@@_ \int_use:N \g_@@_stack_int _cctab }
+ {##1}
+ { \char_value_catcode:n { ##1 - 1 } }
+ }
+ \int_step_inline:nn { 256 }
+ {
+ \char_set_catcode:nn { ##1 - 1 }
+ {
+ \intarray_item:cn
+ { g_@@_ \int_use:N #1 _cctab } {##1}
+ }
+ }
+ }
+ \cs_generate_variant:Nn \intarray_new:Nn { c }
+ \cs_generate_variant:Nn \intarray_gset:Nnn { c }
+ \cs_new_protected:Npn \cctab_end:
+ {
+ \int_step_inline:nn { 256 }
+ {
+ \char_set_catcode:nn { ##1 - 1 }
+ {
+ \intarray_item:cn
+ { g_@@_ \int_use:N \g_@@_stack_int _cctab }
+ {##1}
+ }
+ }
+ \int_gsub:Nn \g_@@_stack_int { 1 }
+ }
+ \cs_generate_variant:Nn \intarray_item:Nn { c }
+ \cs_new_protected:Npn \cctab_gset:Nn #1#2
+ {
+ \group_begin:
+ #2
+ \int_step_inline:nn { 256 }
+ {
+ \intarray_gset:cnn { g_@@_ \int_use:N #1 _cctab } {##1}
+ { \char_value_catcode:n { ##1 - 1 } }
+ }
+ \group_end:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\cctab_const:Nn}
+% \begin{macrocode}
+\cs_new_protected:Npn \cctab_const:Nn #1#2
+ {
+ \cctab_new:N #1
+ \cctab_gset:Nn #1 {#2}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}
+% {
+% \c_initex_cctab ,
+% \c_code_cctab ,
+% \c_document_cctab ,
+% \c_initex_cctab ,
+% \c_other_cctab ,
+% \c_str_cctab
+% }
+% Creating category code tables is easy using the function above.
+% The \texttt{other} and \texttt{string} ones are done by completely
+% ignoring the existing codes as this makes life a lot less complex.
+% \begin{macrocode}
+\cctab_const:Nn \c_code_cctab { }
+\cctab_const:Nn \c_document_cctab
+ {
+ \char_set_catcode_space:n { 9 }
+ \char_set_catcode_space:n { 32 }
+ \char_set_catcode_other:n { 58 }
+ \char_set_catcode_math_subscript:n { 95 }
+ \char_set_catcode_active:n { 126 }
+ }
+\cctab_const:Nn \c_other_cctab
+ {
+ \int_step_inline:nnn { 0 } { 127 }
+ { \char_set_catcode_other:n {#1} }
+ }
+\cctab_const:Nn \c_str_cctab
+ {
+ \int_step_inline:nnn { 0 } { 127 }
+ { \char_set_catcode_other:n {#1} }
+ \char_set_catcode_space:n { 32 }
+ }
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Messages}
+%
+% \begin{macrocode}
+\__kernel_msg_new:nnnn { kernel } { cctab-stack-full }
+ { The~category~code~table~stack~is~exhausted. }
+ {
+ LaTeX~has~been~asked~to~switch~to~a~new~category~code~table,~
+ but~there~is~no~more~space~to~do~this!
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+%\end{implementation}
+%
+%\PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3cctab.ins b/macros/latex/contrib/l3experimental/l3cctab.ins
new file mode 100644
index 0000000000..3c7a9b05ae
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3cctab.ins
@@ -0,0 +1,62 @@
+\iffalse meta-comment
+
+File: l3cctab.ins
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3cctab.sty}
+ {
+ \from{l3cctab.dtx}{package}
+ }
+}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3cctab.pdf b/macros/latex/contrib/l3experimental/l3cctab.pdf
new file mode 100644
index 0000000000..171cf0d26d
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3cctab.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3color.dtx b/macros/latex/contrib/l3experimental/l3color.dtx
new file mode 100644
index 0000000000..107d6684d2
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3color.dtx
@@ -0,0 +1,902 @@
+% \iffalse meta-comment
+%
+%% File: l3color.dtx
+%
+% Copyright (C) 2017-2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \textsf{l3color} package\\ Experimental color support^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Color models}
+%
+% A color \emph{model} is a way to represent sets of colors. Different models
+% are particularly suitable for different output methods, \emph{e.g.}~screen
+% or print. Parameter-based models can describe a very large number of unique
+% colors, and have a varying number of \emph{axes} which define a color
+% space. In contrast, various proprietary models are available which define
+% \emph{spot} colors.
+%
+% The models supported here are
+% \begin{itemize}
+% \item \texttt{gray} Grayscale color, with a single axis running from
+% $0$ (fully black) to $1$ (fully white)
+% \item \texttt{rgb} Red-green-blue color, with three axes, one for each of
+% the components
+% \item \texttt{cmyk} Cyan-magenta-yellow-black color, with four axes, one for
+% each of the components
+% \item \texttt{spot} Spot color, with one value, the name of the color
+% (see \url{https://helpx.adobe.com/indesign/using/spot-process-colors.html}
+% for details of the use of spot colors in print)
+% \end{itemize}
+%
+% Additional models may be created to allow mixing of spot colors
+% with each other or with those from other models. See
+% Section~\ref{l3color:sec:spot} for more detail of spot color support.
+%
+% When color is selected by model, the \meta{values} given are specified as
+% a comma-separated list. The length of the list will therefore be determined
+% by the detail of the model involved.
+%
+% Color models (and interconversion) are complex, and more details are given
+% in the manual to the \LaTeXe{} \pkg{xcolor} package and in the
+% \emph{PostScript Language Reference Manual}, published by Addison--Wesley.
+%
+% \section{Color expressions}
+%
+% In addition to allowing specification of color by model and values,
+% \pkg{l3color} also supports color expressions. These are created
+% by combining one or more color names, with the amount of each specified
+% as a percentage. The latter is given between |!| symbols in the expression.
+% Thus for example
+% \begin{verbatim}
+% red!50!green
+% \end{verbatim}
+% is a mixture of $50\,\%$ red and $50\,\%$ green. A trailing percentage is
+% interpreted as implicitly followed by |white|, and so
+% \begin{verbatim}
+% red!25
+% \end{verbatim}
+% specifies $25\,\%$ red mixed with $75\,\%$ white.
+%
+% Where the models for the mixed colors are different, the model of the first
+% color is used. Thus
+% \begin{verbatim}
+% red!50!cyan
+% \end{verbatim}
+% will result in a color specification using the |rgb| model, made up of
+% $50\,\%$ red and $50\,\%$ of cyan \emph{expressed in \texttt{rgb}}. As color
+% model interconversion is not exact.
+%
+% The one exception to the above is where the first model in an expression is
+% |gray|. In this case, the order of mixing is \enquote{swapped} internally, so
+% that for example
+% \begin{verbatim}
+% black!50!red
+% \end{verbatim}
+% has the same result as
+% \begin{verbatim}
+% red!50!black
+% \end{verbatim}
+% (the predefined colors |black| and |white| use the |gray| model).
+%
+% Where more than two colors are mixed in an expression, evaluation takes place
+% in a stepwise fashion. Thus in
+% \begin{verbatim}
+% cyan!50!magenta!10!yellow
+% \end{verbatim}
+% the sub-expression
+% \begin{verbatim}
+% cyan!50!magenta
+% \end{verbatim}
+% is first evaluated to give an intermediate color specification, before
+% the second step
+% \begin{verbatim}
+% <intermediate>!10!yellow
+% \end{verbatim}
+% where |<intermediate>| represents this transitory calculated value.
+%
+% Within a color expression, |.| may be used to represent the color active
+% for typesetting (the current color). This allows for example
+% \begin{verbatim}
+% .!50
+% \end{verbatim}
+% to mean a mixture of $50\,\%$ of current color with white.
+%
+% (Color expressions supported here are a subset of those provided by
+% the \LaTeXe{} \pkg{xcolor} package. At present, only such features as are
+% clearly useful have been added here.)
+%
+% \section{Named colors}
+%
+% Color names are stored in a single namespace, which makes them accessible
+% as part of color expressions. Whilst they are not reserved in a technical
+% sense, the names |black|, |white|, |red|, |green|, |blue|, |cyan|, |magenta|
+% and |yellow| have special meaning and should not be redefined. Color names
+% should be made up of letters, numbers and spaces only: other characters are
+% reserved for use in color expressions. In particular, |.| represents the
+% current color at the start of a color expression.
+%
+% \begin{function}{\color_set:nn}
+% \begin{syntax}
+% \cs{color_set:nn} \Arg{name} \Arg{color expression}
+% \end{syntax}
+% Evaluates the \meta{color expression} and stores the resulting
+% color specification as the \meta{name}.
+% \end{function}
+%
+% \begin{function}{\color_set:nnn}
+% \begin{syntax}
+% \cs{color_set:nnn} \Arg{name} \Arg{model} \Arg{value(s)}
+% \end{syntax}
+% Stores the color specification equivalent to the \meta{model} and
+% \meta{values} as the \meta{name}.
+% \end{function}
+%
+% \begin{function}{\color_set_eq:nn}
+% \begin{syntax}
+% \cs{color_set_eq:nn} \Arg{name1} \Arg{name2}
+% \end{syntax}
+% Copies the color specification in \meta{name2} to \meta{name1}. The
+% special name |.| may be used to represent the current color, allowing
+% it to be saved to a name.
+% \end{function}
+%
+% \begin{function}{\color_show:n}
+% \begin{syntax}
+% \cs{color_show:n} \Arg{name}
+% \end{syntax}
+% Displays the color specification stored in the \meta{name} on the
+% terminal
+% \end{function}
+%
+% \section{Selecting colors}
+%
+% \begin{function}{\color_select:n}
+% \begin{syntax}
+% \cs{color_select:n} \Arg{color expression}
+% \end{syntax}
+% Parses the \meta{color expression} and then activates the resulting
+% color specification for typeset material.
+% \end{function}
+%
+% \begin{function}{\color_select:nn}
+% \begin{syntax}
+% \cs{color_select:nn} \Arg{model} \Arg{value(s)}
+% \end{syntax}
+% Activates the color specification equivalent to the \meta{model} and
+% \meta{value(s)} for typeset material.
+% \end{function}
+%
+% \begin{variable}{\l_color_fixed_model_tl}
+% When this is set to a non-empty value, colors will be converted to
+% the specified model when they are selected. Note that included images
+% and similar are not influenced by this setting.
+% \end{variable}
+%
+% \section{Core color representation}
+%
+% To allow data to be handled internally, \pkg{l3color} uses a simple
+% representation of color, based on that used by the \pkg{dvips} program.
+% This is a token list made up of the model name followed by one or more
+% data entries, each separated by a \emph{space}. The valid forms are thus
+% \begin{itemize}
+% \item \texttt{gray \meta{gray}} Grayscale color with the \meta{gray}
+% value running from $0$ (fully black) to $1$ (fully white)
+% \item \texttt{cmyk \meta{cyan} \meta{magenta} \meta{yellow} \meta{black}},
+% each of which falls in the range $[0,1]$
+% \item \texttt{rgb \meta{red} \meta{green} \meta{blue}},
+% each of which falls in the range $[0,1]$
+% \item \texttt{spot \meta{name} \meta{tint}} A pre-defined spot color,
+% where the \meta{name} should be a pre-defined string color name and the
+% \meta{tint} should be in the range $[0,1]$.
+% \end{itemize}
+%
+% This core representation is produced when parsing color expressions.
+%
+% \begin{function}{\color_parse:nN}
+% \begin{syntax}
+% \cs{color_parse:nN} \Arg{color expression} \Arg{tl}
+% \end{syntax}
+% Parses the \meta{color expression} as described above, and sets the
+% \meta{tl} the the equivalent \meta{core color representation}.
+% \end{function}
+%
+% \section{Spot colors}
+% \label{l3color:sec:spot}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3color} Implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=color>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3color}{2019-08-25}{}
+ {L3 Experimental color support}
+%</package>
+% \end{macrocode}
+%
+% \subsection{Predefined color names}
+%
+% The ability to predefine colors with a name is a key part of this module and
+% means there has to be a method for storing the results. At first sight, it
+% seems natural to follow the usual \pkg{expl3} model and create a
+% \texttt{color} variable type for the process. That would then allow both
+% local and global colors, constant colors and the like. However, these names
+% need to be accessible in some form at the user level, for selection of colors
+% either simply by name or as part of a more complex expression. This does not
+% require that the full name is exposed but does require that they can be
+% looked up in a predictable way. As such, it is more useful to expose just the
+% color names as part of the interface, with the result that only local color
+% names can be created. (This is also seen for example in key creation in
+% \pkg{l3keys}.) As a result, color names are declarative (no \texttt{new}
+% functions).
+%
+% Since there is no need to manipulate colors \emph{en masse}, each is stored
+% in a separate token list variable, rather than the alternative of using a
+% single property list for all names.
+%
+% \subsection{Setup}
+%
+% \begin{variable}{\l_@@_internal_tl}
+% \begin{macrocode}
+\tl_new:N \l_@@_internal_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Utility functions}
+%
+% \begin{macro}[int, TF, EXP]{\@@_if_defined:n}
+% A simple wrapper to avoid needing to have the lookup repeated in too many
+% places.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_defined:n #1 { T, F, TF }
+ {
+ \tl_if_exist:cTF { l_@@_named_ #1 _tl }
+ \prg_return_true:
+ \prg_return_false:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_extract:nNN, \@@_extract:VNN}
+% \begin{macro}{\@@_extract:NNw}
+% Split the model and color from a named color, and store the two. No test
+% for the existence of the color: that is assumed to be the case (this
+% is internal only). Somewhat \enquote{old-fashioned} but should be quite
+% fast.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_extract:nNN #1#2#3
+ {
+ \exp_after:wN \exp_after:wN \exp_after:wN
+ \@@_extract:NNw
+ \exp_after:wN \exp_after:wN \exp_after:wN #2
+ \exp_after:wN \exp_after:wN \exp_after:wN #3
+ \cs:w l_@@_named_ #1 _tl \cs_end: \q_stop
+ }
+\cs_generate_variant:Nn \@@_extract:nNN { V }
+\cs_new_protected:Npn \@@_extract:NNw #1#2 #3 ~ #4 \q_stop
+ {
+ \tl_set:Nn #1 {#3}
+ \tl_set:Nn #2 {#4}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Model conversion}
+%
+% \begin{macro}{\@@_convert:nnN, \@@_convert:VVN}
+% \begin{macro}{\@@_convert:nnnN}
+% \begin{macro}[aux, EXP]
+% {
+% \@@_convert_gray_rgb:w
+% \@@_convert_gray_cmyk:w
+% \@@_convert_cmyk_gray:w
+% \@@_convert_cmyk_rgb:w
+% \@@_convert_rgb_gray:w
+% \@@_convert_rgb_cmyk:w
+% }
+% \begin{macro}[aux, EXP]{\@@_convert_rgb_cmyk:nnnn}
+% Model conversion is carried out using standard formulae, as described in
+% the manual for \pkg{xcolor} (see also the \emph{PostScript Language
+% Reference Manual}).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_convert:nnN #1#2#3
+ { \@@_convert:nnVN {#1} {#2} #3 #3 }
+\cs_generate_variant:Nn \@@_convert:nnN { VV }
+\cs_new_protected:Npn \@@_convert:nnnN #1#2#3#4
+ {
+ \str_if_eq:nnT {#1} { spot } % TO DO!!!
+ { }
+ \tl_set:Nx #4
+ { \use:c { @@_convert_ #1 _ #2 :w } #3 ~ 0 ~ 0 ~ 0 \q_stop }
+ }
+\cs_generate_variant:Nn \@@_convert:nnnN { nnV }
+\cs_new:Npn \@@_convert_gray_rgb:w #1 ~ #2 \q_stop
+ { #1 ~ #1 ~ #1 }
+\cs_new:Npn \@@_convert_gray_cmyk:w #1 ~ #2 \q_stop
+ { 0 ~ 0 ~ 0 ~ \fp_eval:n { 1 - #1 } }
+% \end{macrocode}
+% These rather odd values are based on \textsc{ntsc} television: the set are
+% used for the |cmyk| conversion.
+% \begin{macrocode}
+\cs_new:Npn \@@_convert_rgb_gray:w #1 ~ #2 ~ #3 ~ #4 \q_stop
+ { \fp_eval:n { 0.3 * #1 + 0.59 * #2 + 0.11 * #3 } }
+% \end{macrocode}
+% The conversion from |rgb| to |cmyk| is the most complex: a two-step
+% procedure which requires \emph{black generation} and \emph{undercolor
+% removal} functions. The PostScript reference describes them as
+% device-dependent, but following \pkg{xcolor} we assume they are linear.
+% Moreover, as the likelihood of anyone using a non-unitary matrix here is
+% tiny, we simplify and treat those two concepts as no-ops.
+% \begin{macrocode}
+\cs_new:Npn \@@_convert_rgb_cmyk:w #1 ~ #2 ~ #3 ~ #4 \q_stop
+ {
+ \exp_args:Nf \@@_convert_rgb_cmyk:nnnn
+ { \fp_eval:n { min ( 1 - #1 , 1 - #2 , 1 - #3 ) } } {#1} {#2} {#3}
+ }
+\cs_new:Npn \@@_convert_rgb_cmyk:nnnn #1#2#3#4
+ {
+ \fp_eval:n { min ( 1 , max ( 0 , 1 - #2 - #1 ) ) } \c_space_tl
+ \fp_eval:n { min ( 1 , max ( 0 , 1 - #3 - #1 ) ) } \c_space_tl
+ \fp_eval:n { min ( 1 , max ( 0 , 1 - #4 - #1 ) ) } \c_space_tl
+ #1
+ }
+\cs_new:Npn \@@_convert_cmyk_gray:w #1 ~ #2 ~ #3 ~ #4 ~ #5 \q_stop
+ { \fp_eval:n { 1 - min ( 1 , 0.3 * #1 + 0.59 * #2 + 0.11 * #3 + #4 ) } }
+\cs_new:Npn \@@_convert_cmyk_rgb:w #1 ~ #2 ~ #3 ~ #4 ~ #5 \q_stop
+ {
+ \fp_eval:n { 1 - min ( 1 , #1 + #4 ) } \c_space_tl
+ \fp_eval:n { 1 - min ( 1 , #2 + #4 ) } \c_space_tl
+ \fp_eval:n { 1 - min ( 1 , #3 + #4 ) }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Color expressions}
+%
+% \begin{variable}
+% {\l_@@_model_tl, \l_@@_value_tl, \l_@@_next_model_tl, \l_@@_next_value_tl}
+% Working space to store the color data whilst doing calculations: keeping
+% it on the stack is attractive but gets tricky (return is non-trivial).
+% \begin{macrocode}
+\tl_new:N \l_@@_model_tl
+\tl_new:N \l_@@_value_tl
+\tl_new:N \l_@@_next_model_tl
+\tl_new:N \l_@@_next_value_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\color_parse:nN}
+% \begin{macro}{\@@_parse:nN}
+% \begin{macro}{\@@_parse:Nw}
+% \begin{macro}{\@@_parse_loop_init:Nnn}
+% \begin{macro}{\@@_parse_loop:w}
+% \begin{macro}{\@@_parse_loop:nn}
+% \begin{macro}{\@@_parse_break:w}
+% \begin{macro}{\@@_parse_end:}
+% \begin{macro}[aux, EXP]{\@@_parse_mix:Nnnn, \@@_parse_mix:NVVn}
+% \begin{macro}[aux, EXP]{\@@_parse_mix:nNnn}
+% \begin{macro}[aux, EXP]
+% {
+% \@@_parse_mix_gray:nw ,
+% \@@_parse_mix_rgb:nw ,
+% \@@_parse_mix_cmyk:nw
+% }
+% The main function for parsing color expressions removes actives but
+% otherwise expands, then starts working through the expression itself.
+% At the end, we apply the payload.
+% \begin{macrocode}
+\cs_new_protected:Npn \color_parse:nN #1#2
+ {
+%<*package>
+ \@@_backend_pickup:N \l_@@_current_tl
+ \tl_set_eq:cN { l_@@_named_ . _tl } \l_@@_current_tl
+%</package>
+ \exp_args:Ne \@@_parse:nN { \tl_to_str:n {#1} } #2
+ }
+% \end{macrocode}
+% Before going to all of the effort of parsing an expression, these two
+% precursor functions look for a pre-defined name, either on its own or
+% with a trailing |!| (which is the same thing).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parse:nN #1#2
+ {
+ \tl_if_exist:cTF { l_@@_named_ #1 _tl }
+ { \tl_set_eq:Nc #2 { l_@@_named_ #1 _tl } }
+ { \@@_parse:Nw #2#1 ! \q_stop }
+ \@@_check_model:N #2
+ }
+\cs_new_protected:Npn \@@_parse:Nw #1#2 ! #3 \q_stop
+ {
+ \@@_if_defined:nTF {#2}
+ {
+ \tl_if_blank:nTF {#3}
+ { \tl_set_eq:Nc #1 { l_@@_named_ #2 _tl } }
+ { \@@_parse_loop_init:Nnn #1 {#2} {#3} }
+ }
+ {
+ \__kernel_msg_error:nnn { color } { unknown-color } {#3}
+ \tl_set_eq:NN \l_@@_current_tl \l_@@_named_black_tl
+ }
+ }
+% \end{macrocode}
+% Once we establish that a full parse is needed, the next job is to get the
+% detail of the first color. That will determine the model we use for the
+% calculation: splitting here makes checking that a bit easier.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parse_loop_init:Nnn #1#2#3
+ {
+ \group_begin:
+ \@@_extract:nNN {#2} \l_@@_model_tl \l_@@_value_tl
+ \@@_parse_loop:w #3 ! ! ! ! \q_stop
+ \tl_set:Nx \l_@@_internal_tl
+ { \l_@@_model_tl \c_space_tl \l_@@_value_tl }
+ \exp_args:NNNV \group_end:
+ \tl_set:Nn #1 \l_@@_internal_tl
+ }
+% \end{macrocode}
+% This is the loop proper: there can be an open-ended set of colors to parse,
+% separated by |!| tokens. There are a few cases to look out for. At the end
+% of the expression and with we find a mix of $100$ then we simply skip the
+% next color entirely (we can't stop the loop as there might be a further
+% valid color to mix in). On the other hand, if we get a mix of $0$ then
+% drop everything so far and start again. There is also a trailing
+% |white| to \enquote{read in} if the final explicit data is a mix.
+% Those conditions are separate from actually looping, which is therefore
+% sorted out by checking if we have further data to process: in contrast
+% to \pkg{xcolor}, we don't allow |!!| so the test can be simplified.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parse_loop:w #1 ! #2 ! #3 ! #4 ! #5 \q_stop
+ {
+ \bool_lazy_or:nnF
+ { \tl_if_blank_p:n {#1} }
+ { \int_compare_p:nNn {#1} = { 100 } }
+ {
+ \int_compare:nNnTF {#1} = { 0 }
+ {
+ \tl_if_blank:nTF {#2}
+ { \@@_extract:nNN { white } }
+ { \@@_extract:nNN {#2} }
+ \l_@@_model_tl \l_@@_value_tl
+ }
+ {
+ \use:x
+ {
+ \@@_parse_loop:nn {#1}
+ { \tl_if_blank:nTF {#2} { white } {#2} }
+ }
+ }
+ }
+ \tl_if_blank:nF {#3}
+ { \@@_parse_loop:w #3 ! #4 ! #5 \q_stop }
+ \@@_parse_end:
+ }
+% \end{macrocode}
+% The \enquote{payload} of calculation in the loop first. If the model for
+% the upcoming color is different from that of the existing (partial) color,
+% convert the model. For |gray| the two are flipped round so that the outcome
+% is something with \enquote{real} color. We are then in a position to do the
+% actual calculation itself. The two auxiliaries here give us a way to break
+% the loop should an invalid name be found.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parse_loop:nn #1#2
+ {
+ \@@_if_defined:nTF {#2}
+ {
+ \@@_extract:nNN {#2} \l_@@_next_model_tl \l_@@_next_value_tl
+ \tl_if_eq:NNF \l_@@_model_tl \l_@@_next_model_tl
+ {
+ \str_if_eq:VnT \l_@@_model_tl { gray }
+ {
+ \use:x
+ {
+ \tl_set:Nn \exp_not:N \l_@@_model_tl
+ { \l_@@_next_model_tl }
+ \tl_set:Nn \exp_not:N \l_@@_value_tl
+ { \l_@@_next_value_tl }
+ \tl_set:Nn \exp_not:N \l_next_@@_model_tl
+ { \l_@@_model_tl }
+ \tl_set:Nn \exp_not:N \l_next_@@_value_tl
+ { \l_@@_value_tl }
+ }
+ }
+ \@@_convert:VVN
+ \l_@@_next_model_tl
+ \l_@@_model_tl
+ \l_@@_next_value_tl
+ }
+ \tl_set:Nx \l_@@_value_tl
+ {
+ \@@_parse_mix:NVVn
+ \l_@@_model_tl \l_@@_value_tl \l_@@_next_value_tl {#1}
+ }
+ }
+ {
+ \__kernel_msg_error:nnn { color } { unknown-color } {#2}
+ \@@_extract:nNN { black } \l_@@_model_tl \l_@@_value_tl
+ \@@_parse_break:w
+ }
+ }
+\cs_new_protected:Npn \@@_parse_break:w #1 \@@_parse_end: { }
+\cs_new_protected:Npn \@@_parse_end: { }
+% \end{macrocode}
+% Do the vector arithmetic: mainly a question of shuffling input, along
+% with one pre-calculation to keep down the use of division.
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_mix:Nnnn #1#2#3#4
+ {
+ \exp_args:Nf \@@_parse_mix:nNnn
+ { \fp_eval:n { #4 / 100 } }
+ #1 {#2} {#3}
+ }
+\cs_generate_variant:Nn \@@_parse_mix:Nnnn { NVV }
+\cs_new:Npn \@@_parse_mix:nNnn #1#2#3#4
+ {
+ \use:c { @@_parse_mix_ #2 :nw } {#1}
+ #3 \q_mark #4 \q_stop
+ }
+\cs_new:Npn \@@_parse_mix_gray:nw #1#2 \q_mark #3 \q_stop
+ { \fp_eval:n { #2 * #1 + #3 * ( 1 - #1 ) } }
+\cs_new:Npn \@@_parse_mix_rgb:nw
+ #1#2 ~ #3 ~ #4 \q_mark #5 ~ #6 ~ #7 \q_stop
+ {
+ \fp_eval:n { #2 * #1 + #5 * ( 1 - #1 ) } \c_space_tl
+ \fp_eval:n { #3 * #1 + #6 * ( 1 - #1 ) } \c_space_tl
+ \fp_eval:n { #4 * #1 + #7 * ( 1 - #1 ) }
+ }
+\cs_new:Npn \@@_parse_mix_cmyk:nw
+ #1#2 ~ #3 ~ #4 ~ #5 \q_mark #6 ~ #7 ~ #8 ~ #9 \q_stop
+ {
+ \fp_eval:n { #2 * #1 + #6 * ( 1 - #1 ) } \c_space_tl
+ \fp_eval:n { #3 * #1 + #7 * ( 1 - #1 ) } \c_space_tl
+ \fp_eval:n { #4 * #1 + #8 * ( 1 - #1 ) } \c_space_tl
+ \fp_eval:n { #5 * #1 + #9 * ( 1 - #1 ) }
+ }
+% \end{macrocode}
+% \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]
+% {
+% \@@_parse_model_gray:w, \@@_parse_model_rgb:w,
+% \@@_parse_model_cmyk:w, \@@_parse_model_spot:w
+% }
+% \begin{macro}[EXP]{\@@_parse_number:n}
+% \begin{macro}[EXP]{\@@_parse_number:w}
+% \begin{macro}[EXP]{\@@_parse_spot_aux:w}
+% Turn the input into internal form, also tidying up the number quickly.
+% \begin{macrocode}
+\cs_new:Npn \@@_parse_model_gray:w #1 , #2 \q_stop { \@@_parse_number:n {#1} }
+\cs_new:Npn \@@_parse_model_rgb:w #1 , #2 , #3 , #4 \q_stop
+ {
+ \@@_parse_number:n {#1} ~
+ \@@_parse_number:n {#2} ~
+ \@@_parse_number:n {#3}
+ }
+\cs_new:Npn \@@_parse_model_cmyk:w #1 , #2 , #3 , #4 , #5 \q_stop
+ {
+ \@@_parse_number:n {#1} ~
+ \@@_parse_number:n {#2} ~
+ \@@_parse_number:n {#3} ~
+ \@@_parse_number:n {#4}
+ }
+\cs_new:Npn \@@_parse_model_spot:w #1 , #2 \q_stop
+ { \@@_parse_spot_aux:w #1 ! 100 ! \q_stop }
+\cs_new:Npn \@@_parse_spot_aux:w #1 ! #2 ! #3 \q_stop
+ { #1 ~ \fp_eval:n { #2 / 100 } }
+\cs_new:Npn \@@_parse_number:n #1
+ { \@@_parse_number:w #1 . 0 . \q_stop }
+\cs_new:Npn \@@_parse_number:w #1 . #2 . #3 \q_stop
+ { \tl_if_blank:nTF {#1} { 0 } {#1} . #2 }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Selecting colors (and color models)}
+%
+% \begin{variable}{\l_color_fixed_model_tl}
+% For selecting a single fixed model.
+% \begin{macrocode}
+\tl_new:N \l_color_fixed_model_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_check_model:N}
+% \begin{macro}{\@@_check_model:w}
+% Check that the model in use is the one required.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_check_model:N #1
+ {
+ \tl_if_empty:NF \l_color_fixed_model_tl
+ {
+ \exp_after:wN \@@_check_model:w #1 \q_stop
+ \tl_if_eq:NNF \l_@@_model_tl \l_color_fixed_model_tl
+ {
+ \@@_convert:VVN \l_@@_model_tl \l_color_fixed_model_tl
+ \l_@@_value_tl
+ }
+ \tl_set:Nx #1
+ { \l_color_fixed_model_tl \c_space_tl \l_@@_value_tl }
+ }
+ }
+\cs_new_protected:Npn \@@_check_model:w #1 ~ #2 \q_stop
+ {
+ \tl_set:Nn \l_@@_model_tl {#1}
+ \tl_set:Nn \l_@@_value_tl {#2}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_select:}
+% A driver-neutral location for \enquote{last minute} manipulations before
+% handing off to the driver code. We set the special |.| syntax here: this
+% will therefore always be available. The finalisation is separate from the
+% main function so it can also be applied to \emph{e.g.}~page color.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_select:
+ {
+ \tl_set_eq:cN { l_@@_named_ . _tl } \l_@@_current_tl
+ \@@_select:V \l_@@_current_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\color_select:n}
+% \begin{macro}{\color_select:nn}
+% Parse the input expressions then get the driver to actually activate
+% them.
+% \begin{macrocode}
+\cs_new_protected:Npn \color_select:n #1
+ {
+ \color_parse:nN {#1} \l_@@_current_tl
+ \@@_select:
+ }
+\cs_new_protected:Npn \color_select:nn #1#2
+ {
+ \@@_direct:nnN {#1} {#2} \l_@@_current_tl
+ \@@_select:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Direct model use}
+%
+% \begin{macro}{\@@_direct:nnN}
+% Directly set a color based on a model/value combination.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_direct:nnN #1#2#3
+ {
+ \cs_if_exist:cTF { @@_parse_model_ #1 :w }
+ {
+ \tl_set:Nx #3
+ { #1 ~ \use:c { @@_parse_model_ #1 :w } #2 , 0 , 0 , 0 , 0 \q_stop }
+ \@@_check_model:N #3
+ }
+ {
+ \__kernel_msg_error:nnn { color } { invalid-model } {#1}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Defining named colors}
+%
+% \begin{variable}{\l_@@_named_tl}
+% Space to store the detail of the named color.
+% \begin{macrocode}
+\tl_new:N \l_@@_named_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_store:Nn}
+% Store the named color unless it has an invalid name.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_store:Nn #1#2
+ {
+ \str_if_eq:nnF {#2} { . }
+ {
+ \tl_clear_new:c { l_@@_named_ #2 _tl }
+ \tl_set_eq:cN { l_@@_named_ #2 _tl } #1
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\color_set:nn}
+% \begin{macro}{\color_set:nnn}
+% \begin{macro}{\color_set_eq:nn}
+% Defining named colors has to include a step to force creation of the
+% underlying token list to avoid errors when checking is enabled.
+% \begin{macrocode}
+\cs_new_protected:Npn \color_set:nn #1#2
+ {
+ \color_parse:nN {#2} \l_@@_named_tl
+ \@@_store:Nn \l_@@_named_tl {#1}
+ }
+\cs_new_protected:Npn \color_set:nnn #1#2#3
+ {
+ \@@_direct:nnN {#2} {#3} \l_@@_named_tl
+ \@@_store:Nn \l_@@_named_tl {#1}
+ }
+\cs_new_protected:Npn \color_set_eq:nn #1#2
+ {
+ \@@_if_defined:nTF {#2}
+ {
+ \tl_clear_new:c { l_@@_named_ #1 _tl }
+ \str_if_eq:nnTF {#2} { . }
+ { \tl_set_eq:cN { l_@@_named_ #1 _tl } \l_@@_current_tl }
+ { \tl_set_eq:cc { l_@@_named_ #1 _tl } { l_@@_named_ #2 _tl } }
+ }
+ {
+ \__kernel_msg_error:nnn { color } { unknown-color } {#2}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% A small set of colors are always defined.
+% \begin{macrocode}
+\color_set:nnn { black } { gray } { 0 }
+\color_set:nnn { white } { gray } { 1 }
+\color_set:nnn { cyan } { cmyk } { 1 , 0 , 0 , 0 }
+\color_set:nnn { magenta } { cmyk } { 0 , 1 , 0 , 0 }
+\color_set:nnn { yellow } { cmyk } { 0 , 0 , 1 , 0 }
+\color_set:nnn { red } { rgb } { 1 , 0 , 0 }
+\color_set:nnn { green } { rgb } { 0 , 1 , 0 }
+\color_set:nnn { blue } { rgb } { 0 , 0 , 1 }
+% \end{macrocode}
+%
+% \begin{variable}{\l_@@_named_._tl}
+% A special named color: this is always defined though not fixed in
+% definition.
+% \begin{macrocode}
+\tl_new:c { l_@@_named_._tl }
+\tl_set_eq:cN { l_@@_named_._tl } \l_@@_current_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Diagnostics}
+%
+% \begin{macro}{\color_show:n}
+% \begin{macro}{\@@_show:w}
+% \begin{macro}{\@@_show_gray:w, \@@_show_rgb:w, \@@_show_cmyk:w}
+% Extract the information about a color and format for the user: the approach
+% is similar to the keys module here.
+% \begin{macrocode}
+\cs_new_protected:Npn \color_show:n #1
+ {
+ \msg_show:nnxxxx { LaTeX / color } { show }
+ {#1}
+ {
+ \@@_if_defined:nTF {#1}
+ { \exp_last_unbraced:Nv \@@_show:w { l_@@_named_ #1 _tl } \q_stop }
+ { }
+ }
+ { }
+ { }
+ }
+\cs_new:Npn \@@_show:w #1 ~ #2 \q_stop
+ {
+ \msg_show_item_unbraced:nn { model } {#1}
+ \exp_args:Nnf \msg_show_item_unbraced:nn { value }
+ { \use:c { @@_show_ #1 :w } #2 \q_stop }
+ }
+\cs_new:Npn \@@_show_gray:w #1 \q_stop { #1 }
+\cs_new:Npn \@@_show_rgb:w #1 ~ #2 ~ #3 \q_stop { #1 ,~ #2 ,~ #3 }
+\cs_new:Npn \@@_show_cmyk:w #1 ~ #2 ~ #3 ~ #4 \q_stop { #1 ,~ #2 ,~ #3 ,~ #4 }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+% \begin{macrocode}
+\__kernel_msg_new:nnnn { color } { invalid-model }
+ { Invalid~color~model~'#1'. }
+ {
+ LaTeX~has~been~asked~to~use~a~color~model~called~'#1',~
+ but~this~model~is~not~set~up.
+ }
+\__kernel_msg_new:nnnn { color } { unknown-color }
+ { Unknown~color~'#1'. }
+ {
+ LaTeX~has~been~asked~to~use~a~color~named~'#1',~
+ but~this~has~never~been~defined.
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+\__kernel_msg_new:nnn { color } { show }
+ {
+ The~color~#1~
+ \tl_if_empty:nTF {#2}
+ { is~undefined. }
+ { has~the~properties: #2 }
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3color.ins b/macros/latex/contrib/l3experimental/l3color.ins
new file mode 100644
index 0000000000..ae12ed89b6
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3color.ins
@@ -0,0 +1,58 @@
+\iffalse meta-comment
+
+File: l3color.ins
+
+Copyright (C) 2017-2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2017-2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3color.sty}{\from{l3color.dtx}{package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3color.pdf b/macros/latex/contrib/l3experimental/l3color.pdf
new file mode 100644
index 0000000000..9483d29d46
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3color.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3draw-boxes.dtx b/macros/latex/contrib/l3experimental/l3draw-boxes.dtx
new file mode 100644
index 0000000000..2eeacd0d0a
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-boxes.dtx
@@ -0,0 +1,155 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-boxes.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-boxes} package\\ Boxes in drawings^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-boxes} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% Inserting boxes requires us to \enquote{interrupt} the drawing state,
+% so is closely linked to scoping. At the same time, there are a few
+% additional features required to make text work in a flexible way.
+%
+% \begin{variable}{\l_@@_tmp_box}
+% \begin{macrocode}
+\box_new:N \l_@@_tmp_box
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_box_use:N}
+% \begin{macro}{\@@_box_use:Nnnnn}
+% Before inserting a box, we need to make sure that the bounding box is being
+% updated correctly. As drawings track transformations as a whole, rather
+% than as separate operations, we do the insertion using an almost-raw
+% matrix. The process is split into two so that coffins are also supported.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_box_use:N #1
+ {
+ \@@_box_use:Nnnnn #1
+ { 0pt } { -\box_dp:N #1 } { \box_wd:N #1 } { \box_ht:N #1 }
+ }
+\cs_new_protected:Npn \@@_box_use:Nnnnn #1#2#3#4#5
+ {
+ \bool_if:NT \l_draw_bb_update_bool
+ {
+ \@@_point_process:nn
+ { \@@_path_update_limits:nn }
+ { \draw_point_transform:n { #2 , #3 } }
+ \@@_point_process:nn
+ { \@@_path_update_limits:nn }
+ { \draw_point_transform:n { #4 , #3 } }
+ \@@_point_process:nn
+ { \@@_path_update_limits:nn }
+ { \draw_point_transform:n { #4 , #5 } }
+ \@@_point_process:nn
+ { \@@_path_update_limits:nn }
+ { \draw_point_transform:n { #2 , #5 } }
+ }
+ \group_begin:
+ \hbox_set:Nn \l_@@_tmp_box
+ {
+ \use:x
+ {
+ \@@_backend_box_use:Nnnnn #1
+ { \fp_use:N \l_@@_matrix_a_fp }
+ { \fp_use:N \l_@@_matrix_b_fp }
+ { \fp_use:N \l_@@_matrix_c_fp }
+ { \fp_use:N \l_@@_matrix_d_fp }
+ }
+ }
+ \hbox_set:Nn \l_@@_tmp_box
+ {
+ \tex_kern:D \l_@@_xshift_dim
+ \box_move_up:nn { \l_@@_yshift_dim }
+ { \box_use_drop:N \l_@@_tmp_box }
+ }
+ \box_set_ht:Nn \l_@@_tmp_box { 0pt }
+ \box_set_dp:Nn \l_@@_tmp_box { 0pt }
+ \box_set_wd:Nn \l_@@_tmp_box { 0pt }
+ \box_use_drop:N \l_@@_tmp_box
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_coffin_use:Nnn}
+% Slightly more than a shortcut: we have to allow for the fact that coffins
+% have no apparent width before the reference point.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_coffin_use:Nnn #1#2#3
+ {
+ \group_begin:
+ \hbox_set:Nn \l_@@_tmp_box
+ { \coffin_typeset:Nnnnn #1 {#2} {#3} { 0pt } { 0pt } }
+ \@@_box_use:Nnnnn \l_@@_tmp_box
+ { \box_wd:N \l_@@_tmp_box - \coffin_wd:N #1 }
+ { -\box_dp:N \l_@@_tmp_box }
+ { \box_wd:N \l_@@_tmp_box }
+ { \box_ht:N \l_@@_tmp_box }
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-code.pdf b/macros/latex/contrib/l3experimental/l3draw-code.pdf
new file mode 100644
index 0000000000..78eeeead50
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-code.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3draw-code.tex b/macros/latex/contrib/l3experimental/l3draw-code.tex
new file mode 100644
index 0000000000..8325f0fd59
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-code.tex
@@ -0,0 +1,64 @@
+\iffalse meta-comment
+
+File: l3draw-code.tex
+
+Copyright (C) 2018,2019 The LaTeX3 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
+
+ https://www.latex-project.org/lppl.txt
+
+This file is part of the "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+The released version of this bundle is available from CTAN.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/latex3
+
+for those people who are interested.
+
+\fi
+
+\documentclass{l3doc}
+
+\begin{document}
+
+\makeatletter
+\let\DelayPrintIndex\PrintIndex
+\let\PrintIndex\@empty
+\makeatother
+
+\ExplSyntaxOn
+\clist_gput_right:Nn \g_docinput_clist
+ {
+ l3draw.dtx ,
+ l3draw-boxes.dtx ,
+ l3draw-layers.dtx ,
+ l3draw-paths.dtx ,
+ l3draw-points.dtx ,
+ l3draw-scopes.dtx ,
+ l3draw-softpath.dtx ,
+ l3draw-state.dtx ,
+ l3draw-transforms.dtx
+ }
+\ExplSyntaxOff
+
+\part{Implementation}
+
+\def\maketitle{}
+\EnableImplementation
+\DisableDocumentation
+\DocInputAgain
+
+\clearpage
+
+\DelayPrintIndex
+
+\end{document}
diff --git a/macros/latex/contrib/l3experimental/l3draw-layers.dtx b/macros/latex/contrib/l3experimental/l3draw-layers.dtx
new file mode 100644
index 0000000000..ad35237954
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-layers.dtx
@@ -0,0 +1,216 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-layers.dtx
+%
+% Copyright (C) 2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-layers} package\\ Layers^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-layers} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% \subsection{User interface}
+%
+% \begin{macro}{\draw_layer_new:n}
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_layer_new:n #1
+ {
+ \str_if_eq:nnTF {#1} { main }
+ { \msg_error:nnn { draw } { main-reserved } }
+ {
+ \box_new:c { g_@@_layer_ #1 _box }
+ \box_new:c { l_@@_layer_ #1 _box }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_layer_tl}
+% The name of the current layer: we start off with |main|.
+% \begin{macrocode}
+\tl_new:N \l_@@_layer_tl
+\tl_set:Nn \l_@@_layer_tl { main }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_layer_close_bool}
+% Used to track if a layer needs to be closed.
+% \begin{macrocode}
+\bool_new:N \l_@@_layer_close_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_draw_layers_clist, \g_@@_layers_clist}
+% The list of layers to use starts off with just the |main| one.
+% \begin{macrocode}
+\clist_new:N \l_draw_layers_clist
+\clist_set:Nn \l_draw_layers_clist { main }
+\clist_new:N \g_@@_layers_clist
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_layer_begin:n}
+% \begin{macro}{\draw_layer_end:}
+% Layers may be called multiple times and have to work when nested. That
+% drives a bit of grouping to get everything in order. Layers have to be zero
+% width, so they get set as we go along.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_layer_begin:n #1
+ {
+ \group_begin:
+ \box_if_exist:cTF { g_@@_layer_ #1 _box }
+ {
+ \str_if_eq:VnTF \l_@@_layer_tl {#1}
+ { \bool_set_false:N \l_@@_layer_close_bool }
+ {
+ \bool_set_true:N \l_@@_layer_close_bool
+ \tl_set:Nn \l_@@_layer_tl {#1}
+ \box_gset_wd:cn { g_@@_layer_ #1 _box } { 0pt }
+ \hbox_gset:cw { g_@@_layer_ #1 _box }
+ \box_use_drop:c { g_@@_layer_ #1 _box }
+ \group_begin:
+ }
+ \draw_linewidth:n { \l_draw_default_linewidth_dim }
+ }
+ {
+ \str_if_eq:nnTF {#1} { main }
+ { \msg_error:nnn { draw } { unknown-layer } {#1} }
+ { \msg_error:nnn { draw } { main-layer } }
+ }
+ }
+\cs_new_protected:Npn \draw_layer_end:
+ {
+ \bool_if:NT \l_@@_layer_close_bool
+ {
+ \group_end:
+ \hbox_gset_end:
+ }
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Internal cross-links}
+%
+% \begin{macro}{\@@_layers_insert:}
+% The |main| layer is special, otherwise just dump the layer box inside
+% a scope.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_layers_insert:
+ {
+ \clist_map_inline:Nn \l_draw_layers_clist
+ {
+ \str_if_eq:nnTF {##1} { main }
+ {
+ \box_set_wd:Nn \l_@@_layer_main_box { 0pt }
+ \box_use_drop:N \l_@@_layer_main_box
+ }
+ {
+ \@@_backend_scope_begin:
+ \box_gset_wd:cn { g_@@_layer_ ##1 _box } { 0pt }
+ \box_use_drop:c { g_@@_layer_ ##1 _box }
+ \@@_backend_scope_end:
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_layers_save:, \@@_layers_restore:}
+% Simple save/restore functions.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_layers_save:
+ {
+ \clist_map_inline:Nn \l_draw_layers_clist
+ {
+ \str_if_eq:nnF {##1} { main }
+ {
+ \box_set_eq:cc { l_@@_layer_ ##1 _box }
+ { g_@@_layer_ ##1 _box }
+ }
+ }
+ }
+\cs_new_protected:Npn \@@_layers_restore:
+ {
+ \clist_map_inline:Nn \l_draw_layers_clist
+ {
+ \str_if_eq:nnF {##1} { main }
+ {
+ \box_gset_eq:cc { g_@@_layer_ ##1 _box }
+ { l_@@_layer_ ##1 _box }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+\msg_new:nnnn { draw } { main-layer }
+ { Material~cannot~be~added~to~'main'~layer. }
+ { The~main~layer~may~only~be~accessed~at~the~top~level. }
+\msg_new:nnn { draw } { main-reserved }
+ { The~'main'~layer~is~reserved. }
+\msg_new:nnnn { draw } { unknown-layer }
+ { Layer~'#1'~has~not~been~created. }
+ { You~have~tried~to~use~layer~'#1',~but~it~was~never~set~up. }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-paths.dtx b/macros/latex/contrib/l3experimental/l3draw-paths.dtx
new file mode 100644
index 0000000000..2981612ac4
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-paths.dtx
@@ -0,0 +1,1037 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-paths.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-paths} package\\ Drawing paths^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-paths} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% This sub-module covers more-or-less the same ideas as
+% \texttt{pgfcorepathconstruct.code.tex}, though using the expandable FPU
+% means that the implementation often varies. At present, equivalents of the
+% following are currently absent:
+% \begin{itemize}
+% \item \cs{pgfpatharcto}, \cs{pgfpatharctoprecomputed}: These are
+% extremely specialised and are very complex in implementation. If the
+% functionality is required, it is likely that it will be set up from
+% scratch here.
+% \item \cs{pgfpathparabola}: Seems to be unused other than defining
+% a Ti\emph{k}Z interface, which itself is then not used further.
+% \item \cs{pgfpathsine}, \cs{pgfpathcosine}: Need to see exactly how
+% these need to work, in particular whether a wider input range is
+% needed and what approximation to make.
+% \item \cs{pgfpathcurvebetweentime}, \cs{pgfpathcurvebetweentimecontinue}:
+% These don't seem to be used at all.
+% \end{itemize}
+%
+% \begin{variable}
+% {\l_@@_path_tmp_tl, \l_@@_path_tmpa_fp, \l_@@_path_tmpb_fp}
+% Scratch space.
+% \begin{macrocode}
+\tl_new:N \l_@@_path_tmp_tl
+\fp_new:N \l_@@_path_tmpa_fp
+\fp_new:N \l_@@_path_tmpb_fp
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Tracking paths}
+%
+% \begin{variable}{\g_@@_path_lastx_dim, \g_@@_path_lasty_dim}
+% The last point visited on a path.
+% \begin{macrocode}
+\dim_new:N \g_@@_path_lastx_dim
+\dim_new:N \g_@@_path_lasty_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \g_@@_path_xmax_dim,
+% \g_@@_path_xmin_dim,
+% \g_@@_path_ymax_dim,
+% \g_@@_path_ymin_dim
+% }
+% The limiting size of a path.
+% \begin{macrocode}
+\dim_new:N \g_@@_path_xmax_dim
+\dim_new:N \g_@@_path_xmin_dim
+\dim_new:N \g_@@_path_ymax_dim
+\dim_new:N \g_@@_path_ymin_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_path_update_limits:nn}
+% \begin{macro}{\@@_path_reset_limits:}
+% Track the limits of a path and (perhaps) of the picture as a whole.
+% (At present the latter is always true: that will change as more complex
+% functionality is added.)
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_update_limits:nn #1#2
+ {
+ \dim_gset:Nn \g_@@_path_xmax_dim
+ { \dim_max:nn \g_@@_path_xmax_dim {#1} }
+ \dim_gset:Nn \g_@@_path_xmin_dim
+ { \dim_min:nn \g_@@_path_xmin_dim {#1} }
+ \dim_gset:Nn \g_@@_path_ymax_dim
+ { \dim_max:nn \g_@@_path_ymax_dim {#2} }
+ \dim_gset:Nn \g_@@_path_ymin_dim
+ { \dim_min:nn \g_@@_path_ymin_dim {#2} }
+ \bool_if:NT \l_draw_bb_update_bool
+ {
+ \dim_gset:Nn \g_@@_xmax_dim
+ { \dim_max:nn \g_@@_xmax_dim {#1} }
+ \dim_gset:Nn \g_@@_xmin_dim
+ { \dim_min:nn \g_@@_xmin_dim {#1} }
+ \dim_gset:Nn \g_@@_ymax_dim
+ { \dim_max:nn \g_@@_ymax_dim {#2} }
+ \dim_gset:Nn \g_@@_ymin_dim
+ { \dim_min:nn \g_@@_ymin_dim {#2} }
+ }
+ }
+\cs_new_protected:Npn \@@_path_reset_limits:
+ {
+ \dim_gset:Nn \g_@@_path_xmax_dim { -\c_max_dim }
+ \dim_gset:Nn \g_@@_path_xmin_dim { \c_max_dim }
+ \dim_gset:Nn \g_@@_path_ymax_dim { -\c_max_dim }
+ \dim_gset:Nn \g_@@_path_ymin_dim { \c_max_dim }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_path_update_last:nn}
+% A simple auxiliary to avoid repetition.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_update_last:nn #1#2
+ {
+ \dim_gset:Nn \g_@@_path_lastx_dim {#1}
+ \dim_gset:Nn \g_@@_path_lasty_dim {#2}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Corner arcs}
+%
+% At the level of path \emph{construction}, rounded corners are handled
+% by inserting a marker into the path: that is then picked up once the
+% full path is constructed. Thus we need to set up the appropriate
+% data structures here, such that this can be applied every time it is
+% relevant.
+%
+% \begin{variable}{\l_@@_corner_xarc_dim, \l_@@_corner_yarc_dim}
+% The two arcs in use.
+% \begin{macrocode}
+\dim_new:N \l_@@_corner_xarc_dim
+\dim_new:N \l_@@_corner_yarc_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_corner_arc_bool}
+% A flag to speed up the repeated checks.
+% \begin{macrocode}
+\bool_new:N \l_@@_corner_arc_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_path_corner_arc:nn}
+% Calculate the arcs, check they are non-zero.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_corner_arc:nn #1#2
+ {
+ \dim_set:Nn \l_@@_corner_xarc_dim {#1}
+ \dim_set:Nn \l_@@_corner_yarc_dim {#2}
+ \bool_lazy_and:nnTF
+ { \dim_compare_p:nNn \l_@@_corner_xarc_dim = { 0pt } }
+ { \dim_compare_p:nNn \l_@@_corner_yarc_dim = { 0pt } }
+ { \bool_set_false:N \l_@@_corner_arc_bool }
+ { \bool_set_true:N \l_@@_corner_arc_bool }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_path_mark_corner:}
+% Mark up corners for arc post-processing.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_mark_corner:
+ {
+ \bool_if:NT \l_@@_corner_arc_bool
+ {
+ \@@_softpath_roundpoint:VV
+ \l_@@_corner_xarc_dim
+ \l_@@_corner_yarc_dim
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Basic path constructions}
+%
+% \begin{macro}{\draw_path_moveto:n, \draw_path_lineto:n}
+% \begin{macro}{\@@_path_moveto:nn, \@@_path_lineto:nn}
+% \begin{macro}{\draw_path_curveto:nnn}
+% \begin{macro}{\@@_path_curveto:nnnnnn}
+% At present, stick to purely linear transformation support and skip the
+% soft path business: that will likely need to be revisited later.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_moveto:n #1
+ {
+ \@@_point_process:nn
+ { \@@_path_moveto:nn }
+ { \draw_point_transform:n {#1} }
+ }
+\cs_new_protected:Npn \@@_path_moveto:nn #1#2
+ {
+ \@@_path_update_limits:nn {#1} {#2}
+ \@@_softpath_moveto:nn {#1} {#2}
+ \@@_path_update_last:nn {#1} {#2}
+ }
+\cs_new_protected:Npn \draw_path_lineto:n #1
+ {
+ \@@_point_process:nn
+ { \@@_path_lineto:nn }
+ { \draw_point_transform:n {#1} }
+ }
+\cs_new_protected:Npn \@@_path_lineto:nn #1#2
+ {
+ \@@_path_mark_corner:
+ \@@_path_update_limits:nn {#1} {#2}
+ \@@_softpath_lineto:nn {#1} {#2}
+ \@@_path_update_last:nn {#1} {#2}
+ }
+\cs_new_protected:Npn \draw_path_curveto:nnn #1#2#3
+ {
+ \@@_point_process:nnnn
+ {
+ \@@_path_mark_corner:
+ \@@_path_curveto:nnnnnn
+ }
+ { \draw_point_transform:n {#1} }
+ { \draw_point_transform:n {#2} }
+ { \draw_point_transform:n {#3} }
+ }
+\cs_new_protected:Npn \@@_path_curveto:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_update_limits:nn {#1} {#2}
+ \@@_path_update_limits:nn {#3} {#4}
+ \@@_path_update_limits:nn {#5} {#6}
+ \@@_softpath_curveto:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
+ \@@_path_update_last:nn {#5} {#6}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_close:}
+% A simple wrapper.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_close:
+ {
+ \@@_path_mark_corner:
+ \@@_softpath_closepath:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Canvas path constructions}
+%
+% \begin{macro}{\draw_path_canvas_moveto:n, \draw_path_canvas_lineto:n}
+% \begin{macro}{\draw_path_canvas_curveto:nnn}
+% Operations with no application of the transformation matrix.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_canvas_moveto:n #1
+ { \@@_point_process:nn { \@@_path_moveto:nn } {#1} }
+\cs_new_protected:Npn \draw_path_canvas_lineto:n #1
+ { \@@_point_process:nn { \@@_path_lineto:nn } {#1} }
+\cs_new_protected:Npn \draw_path_canvas_curveto:nnn #1#2#3
+ {
+ \@@_point_process:nnnn
+ {
+ \@@_path_mark_corner:
+ \@@_path_curveto:nnnnnn
+ }
+ {#1} {#2} {#3}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Computed curves}
+%
+% More complex operations need some calculations. To assist with those, various
+% constants are pre-defined.
+%
+% \begin{macro}{\draw_path_curveto:nn}
+% \begin{macro}{\@@_path_curveto:nnnn}
+% \begin{variable}{\c_@@_path_curveto_a_fp, \c_@@_path_curveto_b_fp}
+% A quadratic curve with one control point $(x_{\mathrm{c}},
+% y_{\mathrm{c}})$. The two required control points are then
+% \[
+% x_{1} = \frac{1}{3}x_{\mathrm{s}} + \frac{2}{3}x_{\mathrm{c}}
+% \quad
+% y_{1} = \frac{1}{3}y_{\mathrm{s}} + \frac{2}{3}y_{\mathrm{c}}
+% \]
+% and
+% \[
+% x_{2} = \frac{1}{3}x_{\mathrm{e}} + \frac{2}{3}x_{\mathrm{c}}
+% \quad
+% x_{2} = \frac{1}{3}y_{\mathrm{e}} + \frac{2}{3}y_{\mathrm{c}}
+% \]
+% using the start (last) point $(x_{\mathrm{s}}, y_{\mathrm{s}})$
+% and the end point $(x_{\mathrm{s}}, y_{\mathrm{s}})$.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_curveto:nn #1#2
+ {
+ \@@_point_process:nnn
+ { \@@_path_curveto:nnnn }
+ { \draw_point_transform:n {#1} }
+ { \draw_point_transform:n {#2} }
+ }
+\cs_new_protected:Npn \@@_path_curveto:nnnn #1#2#3#4
+ {
+ \fp_set:Nn \l_@@_path_tmpa_fp { \c_@@_path_curveto_b_fp * #1 }
+ \fp_set:Nn \l_@@_path_tmpb_fp { \c_@@_path_curveto_b_fp * #2 }
+ \use:x
+ {
+ \@@_path_mark_corner:
+ \@@_path_curveto:nnnnnn
+ {
+ \fp_to_dim:n
+ {
+ \c_@@_path_curveto_a_fp * \g_@@_path_lastx_dim
+ + \l_@@_path_tmpa_fp
+ }
+ }
+ {
+ \fp_to_dim:n
+ {
+ \c_@@_path_curveto_a_fp * \g_@@_path_lasty_dim
+ + \l_@@_path_tmpb_fp
+ }
+ }
+ {
+ \fp_to_dim:n
+ { \c_@@_path_curveto_a_fp * #3 + \l_@@_path_tmpa_fp }
+ }
+ {
+ \fp_to_dim:n
+ { \c_@@_path_curveto_a_fp * #4 + \l_@@_path_tmpb_fp }
+ }
+ {#3}
+ {#4}
+ }
+ }
+\fp_const:Nn \c_@@_path_curveto_a_fp { 1 / 3 }
+\fp_const:Nn \c_@@_path_curveto_b_fp { 2 / 3 }
+% \end{macrocode}
+% \end{variable}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_arc:nnn}
+% \begin{macro}{\draw_path_arc:nnnn}
+% \begin{macro}{\@@_path_arc:nnnn}
+% \begin{macro}{\@@_path_arc:nnNnn}
+% \begin{macro}
+% {
+% \@@_path_arc_auxi:nnnnNnn,
+% \@@_path_arc_auxi:fnnnNnn,
+% \@@_path_arc_auxi:fnfnNnn
+% }
+% \begin{macro}{\@@_path_arc_auxii:nnnNnnnn}
+% \begin{macro}{\@@_path_arc_auxiii:nn}
+% \begin{macro}{\@@_path_arc_auxiv:nnnn}
+% \begin{macro}{\@@_path_arc_auxv:nn, \@@_path_arc_auxvi:nn}
+% \begin{macro}{\@@_path_arc_add:nnnn}
+% \begin{variable}{\l_@@_path_arc_delta_fp, \l_@@_path_arc_start_fp}
+% \begin{variable}{\c_@@_path_arc_90_fp,\c_@@_path_arc_60_fp}
+% Drawing an arc means dividing the total curve required into sections:
+% using Bézier curves we can cover at most $90^{\circ}$ at once. To allow
+% for later manipulations, we aim to have roughly equal last segments to
+% the line, with the split set at a final part of $115^{\circ}$.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_arc:nnn #1#2#3
+ { \draw_path_arc:nnnn {#1} {#2} {#3} {#3} }
+\cs_new_protected:Npn \draw_path_arc:nnnn #1#2#3#4
+ {
+ \use:x
+ {
+ \@@_path_arc:nnnn
+ { \fp_eval:n {#1} }
+ { \fp_eval:n {#2} }
+ { \fp_to_dim:n {#3} }
+ { \fp_to_dim:n {#4} }
+ }
+ }
+\cs_new_protected:Npn \@@_path_arc:nnnn #1#2#3#4
+ {
+ \fp_compare:nNnTF {#1} > {#2}
+ { \@@_path_arc:nnNnn {#1} {#2} - {#3} {#4} }
+ { \@@_path_arc:nnNnn {#1} {#2} + {#3} {#4} }
+ }
+\cs_new_protected:Npn \@@_path_arc:nnNnn #1#2#3#4#5
+ {
+ \fp_set:Nn \l_@@_path_arc_start_fp {#1}
+ \fp_set:Nn \l_@@_path_arc_delta_fp { abs( #1 - #2 ) }
+ \fp_while_do:nNnn { \l_@@_path_arc_delta_fp } > { 90 }
+ {
+ \fp_compare:nNnTF \l_@@_path_arc_delta_fp > { 115 }
+ {
+ \@@_path_arc_auxi:ffnnNnn
+ { \fp_to_decimal:N \l_@@_path_arc_start_fp }
+ { \fp_eval:n { \l_@@_path_arc_start_fp #3 90 } }
+ { 90 } {#2}
+ #3 {#4} {#5}
+ }
+ {
+ \@@_path_arc_auxi:ffnnNnn
+ { \fp_to_decimal:N \l_@@_path_arc_start_fp }
+ { \fp_eval:n { \l_@@_path_arc_start_fp #3 60 } }
+ { 60 } {#2}
+ #3 {#4} {#5}
+ }
+ }
+ \@@_path_mark_corner:
+ \@@_path_arc_auxi:fnfnNnn
+ { \fp_to_decimal:N \l_@@_path_arc_start_fp }
+ {#2}
+ { \fp_eval:n { abs( \l_@@_path_arc_start_fp - #2 ) } }
+ {#2}
+ #3 {#4} {#5}
+ }
+% \end{macrocode}
+% The auxiliary is responsible for calculating the required points.
+% The \enquote{magic} number required to determine the length of the
+% control vectors is well-established for a right-angle:
+% $\frac{4}{3}(\sqrt{2} - 1) = 0.552\,284\,75$. For other cases, we follow
+% the calculation used by \pkg{pgf} but with the second common case of
+% $60^{\circ}$ pre-calculated for speed.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_arc_auxi:nnnnNnn #1#2#3#4#5#6#7
+ {
+ \use:x
+ {
+ \@@_path_arc_auxii:nnnNnnnn
+ {#1} {#2} {#4} #5 {#6} {#7}
+ {
+ \fp_to_dim:n
+ {
+ \cs_if_exist_use:cF
+ { c_@@_path_arc_ #3 _fp }
+ { 4/3 * tand( 0.25 * #3 ) }
+ * #6
+ }
+ }
+ {
+ \fp_to_dim:n
+ {
+ \cs_if_exist_use:cF
+ { c_@@_path_arc_ #3 _fp }
+ { 4/3 * tand( 0.25 * #3 ) }
+ * #7
+ }
+ }
+ }
+ }
+\cs_generate_variant:Nn \@@_path_arc_auxi:nnnnNnn { fnf , ff }
+% \end{macrocode}
+% We can now calculate the required points. As everything here is
+% non-expandable, that is best done by using \texttt{x}-type expansion
+% to build up the tokens. The three points are calculated out-of-order,
+% since finding the second control point needs the position of the end
+% point. Once the points are found, fire-off the fundamental path
+% operation and update the record of where we are up to. The final
+% point has to be
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_arc_auxii:nnnNnnnn #1#2#3#4#5#6#7#8
+ {
+ \tl_clear:N \l_@@_path_tmp_tl
+ \@@_point_process:nn
+ { \@@_path_arc_auxiii:nn }
+ {
+ \@@_point_transform_noshift:n
+ { \draw_point_polar:nnn {#7} {#8} { #1 #4 90 } }
+ }
+ \@@_point_process:nnn
+ { \@@_path_arc_auxiv:nnnn }
+ {
+ \draw_point_transform:n
+ { \draw_point_polar:nnn {#5} {#6} {#1} }
+ }
+ {
+ \draw_point_transform:n
+ { \draw_point_polar:nnn {#5} {#6} {#2} }
+ }
+ \@@_point_process:nn
+ { \@@_path_arc_auxv:nn }
+ {
+ \@@_point_transform_noshift:n
+ { \draw_point_polar:nnn {#7} {#8} { #2 #4 -90 } }
+ }
+ \exp_after:wN \@@_path_curveto:nnnnnn \l_@@_path_tmp_tl
+ \fp_set:Nn \l_@@_path_arc_delta_fp { abs ( #2 - #3 ) }
+ \fp_set:Nn \l_@@_path_arc_start_fp {#2}
+ }
+% \end{macrocode}
+% The first control point.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_arc_auxiii:nn #1#2
+ {
+ \@@_path_arc_aux_add:nn
+ { \g_@@_path_lastx_dim + #1 }
+ { \g_@@_path_lasty_dim + #2 }
+ }
+% \end{macrocode}
+% The end point: simple arithmetic.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_arc_auxiv:nnnn #1#2#3#4
+ {
+ \@@_path_arc_aux_add:nn
+ { \g_@@_path_lastx_dim - #1 + #3 }
+ { \g_@@_path_lasty_dim - #2 + #4 }
+ }
+% \end{macrocode}
+% The second control point: extract the last point, do some
+% rearrangement and record.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_arc_auxv:nn #1#2
+ {
+ \exp_after:wN \@@_path_arc_auxvi:nn
+ \l_@@_path_tmp_tl {#1} {#2}
+ }
+\cs_new_protected:Npn \@@_path_arc_auxvi:nn #1#2#3#4#5#6
+ {
+ \tl_set:Nn \l_@@_path_tmp_tl { {#1} {#2} }
+ \@@_path_arc_aux_add:nn
+ { #5 + #3 }
+ { #6 + #4 }
+ \tl_put_right:Nn \l_@@_path_tmp_tl { {#3} {#4} }
+ }
+\cs_new_protected:Npn \@@_path_arc_aux_add:nn #1#2
+ {
+ \tl_put_right:Nx \l_@@_path_tmp_tl
+ { { \fp_to_dim:n {#1} } { \fp_to_dim:n {#2} } }
+ }
+\fp_new:N \l_@@_path_arc_delta_fp
+\fp_new:N \l_@@_path_arc_start_fp
+\fp_const:cn { c_@@_path_arc_90_fp } { 4/3 * (sqrt(2) - 1) }
+\fp_const:cn { c_@@_path_arc_60_fp } { 4/3 * tand(15) }
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_arc_axes:nnnn}
+% A simple wrapper.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_arc_axes:nnnn #1#2#3#4
+ {
+ \draw_transform_triangle:nnn { 0cm , 0cm } {#3} {#4}
+ \draw_path_arc:nnn {#1} {#2} { 1pt }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_ellipse:nnn}
+% \begin{macro}{\@@_path_ellipse:nnnnnn}
+% \begin{macro}[EXP]
+% {
+% \@@_path_ellipse_arci:nnnnnn ,
+% \@@_path_ellipse_arcii:nnnnnn ,
+% \@@_path_ellipse_arciii:nnnnnn ,
+% \@@_path_ellipse_arciv:nnnnnn
+% }
+% \begin{variable}{\c_@@_path_ellipse_fp}
+% Drawing an ellipse is an optimised version of drawing an arc, in particular
+% reusing the same constant. We need to deal with the ellipse in four parts
+% and also deal with moving to the right place, closing it and ending up
+% back at the center. That is handled on a per-arc basis, each in a
+% separate auxiliary for readability.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_ellipse:nnn #1#2#3
+ {
+ \@@_point_process:nnnn
+ { \@@_path_ellipse:nnnnnn }
+ { \draw_point_transform:n {#1} }
+ { \@@_point_transform_noshift:n {#2} }
+ { \@@_point_transform_noshift:n {#3} }
+ }
+\cs_new_protected:Npn \@@_path_ellipse:nnnnnn #1#2#3#4#5#6
+ {
+ \use:x
+ {
+ \@@_path_moveto:nn
+ { \fp_to_dim:n { #1 + #3 } } { \fp_to_dim:n { #2 + #4 } }
+ \@@_path_ellipse_arci:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
+ \@@_path_ellipse_arcii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
+ \@@_path_ellipse_arciii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
+ \@@_path_ellipse_arciv:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6}
+ }
+ \@@_softpath_closepath:
+ \@@_path_moveto:nn {#1} {#2}
+ }
+\cs_new:Npn \@@_path_ellipse_arci:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_curveto:nnnnnn
+ { \fp_to_dim:n { #1 + #3 + #5 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #2 + #4 + #6 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #1 + #3 * \c_@@_path_ellipse_fp + #5 } }
+ { \fp_to_dim:n { #2 + #4 * \c_@@_path_ellipse_fp + #6 } }
+ { \fp_to_dim:n { #1 + #5 } }
+ { \fp_to_dim:n { #2 + #6 } }
+ }
+\cs_new:Npn \@@_path_ellipse_arcii:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_curveto:nnnnnn
+ { \fp_to_dim:n { #1 - #3 * \c_@@_path_ellipse_fp + #5 } }
+ { \fp_to_dim:n { #2 - #4 * \c_@@_path_ellipse_fp + #6 } }
+ { \fp_to_dim:n { #1 - #3 + #5 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #2 - #4 + #6 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #1 - #3 } }
+ { \fp_to_dim:n { #2 - #4 } }
+ }
+\cs_new:Npn \@@_path_ellipse_arciii:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_curveto:nnnnnn
+ { \fp_to_dim:n { #1 - #3 - #5 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #2 - #4 - #6 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #1 - #3 * \c_@@_path_ellipse_fp - #5 } }
+ { \fp_to_dim:n { #2 - #4 * \c_@@_path_ellipse_fp - #6 } }
+ { \fp_to_dim:n { #1 - #5 } }
+ { \fp_to_dim:n { #2 - #6 } }
+ }
+\cs_new:Npn \@@_path_ellipse_arciv:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_curveto:nnnnnn
+ { \fp_to_dim:n { #1 + #3 * \c_@@_path_ellipse_fp - #5 } }
+ { \fp_to_dim:n { #2 + #4 * \c_@@_path_ellipse_fp - #6 } }
+ { \fp_to_dim:n { #1 + #3 - #5 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #2 + #4 - #6 * \c_@@_path_ellipse_fp } }
+ { \fp_to_dim:n { #1 + #3 } }
+ { \fp_to_dim:n { #2 + #4 } }
+ }
+\fp_const:Nn \c_@@_path_ellipse_fp { \fp_use:c { c_@@_path_arc_90_fp } }
+% \end{macrocode}
+% \end{variable}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_circle:nn}
+% A shortcut.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_circle:nn #1#2
+ { \draw_path_ellipse:nnn {#1} { #2 , 0pt } { 0pt , #2 } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Rectangles}
+%
+% \begin{macro}{\draw_path_rectangle:nn}
+% \begin{macro}{\@@_path_rectangle:nnnn, \@@_path_rectangle_rounded:nnnn}
+% Building a rectangle can be a single operation, or for rounded versions will
+% involve step-by-step construction.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_rectangle:nn #1#2
+ {
+ \@@_point_process:nnn
+ {
+ \bool_lazy_or:nnTF
+ { \l_@@_corner_arc_bool }
+ { \l_@@_matrix_active_bool }
+ { \@@_path_rectangle_rounded:nnnn }
+ { \@@_path_rectangle:nnnn }
+ }
+ { \draw_point_transform:n {#1} }
+ {#2}
+ }
+\cs_new_protected:Npn \@@_path_rectangle:nnnn #1#2#3#4
+ {
+ \@@_path_update_limits:nn {#1} {#2}
+ \@@_path_update_limits:nn { #1 + #3 } { #2 + #4 }
+ \@@_softpath_rectangle:nnnn {#1} {#2} {#3} {#4}
+ \@@_path_update_last:nn {#1} {#2}
+ }
+\cs_new_protected:Npn \@@_path_rectangle_rounded:nnnn #1#2#3#4
+ {
+ \draw_path_moveto:n { #1 + #3 , #2 + #4 }
+ \draw_path_lineto:n { #1 , #2 + #4 }
+ \draw_path_lineto:n { #1 , #2 }
+ \draw_path_lineto:n { #1 + #3 , #2 }
+ \draw_path_close:
+ \draw_path_moveto:n { #1 , #2 }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_path_rectangle_corners:nn}
+% \begin{macro}{\@@_path_rectangle_corners:nnnn}
+% Another shortcut wrapper.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_rectangle_corners:nn #1#2
+ {
+ \@@_point_process:nnn
+ { \@@_path_rectangle_corners:nnnnn {#1} }
+ {#1} {#2}
+ }
+\cs_new_protected:Npn \@@_path_rectangle_corners:nnnnn #1#2#3#4#5
+ { \draw_path_rectangle:nn {#1} { #4 - #2 , #5 - #3 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Grids}
+%
+% \begin{macro}{\draw_path_grid:nnnn}
+% \begin{macro}
+% {
+% \@@_path_grid_auxi:nnnnnn, \@@_path_grid_auxi:ffnnnn,
+% \@@_path_grid_auxii:nnnnnn,
+% \@@_path_grid_auxiii:nnnnnn, \@@_path_grid_auxiiii:ffnnnn
+% }
+% \begin{macro}
+% {\@@_path_grid_auxiv:nnnnnnnn, \@@_path_grid_auxiv:ffnnnnnn}
+% The main complexity here is lining up the grid correctly.
+% To keep it simple, we tidy up the argument ordering first.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_grid:nnnn #1#2#3#4
+ {
+ \@@_point_process:nnn
+ {
+ \@@_path_grid_auxi:ffnnnn
+ { \dim_eval:n { \dim_abs:n {#1} } }
+ { \dim_eval:n { \dim_abs:n {#2} } }
+ }
+ {#3} {#4}
+ }
+\cs_new_protected:Npn \@@_path_grid_auxi:nnnnnn #1#2#3#4#5#6
+ {
+ \dim_compare:nNnTF {#3} > {#5}
+ { \@@_path_grid_auxii:nnnnnn {#1} {#2} {#5} {#4} {#3} {#6} }
+ { \@@_path_grid_auxii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6} }
+ }
+\cs_generate_variant:Nn \@@_path_grid_auxi:nnnnnn { ff }
+\cs_new_protected:Npn \@@_path_grid_auxii:nnnnnn #1#2#3#4#5#6
+ {
+ \dim_compare:nNnTF {#4} > {#6}
+ { \@@_path_grid_auxiii:nnnnnn {#1} {#2} {#3} {#6} {#5} {#4} }
+ { \@@_path_grid_auxiii:nnnnnn {#1} {#2} {#3} {#4} {#5} {#6} }
+ }
+\cs_new_protected:Npn \@@_path_grid_auxiii:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_path_grid_auxiv:ffnnnnnn
+ { \fp_to_dim:n { #1 * trunc(#3/(#1)) } }
+ { \fp_to_dim:n { #2 * trunc(#4/(#2)) } }
+ {#1} {#2} {#3} {#4} {#5} {#6}
+ }
+\cs_new_protected:Npn \@@_path_grid_auxiv:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \dim_step_inline:nnnn
+ {#1}
+ {#3}
+ {#7}
+ {
+ \draw_path_moveto:n { ##1 , #6 }
+ \draw_path_lineto:n { ##1 , #8 }
+ }
+ \dim_step_inline:nnnn
+ {#2}
+ {#4}
+ {#8}
+ {
+ \draw_path_moveto:n { #5 , ##1 }
+ \draw_path_lineto:n { #7 , ##1 }
+ }
+ }
+\cs_generate_variant:Nn \@@_path_grid_auxiv:nnnnnnnn { ff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Using paths}
+%
+% \begin{variable}
+% {
+% \l_@@_path_use_clip_bool ,
+% \l_@@_path_use_fill_bool ,
+% \l_@@_path_use_stroke_bool
+% }
+% Actions to pass to the driver.
+% \begin{macrocode}
+\bool_new:N \l_@@_path_use_clip_bool
+\bool_new:N \l_@@_path_use_fill_bool
+\bool_new:N \l_@@_path_use_stroke_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_path_use_bb_bool, \l_@@_path_use_clear_bool}
+% Actions handled at the macro layer.
+% \begin{macrocode}
+\bool_new:N \l_@@_path_use_bb_bool
+\bool_new:N \l_@@_path_use_clear_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_path_use:n, \draw_path_use_clear:n}
+% \begin{macro}{\@@_path_use:n}
+% \begin{macro}{\@@_path_use_action_draw:, \@@_path_use_action_fillstroke:}
+% \begin{macro}{\@@_path_use_stroke_bb:}
+% \begin{macro}{\@@_path_use_stroke_bb_aux:NnN}
+% There are a range of actions which can apply to a path: they are handled
+% in a single function which can carry out several of them. The first step
+% is to deal with the special case of clearing the path.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_use:n #1
+ {
+ \tl_if_blank:nF {#1}
+ { \@@_path_use:n {#1} }
+ }
+\cs_new_protected:Npn \draw_path_use_clear:n #1
+ {
+ \bool_lazy_or:nnTF
+ { \tl_if_blank_p:n {#1} }
+ { \str_if_eq_p:nn {#1} { clear } }
+ {
+ \@@_softpath_clear:
+ \@@_path_reset_limits:
+ }
+ { \@@_path_use:n { #1 , clear } }
+ }
+% \end{macrocode}
+% Map over the actions and set up the data: mainly just booleans,
+% but with the possibility to cover more complex cases. The business end
+% of the function is a series of checks on the various flags, then
+% taking the appropriate action(s).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_use:n #1
+ {
+ \bool_set_false:N \l_@@_path_use_clip_bool
+ \bool_set_false:N \l_@@_path_use_fill_bool
+ \bool_set_false:N \l_@@_path_use_stroke_bool
+ \clist_map_inline:nn {#1}
+ {
+ \cs_if_exist:cTF { l_@@_path_use_ ##1 _ bool }
+ { \bool_set_true:c { l_@@_path_use_ ##1 _ bool } }
+ {
+ \cs_if_exist_use:cF { @@_path_use_action_ ##1 : }
+ { \msg_error:nnn { draw } { invalid-path-action } {##1} }
+ }
+ }
+ \@@_softpath_round_corners:
+ \bool_lazy_and:nnT
+ { \l_draw_bb_update_bool }
+ { \l_@@_path_use_stroke_bool }
+ { \@@_path_use_stroke_bb: }
+ \@@_softpath_use:
+ \bool_if:NT \l_@@_path_use_clip_bool
+ {
+ \@@_backend_clip:
+ \bool_lazy_or:nnF
+ { \l_@@_path_use_fill_bool }
+ { \l_@@_path_use_stroke_bool }
+ { \@@_backend_discardpath: }
+ }
+ \bool_lazy_or:nnT
+ { \l_@@_path_use_fill_bool }
+ { \l_@@_path_use_stroke_bool }
+ {
+ \use:c
+ {
+ @@_backend_
+ \bool_if:NT \l_@@_path_use_fill_bool { fill }
+ \bool_if:NT \l_@@_path_use_stroke_bool { stroke }
+ :
+ }
+ }
+ \bool_if:NT \l_@@_path_use_clear_bool
+ { \@@_softpath_clear: }
+ }
+\cs_new_protected:Npn \@@_path_use_action_draw:
+ {
+ \bool_set_true:N \l_@@_path_use_stroke_bool
+ }
+\cs_new_protected:Npn \@@_path_use_action_fillstroke:
+ {
+ \bool_set_true:N \l_@@_path_use_fill_bool
+ \bool_set_true:N \l_@@_path_use_stroke_bool
+ }
+% \end{macrocode}
+% Where the path is relevant to size and is stroked, we need to allow for
+% the part which overlaps the edge of the bounding box.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_path_use_stroke_bb:
+ {
+ \@@_path_use_stroke_bb_aux:NnN x { max } +
+ \@@_path_use_stroke_bb_aux:NnN y { max } +
+ \@@_path_use_stroke_bb_aux:NnN x { min } -
+ \@@_path_use_stroke_bb_aux:NnN y { min } -
+ }
+\cs_new_protected:Npn \@@_path_use_stroke_bb_aux:NnN #1#2#3
+ {
+ \dim_compare:nNnF { \dim_use:c { g_@@_ #1#2 _dim } } = { #3 -\c_max_dim }
+ {
+ \dim_gset:cn { g_@@_ #1#2 _dim }
+ {
+ \use:c { dim_ #2 :nn }
+ { \dim_use:c { g_@@_ #1#2 _dim } }
+ {
+ \dim_use:c { g_@@_path_ #1#2 _dim }
+ #3 0.5 \g_@@_linewidth_dim
+ }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Scoping paths}
+%
+% \begin{variable}
+% {
+% \l_@@_path_lastx_dim, \l_@@_path_lasty_dim,
+% \l_@@_path_xmax_dim, \l_@@_path_xmin_dim,
+% \l_@@_path_ymax_dim, \l_@@_path_ymin_dim
+% }
+% \begin{variable}{\l_@@_softpath_corners_bool}
+% Local storage for global data. There is already a
+% \cs{l_@@_softpath_main_tl} for path manipulation, so we can reuse that
+% (it is always grouped when the path is being reconstructed).
+% \begin{macrocode}
+\dim_new:N \l_@@_path_lastx_dim
+\dim_new:N \l_@@_path_lasty_dim
+\dim_new:N \l_@@_path_xmax_dim
+\dim_new:N \l_@@_path_xmin_dim
+\dim_new:N \l_@@_path_ymax_dim
+\dim_new:N \l_@@_path_ymin_dim
+\dim_new:N \l_@@_softpath_lastx_dim
+\dim_new:N \l_@@_softpath_lasty_dim
+\bool_new:N \l_@@_softpath_corners_bool
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+%
+% \begin{macro}{\draw_path_scope_begin:, \draw_path_scope_end:}
+% Scoping a path is a bit more involved, largely as there are a number
+% of variables to keep hold of.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_path_scope_begin:
+ {
+ \group_begin:
+ \dim_set_eq:NN \l_@@_path_lastx_dim \g_@@_path_lastx_dim
+ \dim_set_eq:NN \l_@@_path_lasty_dim \g_@@_path_lasty_dim
+ \dim_set_eq:NN \l_@@_path_xmax_dim \g_@@_path_xmax_dim
+ \dim_set_eq:NN \l_@@_path_xmin_dim \g_@@_path_xmin_dim
+ \dim_set_eq:NN \l_@@_path_ymax_dim \g_@@_path_ymax_dim
+ \dim_set_eq:NN \l_@@_path_ymin_dim \g_@@_path_ymin_dim
+ \dim_set_eq:NN \l_@@_softpath_lastx_dim \g_@@_softpath_lastx_dim
+ \dim_set_eq:NN \l_@@_softpath_lasty_dim \g_@@_softpath_lasty_dim
+ \@@_path_reset_limits:
+ \tl_build_get:NN \g_@@_softpath_main_tl \l_@@_softpath_main_tl
+ \bool_set_eq:NN
+ \l_@@_softpath_corners_bool
+ \g_@@_softpath_corners_bool
+ \@@_softpath_clear:
+ }
+\cs_new_protected:Npn \draw_path_scope_end:
+ {
+ \@@_softpath_clear:
+ \bool_gset_eq:NN
+ \g_@@_softpath_corners_bool
+ \l_@@_softpath_corners_bool
+ \@@_softpath_add:o \l_@@_softpath_main_tl
+ \dim_gset_eq:NN \g_@@_softpath_lastx_dim \l_@@_softpath_lastx_dim
+ \dim_gset_eq:NN \g_@@_softpath_lasty_dim \l_@@_softpath_lasty_dim
+ \dim_gset_eq:NN \g_@@_path_xmax_dim \l_@@_path_xmax_dim
+ \dim_gset_eq:NN \g_@@_path_xmin_dim \l_@@_path_xmin_dim
+ \dim_gset_eq:NN \g_@@_path_ymax_dim \l_@@_path_ymax_dim
+ \dim_gset_eq:NN \g_@@_path_ymin_dim \l_@@_path_ymin_dim
+ \dim_gset_eq:NN \g_@@_path_lastx_dim \l_@@_path_lastx_dim
+ \dim_gset_eq:NN \g_@@_path_lasty_dim \l_@@_path_lasty_dim
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+\msg_new:nnnn { draw } { invalid-path-action }
+ { Invalid~action~'#1'~for~path. }
+ { Paths~can~be~used~with~actions~'draw',~'clip',~'fill'~or~'stroke'. }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-points.dtx b/macros/latex/contrib/l3experimental/l3draw-points.dtx
new file mode 100644
index 0000000000..a171b598e7
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-points.dtx
@@ -0,0 +1,971 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-points.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-points} package\\ Calculating points^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-points} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% This sub-module covers more-or-less the same ideas as
+% \texttt{pgfcorepoints.code.tex}, though the approach taken to returning
+% values is different: point expressions here are processed by expansion
+% and return a co-ordinate pair in the form |{|\meta{x}|}{|\meta{y}|}|.
+% Equivalents of following \pkg{pgf} functions are deliberately omitted:
+% \begin{itemize}
+% \item \cs{pgfpointorigin}: Can be given explicitly as |0pt,0pt|.
+% \item \cs{pgfpointadd}, \cs{pgfpointdiff},
+% \cs{pgfpointscale}: Can be given explicitly.
+% \item \cs{pgfextractx}, \cs{pgfextracty}: Available by applying
+% \cs{use_i:nn}/\cs{use_ii:nn} or similar to the \texttt{x}-type
+% expansion of a point expression.
+% \item \cs{pgfgetlastxy}: Unused in the entire \pkg{pgf} core, may be
+% emulated by \texttt{x}-type expansion of a point expression, then using
+% the result.
+% \end{itemize}
+% In addition, equivalents of the following \emph{may} be added in future but
+% are currently absent:
+% \begin{itemize}
+% \item \cs{pgfpointcylindrical}, \cs{pgfpointspherical}: The usefulness
+% of these commands is not currently clear.
+% \item \cs{pgfpointborderrectangle}, \cs{pgfpointborderellipse}: To be
+% revisited once the semantics and use cases are clear.
+% \item \cs{pgfqpoint}, \cs{pgfqpointscale}, \cs{pgfqpointpolar},
+% \cs{pgfqpointxy}, \cs{pgfqpointxyz}: The expandable approach taken in
+% the code here, along with the absolute requirement for \eTeX{}, means
+% it is likely many use cases for these commands may be covered in other
+% ways. This may be revisited as higher-level structures are constructed.
+% \end{itemize}
+%
+% \subsection{Support functions}
+%
+% \begin{macro}[EXP]{\@@_point_process:nn}
+% \begin{macro}[EXP]{\@@_point_process_auxi:nn}
+% \begin{macro}[EXP]{\@@_point_process_auxii:nw}
+% \begin{macro}[EXP]{\@@_point_process:nnn}
+% \begin{macro}[EXP]{\@@_point_process_auxiii:nnn}
+% \begin{macro}[EXP]{\@@_point_process_auxiv:nw}
+% \begin{macro}[EXP]{\@@_point_process:nnnn}
+% \begin{macro}[EXP]{\@@_point_process_auxv:nnnn}
+% \begin{macro}[EXP]{\@@_point_process_auxvi:nw}
+% \begin{macro}[EXP]{\@@_point_process:nnnnn}
+% \begin{macro}[EXP]{\@@_point_process_auxvii:nnnnn}
+% \begin{macro}[EXP]{\@@_point_process_auxviii:nw}
+% Execute whatever code is passed to extract the $x$ and $y$ co-ordinates.
+% The first argument here should itself absorb two arguments. There is
+% also a version to deal with two co-ordinates: common enough to justify a
+% separate function.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_process:nn #1#2
+ {
+ \exp_args:Nf \@@_point_process_auxi:nn
+ { \@@_point_to_dim:n {#2} }
+ {#1}
+ }
+\cs_new:Npn \@@_point_process_auxi:nn #1#2
+ { \@@_point_process_auxii:nw {#2} #1 \q_stop }
+\cs_new:Npn \@@_point_process_auxii:nw #1 #2 , #3 \q_stop
+ { #1 {#2} {#3} }
+\cs_new:Npn \@@_point_process:nnn #1#2#3
+ {
+ \exp_args:Nff \@@_point_process_auxiii:nnn
+ { \@@_point_to_dim:n {#2} }
+ { \@@_point_to_dim:n {#3} }
+ {#1}
+ }
+\cs_new:Npn \@@_point_process_auxiii:nnn #1#2#3
+ { \@@_point_process_auxiv:nw {#3} #1 \q_mark #2 \q_stop }
+\cs_new:Npn \@@_point_process_auxiv:nw #1 #2 , #3 \q_mark #4 , #5 \q_stop
+ { #1 {#2} {#3} {#4} {#5} }
+\cs_new:Npn \@@_point_process:nnnn #1#2#3#4
+ {
+ \exp_args:Nfff \@@_point_process_auxv:nnnn
+ { \@@_point_to_dim:n {#2} }
+ { \@@_point_to_dim:n {#3} }
+ { \@@_point_to_dim:n {#4} }
+ {#1}
+ }
+\cs_new:Npn \@@_point_process_auxv:nnnn #1#2#3#4
+ { \@@_point_process_auxvi:nw {#4} #1 \q_mark #2 \q_mark #3 \q_stop }
+\cs_new:Npn \@@_point_process_auxvi:nw
+ #1 #2 , #3 \q_mark #4 , #5 \q_mark #6 , #7 \q_stop
+ { #1 {#2} {#3} {#4} {#5} {#6} {#7} }
+\cs_new:Npn \@@_point_process:nnnnn #1#2#3#4#5
+ {
+ \exp_args:Nffff \@@_point_process_auxvii:nnnnn
+ { \@@_point_to_dim:n {#2} }
+ { \@@_point_to_dim:n {#3} }
+ { \@@_point_to_dim:n {#4} }
+ { \@@_point_to_dim:n {#5} }
+ {#1}
+ }
+\cs_new:Npn \@@_point_process_auxvii:nnnnn #1#2#3#4#5
+ {
+ \@@_point_process_auxviii:nw
+ {#5} #1 \q_mark #2 \q_mark #3 \q_mark #4 \q_stop
+ }
+\cs_new:Npn \@@_point_process_auxviii:nw
+ #1 #2 , #3 \q_mark #4 , #5 \q_mark #6 , #7 \q_mark #8 , #9 \q_stop
+ { #1 {#2} {#3} {#4} {#5} {#6} {#7} {#8} {#9} }
+% \end{macrocode}
+% \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]{\@@_point_to_dim:n}
+% \begin{macro}[EXP]{\@@_point_to_dim_aux:n, \@@_point_to_dim_aux:f}
+% \begin{macro}[EXP]{\@@_point_to_dim_aux:w}
+% Co-ordinates are always returned as two dimensions.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_to_dim:n #1
+ { \@@_point_to_dim_aux:f { \fp_eval:n {#1} } }
+\cs_new:Npn \@@_point_to_dim_aux:n #1
+ { \@@_point_to_dim_aux:w #1 }
+\cs_generate_variant:Nn \@@_point_to_dim_aux:n { f }
+\cs_new:Npn \@@_point_to_dim_aux:w ( #1 , ~ #2 ) { #1pt , #2pt }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Polar co-ordinates}
+%
+% \begin{macro}[EXP]{\draw_point_polar:nn}
+% \begin{macro}[EXP]{\draw_point_polar:nnn}
+% \begin{macro}[EXP]{\@@_draw_polar:nnn, \@@_draw_polar:fnn}
+% Polar co-ordinates may have either one or two lengths, so there is a need
+% to do a simple split before the calculation. As the angle gets used twice,
+% save on any expression evaluation there and force expansion.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_polar:nn #1#2
+ { \draw_point_polar:nnn {#1} {#1} {#2} }
+\cs_new:Npn \draw_point_polar:nnn #1#2#3
+ { \@@_draw_polar:fnn { \fp_eval:n {#3} } {#1} {#2} }
+\cs_new:Npn \@@_draw_polar:nnn #1#2#3
+ { \@@_point_to_dim:n { cosd(#1) * (#2) , sind(#1) * (#3) } }
+\cs_generate_variant:Nn \@@_draw_polar:nnn { f }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Point expression arithmetic}
+%
+% These functions all take point expressions as arguments.
+%
+% \begin{macro}[EXP]{\draw_point_unit_vector:n}
+% \begin{macro}[EXP]{\@@_point_unit_vector:nn}
+% \begin{macro}[EXP]{\@@_point_unit_vector:nnn}
+% The outcome is the normalised vector from $(0,0)$ in the direction of
+% the point, \emph{i.e.}
+% \[
+% P_{x} = \frac{x}{\sqrt{x^{2} + y^{2}}} \quad
+% P_{y} = \frac{y}{\sqrt{x^{2} + y^{2}}}
+% \]
+% except where the length is zero, in which case a vertical vector is
+% returned.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_unit_vector:n #1
+ { \@@_point_process:nn { \@@_point_unit_vector:nn } {#1} }
+\cs_new:Npn \@@_point_unit_vector:nn #1#2
+ {
+ \exp_args:Nf \@@_point_unit_vector:nnn
+ { \fp_eval:n { (sqrt(#1 * #1 + #2 * #2)) } }
+ {#1} {#2}
+ }
+\cs_new:Npn \@@_point_unit_vector:nnn #1#2#3
+ {
+ \fp_compare:nNnTF {#1} = \c_zero_fp
+ { 0pt, 1pt }
+ {
+ \@@_point_to_dim:n
+ { ( #2 , #3 ) / #1 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Intersection calculations}
+%
+% \begin{macro}[EXP]{\draw_point_intersect_lines:nnnn}
+% \begin{macro}[EXP]{\@@_point_intersect_lines:nnnnnn}
+% \begin{macro}[EXP]{\@@_point_intersect_lines:nnnnnnnn}
+% \begin{macro}[EXP]
+% {\@@_point_intersect_lines_aux:nnnnnn, \@@_point_intersect_lines_aux:ffffff}
+% The intersection point~$P$ between a line joining points $(x_{1}, y_{1})$
+% and $(x_{2}, y_{2})$ with a second line joining points $(x_{3}, y_{3})$
+% and $(x_{4}, y_{4})$ can be calculated using the formulae
+% \[
+% P_{x} =
+% \frac{(x_{1}y_{2} - y_{1}x_{2})(x_{3} - x_{4})
+% - (x_{3}y_{4} - y_{3}x_{4})(x_{1} - x_{2})}
+% {(x_{1} - x_{2})(y_{3} - y_{4}) - (y_{1} - y_{2})(x_{3} - x_{4})}
+% \]
+% and
+% \[
+% P_{y} =
+% \frac{(x_{1}y_{2} - y_{1}x_{2})(y_{3} - y_{5})
+% - (x_{3}y_{4} - y_{3}x_{4})(y_{1} - y_{2})}
+% {(x_{1} - x_{2})(y_{3} - y_{4}) - (y_{1} - y_{2})(x_{3} - x_{4})}
+% \]
+% The work therefore comes down to expanding the incoming data, then
+% pre-calculating as many parts as possible before the final work to find
+% the intersection. (Expansion and argument re-ordering is much less work
+% than additional floating point calculations.)
+% \begin{macrocode}
+\cs_new:Npn \draw_point_intersect_lines:nnnn #1#2#3#4
+ {
+ \@@_point_process:nnnnn
+ { \@@_point_intersect_lines:nnnnnnnn }
+ {#1} {#2} {#3} {#4}
+ }
+% \end{macrocode}
+% At this stage we have all of the information we need, fully expanded:
+% \begin{enumerate}[label = \#\arabic*, font = \ttfamily]
+% \item $x_{1}$
+% \item $y_{1}$
+% \item $x_{2}$
+% \item $y_{2}$
+% \item $x_{3}$
+% \item $y_{3}$
+% \item $x_{4}$
+% \item $y_{4}$
+% \end{enumerate}
+% so now just have to do all of the calculation.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_intersect_lines:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \@@_point_intersect_lines_aux:ffffff
+ { \fp_eval:n { #1 * #4 - #2 * #3 } }
+ { \fp_eval:n { #5 * #8 - #6 * #7 } }
+ { \fp_eval:n { #1 - #3 } }
+ { \fp_eval:n { #5 - #7 } }
+ { \fp_eval:n { #2 - #4 } }
+ { \fp_eval:n { #6 - #8 } }
+ }
+\cs_new:Npn \@@_point_intersect_lines_aux:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_point_to_dim:n
+ {
+ ( #2 * #3 - #1 * #4 , #2 * #5 - #1 * #6 )
+ / ( #4 * #5 - #6 * #3 )
+ }
+ }
+\cs_generate_variant:Nn \@@_point_intersect_lines_aux:nnnnnn { ffffff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\draw_point_intersect_circles:nnnnn}
+% \begin{macro}[EXP]{\@@_point_intersect_circles_auxi:nnnnnnn}
+% \begin{macro}[EXP]
+% {
+% \@@_point_intersect_circles_auxii:nnnnnnn,
+% \@@_point_intersect_circles_auxii:ffnnnnn,
+% \@@_point_intersect_circles_auxiii:nnnnnnn,
+% \@@_point_intersect_circles_auxiii:ffnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_intersect_circles_auxiv:nnnnnnnn,
+% \@@_point_intersect_circles_auxiv:fnnnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_intersect_circles_auxv:nnnnnnnnn,
+% \@@_point_intersect_circles_auxv:ffnnnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_intersect_circles_auxvi:nnnnnnnn,
+% \@@_point_intersect_circles_auxvi:fnnnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_intersect_circles_auxvii:nnnnnnn,
+% \@@_point_intersect_circles_auxvii:fffnnnn
+% }
+% Another long expansion chain to get the values in the right places.
+% We have two circles, the first with center $(a, b)$ and radius~$r$,
+% the second with center $(c, d)$ and radius~$s$. We use the intermediate
+% values
+% \begin{align*}
+% e &= c - a \\
+% f &= d - b \\
+% p &= \sqrt{e^{2} + f^{2}} \\
+% k &= \frac{p^{2} + r^{2} - s^{2}}{2p}
+% \end{align*}
+% in either
+% \begin{align*}
+% P_{x} &= a + \frac{ek}{p} + \frac{f}{p}\sqrt{r^{2} - k^{2}} \\
+% P_{y} &= b + \frac{fk}{p} - \frac{e}{p}\sqrt{r^{2} - k^{2}}
+% \end{align*}
+% or
+% \begin{align*}
+% P_{x} &= a + \frac{ek}{p} - \frac{f}{p}\sqrt{r^{2} - k^{2}} \\
+% P_{y} &= b + \frac{fk}{p} + \frac{e}{p}\sqrt{r^{2} - k^{2}}
+% \end{align*}
+% depending on which solution is required. The rest of the work is simply
+% forcing the appropriate expansion and shuffling arguments.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_intersect_circles:nnnnn #1#2#3#4#5
+ {
+ \@@_point_process:nnn
+ { \@@_point_intersect_circles_auxi:nnnnnnn {#2} {#4} {#5} }
+ {#1} {#3}
+ }
+\cs_new:Npn \@@_point_intersect_circles_auxi:nnnnnnn #1#2#3#4#5#6#7
+ {
+ \@@_point_intersect_circles_auxii:ffnnnnn
+ { \fp_eval:n {#1} } { \fp_eval:n {#2} } {#4} {#5} {#6} {#7} {#3}
+ }
+% \end{macrocode}
+% At this stage we have all of the information we need, fully expanded:
+% \begin{enumerate}[label = \#\arabic*, font = \ttfamily]
+% \item $r$
+% \item $s$
+% \item $a$
+% \item $b$
+% \item $c$
+% \item $d$
+% \item $n$
+% \end{enumerate}
+% Once we evaluate $e$ and $f$, the co-ordinate $(c,d)$ is no longer
+% required: handy as we will need various intermediate values in the
+% following.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_intersect_circles_auxii:nnnnnnn #1#2#3#4#5#6#7
+ {
+ \@@_point_intersect_circles_auxiii:ffnnnnn
+ { \fp_eval:n { #5 - #3 } }
+ { \fp_eval:n { #6 - #4 } }
+ {#1} {#2} {#3} {#4} {#7}
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxii:nnnnnnn { ff }
+\cs_new:Npn \@@_point_intersect_circles_auxiii:nnnnnnn #1#2#3#4#5#6#7
+ {
+ \@@_point_intersect_circles_auxiv:fnnnnnnn
+ { \fp_eval:n { sqrt( #1 * #1 + #2 * #2 ) } }
+ {#1} {#2} {#3} {#4} {#5} {#6} {#7}
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxiii:nnnnnnn { ff }
+% \end{macrocode}
+% We now have $p$: we pre-calculate $1/p$ as it is needed a few times and
+% is relatively expensive. We also need $r^{2}$ twice so deal with that
+% here too.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_intersect_circles_auxiv:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \@@_point_intersect_circles_auxv:ffnnnnnnn
+ { \fp_eval:n { 1 / #1 } }
+ { \fp_eval:n { #4 * #4 } }
+ {#1} {#2} {#3} {#5} {#6} {#7} {#8}
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxiv:nnnnnnnn { f }
+\cs_new:Npn \@@_point_intersect_circles_auxv:nnnnnnnnn #1#2#3#4#5#6#7#8#9
+ {
+ \@@_point_intersect_circles_auxvi:fnnnnnnn
+ { \fp_eval:n { 0.5 * #1 * ( #2 + #3 * #3 - #6 * #6 ) } }
+ {#1} {#2} {#4} {#5} {#7} {#8} {#9}
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxv:nnnnnnnnn { ff }
+% \end{macrocode}
+% We now have all of the intermediate values we require, with one division
+% carried out up-front to avoid doing this expensive step twice:
+% \begin{enumerate}[label = \#\arabic*, font = \ttfamily]
+% \item $k$
+% \item $1/p$
+% \item $r^{2}$
+% \item $e$
+% \item $f$
+% \item $a$
+% \item $b$
+% \item $n$
+% \end{enumerate}
+% There are some final pre-calculations, $k/p$,
+% $\frac{\sqrt{r^{2} - k^{2}}}{p}$ and the usage of $n$, then we
+% can yield a result.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_intersect_circles_auxvi:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \@@_point_intersect_circles_auxvii:fffnnnn
+ { \fp_eval:n { #1 * #2 } }
+ { \int_if_odd:nTF {#8} { 1 } { -1 } }
+ { \fp_eval:n { sqrt ( #3 - #1 * #1 ) * #2 } }
+ {#4} {#5} {#6} {#7}
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxvi:nnnnnnnn { f }
+\cs_new:Npn \@@_point_intersect_circles_auxvii:nnnnnnn #1#2#3#4#5#6#7
+ {
+ \@@_point_to_dim:n
+ { #6 + #4 * #1 + #2 * #3 * #5 , #7 + #5 * #1 + -1 * #2 * #3 * #4 }
+ }
+\cs_generate_variant:Nn \@@_point_intersect_circles_auxvii:nnnnnnn { fff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Interpolation on a line (vector) or arc}
+%
+% \begin{macro}[EXP]{\draw_point_interpolate_line:nnn}
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_line_aux:nnnnn,
+% \@@_point_interpolate_line_aux:fnnnn,
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_line_aux:nnnnnn,
+% \@@_point_interpolate_line_aux:fnnnnn,
+% }
+% Simple maths after expansion.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_interpolate_line:nnn #1#2#3
+ {
+ \@@_point_process:nnn
+ { \@@_point_interpolate_line_aux:fnnnn { \fp_eval:n {#1} } }
+ {#2} {#3}
+ }
+\cs_new:Npn \@@_point_interpolate_line_aux:nnnnn #1#2#3#4#5
+ {
+ \@@_point_interpolate_line_aux:fnnnnn { \fp_eval:n { 1 - #1 } }
+ {#1} {#2} {#3} {#4} {#5}
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_line_aux:nnnnn { f }
+\cs_new:Npn \@@_point_interpolate_line_aux:nnnnnn #1#2#3#4#5#6
+ { \@@_point_to_dim:n { #2 * #3 + #1 * #5 , #2 * #4 + #1 * #6 } }
+\cs_generate_variant:Nn \@@_point_interpolate_line_aux:nnnnnn { f }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\draw_point_interpolate_distance:nnn}
+% \begin{macro}[EXP]{\@@_point_interpolate_distance:nnnnn}
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_distance:nnnnnn,
+% \@@_point_interpolate_distance:fnnnnn,
+% }
+% Same idea but using the normalised length to obtain the scale factor.
+% The start point is needed twice, so we force evaluation, but the end
+% point is needed only the once.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_interpolate_distance:nnn #1#2#3
+ {
+ \@@_point_process:nn
+ { \@@_point_interpolate_distance:nnnn {#1} {#3} }
+ {#2}
+ }
+\cs_new:Npn \@@_point_interpolate_distance:nnnn #1#2#3#4
+ {
+ \@@_point_process:nn
+ {
+ \@@_point_interpolate_distance:fnnnn
+ { \fp_eval:n {#1} } {#3} {#4}
+ }
+ { \draw_point_unit_vector:n { ( #2 ) - ( #3 , #4 ) } }
+ }
+\cs_new:Npn \@@_point_interpolate_distance:nnnnn #1#2#3#4#5
+ { \@@_point_to_dim:n { #2 + #1 * #4 , #3 + #1 * #5 } }
+\cs_generate_variant:Nn \@@_point_interpolate_distance:nnnnn { f }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\draw_point_interpolate_arcaxes:nnnnnn}
+% \begin{macro}[EXP]{\@@_point_interpolate_arcaxes_auxi:nnnnnnnnn}
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_arcaxes_auxii:nnnnnnnnn,
+% \@@_point_interpolate_arcaxes_auxii:fnnnnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_arcaxes_auxiii:nnnnnnn,
+% \@@_point_interpolate_arcaxes_auxiii:fnnnnnn
+% }
+% \begin{macro}[EXP]
+% {
+% \@@_point_interpolate_arcaxes_auxiv:nnnnnnnn,
+% \@@_point_interpolate_arcaxes_auxiv:ffnnnnnn
+% }
+% Finding a point on an ellipse arc is relatively easy: find the correct
+% angle between the two given, use the sine and cosine of that angle,
+% apply to the axes. We just have to work a bit with the co-ordinate
+% expansion.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_interpolate_arcaxes:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_point_process:nnnn
+ { \@@_point_interpolate_arcaxes_auxi:nnnnnnnnn {#1} {#5} {#6} }
+ {#2} {#3} {#4}
+ }
+\cs_new:Npn \@@_point_interpolate_arcaxes_auxi:nnnnnnnnn #1#2#3#4#5#6#7#8#9
+ {
+ \@@_point_interpolate_arcaxes_auxii:fnnnnnnnn
+ { \fp_eval:n {#1} } {#2} {#3} {#4} {#5} {#6} {#7} {#8} {#9}
+ }
+% \end{macrocode}
+% At this stage, the three co-ordinate pairs are fully expanded but somewhat
+% re-ordered:
+% \begin{enumerate}[label = \#\arabic*, font = \ttfamily]
+% \item $p$
+% \item $\theta_{1}$
+% \item $\theta_{2}$
+% \item $x_{c}$
+% \item $y_{c}$
+% \item $x_{a1}$
+% \item $y_{a1}$
+% \item $x_{a2}$
+% \item $y_{a2}$
+% \end{enumerate}
+% We are now in a position to find the target angle, and from that
+% the sine and cosine required.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_interpolate_arcaxes_auxii:nnnnnnnnn #1#2#3#4#5#6#7#8#9
+ {
+ \@@_point_interpolate_arcaxes_auxiii:fnnnnnn
+ { \fp_eval:n { #1 * (#3) + ( 1 - #1 ) * (#2) } }
+ {#4} {#5} {#6} {#7} {#8} {#9}
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_arcaxes_auxii:nnnnnnnnn { f }
+\cs_new:Npn \@@_point_interpolate_arcaxes_auxiii:nnnnnnn #1#2#3#4#5#6#7
+ {
+ \@@_point_interpolate_arcaxes_auxiv:ffnnnnnn
+ { \fp_eval:n { cosd (#1) } }
+ { \fp_eval:n { sind (#1) } }
+ {#2} {#3} {#4} {#5} {#6} {#7}
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_arcaxes_auxiii:nnnnnnn { f }
+\cs_new:Npn \@@_point_interpolate_arcaxes_auxiv:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \@@_point_to_dim:n
+ { #3 + #1 * #5 + #2 * #7 , #4 + #1 * #6 + #2 * #8 }
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_arcaxes_auxiv:nnnnnnnn { ff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\draw_point_interpolate_curve:nnnnn}
+% \begin{macro}[EXP]{\draw_point_interpolate_curve_auxi:nnnnnnnnn}
+% \begin{macro}[EXP]
+% {
+% \draw_point_interpolate_curve_auxii:nnnnnnnnn,
+% \draw_point_interpolate_curve_auxii:fnnnnnnnn,
+% }
+% \begin{macro}[EXP]
+% {
+% \draw_point_interpolate_curve_auxiii:nnnnnn,
+% \draw_point_interpolate_curve_auxiii:fnnnnn,
+% }
+% \begin{macro}[EXP]{\draw_point_interpolate_curve_auxiv:nnnnnn}
+% \begin{macro}[EXP]
+% {
+% \draw_point_interpolate_curve_auxv:nnw,
+% \draw_point_interpolate_curve_auxv:ffw,
+% }
+% \begin{macro}[EXP]{\draw_point_interpolate_curve_auxvi:n}
+% \begin{macro}[EXP]{\draw_point_interpolate_curve_auxvii:nnnnnnnn}
+% \begin{macro}[EXP]
+% {
+% \draw_point_interpolate_curve_auxviii:nnnnnn,
+% \draw_point_interpolate_curve_auxviii:ffnnnn,
+% }
+% Here we start with a proportion of the curve ($p$) and four points
+% \begin{enumerate}
+% \item The initial point $(x_{1},y_{1})$
+% \item The first control point $(x_{2},y_{2})$
+% \item The second control point $(x_{3},y_{3})$
+% \item The final point $(x_{4},y_{4})$
+% \end{enumerate}
+% The first phase is to expand out all of these values.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_interpolate_curve:nnnnnn #1#2#3#4#5
+ {
+ \@@_point_process:nnnnn
+ { \@@_point_interpolate_curve_auxi:nnnnnnnnn {#1} }
+ {#2} {#3} {#4} {#5}
+ }
+\cs_new:Npn \@@_point_interpolate_curve_auxi:nnnnnnnnn #1#2#3#4#5#6#7#8#9
+ {
+ \@@_point_interpolate_curve_auxii:fnnnnnnnn
+ { \fp_eval:n {#1} }
+ {#2} {#3} {#4} {#5} {#6} {#7} {#8} {#9}
+ }
+% \end{macrocode}
+% At this stage, everything is fully expanded and back in the input order.
+% The approach to finding the required point is iterative. We carry out
+% three phases. In phase one, we need all of the input co-ordinates
+% \begin{align*}
+% x_{1}' &= (1 - p)x_{1} + px_{2} \\
+% y_{1}' &= (1 - p)y_{1} + py_{2} \\
+% x_{2}' &= (1 - p)x_{2} + px_{3} \\
+% y_{2}' &= (1 - p)y_{2} + py_{3} \\
+% x_{3}' &= (1 - p)x_{3} + px_{4} \\
+% y_{3}' &= (1 - p)y_{3} + py_{4}
+% \end{align*}
+% In the second stage, we can drop the final point
+% \begin{align*}
+% x_{1}'' &= (1 - p)x_{1}' + px_{2}' \\
+% y_{1}'' &= (1 - p)y_{1}' + py_{2}' \\
+% x_{2}'' &= (1 - p)x_{2}' + px_{3}' \\
+% y_{2}'' &= (1 - p)y_{2}' + py_{3}'
+% \end{align*}
+% and for the final stage only need one set of calculations
+% \begin{align*}
+% P_{x} &= (1 - p)x_{1}'' + px_{2}'' \\
+% P_{y} &= (1 - p)y_{1}'' + py_{2}''
+% \end{align*}
+% Of course, this does mean a lot of calculations and expansion!
+% \begin{macrocode}
+\cs_new:Npn \@@_point_interpolate_curve_auxii:nnnnnnnnn
+ #1#2#3#4#5#6#7#8#9
+ {
+ \@@_point_interpolate_curve_auxiii:fnnnnn
+ { \fp_eval:n { 1 - #1 } }
+ {#1}
+ { {#2} {#3} } { {#4} {#5} } { {#6} {#7} } { {#8} {#9} }
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_curve_auxii:nnnnnnnnn { f }
+% \begin{macrocode}
+% We need to do the first cycle, but haven't got enough arguments to keep
+% everything in play at once. So her ewe use a but of argument re-ordering
+% and a single auxiliary to get the job done.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_interpolate_curve_auxiii:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_point_interpolate_curve_auxiv:nnnnnn {#1} {#2} #3 #4
+ \@@_point_interpolate_curve_auxiv:nnnnnn {#1} {#2} #4 #5
+ \@@_point_interpolate_curve_auxiv:nnnnnn {#1} {#2} #5 #6
+ \prg_do_nothing:
+ \@@_point_interpolate_curve_auxvi:n { {#1} {#2} }
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_curve_auxiii:nnnnnn { f }
+\cs_new:Npn \@@_point_interpolate_curve_auxiv:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_point_interpolate_curve_auxv:ffw
+ { \fp_eval:n { #1 * #3 + #2 * #5 } }
+ { \fp_eval:n { #1 * #4 + #2 * #6 } }
+ }
+\cs_new:Npn \@@_point_interpolate_curve_auxv:nnw
+ #1#2#3 \prg_do_nothing: #4#5
+ {
+ #3
+ \prg_do_nothing:
+ #4 { #5 {#1} {#2} }
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_curve_auxv:nnw { ff }
+% \begin{macrocode}
+% Get the arguments back into the right places and to the second and
+% third cycles directly.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_interpolate_curve_auxvi:n #1
+ { \@@_point_interpolate_curve_auxvii:nnnnnnnn #1 }
+\cs_new:Npn \@@_point_interpolate_curve_auxvii:nnnnnnnn #1#2#3#4#5#6#7#8
+ {
+ \@@_point_interpolate_curve_auxviii:ffffnn
+ { \fp_eval:n { #1 * #5 + #2 * #3 } }
+ { \fp_eval:n { #1 * #6 + #2 * #4 } }
+ { \fp_eval:n { #1 * #7 + #2 * #5 } }
+ { \fp_eval:n { #1 * #8 + #2 * #6 } }
+ {#1} {#2}
+ }
+\cs_new:Npn \@@_point_interpolate_curve_auxviii:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_point_to_dim:n
+ { #5 * #3 + #6 * #1 , #5 * #4 + #6 * #2 }
+ }
+\cs_generate_variant:Nn \@@_point_interpolate_curve_auxviii:nnnnnn { ffff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Vector support}
+%
+% As well as co-ordinates relative to the drawing
+%
+% \begin{variable}
+% {
+% \l_@@_xvec_x_dim,
+% \l_@@_xvec_y_dim,
+% \l_@@_yvec_x_dim,
+% \l_@@_yvec_y_dim,
+% \l_@@_zvec_x_dim,
+% \l_@@_zvec_y_dim
+% }
+% Base vectors to map to the underlying two-dimensional drawing space.
+% \begin{macrocode}
+\dim_new:N \l_@@_xvec_x_dim
+\dim_new:N \l_@@_xvec_y_dim
+\dim_new:N \l_@@_yvec_x_dim
+\dim_new:N \l_@@_yvec_y_dim
+\dim_new:N \l_@@_zvec_x_dim
+\dim_new:N \l_@@_zvec_y_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_xvec:n, \draw_yvec:n, \draw_zvec:n}
+% \begin{macro}{\@@_vec:nn}
+% \begin{macro}{\@@_vec:nnn}
+% Calculate the underlying position and store it.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_xvec:n #1
+ { \@@_vec:nn { x } {#1} }
+\cs_new_protected:Npn \draw_yvec:n #1
+ { \@@_vec:nn { y } {#1} }
+\cs_new_protected:Npn \draw_zvec:n #1
+ { \@@_vec:nn { z } {#1} }
+\cs_new_protected:Npn \@@_vec:nn #1#2
+ {
+ \@@_point_process:nn { \@@_vec:nnn {#1} } {#2}
+ }
+\cs_new_protected:Npn \@@_vec:nnn #1#2#3
+ {
+ \dim_set:cn { l_@@_ #1 vec_x_dim } {#2}
+ \dim_set:cn { l_@@_ #1 vec_y_dim } {#3}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% Initialise the vectors.
+% \begin{macrocode}
+\draw_xvec:n { 1cm , 0cm }
+\draw_yvec:n { 0cm , 1cm }
+\draw_zvec:n { -0.385cm , -0.385cm }
+% \end{macrocode}
+%
+% \begin{macro}[EXP]{\draw_point_vec:nn}
+% \begin{macro}[EXP]{\@@_point_vec:nn, \@@_point_vec:ff}
+% \begin{macro}[EXP]{\draw_point_vec:nnn}
+% \begin{macro}[EXP]{\@@_point_vec:nnn, \@@_point_vec:fff}
+% Force a single evaluation of each factor, then use these to work out the
+% underlying point.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_vec:nn #1#2
+ { \@@_point_vec:ff { \fp_eval:n {#1} } { \fp_eval:n {#2} } }
+\cs_new:Npn \@@_point_vec:nn #1#2
+ {
+ \@@_point_to_dim:n
+ {
+ #1 * \l_@@_xvec_x_dim + #2 * \l_@@_yvec_x_dim ,
+ #1 * \l_@@_xvec_y_dim + #2 * \l_@@_yvec_y_dim
+ }
+ }
+\cs_generate_variant:Nn \@@_point_vec:nn { ff }
+\cs_new:Npn \draw_point_vec:nnn #1#2#3
+ {
+ \@@_point_vec:fff
+ { \fp_eval:n {#1} } { \fp_eval:n {#2} } { \fp_eval:n {#3} }
+ }
+\cs_new:Npn \@@_point_vec:nnn #1#2#3
+ {
+ \@@_point_to_dim:n
+ {
+ #1 * \l_@@_xvec_x_dim
+ + #2 * \l_@@_yvec_x_dim
+ + #3 * \l_@@_zvec_x_dim
+ ,
+ #1 * \l_@@_xvec_y_dim
+ + #2 * \l_@@_yvec_y_dim
+ + #3 * \l_@@_zvec_y_dim
+ }
+ }
+\cs_generate_variant:Nn \@@_point_vec:nnn { fff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\draw_point_vec_polar:nn}
+% \begin{macro}[EXP]{\draw_point_vec_polar:nnn}
+% \begin{macro}[EXP]{\@@_point_vec_polar:nnn, \@@_point_vec_polar:fnn}
+% Much the same as the core polar approach.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_vec_polar:nn #1#2
+ { \draw_point_vec_polar:nnn {#1} {#1} {#2} }
+\cs_new:Npn \draw_point_vec_polar:nnn #1#2#3
+ { \@@_draw_vec_polar:fnn { \fp_eval:n {#3} } {#1} {#2} }
+\cs_new:Npn \@@_draw_vec_polar:nnn #1#2#3
+ {
+ \@@_point_to_dim:n
+ {
+ cosd(#1) * (#2) * \l_@@_xvec_x_dim ,
+ sind(#1) * (#3) * \l_@@_yvec_y_dim
+ }
+ }
+\cs_generate_variant:Nn \@@_draw_vec_polar:nnn { f }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Transformations}
+%
+% \begin{macro}[EXP]{\draw_point_transform:n}
+% \begin{macro}[EXP]{\@@_point_transform:nn}
+% Applies a transformation matrix to a point: see \texttt{l3draw-transforms}
+% for the business end. Where possible, we avoid the relatively expensive
+% multiplication step.
+% \begin{macrocode}
+\cs_new:Npn \draw_point_transform:n #1
+ {
+ \@@_point_process:nn
+ { \@@_point_transform:nn } {#1}
+ }
+\cs_new:Npn \@@_point_transform:nn #1#2
+ {
+ \bool_if:NTF \l_@@_matrix_active_bool
+ {
+ \@@_point_to_dim:n
+ {
+ (
+ \l_@@_matrix_a_fp * #1
+ + \l_@@_matrix_c_fp * #2
+ + \l_@@_xshift_dim
+ )
+ ,
+ (
+ \l_@@_matrix_b_fp * #1
+ + \l_@@_matrix_d_fp * #2
+ + \l_@@_yshift_dim
+ )
+ }
+ }
+ {
+ \@@_point_to_dim:n
+ {
+ (#1, #2)
+ + ( \l_@@_xshift_dim , \l_@@_yshift_dim )
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_point_transform_noshift:n}
+% \begin{macro}[EXP]{\@@_point_transform_noshift:nn}
+% A version with no shift: used for internal purposes.
+% \begin{macrocode}
+\cs_new:Npn \@@_point_transform_noshift:n #1
+ {
+ \@@_point_process:nn
+ { \@@_point_transform_noshift:nn } {#1}
+ }
+\cs_new:Npn \@@_point_transform_noshift:nn #1#2
+ {
+ \bool_if:NTF \l_@@_matrix_active_bool
+ {
+ \@@_point_to_dim:n
+ {
+ (
+ \l_@@_matrix_a_fp * #1
+ + \l_@@_matrix_c_fp * #2
+ )
+ ,
+ (
+ \l_@@_matrix_b_fp * #1
+ + \l_@@_matrix_d_fp * #2
+ )
+ }
+ }
+ { \@@_point_to_dim:n { (#1, #2) } }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-scopes.dtx b/macros/latex/contrib/l3experimental/l3draw-scopes.dtx
new file mode 100644
index 0000000000..d20921a0a9
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-scopes.dtx
@@ -0,0 +1,270 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-scopes.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw} package\\ Drawing scopes^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-scopes} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% \subsection{Drawing environment}
+%
+% \begin{variable}
+% {\g_@@_xmax_dim, \g_@@_xmin_dim, \g_@@_ymax_dim, \g_@@_ymin_dim}
+% Used to track the overall (official) size of the image created: may
+% not actually be the natural size of the content.
+% \begin{macrocode}
+\dim_new:N \g_@@_xmax_dim
+\dim_new:N \g_@@_xmin_dim
+\dim_new:N \g_@@_ymax_dim
+\dim_new:N \g_@@_ymin_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_draw_bb_update_bool}
+% Flag to indicate that a path (or similar) should update the bounding box
+% of the drawing.
+% \begin{macrocode}
+\bool_new:N \l_draw_bb_update_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_layer_main_box, \l_@@_layer_main_box}
+% Box for setting the drawing itself and the top-level layer.
+% \begin{macrocode}
+\box_new:N \l_@@_main_box
+\box_new:N \l_@@_layer_main_box
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_id_int}
+% The drawing number.
+% \begin{macrocode}
+\int_new:N \g_@@_id_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_reset_bb:}
+% A simple auxiliary.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_reset_bb:
+ {
+ \dim_gset:Nn \g_@@_xmax_dim { -\c_max_dim }
+ \dim_gset:Nn \g_@@_xmin_dim { \c_max_dim }
+ \dim_gset:Nn \g_@@_ymax_dim { -\c_max_dim }
+ \dim_gset:Nn \g_@@_ymin_dim { \c_max_dim }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\draw_begin:, \draw_end:}
+% Drawings are created by setting them into a box, then adjusting the box
+% before inserting into the surroundings. Color is set here using the drawing
+% mechanism largely as it then sets up the internal data structures. It may be
+% that a coffin construct is better here in the longer term: that may become
+% clearer as the code is completed. As we need to avoid any insertion of
+% baseline skips, the outer box here has to be an |hbox|. To allow for
+% layers, there is some box nesting: notice that we
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_begin:
+ {
+ \group_begin:
+ \int_gincr:N \g_@@_id_int
+ \hbox_set:Nw \l_@@_main_box
+ \@@_backend_begin:
+ \@@_reset_bb:
+ \@@_path_reset_limits:
+ \bool_set_true:N \l_draw_bb_update_bool
+ \draw_transform_matrix_reset:
+ \draw_transform_shift_reset:
+ \@@_softpath_clear:
+ \draw_linewidth:n { \l_draw_default_linewidth_dim }
+ \draw_color:n { . }
+ \draw_nonzero_rule:
+ \draw_cap_butt:
+ \draw_join_miter:
+ \draw_miterlimit:n { 10 }
+ \draw_dash_pattern:nn { } { 0cm }
+ \hbox_set:Nw \l_@@_layer_main_box
+ }
+\cs_new_protected:Npn \draw_end:
+ {
+ \exp_args:NNNV \hbox_set_end:
+ \clist_set:Nn \l_draw_layers_clist \l_draw_layers_clist
+ \@@_layers_insert:
+ \@@_backend_end:
+ \hbox_set_end:
+ \dim_compare:nNnT \g_@@_xmin_dim = \c_max_dim
+ {
+ \dim_gzero:N \g_@@_xmax_dim
+ \dim_gzero:N \g_@@_xmin_dim
+ \dim_gzero:N \g_@@_ymax_dim
+ \dim_gzero:N \g_@@_ymin_dim
+ }
+ \hbox_set:Nn \l_@@_main_box
+ {
+ \skip_horizontal:n { -\g_@@_xmin_dim }
+ \box_move_down:nn { \g_@@_ymin_dim }
+ { \box_use_drop:N \l_@@_main_box }
+ }
+ \box_set_ht:Nn \l_@@_main_box
+ { \g_@@_ymax_dim - \g_@@_ymin_dim }
+ \box_set_dp:Nn \l_@@_main_box { 0pt }
+ \box_set_wd:Nn \l_@@_main_box
+ { \g_@@_xmax_dim - \g_@@_xmin_dim }
+ \mode_leave_vertical:
+ \box_use_drop:N \l_@@_main_box
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Scopes}
+%
+% \begin{variable}{\l_@@_linewidth_dim}
+% \begin{variable}{\l_@@_fill_color_tl, \l_@@_stroke_color_tl}
+% Storage for local variables.
+% \begin{macrocode}
+\dim_new:N \l_@@_linewidth_dim
+\tl_new:N \l_@@_fill_color_tl
+\tl_new:N \l_@@_stroke_color_tl
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+%
+% \begin{macro}{\draw_scope_begin:, \draw_scope_begin:}
+% As well as the graphics (and \TeX{}) scope, also deal with global
+% data structures.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_scope_begin:
+ {
+ \@@_backend_scope_begin:
+ \group_begin:
+ \dim_set_eq:NN \l_@@_linewidth_dim \g_@@_linewidth_dim
+ \draw_path_scope_begin:
+ }
+\cs_new_protected:Npn \draw_scope_end:
+ {
+ \draw_path_scope_end:
+ \dim_gset_eq:NN \g_@@_linewidth_dim \l_@@_linewidth_dim
+ \group_end:
+ \@@_backend_scope_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}
+% {\l_@@_xmax_dim, \l_@@_xmin_dim, \l_@@_ymax_dim, \l_@@_ymin_dim}
+% Storage for the bounding box.
+% \begin{macrocode}
+\dim_new:N \l_@@_xmax_dim
+\dim_new:N \l_@@_xmin_dim
+\dim_new:N \l_@@_ymax_dim
+\dim_new:N \l_@@_ymin_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_scope_bb_begin:, \@@_scope_bb_end:}
+% The bounding box is simple: a straight group-based save and restore
+% approach.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_scope_bb_begin:
+ {
+ \group_begin:
+ \dim_set_eq:NN \l_@@_xmax_dim \g_@@_xmax_dim
+ \dim_set_eq:NN \l_@@_xmin_dim \g_@@_xmin_dim
+ \dim_set_eq:NN \l_@@_ymax_dim \g_@@_ymax_dim
+ \dim_set_eq:NN \l_@@_ymin_dim \g_@@_ymin_dim
+ \@@_reset_bb:
+ }
+\cs_new_protected:Npn \@@_scope_bb_end:
+ {
+ \dim_gset_eq:NN \g_@@_xmax_dim \l_@@_xmax_dim
+ \dim_gset_eq:NN \g_@@_xmin_dim \l_@@_xmin_dim
+ \dim_gset_eq:NN \g_@@_ymax_dim \l_@@_ymax_dim
+ \dim_gset_eq:NN \g_@@_ymin_dim \l_@@_ymin_dim
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\draw_suspend_begin:, \draw_suspend_end:}
+% Suspend all parts of a drawing.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_suspend_begin:
+ {
+ \@@_scope_bb_begin:
+ \draw_path_scope_begin:
+ \draw_transform_matrix_reset:
+ \draw_transform_shift_reset:
+ \@@_layers_save:
+ }
+\cs_new_protected:Npn \draw_suspend_end:
+ {
+ \@@_layers_restore:
+ \draw_path_scope_end:
+ \@@_scope_bb_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-softpath.dtx b/macros/latex/contrib/l3experimental/l3draw-softpath.dtx
new file mode 100644
index 0000000000..5fb02c3cea
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-softpath.dtx
@@ -0,0 +1,615 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-softpath.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-softpath} package\\ Soft paths^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-softpath} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% \subsection{Managing soft paths}
+%
+% There are two linked aims in the code here. The most significant is to
+% provide a way to modify paths, for example to shorten the ends or round
+% the corners. This means that the path cannot be written piecemeal as
+% specials, but rather needs to be held in macros. The second aspect that
+% follows from this is performance: simply adding to a single macro a piece
+% at a time will have poor performance as the list gets long so we use
+% \cs[no-index]{tl_build_\ldots{}} functions.
+%
+% Each marker (operation) token takes two arguments, which makes processing
+% more straight-forward. As such, some operations have dummy arguments, whilst
+% others have to be split over several tokens. As the code here is at a low
+% level, all dimension arguments are assumed to be explicit and fully-expanded.
+%
+% \begin{variable}{\g_@@_softpath_main_tl}
+% The soft path itself.
+% \begin{macrocode}
+\tl_new:N \g_@@_softpath_main_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_softpath_internal_tl}
+% The soft path itself.
+% \begin{macrocode}
+\tl_new:N \l_@@_softpath_internal_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_softpath_corners_bool}
+% Allow for optimised path use.
+% \begin{macrocode}
+\bool_new:N \g_@@_softpath_corners_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_softpath_add:n, \@@_softpath_add:o, \@@_softpath_add:x}
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_add:n
+ { \tl_build_gput_right:Nn \g_@@_softpath_main_tl }
+\cs_generate_variant:Nn \@@_softpath_add:n { o, x }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {\@@_softpath_use:, \@@_softpath_clear:}
+% Using and clearing is trivial.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_use:
+ {
+ \tl_build_get:NN \g_@@_softpath_main_tl \l_@@_softpath_internal_tl
+ \l_@@_softpath_internal_tl
+ }
+\cs_new_protected:Npn \@@_softpath_clear:
+ {
+ \tl_build_gclear:N \g_@@_softpath_main_tl
+ \bool_gset_false:N \g_@@_softpath_corners_bool
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\g_@@_softpath_lastx_dim, \g_@@_softpath_lasty_dim}
+% For tracking the end of the path (to close it).
+% \begin{macrocode}
+\dim_new:N \g_@@_softpath_lastx_dim
+\dim_new:N \g_@@_softpath_lasty_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_softpath_move_bool}
+% Track if moving a point should update the close position.
+% \begin{macrocode}
+\bool_new:N \g_@@_softpath_move_bool
+\bool_gset_true:N \g_@@_softpath_move_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_softpath_curveto:nnnnnn}
+% \begin{macro}
+% {
+% \@@_softpath_lineto:nn,
+% \@@_softpath_moveto:nn
+% }
+% \begin{macro}{\@@_softpath_rectangle:nnnn}
+% \begin{macro}{\@@_softpath_roundpoint:nn, \@@_softpath_roundpoint:VV}
+% The various parts of a path expressed as the appropriate soft path
+% functions.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_closepath:
+ {
+ \@@_softpath_add:x
+ {
+ \@@_softpath_close_op:nn
+ { \dim_use:N \g_@@_softpath_lastx_dim }
+ { \dim_use:N \g_@@_softpath_lasty_dim }
+ }
+ }
+\cs_new_protected:Npn \@@_softpath_curveto:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_softpath_add:n
+ {
+ \@@_softpath_curveto_opi:nn {#1} {#2}
+ \@@_softpath_curveto_opii:nn {#3} {#4}
+ \@@_softpath_curveto_opiii:nn {#5} {#6}
+ }
+ }
+\cs_new_protected:Npn \@@_softpath_lineto:nn #1#2
+ {
+ \@@_softpath_add:n
+ { \@@_softpath_lineto_op:nn {#1} {#2} }
+ }
+\cs_new_protected:Npn \@@_softpath_moveto:nn #1#2
+ {
+ \@@_softpath_add:n
+ { \@@_softpath_moveto_op:nn {#1} {#2} }
+ \bool_if:NT \g_@@_softpath_move_bool
+ {
+ \dim_gset:Nn \g_@@_softpath_lastx_dim {#1}
+ \dim_gset:Nn \g_@@_softpath_lasty_dim {#2}
+ }
+ }
+\cs_new_protected:Npn \@@_softpath_rectangle:nnnn #1#2#3#4
+ {
+ \@@_softpath_add:n
+ {
+ \@@_softpath_rectangle_opi:nn {#1} {#2}
+ \@@_softpath_rectangle_opii:nn {#3} {#4}
+ }
+ }
+\cs_new_protected:Npn \@@_softpath_roundpoint:nn #1#2
+ {
+ \@@_softpath_add:n
+ { \@@_softpath_roundpoint_op:nn {#1} {#2} }
+ \bool_gset_true:N \g_@@_softpath_corners_bool
+ }
+\cs_generate_variant:Nn \@@_softpath_roundpoint:nn { VV }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \@@_softpath_close_op:nn ,
+% \@@_softpath_curveto_opi:nn ,
+% \@@_softpath_curveto_opii:nn ,
+% \@@_softpath_curveto_opiii:nn ,
+% \@@_softpath_lineto_op:nn ,
+% \@@_softpath_moveto_op:nn ,
+% \@@_softpath_roundpoint_op:nn ,
+% \@@_softpath_rectangle_opi:nn ,
+% \@@_softpath_rectangle_opii:nn
+% }
+% \begin{macro}{\@@_softpath_curveto_opi:nnNnnNnn}
+% \begin{macro}{\@@_softpath_rectangle_opi:nnNnn}
+% The markers for operations: all the top-level ones take two arguments.
+% The support tokens for curves have to be different in meaning to a
+% round point, hence being quark-like.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_close_op:nn #1#2
+ { \@@_backend_closepath: }
+\cs_new_protected:Npn \@@_softpath_curveto_opi:nn #1#2
+ { \@@_softpath_curveto_opi:nnNnnNnn {#1} {#2} }
+\cs_new_protected:Npn \@@_softpath_curveto_opi:nnNnnNnn #1#2#3#4#5#6#7#8
+ { \@@_backend_curveto:nnnnnn {#1} {#2} {#4} {#5} {#7} {#8} }
+\cs_new_protected:Npn \@@_softpath_curveto_opii:nn #1#2
+ { \@@_softpath_curveto_opii:nn }
+\cs_new_protected:Npn \@@_softpath_curveto_opiii:nn #1#2
+ { \@@_softpath_curveto_opiii:nn }
+\cs_new_protected:Npn \@@_softpath_lineto_op:nn #1#2
+ { \@@_backend_lineto:nn {#1} {#2} }
+\cs_new_protected:Npn \@@_softpath_moveto_op:nn #1#2
+ { \@@_backend_moveto:nn {#1} {#2} }
+\cs_new_protected:Npn \@@_softpath_roundpoint_op:nn #1#2 { }
+\cs_new_protected:Npn \@@_softpath_rectangle_opi:nn #1#2
+ { \@@_softpath_rectangle_opi:nnNnn {#1} {#2} }
+\cs_new_protected:Npn \@@_softpath_rectangle_opi:nnNnn #1#2#3#4#5
+ { \@@_backend_rectangle:nnnn {#1} {#2} {#4} {#5} }
+ \cs_new_protected:Npn \@@_softpath_rectangle_opii:nn #1#2 { }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Rounding soft path corners}
+%
+% The aim here is to find corner rounding points and to replace them with
+% arcs of appropriate length. The approach is exactly that in \pkg{pgf}:
+% step through, find the corners, find the supporting data, do the rounding.
+%
+% \begin{variable}{\l_@@_softpath_main_tl}
+% For constructing the updated path.
+% \begin{macrocode}
+\tl_new:N \l_@@_softpath_main_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_softpath_part_tl}
+% Data structures.
+% \begin{macrocode}
+\tl_new:N \l_@@_softpath_part_tl
+\tl_new:N \l_@@_softpath_curve_end_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\l_@@_softpath_lastx_fp, \l_@@_softpath_lasty_fp}
+% \begin{variable}
+% {\l_@@_softpath_corneri_dim, \l_@@_softpath_cornerii_dim}
+% \begin{variable}{\l_@@_softpath_first_tl, \l_@@_softpath_move_tl}
+% Position tracking: the token list data may be entirely empty or set to
+% a co-ordinate.
+% \begin{macrocode}
+\fp_new:N \l_@@_softpath_lastx_fp
+\fp_new:N \l_@@_softpath_lasty_fp
+\dim_new:N \l_@@_softpath_corneri_dim
+\dim_new:N \l_@@_softpath_cornerii_dim
+\tl_new:N \l_@@_softpath_first_tl
+\tl_new:N \l_@@_softpath_move_tl
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{variable}
+%
+% \begin{variable}{\c_@@_softpath_arc_fp}
+% The magic constant.
+% \begin{macrocode}
+\fp_const:Nn \c_@@_softpath_arc_fp { 4/3 * (sqrt(2) - 1) }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_softpath_round_corners:}
+% \begin{macro}{\@@_softpath_round_loop:Nnn}
+% \begin{macro}{\@@_softpath_round_action:nn}
+% \begin{macro}{\@@_softpath_round_action:Nnn}
+% \begin{macro}{\@@_softpath_round_action_curveto:NnnNnn}
+% \begin{macro}{\@@_softpath_round_action_close:}
+% \begin{macro}{\@@_softpath_round_lookahead:NnnNnn}
+% \begin{macro}{\@@_softpath_round_roundpoint:NnnNnnNnn}
+% \begin{macro}{\@@_softpath_round_calc:NnnNnn}
+% \begin{macro}[EXP]
+% {\@@_softpath_round_calc:nnnnnn, \@@_softpath_round_calc:fVnnnn}
+% \begin{macro}[EXP]{\@@_softpath_round_calc:nnnnw}
+% \begin{macro}{\@@_softpath_round_close:nn}
+% \begin{macro}[EXP]{\@@_softpath_round_close:w}
+% \begin{macro}{\@@_softpath_round_end:}
+% Rounding corners on a path means going through the entire path and
+% adjusting it. As such, we avoid this entirely if we know there are no
+% corners to deal with. Assuming there is work to do, we recover the existing
+% path and start a loop.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_corners:
+ {
+ \bool_if:NT \g_@@_softpath_corners_bool
+ {
+ \group_begin:
+ \tl_clear:N \l_@@_softpath_main_tl
+ \tl_clear:N \l_@@_softpath_part_tl
+ \fp_zero:N \l_@@_softpath_lastx_fp
+ \fp_zero:N \l_@@_softpath_lasty_fp
+ \tl_clear:N \l_@@_softpath_first_tl
+ \tl_clear:N \l_@@_softpath_move_tl
+ \tl_build_get:NN \g_@@_softpath_main_tl \l_@@_softpath_internal_tl
+ \exp_after:wN \@@_softpath_round_loop:Nnn
+ \l_@@_softpath_internal_tl
+ \q_recursion_tail ? ?
+ \q_recursion_stop
+ \group_end:
+ }
+ \bool_gset_false:N \g_@@_softpath_corners_bool
+ }
+% \end{macrocode}
+% The loop can take advantage of the fact that all soft path operations are
+% made up of a token followed by two arguments. At this stage, there is
+% a simple split: have we round a round point. If so, is there any actual
+% rounding to be done: if the arcs have come through zero, just ignore it.
+% In cases where we are not at a corner, we simply move along the path,
+% allowing for any new part starting due to a \texttt{moveto}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_loop:Nnn #1#2#3
+ {
+ \quark_if_recursion_tail_stop_do:Nn #1 { \@@_softpath_round_end: }
+ \token_if_eq_meaning:NNTF #1 \@@_softpath_roundpoint_op:nn
+ { \@@_softpath_round_action:nn {#2} {#3} }
+ {
+ \tl_if_empty:NT \l_@@_softpath_first_tl
+ { \tl_set:Nn \l_@@_softpath_first_tl { {#2} {#3} } }
+ \fp_set:Nn \l_@@_softpath_lastx_fp {#2}
+ \fp_set:Nn \l_@@_softpath_lasty_fp {#3}
+ \token_if_eq_meaning:NNTF #1 \@@_softpath_moveto_op:nn
+ {
+ \tl_put_right:No \l_@@_softpath_main_tl
+ \l_@@_softpath_move_tl
+ \tl_put_right:No \l_@@_softpath_main_tl
+ \l_@@_softpath_part_tl
+ \tl_set:Nn \l_@@_softpath_move_tl { #1 {#2} {#3} }
+ \tl_clear:N \l_@@_softpath_first_tl
+ \tl_clear:N \l_@@_softpath_part_tl
+ }
+ { \tl_put_right:Nn \l_@@_softpath_part_tl { #1 {#2} {#3} } }
+ \@@_softpath_round_loop:Nnn
+ }
+ }
+\cs_new_protected:Npn \@@_softpath_round_action:nn #1#2
+ {
+ \dim_set:Nn \l_@@_softpath_corneri_dim {#1}
+ \dim_set:Nn \l_@@_softpath_cornerii_dim {#2}
+ \bool_lazy_and:nnTF
+ { \dim_compare_p:nNn \l_@@_softpath_corneri_dim = { 0pt } }
+ { \dim_compare_p:nNn \l_@@_softpath_cornerii_dim = { 0pt } }
+ { \@@_softpath_round_loop:Nnn }
+ { \@@_softpath_round_action:Nnn }
+ }
+% \end{macrocode}
+% We now have a round point to work on and have grabbed the next item in
+% the path. There are only a few cases where we have to do anything. Each of
+% them is picked up by looking for the appropriate action.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_action:Nnn #1#2#3
+ {
+ \tl_if_empty:NT \l_@@_softpath_first_tl
+ { \tl_set:Nn \l_@@_softpath_first_tl { {#2} {#3} } }
+ \token_if_eq_meaning:NNTF #1 \@@_softpath_curveto_opi:nn
+ { \@@_softpath_round_action_curveto:NnnNnn }
+ {
+ \token_if_eq_meaning:NNTF #1 \@@_softpath_close_op:nn
+ { \@@_softpath_round_action_close: }
+ {
+ \token_if_eq_meaning:NNTF #1 \@@_softpath_lineto_op:nn
+ { \@@_softpath_round_lookahead:NnnNnn }
+ { \@@_softpath_round_loop:Nnn }
+ }
+ }
+ #1 {#2} {#3}
+ }
+% \end{macrocode}
+% For a curve, we collect the two control points then move on to grab the
+% end point and add the curve there: the second control point becomes our
+% starter.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_action_curveto:NnnNnn
+ #1#2#3#4#5#6
+ {
+ \tl_put_right:Nn \l_@@_softpath_part_tl
+ { #1 {#2} {#3} #4 {#5} {#6} }
+ \fp_set:Nn \l_@@_softpath_lastx_fp {#5}
+ \fp_set:Nn \l_@@_softpath_lasty_fp {#6}
+ \@@_softpath_round_lookahead:NnnNnn
+ }
+\cs_new_protected:Npn \@@_softpath_round_action_close:
+ {
+ \bool_lazy_and:nnTF
+ { ! \tl_if_empty_p:N \l_@@_softpath_first_tl }
+ { ! \tl_if_empty_p:N \l_@@_softpath_move_tl }
+ {
+ \exp_after:wN \@@_softpath_round_close:nn
+ \l_@@_softpath_first_tl
+ }
+ { \@@_softpath_round_loop:Nnn }
+ }
+% \end{macrocode}
+% At this stage we have a current (sub)operation (|#1|) and the next
+% operation (|#4|), and can therefore decide whether to round or not.
+% In the case of yet another rounding marker, we have to look a bit
+% further ahead.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_lookahead:NnnNnn #1#2#3#4#5#6
+ {
+ \bool_lazy_any:nTF
+ {
+ { \token_if_eq_meaning_p:NN #4 \@@_softpath_lineto_op:nn }
+ { \token_if_eq_meaning_p:NN #4 \@@_softpath_curveto_opi:nn }
+ { \token_if_eq_meaning_p:NN #4 \@@_softpath_close_op:nn }
+ }
+ {
+ \@@_softpath_round_calc:NnnNnn
+ \@@_softpath_round_loop:Nnn
+ {#5} {#6}
+ }
+ {
+ \token_if_eq_meaning:NNTF #4 \@@_softpath_roundpoint_op:nn
+ { \@@_softpath_round_roundpoint:NnnNnnNnn }
+ { \@@_softpath_round_loop:Nnn }
+ }
+ #1 {#2} {#3}
+ #4 {#5} {#6}
+ }
+\cs_new_protected:Npn \@@_softpath_round_roundpoint:NnnNnnNnn
+ #1#2#3#4#5#6#7#8#9
+ {
+ \@@_softpath_round_calc:NnnNnn
+ \@@_softpath_round_loop:Nnn
+ {#8} {#9}
+ #1 {#2} {#3}
+ #4 {#5} {#6} #7 {#8} {#9}
+ }
+% \end{macrocode}
+% We now have all of the data needed to construct a rounded corner: all that
+% is left to do is to work out the detail! At this stage, we have details
+% of where the corner itself is (|#5|, |#6|), and where the next point is
+% (|#2|, |#3|). There are two types of calculations to do. First, we
+% need to interpolate from those two points in the direction of the
+% corner, in order to work out where the curve we are adding will start
+% and end. From those, plus the points we already have, we work out where
+% the control points will lie. All of this is done in an expansion to
+% avoid multiple calls to |\tl_put_right:Nx|. The end point of the line
+% is worked out up-front and saved: we need that if dealing with a
+% close-path operation.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_calc:NnnNnn #1#2#3#4#5#6
+ {
+ \tl_set:Nx \l_@@_softpath_curve_end_tl
+ {
+ \draw_point_interpolate_distance:nnn
+ \l_@@_softpath_cornerii_dim
+ { #5 , #6 } { #2 , #3 }
+ }
+ \tl_put_right:Nx \l_@@_softpath_part_tl
+ {
+ \exp_not:N #4
+ \@@_softpath_round_calc:fVnnnn
+ {
+ \draw_point_interpolate_distance:nnn
+ \l_@@_softpath_corneri_dim
+ { #5 , #6 }
+ {
+ \l_@@_softpath_lastx_fp ,
+ \l_@@_softpath_lasty_fp
+ }
+ }
+ \l_@@_softpath_curve_end_tl
+ {#5} {#6} {#2} {#3}
+ }
+ \fp_set:Nn \l_@@_softpath_lastx_fp {#5}
+ \fp_set:Nn \l_@@_softpath_lasty_fp {#6}
+ #1
+ }
+% \end{macrocode}
+% At this stage we have the two curve end points, but they are in
+% co-ordinate form. So we split them up (with some more reordering).
+% \begin{macrocode}
+\cs_new:Npn \@@_softpath_round_calc:nnnnnn #1#2#3#4#5#6
+ {
+ \@@_softpath_round_calc:nnnnw {#3} {#4} {#5} {#6}
+ #1 \q_mark #2 \q_stop
+ }
+\cs_generate_variant:Nn \@@_softpath_round_calc:nnnnnn { fV }
+% \end{macrocode}
+% The calculations themselves are relatively straight-forward, as we use a
+% quadratic Bézier curve.
+% \begin{macrocode}
+\cs_new:Npn \@@_softpath_round_calc:nnnnw
+ #1#2#3#4 #5 , #6 \q_mark #7 , #8 \q_stop
+ {
+ {#5} {#6}
+ \exp_not:N \@@_softpath_curveto_opi:nn
+ {
+ \fp_to_dim:n
+ { #5 + \c_@@_softpath_arc_fp * ( #1 - #5 ) }
+ }
+ {
+ \fp_to_dim:n
+ { #6 + \c_@@_softpath_arc_fp * ( #2 - #6 ) }
+ }
+ \exp_not:N \@@_softpath_curveto_opii:nn
+ {
+ \fp_to_dim:n
+ { #7 + \c_@@_softpath_arc_fp * ( #1 - #7 ) }
+ }
+ {
+ \fp_to_dim:n
+ { #8 + \c_@@_softpath_arc_fp* ( #2 - #8 ) }
+ }
+ \exp_not:N \@@_softpath_curveto_opiii:nn
+ {#7} {#8}
+ }
+% \end{macrocode}
+% To deal with a close-path operation, we need to do some manipulation.
+% It needs to be treated as a line operation for rounding, and then
+% have the close path operation re-added at the point where the curve
+% ends. That means saving the end point in the calculation step (see
+% earlier), and shuffling a lot.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_close:nn #1#2
+ {
+ \use:x
+ {
+ \@@_softpath_round_calc:NnnNnn
+ {
+ \tl_set:Nx \exp_not:N \l_@@_softpath_move_tl
+ {
+ \@@_softpath_moveto_op:nn
+ \exp_not:N \exp_after:wN
+ \exp_not:N \@@_softpath_round_close:w
+ \exp_not:N \l_@@_softpath_curve_end_tl
+ \exp_not:N \q_stop
+ }
+ \use:x
+ {
+ \exp_not:N \exp_not:N \exp_not:N \use_i:nnnn
+ {
+ \@@_softpath_round_loop:Nnn
+ \@@_softpath_close_op:nn
+ \exp_not:N \exp_after:wN
+ \exp_not:N \@@_softpath_round_close:w
+ \exp_not:N \l_@@_softpath_curve_end_tl
+ \exp_not:N \q_stop
+ }
+ }
+ }
+ {#1} {#2}
+ \@@_softpath_lineto_op:nn
+ \exp_after:wN \use_none:n \l_@@_softpath_move_tl
+ }
+ }
+\cs_new:Npn \@@_softpath_round_close:w #1 , #2 \q_stop { {#1} {#2} }
+% \end{macrocode}
+% Tidy up the parts of the path, complete the built token list and put
+% it back into action.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_softpath_round_end:
+ {
+ \tl_put_right:No \l_@@_softpath_main_tl
+ \l_@@_softpath_move_tl
+ \tl_put_right:No \l_@@_softpath_main_tl
+ \l_@@_softpath_part_tl
+ \tl_build_gclear:N \g_@@_softpath_main_tl
+ \@@_softpath_add:o \l_@@_softpath_main_tl
+ }
+% \end{macrocode}
+% \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{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-state.dtx b/macros/latex/contrib/l3experimental/l3draw-state.dtx
new file mode 100644
index 0000000000..e0718ef4d9
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-state.dtx
@@ -0,0 +1,205 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-state.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-state} package\\ Drawing graphics state^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-state} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% This sub-module covers more-or-less the same ideas as
+% \texttt{pgfcoregraphicstate.code.tex}. At present, equivalents of the
+% following are currently absent:
+% \begin{itemize}
+% \item \cs{pgfsetinnerlinewidth}, \cs{pgfinnerlinewidth},
+% \cs{pgfsetinnerstrokecolor}, \cs{pgfsetinnerstrokecolor}: Likely to
+% be added on further work is done on paths/stroking.
+% \end{itemize}
+%
+% \begin{variable}{\g_@@_linewidth_dim}
+% Linewidth for strokes: global as the scope for this relies on the graphics
+% state. The inner line width is used for places where two lines are used.
+% \begin{macrocode}
+\dim_new:N \g_@@_linewidth_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_draw_default_linewidth_dim}
+% A default: this is used at the start of every drawing.
+% \begin{macrocode}
+\dim_new:N \l_draw_default_linewidth_dim
+\dim_set:Nn \l_draw_default_linewidth_dim { 0.4pt }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_linewidth:n}
+% Set the linewidth: we need a wrapper as this has to pass to the driver
+% layer.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_linewidth:n #1
+ {
+ \dim_gset:Nn \g_@@_linewidth_dim { \fp_to_dim:n {#1} }
+ \@@_backend_linewidth:n \g_@@_linewidth_dim
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\draw_dash_pattern:nn}
+% \begin{variable}{\l_@@_tmp_seq}
+% Evaluated all of the list and pass it to the driver layer.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_dash_pattern:nn #1#2
+ {
+ \group_begin:
+ \seq_set_from_clist:Nn \l_@@_tmp_seq {#1}
+ \seq_set_map:NNn \l_@@_tmp_seq \l_@@_tmp_seq
+ { \fp_to_dim:n {##1} }
+ \use:x
+ {
+ \@@_backend_dash_pattern:nn
+ { \seq_use:Nn \l_@@_tmp_seq { , } }
+ { \fp_to_dim:n {#2} }
+ }
+ \group_end:
+ }
+\seq_new:N \l_@@_tmp_seq
+% \end{macrocode}
+% \end{variable}
+% \end{macro}
+%
+% \begin{macro}{\draw_miterlimit:n}
+% Pass through to the driver layer.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_miterlimit:n #1
+ { \@@_backend_miterlimit:n { \fp_eval:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \draw_cap_butt:, \draw_cap_rectangle:, \draw_cap_round:,
+% \draw_evenodd_rule:, \draw_nonzero_rule:,
+% \draw_join_bevel:, \draw_join_miter:, \draw_join_round:
+% }
+% All straight wrappers.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_cap_butt: { \@@_backend_cap_butt: }
+\cs_new_protected:Npn \draw_cap_rectangle: { \@@_backend_cap_rectangle: }
+\cs_new_protected:Npn \draw_cap_round: { \@@_backend_cap_round: }
+\cs_new_protected:Npn \draw_evenodd_rule: { \@@_backend_evenodd_rule: }
+\cs_new_protected:Npn \draw_nonzero_rule: { \@@_backend_nonzero_rule: }
+\cs_new_protected:Npn \draw_join_bevel: { \@@_backend_join_bevel: }
+\cs_new_protected:Npn \draw_join_miter: { \@@_backend_join_miter: }
+\cs_new_protected:Npn \draw_join_round: { \@@_backend_join_round: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_color_tmp_tl}
+% Scratch space.
+% \begin{macrocode}
+\tl_new:N \l_@@_color_tmp_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_color:n, \draw_color_fill:n, \draw_color_stroke:n}
+% \begin{macro}{\@@_color:nn}
+% \begin{macro}{\@@_color_aux:nn, \@@_color_aux:Vn}
+% \begin{macro}{\@@_color:nw}
+% \begin{macro}
+% {
+% \@@_select_cmyk:nw, \@@_select_gray:nw,
+% \@@_select_rgb:nw, \@@_split_select:nw
+% }
+% Much the same as for core color support but calling the relevant
+% driver-level function.
+% \begin{macrocode}
+\cs_new_eq:NN \draw_color:n \color_select:n
+\cs_new_protected:Npn \draw_color_fill:n #1
+ { \@@_color:nn { fill } {#1} }
+\cs_new_protected:Npn \draw_color_stroke:n #1
+ { \@@_color:nn { stroke } {#1} }
+\cs_new_protected:Npn \@@_color:nn #1#2
+ {
+ \color_parse:nN {#2} \l_@@_color_tmp_tl
+ \@@_color_aux:Vn \l_@@_color_tmp_tl {#1}
+ }
+\cs_new_protected:Npn \@@_color_aux:nn #1#2
+ { \@@_color:nw {#2} #1 \q_stop }
+\cs_generate_variant:Nn \@@_color_aux:nn { V }
+\cs_new_protected:Npn \@@_color:nw #1#2 ~ #3 \q_stop
+ { \use:c { @@_color_ #2 :nw } {#1} #3 \q_stop }
+\cs_new_protected:Npn \@@_color_cmyk:nw #1#2 ~ #3 ~ #4 ~ #5 \q_stop
+ { \use:c { @@_backend_color_ #1 _cmyk:nnnn } {#2} {#3} {#4} {#5} }
+\cs_new_protected:Npn \@@_color_gray:nw #1#2 \q_stop
+ { \use:c { @@_backend_color_ #1 _gray:n } {#2} }
+\cs_new_protected:Npn \@@_color_rgb:nw #1#2 ~ #3 ~ #4 \q_stop
+ { \use:c { @@_backend_color_ #1 _rgb:nnn } {#2} {#3} {#4} }
+\cs_new_protected:Npn \@@_color_spot:nw #1#2 ~ #3 \q_stop
+ { \use:c { @@_backend_color_ #1 _spot:nn } {#2} {#3} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw-transforms.dtx b/macros/latex/contrib/l3experimental/l3draw-transforms.dtx
new file mode 100644
index 0000000000..3ab6a81bd6
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw-transforms.dtx
@@ -0,0 +1,339 @@
+% \iffalse meta-comment
+%
+%% File: l3draw-transforms.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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>
+\RequirePackage{expl3}
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw-transforms} package\\ Transformations^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw-transforms} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% This sub-module covers more-or-less the same ideas as
+% \texttt{pgfcoretransformations.code.tex}. At present, equivalents of the
+% following are currently absent:
+% \begin{itemize}
+% \item \cs{pgfgettransform}, \cs{pgfgettransformentries}: Awaiting use cases.
+% \item \cs{pgftransformlineattime}, \cs{pgftransformarcaxesattime},
+% \cs{pgftransformcurveattime}: Need to look at the use cases for
+% these to fully understand them.
+% \item \cs{pgftransformarrow}: Likely to be done when other arrow functions
+% are added.
+% \item \cs{pgflowlevelsynccm}, \cs{pgflowlevel}: Likely to be added when
+% use cases are encountered in other parts of the code.
+% \end{itemize}
+%
+% \begin{variable}{\l_@@_matrix_active_bool}
+% An internal flag to avoid redundant calculations.
+% \begin{macrocode}
+\bool_new:N \l_@@_matrix_active_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \l_@@_matrix_a_fp,
+% \l_@@_matrix_b_fp,
+% \l_@@_matrix_c_fp,
+% \l_@@_matrix_a_fp,
+% \l_@@_xshift_dim,
+% \l_@@_yshift_dim
+% }
+% The active matrix and shifts.
+% \begin{macrocode}
+\fp_new:N \l_@@_matrix_a_fp
+\fp_new:N \l_@@_matrix_b_fp
+\fp_new:N \l_@@_matrix_c_fp
+\fp_new:N \l_@@_matrix_d_fp
+\dim_new:N \l_@@_xshift_dim
+\dim_new:N \l_@@_yshift_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\draw_transform_matrix_reset:, \draw_transform_shift_reset:}
+% Fast resetting.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_matrix_reset:
+ {
+ \fp_set:Nn \l_@@_matrix_a_fp { 1 }
+ \fp_zero:N \l_@@_matrix_b_fp
+ \fp_zero:N \l_@@_matrix_c_fp
+ \fp_set:Nn \l_@@_matrix_d_fp { 1 }
+ }
+\cs_new_protected:Npn \draw_transform_shift_reset:
+ {
+ \dim_zero:N \l_@@_xshift_dim
+ \dim_zero:N \l_@@_yshift_dim
+ }
+\draw_transform_matrix_reset:
+\draw_transform_shift_reset:
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\draw_transform_matrix_absolute:nnnn}
+% \begin{macro}{\draw_transform_shift_absolute:n}
+% \begin{macro}{\@@_transform_shift_absolute:nn}
+% Setting the transform matrix is straight-forward, with just a bit
+% of expansion to sort out. With the mechanism active, the identity
+% matrix is set.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_matrix_absolute:nnnn #1#2#3#4
+ {
+ \fp_set:Nn \l_@@_matrix_a_fp {#1}
+ \fp_set:Nn \l_@@_matrix_b_fp {#2}
+ \fp_set:Nn \l_@@_matrix_c_fp {#3}
+ \fp_set:Nn \l_@@_matrix_d_fp {#4}
+ \bool_lazy_all:nTF
+ {
+ { \fp_compare_p:nNn \l_@@_matrix_a_fp = \c_one_fp }
+ { \fp_compare_p:nNn \l_@@_matrix_b_fp = \c_zero_fp }
+ { \fp_compare_p:nNn \l_@@_matrix_c_fp = \c_zero_fp }
+ { \fp_compare_p:nNn \l_@@_matrix_d_fp = \c_one_fp }
+ }
+ { \bool_set_false:N \l_@@_matrix_active_bool }
+ { \bool_set_true:N \l_@@_matrix_active_bool }
+ }
+\cs_new_protected:Npn \draw_transform_shift_absolute:n #1
+ {
+ \@@_point_process:nn
+ { \@@_transform_shift_absolute:nn } {#1}
+ }
+\cs_new_protected:Npn \@@_transform_shift_absolute:nn #1#2
+ {
+ \dim_set:Nn \l_@@_xshift_dim {#1}
+ \dim_set:Nn \l_@@_yshift_dim {#2}
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_transform_matrix:nnnn}
+% \begin{macro}{\@@_transform:nnnn}
+% \begin{macro}{\draw_transform_shift:n}
+% \begin{macro}{\@@_transform_shift:nn}
+% Much the same story for adding to an existing matrix, with a bit of
+% pre-expansion so that the calculation uses \enquote{frozen} values.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_matrix:nnnn #1#2#3#4
+ {
+ \use:x
+ {
+ \@@_transform:nnnn
+ { \fp_eval:n {#1} }
+ { \fp_eval:n {#2} }
+ { \fp_eval:n {#3} }
+ { \fp_eval:n {#4} }
+ }
+ }
+\cs_new_protected:Npn \@@_transform:nnnn #1#2#3#4
+ {
+ \use:x
+ {
+ \draw_transform_matrix_absolute:nnnn
+ { #1 * \l_@@_matrix_a_fp + #2 * \l_@@_matrix_c_fp }
+ { #1 * \l_@@_matrix_b_fp + #2 * \l_@@_matrix_d_fp }
+ { #3 * \l_@@_matrix_a_fp + #4 * \l_@@_matrix_c_fp }
+ { #3 * \l_@@_matrix_b_fp + #4 * \l_@@_matrix_d_fp }
+ }
+ }
+\cs_new_protected:Npn \draw_transform_shift:n #1
+ {
+ \@@_point_process:nn
+ { \@@_transform_shift:nn } {#1}
+ }
+\cs_new_protected:Npn \@@_transform_shift:nn #1#2
+ {
+ \dim_set:Nn \l_@@_xshift_dim { \l_@@_xshift_dim + #1 }
+ \dim_set:Nn \l_@@_yshift_dim { \l_@@_yshift_dim + #2 }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_transform_matrix_invert:}
+% \begin{macro}{\@@_transform_invert:n, \@@_transform_invert:f}
+% \begin{macro}{\draw_transform_shift_invert:}
+% Standard mathematics: calculate the inverse matrix and use that, then
+% undo the shifts.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_matrix_invert:
+ {
+ \bool_if:NT \l_@@_matrix_active_bool
+ {
+ \@@_transform_invert:f
+ {
+ \fp_eval:n
+ {
+ 1 /
+ (
+ \l_@@_matrix_a_fp * \l_@@_matrix_d_fp
+ - \l_@@_matrix_b_fp * \l_@@_matrix_c_fp
+ )
+ }
+ }
+ }
+ }
+\cs_new_protected:Npn \@@_transform_invert:n #1
+ {
+ \fp_set:Nn \l_@@_matrix_a_fp
+ { \l_@@_matrix_d_fp * #1 }
+ \fp_set:Nn \l_@@_matrix_b_fp
+ { -\l_@@_matrix_b_fp * #1 }
+ \fp_set:Nn \l_@@_matrix_c_fp
+ { -\l_@@_matrix_c_fp * #1 }
+ \fp_set:Nn \l_@@_matrix_d_fp
+ { \l_@@_matrix_a_fp * #1 }
+ }
+\cs_generate_variant:Nn \@@_transform_invert:n { f }
+\cs_new_protected:Npn \draw_transform_shift_invert:
+ {
+ \dim_set:Nn \l_@@_xshift_dim { -\l_@@_xshift_dim }
+ \dim_set:Nn \l_@@_yshift_dim { -\l_@@_yshift_dim }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_transform_triangle:nnn}
+% Simple maths to move the canvas origin to |#1| and the two axes to
+% |#2| and |#3|.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_triangle:nnn #1#2#3
+ {
+ \@@_point_process:nnn
+ {
+ \@@_point_process:nn
+ { \@@_tranform_triangle:nnnnnn }
+ {#1}
+ }
+ {#2} {#3}
+ }
+\cs_new_protected:Npn \@@_tranform_triangle:nnnnnn #1#2#3#4#5#6
+ {
+ \use:x
+ {
+ \draw_transform_matrix_absolute:nnnn
+ { #3 - #1 }
+ { #4 - #2 }
+ { #5 - #1 }
+ { #6 - #2 }
+ \draw_transform_shift_absolute:n { #1 , #2 }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {\draw_transform_scale:n, \draw_transform_xscale:n, \draw_transform_yscale:n}
+% \begin{macro}
+% {\draw_transform_xshift:n, \draw_transform_yshift:n}
+% \begin{macro}
+% {\draw_transform_xslant:n, \draw_transform_yslant:n}
+% Lots of shortcuts.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_scale:n #1
+ { \draw_transform_matrix:nnnn { #1 } { 0 } { 0 } { #1 } }
+\cs_new_protected:Npn \draw_transform_xscale:n #1
+ { \draw_transform_matrix:nnnn { #1 } { 0 } { 0 } { 1 } }
+\cs_new_protected:Npn \draw_transform_yscale:n #1
+ { \draw_transform_matrix:nnnn { 1 } { 0 } { 0 } { #1 } }
+\cs_new_protected:Npn \draw_transform_xshift:n #1
+ { \draw_transform_shift:n { #1 , 0pt } }
+\cs_new_protected:Npn \draw_transform_yshift:n #1
+ { \draw_transform_shift:n { 0pt , #1 } }
+\cs_new_protected:Npn \draw_transform_xslant:n #1
+ { \draw_transform_matrix:nnnn { 1 } { 0 } { #1 } { 1 } }
+\cs_new_protected:Npn \draw_transform_yslant:n #1
+ { \draw_transform_matrix:nnnn { 1 } { #1 } { 0 } { 1 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\draw_transform_rotate:n}
+% \begin{macro}
+% {
+% \@@_transform_rotate:n, \@@_transform_rotate:f,
+% \@@_transform_rotate:nn, \@@_transform_rotate:ff
+% }
+% Slightly more involved: evaluate the angle only once, and the sine and
+% cosine only once.
+% \begin{macrocode}
+\cs_new_protected:Npn \draw_transform_rotate:n #1
+ { \@@_transform_rotate:f { \fp_eval:n {#1} } }
+\cs_new_protected:Npn \@@_transform_rotate:n #1
+ {
+ \@@_transform_rotate:ff
+ { \fp_eval:n { cosd(#1) } }
+ { \fp_eval:n { sind(#1) } }
+ }
+\cs_generate_variant:Nn \@@_transform_rotate:n { f }
+\cs_new_protected:Npn \@@_transform_rotate:nn #1#2
+ { \draw_transform_matrix:nnnn {#1} {#2} { -#2 } { #1 } }
+\cs_generate_variant:Nn \@@_transform_rotate:nn { ff }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw.dtx b/macros/latex/contrib/l3experimental/l3draw.dtx
new file mode 100644
index 0000000000..971ebc6caa
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw.dtx
@@ -0,0 +1,1166 @@
+% \iffalse meta-comment
+%
+%% File: l3draw.dtx
+%
+% Copyright(C) 2018,2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\usepackage{l3draw}
+% For creating code demonstrations
+% This needs access to some code-level interfaces in listings
+\usepackage{listings}
+\makeatletter
+\lst@RequireAspects{writefile}
+\newsavebox\demo@box
+\lstnewenvironment{demo}[1][code and example]
+ {%
+ \global\let\lst@intname\@empty
+ \edef\demo@end{%
+ \expandafter\noexpand\csname demo@@#1@end\endcsname
+ }%
+ \@nameuse{demo@@#1}%
+ }
+ {\demo@end}
+\newcommand\demo@new[3]{%
+ \@namedef{demo@@#1}{#2}%
+ \@namedef{demo@@#1@end}{#3}%
+}
+\newcommand*\demo@common{%
+ \setkeys{lst}
+ {%
+ basicstyle = \small\ttfamily,
+ basewidth = 0.51em,
+ gobble = 5,
+ language = [LaTeX]{TeX},
+ }%
+}
+\newcommand*\demo@input{%
+ \ExplSyntaxOn
+ \catcode`\^^M = 10\relax
+ \catcode`\% = 14\relax
+ \input{\jobname.tmp}%
+ \ExplSyntaxOff
+}
+\demo@new{code and example}{%
+ \setbox\demo@box=\hbox\bgroup
+ \lst@BeginAlsoWriteFile{\jobname.tmp}%
+ \demo@common
+}{%
+ \lst@EndWriteFile
+ \egroup
+ \begin{center}
+ \ifdim\wd\demo@box > 0.75\linewidth
+ \begin{minipage}{\linewidth}
+ \usebox\demo@box
+ \end{minipage}%
+ \par
+ \begin{minipage}{\linewidth}
+ \demo@input
+ \end{minipage}
+ \else
+ \begin{minipage}{0.25\linewidth}
+ \demo@input
+ \end{minipage}%
+ \hfil
+ \begin{minipage}{0.75\linewidth}
+ \usebox\demo@box
+ \end{minipage}%
+ \fi
+ \end{center}
+}
+\makeatother
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3draw} package\\ Core drawing support^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{\pkg{l3draw} documentation}
+%
+% The \pkg{l3draw} package provides a set of tools for creating (vector)
+% drawings in \pkg{expl3}. It is heavily inspired by the \pkg{pgf} layer of
+% the Ti\textit{k}Z system, with many of the interfaces having the same form.
+% However, the code provided here is build entirely on core \pkg{expl3} ideas
+% and uses the \LaTeX3 FPU for numerical support.
+%
+% Numerical expressions in \pkg{l3draw} are handled as floating point
+% expressions, unless otherwise noted. This means that they may contain or
+% omit explicit units. Where units are omitted, they will automatically be
+% taken as given in (\TeX{}) points.
+%
+% The code here is \emph{highly} experimental.
+%
+% \subsection{Drawings}
+%
+% \begin{function}{\draw_begin:, \draw_end:}
+% \begin{syntax}
+% \cs{draw_begin:}
+% ...
+% \cs{draw_end:}
+% \end{syntax}
+% Each drawing should be created within a \cs{draw_begin:}/\cs{draw_end:}
+% function pair. The \texttt{begin} function sets up a number of key
+% data structures for the rest of the functions here: unless otherwise
+% specified, use of |\draw_...| functions outside of this
+% \enquote{environment} is \emph{not supported}.
+%
+% The drawing created within the environment will be inserted into
+% the typesetting stream by the \cs{draw_end:} function, which will
+% switch out of vertical mode if required.
+% \begin{demo}
+% \dim_new:N \l_mypos_dim
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , \l_mypos_dim }
+% \draw_path_lineto:n { 1cm , \l_mypos_dim }
+% \dim_set:Nn \l_mypos_dim { 1cm }
+% \draw_path_lineto:n { 1cm , \l_mypos_dim }
+% \draw_path_close:
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+%
+% Within a drawing, the only commands that should appear are those directly
+% aimed at drawing (from \pkg{l3draw}) and those which produce \emph{no}
+% typeset output. For example, it is possible to include loops inside a
+% drawing using |\int_step_function:nnnn| or similar. On the other hand,
+% text should not be included directly in drawings, but should rather be
+% inserted using the appropriate \pkg{l3draw} command.
+%
+% The drawing environment sets the following standard behaviors
+% \begin{itemize}
+% \item Non-zero rule for fill overlaps
+% \item Butt caps for lines
+% \item Mitering for line joins with a miter factor of $10$
+% \item Solid line strokes
+% \end{itemize}
+% \end{function}
+%
+% Within a drawing, there are different ways of referring to a position.
+% The co-ordinates of a point are given relative to the current
+% \emph{drawing axes}. These can be manipulated and tracked at the code
+% level. Underlying this is the \meta{canvas}, which is at the \pkg{draw}
+% level essentially fixed and in line with the paper. Initially, the two
+% sets of axes are coincident. (It is possible to manipulate the canvas
+% axes at the driver level: this is then \enquote{transparent} to the
+% \pkg{draw} level, and so should be used only when strictly required.)
+%
+% \begin{function}{\draw_suspend_begin:, \draw_suspend_end:}
+% \begin{syntax}
+% \cs{draw_suspend_begin:}
+% ...
+% \cs{draw_suspend_end:}
+% \end{syntax}
+% Suspends all of the drawing mechanisms to allow \enquote{normal}
+% material to be created. Typically, this environment will be applied
+% inside a box which may contain nested pictures.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \hbox_set:Nn \l_tmpa_box
+% {
+% \draw_suspend_begin:
+% This~is~normal~text.
+% \draw_begin: % A subpicture
+% \draw_path_moveto:n { 1cm , 0cm }
+% \draw_path_lineto:n { 1cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% More~text.
+% \draw_suspend_end:
+% }
+% \draw_box_use:N \l_tmpa_box
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \subsection{Graphics state}
+%
+% Within the drawing environment, a number of functions control how drawings
+% will appear. Note that these all apply \emph{globally}, though some are
+% rest at the start of each drawing (\cs{draw_begin:}).
+%
+% \begin{variable}{\g_draw_linewidth_default_dim}
+% The default value of the linewidth for stokes, set at the start
+% of every drawing (\cs{draw_begin:}).
+% \end{variable}
+%
+% \begin{function}{\draw_linewidth:n}
+% \begin{syntax}
+% \cs{draw_linewidth:n} \Arg{width}
+% \end{syntax}
+% Sets the width to be used for stroking to the \meta{width} (an
+% \meta{fp expr}).
+% \end{function}
+%
+% \begin{function}{\draw_dash_pattern:nn}
+% \begin{syntax}
+% \cs{draw_dash_pattern:nn} \Arg{pattern} \Arg{phase}
+% \end{syntax}
+% Specifies a dash pattern. The \meta{pattern} itself is a comma-separated
+% list of entries which represent the \enquote{on} and \enquote{off}
+% parts of the line. These are all \meta{fp expr} and repeat as required.
+% Thus the \meta{pattern} may be of arbitrary length. The \meta{phase}
+% specifies where during the first \enquote{on} line the pattern should
+% start.
+% \begin{demo}
+% \draw_begin:
+% \draw_dash_pattern:nn
+% { 0.5cm , 0.5cm , 0.1cm , 0.2cm }
+% { 0cm }
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 2cm , 0cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_dash_pattern:nn
+% { 0.5cm , 0.5cm , 0.1cm , 0.2cm }
+% { 0.1cm }
+% \draw_path_moveto:n { 0cm , 1mm }
+% \draw_path_lineto:n { 2cm , 1mm }
+% \draw_path_use_clear:n { stroke }
+% \draw_dash_pattern:nn
+% { 0.5cm , 0.5cm , 0.1cm , 0.2cm }
+% { 0.2cm }
+% \draw_path_moveto:n { 0cm , 2mm }
+% \draw_path_lineto:n { 2cm , 2mm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% Setting an empty pattern will produce a solid line.
+%
+% Note the \meta{pattern} interface here is different from that in \pkg{pgf}:
+% the list is comma-separated not given in brace groups.
+% \end{function}
+%
+% \begin{function}{\draw_nonzero_rule:, \draw_evenodd_rule:}
+% \begin{syntax}
+% \cs{draw_nonzero_rule:}
+% \end{syntax}
+% Active either the non-zero winding number or the even-odd rule,
+% respectively, for determining what is inside a fill or clip area.
+% For technical reasons, these command are not influenced by scoping
+% and apply on an ongoing basis.
+% \end{function}
+%
+% \begin{function}
+% {
+% \draw_cap_butt: ,
+% \draw_cap_rectangle: ,
+% \draw_cap_round:
+% }
+% \begin{syntax}
+% \cs{draw_cap_butt:}
+% \end{syntax}
+% Sets the style of terminal stroke position to one of butt, rectangle or
+% round.
+% \end{function}
+%
+% \begin{function}
+% {
+% \draw_join_bevel: ,
+% \draw_join_miter: ,
+% \draw_join_round:
+% }
+% \begin{syntax}
+% \cs{draw_cap_butt:}
+% \end{syntax}
+% Sets the style of stroke joins to one of bevel, miter or round.
+% \end{function}
+%
+% \begin{function}{\draw_miterlimit:n}
+% \begin{syntax}
+% \cs{draw_miterlimit:n} \Arg{factor}
+% \end{syntax}
+% Sets the miter \meta{factor} of lines joined as a miter, as described in the
+% PDF and PostScript manuals. The \meta{factor} is an \meta{fp expr}.
+% \end{function}
+%
+% \subsection{Scoping drawing elements}
+%
+% Scoping drawing elements is necessary to allowing nesting of subparts.
+% These features have specific use requirements: the preconditions must be
+% met. In particular, each type of drawing scope also constitutes a
+% group (\cs{group_begin:}/\cs{group_end:} pair): as such, they must be
+% nested correctly.
+%
+% \begin{function}{\draw_scope_begin:, \draw_scope_end:}
+% \begin{syntax}
+% \cs{draw_scope_begin:}
+% ...
+% \cs{draw_scope_end:}
+% \end{syntax}
+% Creates a scope for localisation of state settings within a drawing.
+% A scope forms a \TeX{} group but will also localise global state
+% variables (such as \cs{g_draw_linewidth_default_dim}), and driver-level
+% concepts such as the termination of lines.
+% \begin{demo}
+% \draw_begin:
+% \draw_scope_begin:
+% \group_begin:
+% \draw_linewidth:n { 2pt }
+% \draw_path_rectangle:nn { 0 , 0 } { 2ex , 2ex }
+% \draw_path_use:n { stroke }
+% \group_end:
+% \draw_path_rectangle:nn { 3ex , 0ex } { 2ex , 2ex }
+% \draw_path_use:n { stroke }
+% \draw_scope_end:
+% \draw_path_rectangle:nn { 6ex , 0ex } { 2ex , 2ex }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% Global graphical concepts restricted by scope are
+% \begin{itemize}
+% \item Line width
+% \item Stroke and fill color
+% \item Dash pattern
+% \item Line joining and capping, including the miter limit
+% \item Clipping paths
+% \item Canvas (driver) transformations
+% \end{itemize}
+% \end{function}
+%
+% \subsection{Points}
+%
+% Functions supporting the calculation of points (co-ordinates) are expandable
+% and may be used outside of the drawing environment. When used in this
+% way, they all yield a co-ordinate tuple, for example
+% \begin{verbatim}
+% \tl_set:Nx \l_tmpa_tl { \draw_point:nn { 1 } { 2 } }
+% \tl_show:N \l_tmpa_tl
+% \end{verbatim}
+% gives
+% \begin{verbatim}
+% > \l_tmpa_tl=1pt,2pt.
+% <recently read> }
+% \end{verbatim}
+%
+% This output form is then suitable as \emph{input} for subsequent point
+% calculations, \emph{i.e.}~where a \meta{point} is required it may be
+% given as a tuple. This \emph{may} include units and surrounding
+% parentheses, for example
+% \begin{verbatim}
+% 1,2
+% (1,2)
+% 1cm,3pt
+% (1pt,2cm)
+% 2 * sind(30), 2^4in
+% \end{verbatim}
+% are all valid input forms. Notice that each part of the tuple may itself
+% be a float point expression.
+%
+% Point co-ordinates are relative to the canvas axes, but can be transformed
+% by \cs{draw_point_transform:n}. These manipulation is applied by many
+% higher-level functions, for example path construction, and allows parts of
+% a drawing to be rotated, scaled or skewed. This occurs before writing any
+% data to the driver, and so such manipulations are tracked by the drawing
+% mechanisms. See \cs{@@_backend_cm:nnnn} for backend-level
+% manipulation of the canvas axes themselves.
+%
+% Notice that in contrast to \pkg{pgf} it is possible to give the positions
+% of points \emph{directly}.
+%
+% \subsubsection{Basic point functions}
+%
+% \begin{function}[EXP]{\draw_point_polar:nn, \draw_point_polar:nnn}
+% \begin{syntax}
+% \cs{draw_point_polar:nn} \Arg{radius} \Arg{angle}
+% \cs{draw_point_polar:nnn} \Arg{radius-a} \Arg{radius-b} \Arg{angle}
+% \end{syntax}
+% Gives the co-ordinates of the point at \meta{angle} (an \meta{fp expr} in
+% \emph{degrees}) and \meta{radius}. The three-argument version accepts
+% two radii of different lengths.
+%
+% Note the interface here is somewhat different from that in \pkg{pgf}:
+% the one- and two-radii versions in \pkg{l3draw} use separate functions,
+% whilst in \pkg{pgf} they use the same function and a keyword.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_unit_vector:n}
+% \begin{syntax}
+% \cs{draw_point_unit_vector:n} \Arg{point}
+% \end{syntax}
+% Expands to the co-ordinates of a unit vector in the direction of the
+% \meta{point} from the origin. If the \meta{point} is at the origin,
+% a vertical unit vector is returned
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_transform:n}
+% \begin{syntax}
+% \cs{draw_point_transform:n} \Arg{point}
+% \end{syntax}
+% Evaluates the position of the \meta{point} subject to the current
+% transformation matrix. This operation is applied automatically by
+% most higher-level functions (\emph{e.g.}~path manipulations).
+% \end{function}
+%
+% \subsubsection{Points on a vector basis}
+%
+% As well as giving explicit values, it is possible to describe points
+% in terms of underlying direction vectors. The latter are initially
+% co-incident with the standard Cartesian axes, but may be altered by
+% the user.
+%
+% \begin{function}{\draw_xvec:n, \draw_yvec:n, \draw_zvec:n}
+% \begin{syntax}
+% \cs{draw_xvec:n} \Arg{point}
+% \end{syntax}
+% Defines the appropriate base vector to point toward the \meta{point}
+% on the canvas. The standard settings for the $x$- and $y$-vectors are
+% $1\,\mathrm{cm}$ along the relevant canvas axis, whilst for the
+% $z$-vector an appropriate direction is taken.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_vec:nn, \draw_point_vec:nnn}
+% \begin{syntax}
+% \cs{draw_point_vec:nn} \Arg{xscale} \Arg{yscale}
+% \cs{draw_point_vec:nnn} \Arg{xscale} \Arg{yscale} \Arg{zscale}
+% \end{syntax}
+% Expands to the co-ordinate of the point at \meta{xscale} times the
+% $x$-vector and \meta{yscale} times the $y$-vector. The three-argument
+% version extends this to include the $z$-vector.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_vec_polar:nn, \draw_point_vec_polar:nnn}
+% \begin{syntax}
+% \cs{draw_point_vec_polar:nn} \Arg{radius} \Arg{angle}
+% \cs{draw_point_vec_polar:nnn} \Arg{radius-a} \Arg{radius-b} \Arg{angle}
+% \end{syntax}
+% Gives the co-ordinates of the point at \meta{angle} (an \meta{fp expr} in
+% \emph{degrees}) and \meta{radius}, relative to the prevailing
+% $x$- and $y$-vectors. The three-argument version accepts two radii of
+% different lengths.
+%
+% Note the interface here is somewhat different from that in \pkg{pgf}:
+% the one- and two-radii versions in \pkg{l3draw} use separate functions,
+% whilst in \pkg{pgf} they use the same function and a keyword.
+% \end{function}
+%
+% \subsubsection{Intersections}
+%
+% \begin{function}[EXP]{\draw_point_intersect_lines:nnnn}
+% \begin{syntax}
+% \cs{draw_point_intersect_lines:nnnn} \Arg{point1} \Arg{point2} \Arg{point3} \Arg{point4}
+% \end{syntax}
+% Evaluates the point at the intersection of one line, joining
+% \meta{point1} and \meta{point2}, and a second line joining \meta{point3}
+% and \meta{point4}. If the lines do not intersect, or are coincident, and
+% error will occur.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_intersect_circles:nnnnn}
+% \begin{syntax}
+% \cs{draw_point_intersect_circles:nnnnn}
+% \Arg{center1} \Arg{radius1} \Arg{center2} \Arg{radius2} \Arg{root}
+% \end{syntax}
+% Evaluates the point at the intersection of one circle with
+% \meta{center1} and \meta{radius1}, and a second circle with \meta{center2}
+% and \meta{radius2}. If the circles do not intersect, or are coincident, and
+% error will occur.
+%
+% Note the interface here has a different argument ordering from that in
+% \pkg{pgf}, which has the two centers then the two radii.
+% \end{function}
+%
+% \subsubsection{Interpolations}
+%
+% \begin{function}[EXP]{\draw_point_interpolate_line:nnn}
+% \begin{syntax}
+% \cs{draw_point_interpolate_line:nnn} \Arg{part} \Arg{point1} \Arg{point2}
+% \end{syntax}
+% Expands to the point which is \meta{part} way along the line joining
+% \meta{point1} and \meta{point2}. The \meta{part} may be an interpolation or
+% an extrapolation, and is a floating point value expressing a percentage
+% along the line, \emph{e.g.}~a value of \texttt{0.5} would be half-way
+% between the two points.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_interpolate_distance:nnn}
+% \begin{syntax}
+% \cs{draw_point_interpolate_distance:nnn} \Arg{distance} \Arg{point expr1} \Arg{point expr2}
+% \end{syntax}
+% Expands to the point which is \meta{distance} way along the line joining
+% \meta{point1} and \meta{point2}. The \meta{distance} may be an interpolation
+% or an extrapolation.
+% \end{function}
+%
+% \begin{function}[EXP]{\draw_point_interpolate_curve:nnnnnn}
+% \begin{syntax}
+% \cs{draw_point_interpolate_curve:nnnnnn} \Arg{part}
+% \Arg{start} \Arg{control1} \Arg{control2} \Arg{end}
+% \end{syntax}
+% Expands to the point which is \meta{part} way along the curve between
+% \meta{start} and \meta{end} and defined by \meta{control1} and
+% \meta{control2}. The \meta{part} may be an interpolation or
+% an extrapolation, and is a floating point value expressing a percentage
+% along the curve, \emph{e.g.}~a value of \texttt{0.5} would be half-way
+% along the curve.
+% \end{function}
+%
+% \subsection{Paths}
+%
+% Paths are constructed by combining one or more operations before applying
+% one or more actions. Thus until a path is \enquote{used}, it may be
+% manipulated or indeed discarded entirely. Only one path is active at
+% any one time, and the path is \emph{not} affected by \TeX{} grouping.
+%
+% \begin{function}{\draw_path_corner_arc:nn}
+% \begin{syntax}
+% \cs{draw_path_corner_arc:n} \Arg{length1} \Arg{length2}
+% \end{syntax}
+% Sets the degree of rounding applied to corners in a path: the two
+% \meta{length} values are the distances from the corner at which the curving
+% should start. The first \meta{length} applies to the part of the path
+% \enquote{leading in} to the corner (\emph{i.e.}~from the previous path
+% operation), and the second to that \enquote{leading out}. If both
+% values are \texttt{0pt} then corners will not be rounded. The values
+% apply within the scope of the current \TeX{} group.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_corner_arc:nn { 5mm } { 5mm }
+% \draw_path_rectangle_corners:nn
+% { 0cm , 0cm } { 3cm , 2cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \begin{demo}
+% \draw_begin:
+% \draw_path_corner_arc:nn { 10mm } { 5mm }
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 2cm }
+% \draw_path_lineto:n { 3cm , 2cm }
+% \draw_path_curveto:nnn
+% { 3cm , 0cm } { 2cm , 0cm } { 1cm , 0cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% The corners created are quarter-circles for exact right-angles and
+% close approximations otherwise. Closing a path will result in rounding
+% correctly.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_corner_arc:nn { 4pt } { 4pt }
+% \draw_path_moveto:n
+% { \draw_point_polar:nn { 1cm } { 0 } }
+% \int_step_inline:nnnn { 72 } { 72 } { 359 }
+% {
+% \draw_path_lineto:n
+% { \draw_point_polar:nn { 1cm } { #1 } }
+% }
+% \draw_path_close:
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_close:}
+% \begin{syntax}
+% \cs{draw_path_close:}
+% \end{syntax}
+% Closes the current part of the path by appending a straight line from
+% the current point to the starting point of the path. In general, any
+% path to be \emph{filled} should include a close instructions.
+% \end{function}
+%
+% \begin{function}{\draw_path_use:n, \draw_path_use_clear:n}
+% \begin{syntax}
+% \cs{draw_path_use:n} \Arg{action(s)}
+% \end{syntax}
+% Inserts the current path, carrying out one ore more possible \meta{actions}
+% (a comma list):
+% \begin{itemize}
+% \item \texttt{stroke} Draws a line along the current path
+% \item \texttt{draw} A synonym for \texttt{stroke}
+% \item \texttt{fill} Fills the interior of the path with the current
+% file color
+% \item \texttt{clip} Clips any content outside of the path
+% \end{itemize}
+% Actions are applied in the order given irrespective of the input order.
+% Note that whilst it is possible to use a path without clearing it, the
+% normal use case would be to clear the path (this resets data structures
+% at the macro level).
+% \end{function}
+%
+% \subsubsection{Path operations on drawing axes}
+%
+% The standard path functions are all influenced by the active transformation
+% matrix, \emph{i.e.}~the work relative to the drawing axes rather than
+% the canvas.
+%
+% \begin{function}{\draw_path_moveto:n}
+% \begin{syntax}
+% \cs{draw_path_moveto:n} \Arg{point}
+% \end{syntax}
+% Moves the reference point of the path to the \meta{point}, but will
+% not join this to any previous point.
+% \end{function}
+%
+% \begin{function}{\draw_path_lineto:n}
+% \begin{syntax}
+% \cs{draw_path_lineto:n} \Arg{point}
+% \end{syntax}
+% Joins the current path to the \meta{point} with a straight line.
+% In general, for reliable treatment by viewers, a \cs{draw_path_moveto:n}
+% operation should precede the first use of a \cs{draw_path_lineto:n}
+% on a path.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 1cm , 1cm }
+% \draw_path_lineto:n { 2cm , 1cm }
+% \draw_path_lineto:n { 3cm , 0.5cm }
+% \draw_path_lineto:n { 3cm , 0cm }
+% \draw_color_fill:n { yellow!80!black }
+% \draw_path_use_clear:n { fill , stroke }
+% \draw_end:
+% \end{demo}
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 1cm , 1cm }
+% \draw_path_lineto:n { 2cm , 1cm }
+% \draw_path_moveto:n { 2cm , 1cm } % Begins a new part
+% \draw_path_lineto:n { 3cm , 0.5cm }
+% \draw_path_lineto:n { 3cm , 0cm }
+% \draw_color_fill:n { yellow!80!black }
+% \draw_path_use_clear:n { fill , stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_curveto:nnn}
+% \begin{syntax}
+% \cs{draw_path_curveto:nnn} \Arg{control1} \Arg{control2} \Arg{end}
+% \end{syntax}
+% Joins the current path to the \meta{end} with a curved line defined by
+% cubic Bézier points \meta{control1} and \meta{control2}. The bounding box
+% of the path (and image) will fully-contain the curve and control points,
+% \emph{i.e.}~it may be bigger than strictly necessary to contain the curve
+% \emph{alone}.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_curveto:nnn
+% { 1cm , 1cm } % First control
+% { 2cm , 1cm } % Second control
+% { 3cm , 0cm } % End
+% \draw_color_fill:n { yellow!80!black }
+% \draw_path_use_clear:n { fill , stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_curveto:nn}
+% \begin{syntax}
+% \cs{draw_path_curveto:nn} \Arg{control} \Arg{end}
+% \end{syntax}
+% Joins the current path to the \meta{end} with a curved line defined by
+% quadratic Bézier point \meta{control}. The bounding box
+% of the path (and image) will fully-contain the curve and computed (cubic)
+% control points, \emph{i.e.}~it may be bigger than strictly necessary to
+% contain the curve \emph{alone}.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_curveto:nn
+% { 1cm , 1cm }
+% { 2cm , 0cm }
+% \draw_color_fill:n { yellow!80!black }
+% \draw_path_use_clear:n { fill , stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_arc:nnn, \draw_path_arc:nnnn}
+% \begin{syntax}
+% \cs{draw_path_arc:nnn} \Arg{angle1} \Arg{angle2} \Arg{radius}
+% \cs{draw_path_arc:nnnn} \Arg{angle1} \Arg{angle2} \Arg{radius-a} \Arg{radius-b}
+% \end{syntax}
+% Joins the current path with an arc between \meta{angle1} and \meta{angle2}
+% and of \meta{radius}. The four-argument version accepts two radii of
+% different lengths.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_arc:nnn { 180 } { 90 } { 0.5cm }
+% \draw_path_lineto:n { 3cm , 1.5cm }
+% \draw_path_arc:nnn { 90 } { -45 } { 0.5cm }
+% \draw_path_use_clear:n { fill }
+% \draw_end:
+% \end{demo}
+%
+% Note the interface here has a different argument ordering from that in
+% \pkg{pgf}, which has the two centers then the two radii.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_arc:nnnn { 180 } { 45 } { 2cm } { 1cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_arc_axes:nnnn}
+% \begin{syntax}
+% \cs{draw_path_arc_axes:nnn} \Arg{angle1} \Arg{angle2} \Arg{vector1} \Arg{vector2}
+% \end{syntax}
+% Appends the portion of an ellipse from \meta{angle1} to \meta{angle2} of an
+% ellipse with axes along \meta{vector1} and \meta{vector2} to the current
+% path.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 2cm , 5mm }
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_moveto:n { 2cm , 5mm }
+% \draw_path_arc_axes:nnnn { 0 } { 90 }
+% { 2cm , 5mm } { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_ellipse:nnn}
+% \begin{syntax}
+% \cs{draw_path_ellipse:nnn} \Arg{center} \Arg{vector1} \Arg{vector2}
+% \end{syntax}
+% Appends an ellipse at \meta{center} with axes along \meta{vector1} and
+% \meta{vector2} to the current path. A new part is started if the path
+% is already non-empty. Notice that the underlying drawing is constructed
+% from arcs with appropriate moves: the interfaces is a more efficient
+% convenience.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_ellipse:nnn
+% { 1cm , 0cm }
+% { 1.5cm , 0cm }
+% { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_color:n { red }
+% \draw_path_ellipse:nnn
+% { 1cm , 0cm }
+% { 1cm , 1cm }
+% { -0.5cm , 0.5cm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+%
+% Note that any transformation is applied to the completed ellipse rather than
+% to the axes.
+% \end{function}
+%
+% \begin{function}{\draw_path_circle:nn}
+% \begin{syntax}
+% \cs{draw_path_circle:nn} \Arg{center} \Arg{radius}
+% \end{syntax}
+% Appends a circle of \meta{radius} at \meta{center} to the current path.
+% This is a shortcut for \cs{draw_path_ellipse:nnn}.
+% \end{function}
+%
+% \begin{function}{\draw_path_rectangle:nn, \draw_path_rectangle_corners:nn}
+% \begin{syntax}
+% \cs{draw_path_rectangle:nn} \Arg{lower-left} \Arg{displacement}
+% \cs{draw_path_rectangle_corners:nn} \Arg{lower-left} \Arg{top-right}
+% \end{syntax}
+% Appends a rectangle starting at \meta{lower-left} to the current path,
+% with the size of the rectangle determined either by a \meta{displacement}
+% or the position of the \meta{top-right}.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_rectangle:nn
+% { 1cm , 0cm }
+% { 1.5cm , 1cm }
+% \draw_path_rectangle:nn
+% { 1.5cm , 0.25cm }
+% { 1.5cm , 1cm }
+% \draw_path_rectangle:nn
+% { 2cm , 0.5cm }
+% { 1.5cm , 1cm }
+% \draw_path_use_clear:n { draw }
+% \draw_end:
+% \end{demo}
+% \begin{demo}
+% \draw_begin:
+% \draw_path_rectangle_corners:nn
+% { 1cm , 0cm }
+% { 1.5cm , 1cm }
+% \draw_path_use_clear:n { draw }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_path_grid:nnnn}
+% \begin{syntax}
+% \cs{draw_path_grid:nnnn} \Arg{xstep} \Arg{ystep} \Arg{lower-left} \Arg{upper-right}
+% \end{syntax}
+% Constructs a grid of \meta{xstep} and \meta{ystep} inside the rectangle
+% defined by the \meta{lower-left} and the \meta{upper-right}, and appends
+% this to the current path. The grid will be aligned such that grid lines
+% pass through the origin, which may result in \enquote{protruding} ends
+% if the start/end positions do not fully align.
+% \begin{demo}
+% \draw_begin:
+% \draw_linewidth:n { 0.8pt }
+% \draw_path_grid:nnnn
+% { 1cm } { 1cm }
+% { -3mm , -3mm }
+% { 33mm , 23mm }
+% \draw_path_use_clear:n { stroke }
+% \draw_linewidth:n { 0.4pt }
+% \draw_path_grid:nnnn
+% { 1mm } { 1mm }
+% { -1.5mm , -1.5mm }
+% { 31.5mm , 21.5mm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+%
+% Any transformation is applied to the finished grid.
+% \begin{demo}
+% \draw_begin:
+% \draw_transform_rotate:n { 10 }
+% \draw_path_grid:nnnn
+% { 1mm } { 2mm }
+% { 0mm , 0mm }
+% { 30mm , 30mm }
+% \draw_path_use_clear:n { stroke }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \subsubsection{Path scope}
+%
+% \begin{function}{\draw_path_scope_begin:, \draw_path_scope_end:}
+% \begin{syntax}
+% \cs{draw_path_scope_begin:}
+% ...
+% \cs{draw_path_scope_end:}
+% \end{syntax}
+% Suspends (and saves) the current (partial) path, initialising a new
+% path within the scope. Path operations are written to output only when
+% used, so the scoped path is stored at the \pkg{expl3} level, not
+% in the output.
+% \end{function}
+%
+% \subsubsection{Path operations on canvas axes}
+%
+% For \emph{specialist} work, a small number of functions are provided
+% which work relative to the canvas axes, \emph{i.e.}~these functions
+% ignore the transformation matrix.
+%
+% \begin{function}{\draw_path_canvas_moveto:n}
+% \begin{syntax}
+% \cs{draw_path_canvas_moveto:n} \Arg{canvas point}
+% \end{syntax}
+% Moves the reference point of the path to the \meta{canvas point}, but will
+% not join this to any previous point.
+% \end{function}
+%
+% \begin{function}{\draw_path_canvas_lineto:n}
+% \begin{syntax}
+% \cs{draw_path_canvas_lineto:n} \Arg{canvas point}
+% \end{syntax}
+% Joins the current path to the \meta{canvas point} with a straight line.
+% \end{function}
+%
+% \begin{function}{\draw_path_canvas_curveto:nnn}
+% \begin{syntax}
+% \cs{draw_path_canvas_curveto:nnn} \Arg{control1} \Arg{control2} \Arg{end}
+% \end{syntax}
+% Joins the current path to the \meta{end} with a curved line defined by
+% cubic Bézier points \meta{control1} and \meta{control2}. These positions
+% are given as canvas points.
+% \end{function}
+%
+% \subsection{Bounding box}
+%
+% \begin{variable}{\l_draw_bb_update_bool}
+% All functions automatically update the bounding box of the image, unless
+% specified otherwise. This behavior is selectable using the
+% \cs{l_draw_bb_update_bool} boolean.
+% \end{variable}
+%
+% \subsection{Boxes and coffins}
+%
+% \begin{function}{\draw_box_use:N}
+% \begin{syntax}
+% \cs{draw_box_use:N} \meta{box}
+% \end{syntax}
+% Inserts the \meta{box} into a drawing, taking account of the current
+% transformation matrix and shift, and adjusting the drawing bounding
+% box to contain the (apparent) size of the box if this is active
+% (see \cs{l_draw_bb_update_bool}).
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \hbox_set:Nn \l_tmpa_box
+% { This~is~text. }
+% \draw_box_use:N \l_tmpa_box
+% \draw_end:
+% \end{demo}
+% \begin{demo}
+% \draw_begin:
+% \draw_transform_matrix_absolute:nnnn { 2 } { 0 } { 1 } { 2 }
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \hbox_set:Nn \l_tmpa_box
+% { This~is~text. }
+% \draw_box_use:N \l_tmpa_box
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \begin{function}{\draw_coffin_use:Nnn}
+% \begin{syntax}
+% \cs{draw_coffin_use:N} \meta{coffin} \Arg{hpole} \Arg{vpole}
+% \end{syntax}
+% Inserts the \meta{coffin} into a drawing, taking account of the current
+% transformation matrix and shift, and adjusting the drawing bounding
+% box to contain the (apparent) size of the box if this is active
+% (see \cs{l_draw_bb_update_bool}). The alignment point of the coffin to
+% the origin is specified by the intersection of the \meta{hpole} and the
+% \meta{vpole}.
+% \begin{demo}
+% \draw_begin:
+% \draw_path_moveto:n { 0cm , 0cm }
+% \draw_path_lineto:n { 0cm , 1cm }
+% \draw_path_use_clear:n { stroke }
+% \hcoffin_set:Nn \l_tmpa_coffin
+% { This~is~text. }
+% \draw_coffin_use:Nnn \l_tmpa_coffin { hc } { vc }
+% \draw_end:
+% \end{demo}
+% \end{function}
+%
+% \subsection{Color}
+%
+% \begin{function}{\draw_color:n, \draw_color_fill:n, \draw_color_stroke:n}
+% \begin{syntax}
+% \cs{draw_color:n} \Arg{color expression}
+% \end{syntax}
+% Evaluates the \meta{color expression} as described for \pkg{l3color}.
+% \end{function}
+%
+% \subsection{Transformations}
+%
+% Points are normally used unchanged relative to the canvas axes. This can
+% be modified by applying a transformation matrix. The canvas axes themselves
+% may be adjusted using \cs{driver_draw_cm:nnnn}: note that this
+% is transparent to the drawing code so is not tracked.
+%
+% \begin{function}
+% {\draw_transform_matrix:nnnn, \draw_transform_matrix_absolute:nnnn}
+% \begin{syntax}
+% \cs{draw_transform_matrix:nnnn}
+% \Arg{a} \Arg{b} \Arg{c} \Arg{d}
+% \end{syntax}
+% Applies the transformation matrix $[ \meta{a} \meta{b} \meta{c} \meta{d}]$.
+% The basic applies the transformation in addition to those active; the
+% |absolute| version overwrites any active transformation.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}{\draw_transform_shift:n, \draw_transform_shift_absolute:n}
+% \begin{syntax}
+% \cs{draw_transform_shift:n} \Arg{vector}
+% \end{syntax}
+% Applies the transformation \meta{vector} to points.
+% The basic applies the vector in addition to those active; the
+% |absolute| version overwrites any active vector.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}{\draw_transform_triangle:nnn}
+% \begin{syntax}
+% \cs{draw_transform_triangle:nnn}
+% \Arg{origin} \Arg{point1} \Arg{point2}
+% \end{syntax}
+% Applies a transformation such that the co-ordinates $(0, 0)$, $(1, 0)$
+% and $(0, 1)$ are given by the \meta{origin}, \meta{point1} and
+% \meta{point2}, respectively.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}{\draw_transform_rotate:n}
+% \begin{syntax}
+% \cs{draw_transform_rotate:n} \Arg{angle}
+% \end{syntax}
+% Applies a rotation by the \meta{angle}, measured anti-clockwise in degrees.
+% This rotation is \emph{additional} to any prevailing transformation.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}
+% {
+% \draw_transform_scale:n,
+% \draw_transform_xscale:n,
+% \draw_transform_yscale:n
+% }
+% \begin{syntax}
+% \cs{draw_transform_scale:n} \Arg{scale}
+% \end{syntax}
+% Applies the \meta{scale} in either $x$ or $y$ (or both).
+% This scale is \emph{added} to any prevailing transformation.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}
+% {
+% \draw_transform_xshift:n,
+% \draw_transform_yshift:n
+% }
+% \begin{syntax}
+% \cs{draw_transform_xshift:n} \Arg{xshift}
+% \end{syntax}
+% Applies an \meta{xshift} or \meta{yshift}, as appropriate. This shift is
+% \emph{added} to any prevailing one.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}
+% {
+% \draw_transform_xslant:n,
+% \draw_transform_yslant:n
+% }
+% \begin{syntax}
+% \cs{draw_transform_xslant:n} \Arg{slant}
+% \end{syntax}
+% Applies the \meta{slant} (a factor) in either $x$ or $y$.
+% This slant is \emph{added} to any prevailing transformation.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}
+% {\draw_transform_matrix_invert:, \draw_transform_shift_invert:}
+% \begin{syntax}
+% \cs{draw_transform_matrix_invert:}
+% \end{syntax}
+% Inverts the current transformation matrix or shift vector, as
+% appropriate.
+% This assignment is local.
+% \end{function}
+%
+% \begin{function}
+% {\draw_transform_matrix_reset:, \draw_transform_shift_reset:}
+% \begin{syntax}
+% \cs{draw_transform_matrix_reset:}
+% \end{syntax}
+% Resets the current transformation matrix or shift vector, as
+% appropriate.
+% This assignment is local.
+% \end{function}
+%
+% \subsection{Layers}
+%
+% Drawing layers may be used to alter the way in which elements are stacked
+% on top of one another. In particular, they are useful when the nature of
+% an element depends on another, but where it needs to be behind it's
+% \enquote{parent}. A classic example is a filled background: this needs to
+% know the size of the material it is behind.
+%
+% All drawings feature a layer called |main|. This layer should always be
+% present, and is the one which \enquote{non-layered} content is added to.
+%
+% \begin{function}{\draw_layer_new:n}
+% \begin{syntax}
+% \cs{draw_layer_new:n} \Arg{layer}
+% \end{syntax}
+% Creates a new \meta{layer} which can then be used in drawing. The layer
+% |main| is pre-defined.
+% \end{function}
+%
+% \begin{function}{\draw_layer_begin:n, \draw_layer_end:}
+% \begin{syntax}
+% \cs{draw_layer_begin:n} \Arg{layer}
+% ...
+% \cs{draw_layer_end:}
+% \end{syntax}
+% Begin and end collection of material for the \meta{layer}, which should
+% previously have been created (and which cannot be |main|). Material
+% collected for the layer is available globally within the current drawing.
+% \end{function}
+%
+% \begin{variable}{\l_draw_layers_clist}
+% The list of active layers: may be given anywhere before \cs{draw_end:}.
+% The |main| layer should always be included.
+% \end{variable}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3draw} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=draw>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3draw}{2019-08-25}{}
+ {L3 Experimental core drawing support}
+%</package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+\RequirePackage { l3color }
+% \end{macrocode}
+%
+% Everything else is in the sub-files!
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3draw.ins b/macros/latex/contrib/l3experimental/l3draw.ins
new file mode 100644
index 0000000000..836d9a32d6
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw.ins
@@ -0,0 +1,70 @@
+\iffalse meta-comment
+
+File: l3draw.ins
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3draw.sty}
+ {
+ \from{l3draw.dtx} {package}
+ \from{l3draw-boxes.dtx} {package}
+ \from{l3draw-layers.dtx} {package}
+ \from{l3draw-paths.dtx} {package}
+ \from{l3draw-points.dtx} {package}
+ \from{l3draw-scopes.dtx} {package}
+ \from{l3draw-softpath.dtx} {package}
+ \from{l3draw-state.dtx} {package}
+ \from{l3draw-transforms.dtx} {package}
+ }
+}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3draw.pdf b/macros/latex/contrib/l3experimental/l3draw.pdf
new file mode 100644
index 0000000000..9be0aca173
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3draw.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3galley.dtx b/macros/latex/contrib/l3experimental/l3galley.dtx
new file mode 100644
index 0000000000..2db96ef33b
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3galley.dtx
@@ -0,0 +1,2662 @@
+% \iffalse
+%
+%% File: l3galley.dtx
+%
+% Copyright (C) 1999-2001,2004-2009 Frank Mittelbach
+% (C) 2010-2019 The LaTeX3 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
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3experimental 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|package>
+% The version of expl3 required is tested as early as possible, as
+% some really old versions do not define \ProvidesExplPackage.
+\RequirePackage{expl3}[2018/02/21]
+%<package>\@ifpackagelater{expl3}{2018/02/21}
+%<package> {}
+%<package> {%
+%<package> \PackageError{l3galley}{Support package l3kernel too old}
+%<package> {%
+%<package> Please install an up to date version of l3kernel\MessageBreak
+%<package> using your TeX package manager or from CTAN.\MessageBreak
+%<package> \MessageBreak
+%<package> Loading l3galley will abort!%
+%<package> }%
+%<package> \endinput
+%<package> }
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \textsf{l3galley} package\\ Galley code^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Introduction}
+%
+% In \LaTeX3 terminology a galley is a rectangular area which receives
+% text and other material filling it from top. The vertically extend of
+% a galley is normally not restricted: instead certain chunks are taken
+% off the top of an already partially filled galley to form columns or
+% similar areas on a page. This process is typically asynchronous but
+% there are ways to control or change its behaviour.
+%
+% Examples for galleys are \enquote{the main galley}, where the
+% continuous document data gets formatted into and from which columns
+% and pages are constructed, and \enquote{vertical box galleys}, such
+% as the body of a minipage environment. The latter galleys are
+% typically not split after formatting, though there can be exceptions.
+%
+% \section{Formatting layers}
+%
+% The present module is mainly concerned with the formatting of text
+% in galleys. The mechanism by which this is achieved uses four
+% (somewhat) distinct layers, some of which can be addressed using the
+% templates provided here.
+%
+% \subsection{Layer one: external dimensions}
+%
+% The bottom layer of the system is the external dimensions of the
+% galley. Normally only the horizontal dimension is fixed externally,
+% while the vertical (filling) dimension is unspecified. The external
+% dimensions are fixed when starting a new galley, and are therefore
+% not modifiable within the galley.
+%
+% There are no templates for setting this layer directly, although the
+% external values are influenced by other parts of the system (for
+% example when creating minipage environments).
+%
+% \subsection{Layer two: internal dimensions}
+%
+% The second layer is the internal dimensions of the galley: the
+% \emph{measure} used for paragraph text and the position of the
+% paragraph relative to the edges of the galley.
+%
+% This layer is normally accessed by higher-level templates
+% \emph{via} the object type \texttt{measure}. Changes made using
+% level two templates will often extend for large parts of a document
+% (up to and including the entire document).
+%
+% \subsection{Layer three: paragraph shape}
+%
+% The third layer defines the paragraph shape within the measure as
+% provided by the second layer. In the absence of any specification
+% for that layer the paragraph shape used will be that of a
+% rectangular area of the width of the current measure.
+%
+% There are some restrictions imposed on the shape of a paragraph by the
+% underlying \TeX{} mechanisms. For example, cut out sections in
+% paragraphs can be specified from the top of the paragraph but not from
+% the bottom.
+%
+% \subsection{Layer four: formatting inside the paragraph}
+%
+% The forth layer deals with the paragraph formatting aspects such as
+% hyphenation and justification within the paragraph (this is sometimes
+% referred to as \enquote{\texttt{h\&j}} or \enquote{\texttt{hj}}). This
+% layer is somewhat distinct from the galley as such, but is handled in
+% the same place as there is, internally, interaction between the different
+% layers.
+%
+% \section{Code interfaces}
+%
+% \subsection{Galley layers}
+%
+% \begin{variable}{\l_galley_width_dim}
+% The total width of a galley, set either by the page geometry code
+% for the main vertical galley or when creating an independent galley,
+% such as a minipage.
+% \end{variable}
+%
+% \begin{function}{\galley_level:}
+% \begin{syntax}
+% \cs{galley_level:}
+% \end{syntax}
+% Sets up a vertical box to contain a new galley level. The box should
+% include \enquote{wrapper} group (be \enquote{color safe}): this is
+% automatically true for all \LaTeX3 boxes and coffins.
+% \end{function}
+%
+% \subsection{Measure}
+%
+% \begin{function}
+% {\galley_margins_set_absolute:nn, \galley_margins_set_relative:nn}
+% \begin{syntax}
+% \cs{galley_margins_set_absolute:nn} \Arg{left margin} \Arg{right margin}
+% \cs{galley_margins_set_relative:nn} \Arg{left margin} \Arg{right margin}
+% \end{syntax}
+% Sets the width of the measure to have the \meta{left margin} and
+% \meta{right margin} specified by the arguments, both of which are
+% \meta{dimension expressions}. The \texttt{relative} function will adjust
+% the text width within any existing margins, whereas the \texttt{absolute}
+% measure sets the margins based on the edges of the galley only. One or
+% both of the \meta{margins} may be negative, to specify and outdent.
+% \end{function}
+%
+% \subsection{Between paragraphs}
+%
+% \begin{variable}{\g_galley_previous_par_lines_int}
+% The number of lines in the preceding conceptual paragraph. This may not
+% correspond to the \TeX{} \tn{prevgraf} primitive value as one conceptual
+% paragraph may contain several \TeX{} \tn{par} primitive tokens.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_restore_running_tl}
+% When galley settings need to be reset at the end of a paragraph, the
+% appropriate detail should be added to this token list. It is inserted
+% immediately before the start of each paragraph, and can therefore be
+% used to clear otherwise global settings. The token list itself is
+% also cleared as part of this process.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_no_break_next_bool}
+% Indicates that no page break should be allowed between the current
+% paragraph and the next paragraph.
+% \end{variable}
+%
+% \begin{function}{\g_galley_omit_next_indent_bool}
+% Indicates that the indent should be omitted from the start of the next
+% paragraph started.
+% \end{function}
+%
+% \begin{variable}{\l_galley_interpar_penalty_int}
+% The \meta{penalty} for a break between paragraphs. The \meta{penalty}
+% should be in the range $-10\,000$ to $10\,000$, where $-10\,000$ forces a
+% page break, $0$ has no effect at all and $10\,000$ forbids a page break.
+% Note that setting \cs{g_galley_no_break_next_bool} to \texttt{true}
+% will override any setting of \cs{l_galley_interpar_penalty_int}.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_interpar_vspace_skip}
+% Stretchable space to be inserted between paragraphs, set at the design
+% or template level.
+% \end{variable}
+%
+% \begin{function}{\galley_penalty_set_single:n}
+% \begin{syntax}
+% \cs{galley_penalty_set_single:n} \Arg{penalty}
+% \cs{galley_penalty_add_single:n} \Arg{penalty}
+% \end{syntax}
+% Sets the \meta{penalty} for a break between the current and next
+% paragraph on a one-off basis. This function is intended for user-level
+% adjustments to design, and takes precedent over both settings from
+% \cs{l_galley_interpar_penalty_int} and from \cs{galley_no_break_next:}.
+% The \texttt{add} variant adds the penalty to any existing values.
+% \end{function}
+%
+% \begin{function}
+% {
+% \galley_vspace_set_single:n ,
+% \galley_vspace_add_single:n ,
+% \galley_vspace_max_single:n
+% }
+% \begin{syntax}
+% \cs{galley_vspace_set_single:n} \Arg{space}
+% \end{syntax}
+% Sets the \meta{space} to be inserted between the current and next
+% paragraph on a one-off basis. This function is intended for user-level
+% adjustments to design. The \texttt{add} and \texttt{max} variants add
+% to the existing spacing and set to the maximum of the existing value
+% and the argument, respectively.
+% \end{function}
+%
+% \subsection{Paragraph shape}
+%
+% \begin{function}
+% {
+% \galley_parshape_set_multi:nnnN,
+% \galley_parshape_set_multi:nVVN,
+% \galley_parshape_set_single:nnnN,
+% \galley_parshape_set_single:nVVN
+% }
+% \begin{syntax}
+% \cs{galley_parshape_set_multi:nnnN} \Arg{unaltered lines} \Arg{left indents} \Arg{right indents} \meta{resume flag}
+% \cs{galley_parshape_set_single:nnnN} \Arg{unaltered lines} \Arg{left indents} \Arg{right indents} \meta{resume flag}
+% \end{syntax}
+% Sets the current paragraph shape to create an arbitrary paragraph shape.
+% The paragraph shape is set such that there are \meta{unaltered lines} which
+% have width and indent as set by the measure. The \enquote{altered} lines
+% are then defined by the comma-separated lists of \meta{left indents} and
+% \meta{right indents}. These are both indents from the edge of the measure,
+% and may be negative, and should both contain the same number of items.
+% If the \meta{resume flag} is \texttt{true}, after the last
+% altered line the paragraph shape returns to that of the measure. On the
+% other hand, if the flag is \texttt{false} then the shape of the last line
+% is retained for the rest of the paragraph.
+% For example,
+% \begin{verbatim}
+% \galley_parshape_set_multi:nnnN { 1 }
+% { 2 pt , 4 pt , 6 pt } { 2 pt , 4 pt , 6 pt } \c_true_bool
+% \end{verbatim}
+% would create a paragraph shape in which the first line is the full
+% width of the measure, the second line is indented by $2$\,pt on each side,
+% the third line by $4$\,pt and the fourth line and subsequent lines by
+% $6$\,pt from the edge of the measure on each side.
+%
+% The \texttt{single} version applies only to a single paragraph,
+% while the \texttt{multi} function sets the paragraph shape on an
+% ongoing basis within the scope of the current \TeX{} group.
+% \end{function}
+%
+% \begin{function}
+% {
+% \galley_cutout_left:nn,
+% \galley_cutout_right:nn
+% }
+% \begin{syntax}
+% \cs{galley_cutout_left:nn} \Arg{unaltered lines} \Arg{indents}
+% \cs{galley_cutout_right:nn} \Arg{unaltered lines} \Arg{indents}
+% \end{syntax}
+% Adds a cutout section to the active paragraph shape, leaving
+% \meta{unaltered lines} unchanged and then applying the \meta{indents}
+% (a comma list). The cutout will be placed on the left or right as indicated
+% by the function name, and will apply to exactly the number of lines
+% specified (the total of the \meta{unaltered lines} and the number of
+% entries in the \meta{indents} list). Several cutouts may be applied
+% sequentially: these act in an additive sense.
+% \end{function}
+%
+% \subsection{Formatting inside the paragraph}
+%
+% The settings described here apply \enquote{inside} the paragraph, and so
+% are active irrespective of any paragraph shape within the measure.
+%
+% \begin{variable}{\l_galley_line_left_skip, \l_galley_line_right_skip}
+% Stretchable space added to the appropriate side each line in a paragraph.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_par_begin_skip, \l_galley_par_end_skip}
+% Stretchable space added to the beginning of the first line and end of
+% the last line of a paragraph, respectively.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_par_indent_dim}
+% Fixed space added to the start of each paragraph except for those where
+% \cs{g_galley_omit_next_indent_bool} is \texttt{true}.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_last_line_fit_int}
+% Determines how the inter-word stretch is set for the last line of a
+% paragraph when
+% \begin{enumerate}
+% \item the value of \cs{l_galley_par_end_skip} contains an infinite
+% (\texttt{fil(l)}) component;
+% \item the values of \cs{l_galley_line_left_skip} and
+% \cs{l_galley_line_right_skip} do \emph{not} contain an infinite
+% (\texttt{fil(l)}) component.
+% \end{enumerate}
+% Under these circumstances, \cs{l_galley_last_line_fit_int} is active, and
+% applies as follows:
+% \begin{itemize}
+% \item when set to~$0$, the last line of the paragraph is set with
+% the inter-word spacing at natural width;
+% \item when set to a $1000$ (or above), the inter-word spacing in the last
+% line is stretched by the same factor as that applied to the penultimate
+% line;
+% \item when set to~$n$ between these extremes, the inter-word spacing in
+% the last line is stretched by $n/1000$ times the factor used for the
+% penultimate line.
+% \end{itemize}
+% \end{variable}
+%
+% \begin{function}{\galley_interword_spacing_set:N}
+% \begin{syntax}
+% \cs{galley_interword_spacing_set:N} \meta{fixed spacing bool}
+% \end{syntax}
+% Sets the inter-word spacing used based on the values supplied by the
+% current font. If the \meta{fixed spacing bool} flag is \texttt{true} then
+% no stretch is permitted between words, otherwise the stretch specified by
+% the font designer is used.
+% \end{function}
+%
+% \subsection{Display material}
+%
+% Material which is set in \enquote{display-style} require additional settings
+% to control the relationship with the surrounding material.
+%
+% \begin{function}{\galley_display_begin:, \galley_display_end:}
+% \begin{syntax}
+% \cs{galley_display_begin:}
+% \ldots
+% \cs{galley_display_end:}
+% \end{syntax}
+% Sets up a group to contain display-style material. Unlike an independent
+% galley level, settings are inherited from the surroundings. However,
+% the interaction of a display block with the paragraphs before and after it
+% can be adjusted independent of the design of text.
+% \end{function}
+%
+% \subsection{Hyphenation}
+%
+% \begin{variable}{\l_galley_hyphen_left_int}
+% THIS IS A HACK: SEE CODE!
+% \end{variable}
+%
+% \subsection{Line breaking}
+%
+% \begin{variable}{\l_galley_binop_penalty_int}
+% Penalty charged if an inline math formula is broken at a
+% binary operator.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_double_hyphen_demerits_int}
+% Extra demerit charge of two (or more) lines in succession end
+% in a hyphen.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_emergency_stretch_skip}
+% Additional stretch assumed for each line if no better line breaking
+% can be found without it. This stretch is not actually added to lines,
+% so its use may result in underfull box warnings.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_final_hyphen_demerits_int}
+% Extra demerit charge if the second last line is hyphenated.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_linebreak_badness_int}
+% Boundary that if exceeded will cause \TeX{} to report an
+% underfull line.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_linebreak_fuzz_dim}
+% Boundary below which overfull lines are not reported.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_linebreak_penalty_int}
+% Extra penalty charged per line in the paragraph. By making
+% this penalty higher \TeX{} will try harder to produce compact
+% paragraphs.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_linebreak_pretolerance_int}
+% Maximum tolerance allowed for individual lines to break the
+% paragraph without attempting hyphenation.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_linebreak_tolerance_int}
+% Maximum tolerance allowed for individual lines when breaking a
+% paragraph while attempting hyphenation (if this limit can't be
+% met \cs{l_galley_emergency_stretch_skip} comes into play).
+% \end{variable}
+%
+% \begin{variable}{\l_galley_mismatch_demerits_int}
+% Extra demerit charge if two visually incompatible lines follow
+% each other.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_relation_penalty_int}
+% Penalty charged if an inline math formula is broken at a
+% relational symbol.
+% \end{variable}
+%
+% \begin{function}{\galley_break_line:Nn}
+% \begin{syntax}
+% \cs{galley_break_line:Nn} \meta{boolean} \Arg{dimexpr}
+% \end{syntax}
+% Breaks the current line, filling the remaining space with \texttt{fil}
+% glue. If the \meta{boolean} is \texttt{true} then a page break is possible
+% after the broken line. Vertical space as given by the \meta{dimexpr} will
+% be inserted between the broken line and the next line.
+% \end{function}
+%
+% \subsection{Paragraph breaking}
+%
+% \begin{variable}{\l_galley_parbreak_badness_int}
+% Boundary that if exceeded will cause \TeX{} to report an
+% underfull vertical box.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_parbreak_fuzz_dim}
+% Boundary below which overfull vertical boxes are not reported.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_broken_penalty_int}
+% Penalty for page breaking after a hyphenated line.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_pre_display_penalty_int}
+% Penalty for breaking between immediately before display math material.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_post_display_penalty_int}
+% Penalty for breaking between immediately after display math material.
+% \end{variable}
+%
+% \begin{function}
+% {
+% \galley_club_penalties_set:n,
+% \galley_club_penalties_set:V,
+% \galley_club_penalties_set:v,
+% \galley_display_club_penalties_set:n,
+% \galley_display_club_penalties_set:V,
+% \galley_display_club_penalties_set:v,
+% \galley_display_widow_penalties_set:n,
+% \galley_display_widow_penalties_set:V,
+% \galley_display_widow_penalties_set:v,
+% \galley_widow_penalties_set:n,
+% \galley_widow_penalties_set:V,
+% \galley_widow_penalties_set:v
+% }
+% \begin{syntax}
+% \cs{galley_club_penalties_set:n} \Arg{penalty list}
+% \end{syntax}
+% Set the penalties for breaking lines at the beginning and end of
+% (partial) paragraphs. In each case, the \meta{penalty list} is a
+% comma-separated list of penalty values. The list applies as follows:
+% \begin{itemize}
+% \item[\texttt{club}] Penalties for breaking after the first, second,
+% third, \emph{etc.}~line of the paragraph.
+% \item[\texttt{display_club}] Penalties for breaking after the first,
+% second, third, \emph{etc.}~line after a display math environment.
+% \item[\texttt{display_club}] Penalties for breaking before the last,
+% penultimate, antepenultimate, \emph{etc.}~line before a display
+% math environment.
+% \item[\texttt{widow}] Penalties for breaking before the last,
+% penultimate, antepenultimate, \emph{etc.}~line of the paragraph.
+% \end{itemize}
+% In all cases, these penalties apply in addition to the general interline
+% penalty or to any \enquote{special} line penalties.
+% \end{function}
+%
+% \begin{function}{\galley_interline_penalty_set:n}
+% \begin{syntax}
+% \cs{galley_interline_penalty_set:n} \Arg{penalty}
+% \end{syntax}
+% Sets the standard interline penalty applied between lines of a paragraph.
+% This value is added to any (display) club or widow penalty in force.
+% \end{function}
+%
+% \begin{function}
+% {
+% \galley_interline_penalties_set:n,
+% \galley_interline_penalties_set:V
+% }
+% \begin{syntax}
+% \cs{galley_interline_penalties_set:n} \Arg{penalty list}
+% \end{syntax}
+% Sets \enquote{special} interline penalties to be used in place of
+% the standard value, specified as a comma-separated \meta{penalty list}.
+% The \meta{penalties} apply to the first, second, third, \emph{etc.}~line
+% of the paragraph.
+% \end{function}
+%
+% \begin{function}
+% {
+% \galley_save_club_penalties:N,
+% \galley_save_display_club_penalties:N,
+% \galley_save_display_widow_penalties:N,
+% \galley_save_interline_penalties:N,
+% \galley_save_widow_penalties:N
+% }
+% \begin{syntax}
+% \cs{galley_save_club_penalties:N} \Arg{comma list}
+% \end{syntax}
+% These functions save the current value of the appropriate penalty to the
+% comma list specified, within the current \TeX{} group.
+% \end{function}
+%
+% \begin{function}[EXP]{\galley_interline_penalty:}
+% \begin{syntax}
+% \cs{galley_interline_penalty:}
+% \end{syntax}
+% Expands to the current interline penalty as a \meta{integer denotation}.
+% \end{function}
+%
+% \section{Hooks and insertion points}
+%
+% \begin{variable}{\g_galley_par_begin_hook_tl}
+% Token list inserted at the beginning of every paragraph in horizontal mode.
+% This is inserted after any paragraph indent but before any other horizontal
+% mode material.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_par_end_hook_tl}
+% Token list inserted at the end of every paragraph in horizontal mode.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_par_reset_hook_tl}
+% Token list inserted after each paragraph. This is used for resetting
+% galley parameters, and is therefore cleared after use. It is inserted
+% in vertical mode and must not contain horizontal mode material.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_whatsit_next_tl}
+% Token list for whatsits to be inserted at the very beginning of the next
+% paragraph started.
+% \end{variable}
+%
+% \begin{variable}{\g_galley_whatsit_previous_tl}
+% Token list for whatsits to be inserted at the very end of the last
+% paragraph started.
+% \end{variable}
+%
+% \section{Paragraphs}
+%
+% \begin{function}{\galley_par:}
+% \begin{syntax}
+% \cs{galley_par:}
+% \end{syntax}
+% Finalises the material collected as the last paragraph, inserts tokens
+% at the start of the new paragraph, setting paragraph shape and any
+% special values as required.
+% \end{function}
+%
+% \begin{function}{\galley_par:n}
+% \begin{syntax}
+% \cs{galley_par:n} \Arg{tokens}
+% \end{syntax}
+% Adds the \meta{tokens} to the material collected for the last paragraph
+% before finalising it in the usual way. This function should
+% therefore be the \emph{first} non-expandable entry used when a function
+% needs to add tokens to the preceding paragraph.
+% \end{function}
+%
+% \section{Information variables}
+%
+% Some of the variables for the galley mechanism may be of
+% interest to the programmer. These should all be treated as read-only
+% values and accessed only through the defined interfaces described above.
+%
+% \begin{variable}{\l_galley_total_left_margin_dim}
+% The total margin between the left side of the galley and the left side of
+% the text block. This may be negative if the measure is set to overlap
+% the text beyond the edge of the galley.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_total_right_margin_dim}
+% The total margin between the right side of the galley and the right side
+% of the text block. This may be negative if the measure is set to overlap
+% the text beyond the edge of the galley.
+% \end{variable}
+%
+% \begin{variable}{\l_galley_text_width_dim}
+% The width of a line of text within the galley, taking account of
+% any margins added. This may be larger than \cs{l_galley_width_dim}
+% if the margins are negative.
+% \end{variable}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3galley} Implementation}
+%
+% At the implementation level, there are a number of challenges which
+% have to be overcome in order to make the galley easy to use at the
+% designer and user levels. Inserting material into the main vertical
+% list is in many ways an irreversible operation. Inserting items as
+% they appear in the source is therefore not desirable. Instead,
+% inserting vertical-mode material needs to be delayed until the start
+% of the \enquote{next} paragraph. This is particularly notable for
+% invisible items such as whatsits and specials, which will otherwise
+% cause changes in spacing. Delaying insertion enables user-supplied
+% settings to override design settings in a reliable fashion. This can
+% be achieved as the design-level material can be ignored if a user
+% value is supplied. There is a need to allow proper nesting of
+% galleys, which means that all of the above needs to be set up so that
+% it can be saved and restored. All of these manipulations require
+% altering the meaning of the \cs{par} token, which is particularly
+% awkward as \TeX{} inserts a token \emph{called} \cs{par} rather than
+% one with a particular meaning. This makes moving \cs{par} to somewhere
+% \enquote{safe} extremely challenging.
+%
+% Added to all of this complexity, there is a need to deal with
+% \enquote{display-like} material. The most obvious example is the way
+% lists are handled. These use \cs{par} tokens to achieve the correct
+% appearance, but at the same time
+% \begin{verbatim}
+% Text
+% \begin{itemize}
+% \item An item
+% \end{itemize}
+% More text
+% \end{verbatim}
+% should form one visual paragraph while
+% \begin{verbatim}
+% Text
+% \begin{itemize}
+% \item An item
+% \end{itemize}
+%
+% More text
+% \end{verbatim}
+% should be shown as two paragraphs. This requires an additional level
+% of handling so that the \cs{par} token used to end the list in the
+% first case does not start a new paragraph in a visual sense while the
+% second does.
+%
+% Another factor to bear in mind is that \cs{tex_everypar:D} may be
+% executed inside a group. For example, a paragraph starting
+% \begin{verbatim}
+% {Text} here
+% \end{verbatim}
+% will insert the tokens such that the current group level is $1$
+% higher for \enquote{Text} than for \enquote{here}. The result of this
+% is that it's very important to watch how flags are set and reset. This
+% can only be done reliably on a global level, which then has a knock-on
+% effect on the rest of the implementation.
+%
+% At a \TeX{} level, settings can only apply to the current paragraph,
+% but conceptually there is a need to allow for both single-paragraph
+% and \enquote{running} settings. Whenever the code switches galley
+% level both of these need to be correctly saved.
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=galley>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3galley}{2019-08-25}{}
+ {L3 Experimental galley code}
+%</package>
+% \end{macrocode}
+%
+% \subsection{Support items}
+%
+% Functions or settings which are needed by the galley but perhaps also
+% elsewhere.
+%
+% The default hyphenation character should be set and hyphenation should be
+% enabled.
+% \begin{macrocode}
+%<*initex>
+\tex_defaulthyphenchar:D 45 \scan_stop:
+%</initex>
+% \end{macrocode}
+%
+% \subsection{Scratch variables}
+%
+% \begin{variable}{\l_@@_tmp_int}
+% \begin{variable}{\g_@@_tmpa_seq, \g_@@_tmpb_seq, \l_@@_tmp_seq}
+% \begin{variable}{\l_@@_tmp_tl}
+% Used for manipulating cutouts and paragraph shapes.
+% \begin{macrocode}
+\int_new:N \l_@@_tmp_int
+\seq_new:N \g_@@_tmpa_seq
+\seq_new:N \g_@@_tmpb_seq
+\seq_new:N \l_@@_tmp_seq
+\tl_new:N \l_@@_tmp_tl
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{variable}
+%
+% \subsection{Galley settings}
+%
+% Settings for application by the galley respect the usual \TeX{} grouping and
+% so are all local variables.
+%
+% \begin{variable}{\g_@@_cutout_active_bool, \g_@@_cutout_active_bool}
+% \begin{macrocode}
+\bool_new:N \g_@@_cutout_active_bool
+\bool_new:N \l_@@_cutout_active_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\l_@@_cutout_left_indent_seq, \l_@@_cutout_right_indent_seq}
+% Essentially scratch space, used when setting up a cutout in a paragraph,
+% but named as there is quite a bit to do in that code.
+% \begin{macrocode}
+\seq_new:N \l_@@_cutout_left_indent_seq
+\seq_new:N \l_@@_cutout_right_indent_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_parshape_multipar_bool}
+% Indicates whether the paragraph shape in use applies to one paragraph
+% only or to more than one.
+% \begin{macrocode}
+\bool_new:N \l_@@_parshape_multipar_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\l_@@_parshape_left_indent_seq, \l_@@_parshape_right_indent_seq}
+% Used to convert user input comma lists into sequences for mapping, and also
+% to keep the information on paragraph shape available for the case where a
+% cutout is also active.
+% \begin{macrocode}
+\seq_new:N \l_@@_parshape_left_indent_seq
+\seq_new:N \l_@@_parshape_right_indent_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_galley_text_width_dim}
+% The width of the current measure: the \enquote{running} setting can be
+% inherited from \LaTeXe{}.
+% \begin{macrocode}
+%<*initex>
+\dim_new:N \l_galley_text_width_dim
+%</initex>
+%<*package>
+\cs_new_eq:NN \l_galley_text_width_dim \linewidth
+%</package>
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\l_galley_total_left_margin_dim, \l_galley_total_right_margin_dim}
+% Margins of the current text within the galley: these plus the galley
+% width are one way to define the measure width. See also the text width,
+% which is an alternative view (and should be in sync with this one!).
+% \begin{macrocode}
+%<*initex>
+\dim_new:N \l_galley_total_left_margin_dim
+%</initex>
+%<*package>
+\cs_new_eq:NN \l_galley_total_left_margin_dim \@totalleftmargin
+%</package>
+\dim_new:N \l_galley_total_right_margin_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\l_galley_interpar_penalty_int, \l_galley_interpar_vspace_skip}
+% Items between paragraphs at the design level.
+% \begin{macrocode}
+\int_new:N \l_galley_interpar_penalty_int
+\skip_new:N \l_galley_interpar_vspace_skip
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_galley_width_dim}
+% The external size of a galley is the stored in the \TeX{} primitive
+% \cs{tex_hsize:D}, which is renamed. This will only ever be reset by
+% the code constructing a new galley, for example the start of a
+% minipage. This value will be set for the main galley by the page
+% layout system.
+% \begin{macrocode}
+\cs_new_eq:NN \l_galley_width_dim \tex_hsize:D
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Galley data structures}
+%
+% In contrast to settings, the data structures used by the galley are all set
+% globally. To allow different galley levels to exist, a local variant is
+% defined for each one to save the value when starting a new level.
+%
+% \begin{variable}{\g_@@_begin_level_bool, \l_@@_begin_level_bool}
+% Indicates that the galley is at the very beginning of the level, and that
+% no material has yet been set. As a result, the global version is set
+% \texttt{true} to begin with.
+% \begin{macrocode}
+\bool_new:N \g_@@_begin_level_bool
+\bool_new:N \l_@@_begin_level_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \g_@@_cutout_left_seq, \l_@@_cutout_left_seq,
+% \g_@@_cutout_right_seq, \l_@@_cutout_right_seq
+% }
+% To apply cutouts across paragraphs, they need to be tracked. The
+% information has to be global, so is handled here.
+% \begin{macrocode}
+\seq_new:N \g_@@_cutout_left_seq
+\seq_new:N \l_@@_cutout_left_seq
+\seq_new:N \g_@@_cutout_right_seq
+\seq_new:N \l_@@_cutout_right_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\g_galley_omit_next_indent_bool, \l_@@_omit_next_indent_bool}
+% A global flag is needed for suppressing indentation of the next
+% paragraph. This does not need a \enquote{running} version since that
+% should be handled using the \texttt{justification} object: the two
+% concepts are related but not identical. The flag here is needed in
+% cases such as the very first paragraph in a galley or immediately
+% following a heading.
+% \begin{macrocode}
+\bool_new:N \g_galley_omit_next_indent_bool
+\bool_new:N \l_@@_omit_next_indent_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_galley_no_break_next_bool, \l_@@_no_break_next_bool}
+% Dealing with the no-break flag is pretty much the same as the case
+% for the indent: this applies on a single paragraph basis.
+% \begin{macrocode}
+\bool_new:N \g_galley_no_break_next_bool
+\bool_new:N \l_@@_no_break_next_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \g_galley_par_begin_hook_tl, \l_@@_par_begin_hook_tl,
+% \g_galley_par_end_hook_tl, \l_@@_par_end_hook_tl,
+% }
+% Hooks for user-level code: these are not used by the galley itself.
+% \begin{macrocode}
+\tl_new:N \g_galley_par_begin_hook_tl
+\tl_new:N \l_@@_par_begin_hook_tl
+\tl_new:N \g_galley_par_end_hook_tl
+\tl_new:N \l_@@_par_end_hook_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\g_galley_par_reset_hook_tl, \l_@@_par_reset_hook_tl}
+% This one is used by the galley: it happens \enquote{after} the current
+% paragraph, and is used for reset purposes.
+% \begin{macrocode}
+\tl_new:N \g_galley_par_reset_hook_tl
+\tl_new:N \l_@@_par_reset_hook_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \g_galley_previous_par_lines_int, \l_@@_previous_par_lines_int,
+% \g_@@_current_par_lines_int, \l_@@_current_par_lines_int
+% }
+% The number of lines in the previous typeset paragraph. This is reset at
+% the start of the paragraph and \emph{added to} when each \cs{tex_par:D}
+% primitive is used: \LaTeX{} uses the primitive in places that do not end
+% a (conceptual) paragraph. There is also a set of variables to deal with
+% the current (conceptual) paragraph, so that the information can be
+% build up correctly.
+% \begin{macrocode}
+\int_new:N \g_galley_previous_par_lines_int
+\int_new:N \l_@@_previous_par_lines_int
+\int_new:N \g_@@_current_par_lines_int
+\int_new:N \l_@@_current_par_lines_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_galley_restore_running_tl, \l_@@_restore_running_tl}
+% When a parameter is altered from the \enquote{running} value to a
+% different \enquote{current} one, there needs to be a method to restore
+% the \enquote{running} value. This is done by adding the necessary
+% assignment to a token list, which can be executed when needed. At the
+% same time, this information is itself part of the galley parameter
+% structure, and so there has to be a local save version.
+% \begin{macrocode}
+\tl_new:N \g_galley_restore_running_tl
+\tl_new:N \l_@@_restore_running_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {
+% \g_galley_whatsit_next_tl, \l_@@_whatsit_next_tl,
+% \g_galley_whatsit_previous_tl, \l_@@_whatsit_previous_tl
+% }
+% Whatsits only apply on a per-paragraph basis and so there is no need
+% to differentiate between current and running values. However, there
+% is a need to differentiate between whatsits that attach to the
+% previous (completed) paragraph and those that attach to the next
+% paragraph.
+% \begin{macrocode}
+\tl_new:N \g_galley_whatsit_next_tl
+\tl_new:N \l_@@_whatsit_next_tl
+\tl_new:N \g_galley_whatsit_previous_tl
+\tl_new:N \l_@@_whatsit_previous_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\g_@@_interpar_penalty_user_tl, \l_@@_interpar_penalty_user_tl}
+% The user may want to over-ride the penalty for a break between
+% paragraphs, for example to prevent a break when the overall design
+% allows one. This is handled using an additional penalty.
+% \begin{macrocode}
+\tl_new:N \g_@@_interpar_penalty_user_tl
+\tl_new:N \l_@@_interpar_penalty_user_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}
+% {\g_@@_interpar_vspace_user_tl, \l_@@_interpar_vspace_user_tl}
+% Arbitrary vertical space can be inserted by the user on a one-off
+% basis. This is used in place of any running space between paragraphs.
+% \begin{macrocode}
+\tl_new:N \g_@@_interpar_vspace_user_tl
+\tl_new:N \l_@@_interpar_vspace_user_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\c_@@_max_penalty_int}
+% \enquote{Infinite} penalty.
+% \begin{macrocode}
+\int_const:Nn \c_@@_max_penalty_int { 10000 }
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Independent galley levels}
+%
+% As well as the main vertical list, independent galleys are required
+% for items such as minipages and marginal notes. Each of these galleys
+% requires an independent set of global data structures. This is
+% achieved by storing the data structures in \emph{local} variables. The
+% later are only used to save and restore the global value, and so \TeX{}
+% grouping will manage the values correctly. This implies that each
+% galley level must form a group: galley levels are tied to vertical
+% boxes and so this is a reasonable requirements.
+%
+% \begin{variable}{\l_@@_group_level_int}
+% The top group level for the galley needs to be known to allow
+% some data structures to be reset correctly: they can only be
+% fully cleared at the outer level.
+% \begin{macrocode}
+\int_new:N \l_@@_group_level_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_initialise_variables:}
+% At the start of a galley level the global variables need to be reset to
+% standard values. For example, the measure is set to the galley width and
+% any paragraph shape is cleared.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_initialise_variables:
+ {
+ \int_set:Nn \l_@@_group_level_int { \tex_currentgrouplevel:D }
+ \bool_gset_true:N \g_@@_begin_level_bool
+ \bool_gset_false:N \g_@@_cutout_active_bool
+ \seq_gclear:N \g_@@_cutout_left_seq
+ \seq_gclear:N \g_@@_cutout_right_seq
+ \tl_gclear:N \g_@@_interpar_penalty_user_tl
+ \tl_gclear:N \g_@@_interpar_vspace_user_tl
+ \bool_gset_true:N \g_galley_omit_next_indent_bool
+ \bool_gset_false:N \g_galley_no_break_next_bool
+ \tl_gclear:N \g_galley_par_begin_hook_tl
+ \tl_gclear:N \g_galley_par_end_hook_tl
+ \tl_gclear:N \g_galley_par_reset_hook_tl
+ \int_gzero:N \g_@@_current_par_lines_int
+ \int_gzero:N \g_galley_previous_par_lines_int
+ \tl_gclear:N \g_galley_restore_running_tl
+ \tl_gclear:N \g_galley_whatsit_previous_tl
+ \tl_gclear:N \g_galley_whatsit_next_tl
+ }
+\@@_initialise_variables:
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_initialise_settings:}
+% This sets the local values of the various galley settings. The
+% flag \cs{l_@@_parshape_multipar_bool} is set \texttt{true} here
+% as it allows an efficiency saving in parshape setting: only
+% when it is actively set \texttt{false} is action needed.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_initialise_settings:
+ {
+ \bool_set_true:N \l_@@_parshape_multipar_bool
+ \seq_clear:N \l_@@_parshape_left_indent_seq
+ \seq_clear:N \l_@@_parshape_right_indent_seq
+ \dim_set_eq:NN \l_galley_text_width_dim \l_galley_width_dim
+ \dim_zero:N \l_galley_total_left_margin_dim
+ \dim_zero:N \l_galley_total_right_margin_dim
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_save_parameters:, \@@_restore_parameters:}
+% Saving and restoring parameters is carried out by a series of copy
+% functions.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_save_parameters:
+ {
+ \bool_set_eq:NN \l_@@_begin_level_bool
+ \g_@@_begin_level_bool
+ \bool_set_eq:NN \l_@@_cutout_active_bool
+ \g_@@_cutout_active_bool
+ \seq_set_eq:NN \l_@@_cutout_left_seq
+ \g_@@_cutout_left_seq
+ \seq_set_eq:NN \l_@@_cutout_right_seq
+ \g_@@_cutout_right_seq
+ \tl_set_eq:NN \l_@@_interpar_penalty_user_tl
+ \g_@@_interpar_penalty_user_tl
+ \tl_set_eq:NN \l_@@_interpar_vspace_user_tl
+ \g_@@_interpar_vspace_user_tl
+ \bool_set_eq:NN \l_@@_omit_next_indent_bool
+ \g_galley_omit_next_indent_bool
+ \bool_set_eq:NN \l_@@_no_break_next_bool
+ \g_galley_no_break_next_bool
+ \tl_set_eq:NN \l_@@_par_begin_hook_tl
+ \g_galley_par_begin_hook_tl
+ \tl_set_eq:NN \l_@@_par_end_hook_tl
+ \g_galley_par_end_hook_tl
+ \tl_set_eq:NN \l_@@_par_reset_hook_tl
+ \g_galley_par_reset_hook_tl
+ \int_set_eq:NN \l_@@_current_par_lines_int
+ \g_@@_current_par_lines_int
+ \int_set_eq:NN \l_@@_previous_par_lines_int
+ \g_galley_previous_par_lines_int
+ \tl_set_eq:NN \l_@@_restore_running_tl
+ \g_galley_restore_running_tl
+ \tl_set_eq:NN \l_@@_whatsit_previous_tl
+ \g_galley_whatsit_previous_tl
+ \tl_set_eq:NN \l_@@_whatsit_next_tl
+ \g_galley_whatsit_next_tl
+ }
+\cs_new_protected:Npn \@@_restore_parameters:
+ {
+ \bool_gset_eq:NN \g_@@_begin_level_bool
+ \l_@@_begin_level_bool
+ \bool_gset_eq:NN \g_@@_cutout_active_bool
+ \l_@@_cutout_active_bool
+ \seq_gset_eq:NN \g_@@_cutout_left_seq
+ \l_@@_cutout_left_seq
+ \seq_gset_eq:NN \g_@@_cutout_right_seq
+ \l_@@_cutout_right_seq
+ \tl_gset_eq:NN \g_@@_interpar_penalty_user_tl
+ \l_@@_interpar_penalty_user_tl
+ \tl_gset_eq:NN \g_@@_interpar_vspace_user_tl
+ \l_@@_interpar_vspace_user_tl
+ \bool_gset_eq:NN \g_galley_omit_next_indent_bool
+ \l_@@_omit_next_indent_bool
+ \bool_gset_eq:NN \g_galley_no_break_next_bool
+ \l_@@_no_break_next_bool
+ \tl_gset_eq:NN \g_galley_par_begin_hook_tl
+ \l_@@_par_begin_hook_tl
+ \tl_gset_eq:NN \g_galley_par_end_hook_tl
+ \l_@@_par_end_hook_tl
+ \tl_gset_eq:NN \g_galley_par_reset_hook_tl
+ \l_@@_par_reset_hook_tl
+ \int_gset_eq:NN \g_@@_current_par_lines_int
+ \l_@@_current_par_lines_int
+ \int_gset_eq:NN \g_galley_previous_par_lines_int
+ \l_@@_previous_par_lines_int
+ \tl_gset_eq:NN \g_galley_restore_running_tl
+ \l_@@_restore_running_tl
+ \tl_gset_eq:NN \g_galley_whatsit_previous_tl
+ \l_@@_whatsit_previous_tl
+ \tl_gset_eq:NN \g_galley_whatsit_next_tl
+ \l_@@_whatsit_next_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\galley_level:}
+% \begin{macro}{\@@_level_end:}
+% Galley levels are created by saving all of the current global
+% settings, starting a group then initialising both the local and global
+% variables.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_level:
+ {
+ \@@_save_parameters:
+ \group_begin:
+ \@@_initialise_variables:
+ \@@_initialise_settings:
+ \group_insert_after:N \@@_level_end:
+ }
+% \end{macrocode}
+% At the end of the level, the global values are restored using the
+% saved \emph{local} versions, hence the position of the close-of-group
+% instruction. As this code can be inserted automatically, at the point
+% of use only the start of a galley level needs to be marked up: the end
+% must come in a fixed location. All of this relies on the
+% \enquote{color safe} group used inside a box.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_level_end:
+ {
+ \par
+ \@@_restore_parameters:
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{The \cs{par} token}
+%
+% \begin{variable}{\s_@@_par_omit}
+% Used to indicate that a paragraph should be omitted.
+% \begin{macrocode}
+\scan_new:N \s_@@_par_omit
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\galley_par:}
+% \begin{macro}{\@@_par_auxi:, \@@_par_auxii:, \@@_par_auxiii:}
+% \begin{macro}{\@@_par_aux:N}
+% \begin{macro}{\@@_par_update_cutout:}
+% The idea here is to expand the next token in exactly the same way as \TeX{}
+% would do anyway. The \texttt{f}-type expansion will ignore any protection,
+% but will stop at a scan marker. Thus the code can test for an
+% \enquote{omit paragraph} marker.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_par:
+ {
+ \s_@@_par_omit
+ \exp_after:wN \@@_par_auxi: \exp:w \exp_end_continue_f:w
+ }
+\cs_new_protected:Npn \@@_par_auxi:
+ {
+ \peek_meaning:NTF \s_@@_par_omit
+ { \@@_par_aux:N }
+ { \@@_par_auxii: }
+ }
+\cs_new_protected:Npn \@@_par_aux:N #1
+ {
+ \str_if_eq:nnF {#1} { \s_@@_par_omit }
+ {
+ \@@_par_auxii:
+ #1
+ }
+ }
+% \end{macrocode}
+% No marker, so really insert a paragraph: the \cs{tex_par:D} is inside a
+% group to preserve some dynamic settings (for example
+% \cs{tex_interlinepenalties:D}). In vertical mode, that means just
+% inserting the primitive.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_par_auxii:
+ {
+ \mode_if_vertical:TF
+ {
+ \group_begin:
+ \tex_par:D
+ \group_end:
+ }
+% \end{macrocode}
+% In horizontal mode, after the end-of-paragraph hook we check if there
+% is a cutout active. If there is, the paragraph shape has to be set.
+% Inserting \cs{tex_par:D} will then carry out the typesetting
+% operation. Once the paragraph has been typeset, the number of lines is
+% added to the running total. It's possible that the conceptual paragraph
+% contains display-like material, and simply setting the number of lines
+% equal to \cs{tex_prevgraf:D} would lose these.
+% \begin{macrocode}
+ {
+ \g_galley_par_end_hook_tl
+ \bool_if:NT \g_@@_cutout_active_bool
+ { \@@_parshape_set: }
+ \group_begin:
+ \tex_par:D
+ \group_end:
+ \int_gset:Nn \g_galley_previous_par_lines_int
+ { \tex_prevgraf:D + \g_@@_current_par_lines_int }
+ \int_gzero:N \g_@@_current_par_lines_int
+ \bool_if:NF \l_@@_parshape_multipar_bool
+ {
+ \seq_clear:N \l_@@_parshape_left_indent_seq
+ \seq_clear:N \l_@@_parshape_right_indent_seq
+ \bool_set_true:N \l_@@_parshape_multipar_bool
+ \@@_parshape_set:
+ }
+ \@@_par_update_cutout:
+ }
+ \g_galley_par_reset_hook_tl
+ \tl_gclear:N \g_galley_par_reset_hook_tl
+% \end{macrocode}
+% The non-breaking penalty is needed here as within the \cs{tex_everypar:D}
+% hook there is an additional \cs{tex_par:D}. This leads to an extra
+% \cs{tex_parskip:D}, which will leave an unwanted break-point here
+% otherwise.
+% \begin{macrocode}
+ \tex_penalty:D \c_@@_max_penalty_int
+ }
+% \end{macrocode}
+% Once a cutout has been set, properly tidying up needs some work. The
+% cutout is applied \enquote{just in time} at the start of each paragraph
+% (see above). After the paragraph, the consumed lines are removed from
+% the tracking sequences and the paragraph shape is reset. The latter step
+% is required as we do not set the paragraph shape for every paragraph.
+% Once the two sequences are empty, there is no cutout to apply. However,
+% it can still be \enquote{active}. If a cutout is started then we have a
+% group, it is possible for the cutout to be entirely applied before the
+% end of the group. At that point, the two sequences show there is nothing
+% to do \emph{but} the active paragraph shape still has some lines indented.
+% As we avoid setting the \tn{parshape} primitive for every paragraph,
+% the result of only checking the sequences would be that the cutout would
+% reappear after the group! Instead, we use a boolean and only set it
+% false once the group level is that of the galley itself. (There is a slight
+% inefficiency here in that we thus do set the paragraph shape for each
+% paragraph after a cutout until we reach the top group level of the galley.
+% That should be an acceptable hit given the extra work needed to track
+% reset-in-this-group as well.)
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_par_update_cutout:
+ {
+ \bool_lazy_and:nnF
+ { \seq_if_empty_p:N \g_@@_cutout_left_seq }
+ { \seq_if_empty_p:N \g_@@_cutout_right_seq }
+ {
+ \prg_replicate:nn \tex_prevgraf:D
+ {
+ \seq_gpop_left:NN \g_@@_cutout_left_seq \l_@@_tmp_tl
+ \seq_gpop_left:NN \g_@@_cutout_right_seq \l_@@_tmp_tl
+ }
+ \@@_parshape_set:
+ }
+ \int_compare:nNnT \l_@@_group_level_int = \tex_currentgrouplevel:D
+ {
+ \bool_lazy_and:nnT
+ { \seq_if_empty_p:N \g_@@_cutout_left_seq }
+ { \seq_if_empty_p:N \g_@@_cutout_right_seq }
+ { \bool_gset_false:N \g_@@_cutout_active_bool }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\galley_par:n}
+% Inserts tokens such that they are appended to the end of the last
+% paragraph, using the paragraph-omitting system.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_par:n #1
+ {
+ \s_@@_par_omit
+ \bool_if:NF \g_@@_begin_level_bool
+ {
+ #1
+ \galley_par:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\par}
+% The meaning of the token \cs{par} itself starts off as a standard
+% paragraph.
+% \begin{macrocode}
+\cs_set_protected:Npn \par { \galley_par: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@par}
+% \LaTeXe{} requires a \enquote{long term} version of \cs{par}, which is
+% stored as \cs{@par}. Things are done a bit differently by \LaTeX3 and
+% so this will only be needed in package mode.
+% \begin{macrocode}
+%<*package>
+\tl_set:Nn \@par { \galley_par: }
+%</package>
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Display levels}
+%
+% \begin{macro}{\galley_display_begin:, \galley_display_end:}
+% \begin{macro}{\@@_display_penalty:N, \@@_display_vspace:N}
+% \begin{macro}{\@@_display_par_setup:, \@@_display_par:}
+% Display items within the galley are a bit like galley levels: they
+% may have different paragraph settings to the main part of the galley.
+% On the other hand, unlike independent galleys they should inherit the
+% settings from the surrounding material. They may also start and end with
+% special spacing values.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_display_begin:
+ {
+ \group_begin:
+ \@@_save_parameters:
+ \mode_if_vertical:TF
+ {
+ \@@_display_penalty:N \l_galley_display_begin_par_penalty_tl
+ \@@_display_vspace:N \l_galley_display_begin_par_vspace_tl
+ }
+ {
+ \@@_display_penalty:N \l_galley_display_begin_penalty_tl
+ \@@_display_vspace:N \l_galley_display_begin_vspace_tl
+ }
+ \par
+ }
+% \end{macrocode}
+% Two short-cuts for setting up any special penalty or vertical space.
+% The idea is that the standard value is saved to the \enquote{restore}
+% token list, before setting up the value to the special value needed
+% in this one case.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_display_penalty:N #1
+ {
+ \tl_if_empty:NF #1
+ {
+ \tl_gput_right:Nx \g_galley_restore_running_tl
+ {
+ \int_gset:Nn \exp_not:N \g_galley_penalty_int
+ { \int_use:N \g_galley_penalty_int }
+ }
+ \int_gset:Nn \g_galley_penalty_int {#1}
+ }
+ }
+\cs_new_protected:Npn \@@_display_vspace:N #1
+ {
+ \tl_if_empty:NF #1
+ {
+ \tl_gput_right:Nx \g_galley_restore_running_tl
+ {
+ \skip_gset:Nn \exp_not:N \g_galley_vspace_skip
+ { \skip_use:N \g_galley_vspace_skip }
+ }
+ \skip_gset:Nn \g_galley_vspace_int {#1}
+ }
+ }
+% \end{macrocode}
+% The \cs{par} token at the end of the display needs to go in at the same
+% group level as the text, hence this function cannot be placed using
+% \cs{group_insert_after:N}. Resetting the meaning of the \cs{par} token
+% needs to be carried out after the group used for the environment.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_display_end:
+ {
+ \par
+ \@@_restore_parameters:
+ \group_end:
+ \group_insert_after:N \@@_display_par_setup:
+ }
+% \end{macrocode}
+% The method used here is to assume that the next piece of horizontal
+% mode material will follow on from the displayed output without an
+% intervening \cs{par} token (probably a blank line). The meaning of the
+% \cs{par} token is then altered so that a check can be made to see if
+% this assumption was correct.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_display_par_setup:
+ {
+ \bool_gset_false:N \g_galley_omit_next_indent_bool
+ \cs_set_eq:NN \par \@@_display_par:
+ }
+% \end{macrocode}
+% The \enquote{special} meaning of the paragraph token starts by putting
+% things back to normal: there should never need to be more than one
+% special paragraph marker in one group. If \TeX{} is in vertical mode,
+% then there has been a paragraph token inserted, most likely by a
+% blank line. Thus the next piece of material is a separate conceptual
+% paragraph from the display. In that case, the assumption from above is
+% undone and the indent is turned back on. On the other hand, for the
+% case where \TeX{} is in horizontal mode then a \cs{tex_par:D} primitive
+% is required in the same way as in \cs{galley_standard_par:}. Most of
+% the settings for a conceptual paragraph will carry over there but any
+% cutout will have to be updated as it depends on the number of lines
+% \TeX{} has typeset.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_display_par:
+ {
+ \cs_set_eq:NN \par \galley_par:
+ \mode_if_vertical:TF
+ {
+ \par
+ \bool_gset_false:N \g_galley_omit_next_indent_bool
+ \@@_display_penalty:N \l_galley_display_end_par_penalty_tl
+ \@@_display_vspace:N \l_galley_display_end_par_vspace_tl
+ }
+ {
+ \@@_par_set_cutout:
+ \group_begin:
+ \tex_par:D
+ \group_end:
+ \int_gadd:Nn \g_@@_current_par_lines_int \tex_prevgraf:D
+ \bool_set_false:N \l_@@_tmp_bool
+ \@@_par_update_cutout:
+ \@@_display_penalty:N \l_galley_display_end_penalty_tl
+ \@@_display_vspace:N \l_galley_display_end_vspace_tl
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Insertions using \cs{tex_everypar:D}}
+%
+% The key to the entire galley mechanism is hooking into the
+% \cs{tex_everypar:D} token register. This requires that the original
+% is moved out of the way, with appropriate hooks left attached for
+% further modification by other modules and by the user. This is all
+% done such that there is no danger of accidentally deactivating the
+% galley mechanism.
+%
+% \begin{macro}{\everypar}
+% When used on top of \LaTeXe{} the original primitive name needs to be
+% available without the risk of completely overwriting the new
+% mechanism. This is implemented as a token register in case low-level
+% \TeX{} is used. The \TeX{} primitive is set here as otherwise the
+% \LaTeXe{} \cs{@nodocument} is never removed from the register.
+% This precaution is not be needed for a stand-alone format.
+% \begin{macrocode}
+%<*initex>
+\tex_everypar:D % TEMP
+ {
+ \bool_if:NTF \g_@@_begin_level_bool
+ { \@@_start_paragraph_first: }
+ { \@@_start_paragraph_std: }
+ }
+%</initex>
+%<*package>
+\cs_undefine:N \everypar
+\newtoks \everypar
+\AtBeginDocument
+ {
+ \tex_everypar:D
+ {
+ \bool_if:NTF \g_@@_begin_level_bool
+ { \@@_start_paragraph_first: }
+ { \@@_start_paragraph_std: }
+ \tex_the:D \everypar
+ }
+ }
+%</package>
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{The galley mechanism}
+%
+% \begin{variable}{\g_@@_last_box}
+% A temporary box to hold the box inserted by \TeX{} when a paragraph
+% is inserted with an indent. The galley actually inserts the space
+% (\emph{i.e.}~\cs{tex_parindent:D} is globally zero), but there is
+% still an empty box to test for.
+% \begin{macrocode}
+\box_new:N \g_@@_last_box
+% \end{macrocode}
+% \end{variable}
+%
+% The \enquote{start of paragraph} routines are fired by \cs{tex_everypar:D}.
+% This can take place within a group in a construction such as
+% \begin{verbatim}
+% ... end of last par.
+%
+% {\Large Start} of par
+% \end{verbatim}
+% and so anything applied here must be done globally.
+%
+% \begin{macro}{\@@_start_paragraph_common:N}
+% Share some code between all types of paragraph start.
+% The routine at the start of a paragraph starts by removing any
+% (empty) indent box from the vertical list. As there may be vertical
+% mode items still to insert (|#1|), a \cs{tex_par:D} primitive is used to
+% get back into vertical mode before they are tidied up. To get back
+% again to horizontal mode, \cs{tex_noindent:D} can be used. To avoid
+% an infinite loop, \cs{tex_everypar:D} is locally cleared before doing
+% that.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_start_paragraph_common:N #1
+ {
+ \group_begin:
+ \box_gset_to_last:N \g_@@_last_box
+ \tex_par:D
+ #1
+ \tex_everypar:D { }
+ \tex_noindent:D
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_start_paragraph_std:}
+% After dealign with the common items, the horizontal mode items can be
+% tidied up before sorting out any items which have been set on a
+% single-paragraph basis.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_start_paragraph_std:
+ {
+ \@@_start_paragraph_common:N \@@_insert_vertical_items:
+ \@@_insert_horizontal_items:
+ \@@_restore_running_parameters:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_start_paragraph_first:}
+% \begin{macro}{\@@_insert_vspace_first:}
+% For the very first paragraph in a galley, the code needs to avoid
+% adding any unnecessary vertical items at the top as it will interfere with
+% vertical positioning in \cs{tex_vtop:D}. That requirement also means we
+% must not add any vertical space as the start of the box unless there
+% is a user space to insert.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_start_paragraph_first:
+ {
+ \bool_gset_false:N \g_@@_begin_level_bool
+ \mode_if_horizontal:TF
+ { \@@_start_paragraph_common:N \@@_insert_vspace_first: }
+ { \@@_insert_vspace_first: }
+ \@@_insert_horizontal_items:
+ \@@_restore_running_parameters:
+ }
+\cs_new_protected:Npn \@@_insert_vspace_first:
+ {
+ \tl_if_empty:NF \g_@@_interpar_vspace_user_tl
+ { \skip_vertical:n { \g_@@_interpar_vspace_user_tl } }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_insert_vertical_items}
+% The aim here is to insert the vertical items such that they attach to
+% the correct place. This function is used as part of the \cs{tex_everypar:D}
+% mechanism, meaning that the immediately-preceding item on the vertical
+% list is the \cs{tex_parskip:D}, always zero-length but an implicit
+% penalty. So any whatsits \enquote{attached} to the previous paragraph
+% should stay glued on. After the whatsits, a penalty for
+% breaking will be inserted. This will be the user penalty if supplied,
+% or the running penalty unless the no-break flag is set. Finally,
+% the inter-paragraph space is applied: only one space is ever added
+% there.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_insert_vertical_items:
+ {
+ \g_galley_whatsit_previous_tl
+ \tl_gclear:N \g_galley_whatsit_previous_tl
+ \tl_if_empty:NTF \g_@@_interpar_penalty_user_tl
+ {
+ \bool_if:NTF \g_galley_no_break_next_bool
+ { \tex_penalty:D \c_@@_max_penalty_int }
+ { \tex_penalty:D \l_galley_interpar_penalty_int }
+ }
+ {
+ \tex_penalty:D
+ \int_eval:n { \g_@@_interpar_penalty_user_tl } \exp_stop_f:
+ \tl_gclear:N \g_@@_interpar_penalty_user_tl
+ }
+ \bool_gset_false:N \g_galley_no_break_next_bool
+ \tl_if_empty:NTF \g_@@_interpar_vspace_user_tl
+ { \skip_vertical:N \l_galley_interpar_vspace_skip }
+ {
+ \skip_vertical:n { \g_@@_interpar_vspace_user_tl }
+ \tl_gclear:N \g_@@_interpar_vspace_user_tl
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_insert_horizontal_items:}
+% Horizontal mode objects start with the whatsits for the next paragraph. An
+% indent is then included if the removed box was not void.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_insert_horizontal_items:
+ {
+ \g_galley_whatsit_next_tl
+ \tl_gclear:N \g_galley_whatsit_next_tl
+ \bool_if:NF \g_galley_omit_next_indent_bool
+ {
+ \box_if_empty:NF \g_@@_last_box
+ { \hbox_to_wd:nn \l_galley_par_indent_dim { } }
+ }
+ \skip_horizontal:N \l_galley_par_begin_skip
+ \g_galley_par_begin_hook_tl
+ \bool_gset_false:N \g_galley_omit_next_indent_bool
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_restore_running_parameters:}
+% Restoring the ongoing parameters just means using the token list
+% variable in which the appropriate assignments are stored. The
+% list can then be cleared.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_restore_running_parameters:
+ {
+ \g_galley_restore_running_tl
+ \tl_gclear:N \g_galley_restore_running_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Measure}
+%
+% \begin{variable}{\l_@@_total_left_margin_dim, \l_@@_total_right_margin_dim}
+% Used to set the measure, first by providing a place to save the existing
+% values, then allowing calculation of the difference between old and new
+% settings.
+% \begin{macrocode}
+\dim_new:N \l_@@_total_left_margin_dim
+\dim_new:N \l_@@_total_right_margin_dim
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}
+% {\galley_margins_set_absolute:nn, \galley_margins_set_relative:nn}
+% Setting the measure is just a question of adjusting margins, either
+% in a relative or absolute sense. One the settings are sorted, the
+% paragraph shape can be reset.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_margins_set_absolute:nn #1#2
+ {
+ \dim_set:Nn \l_galley_total_left_margin_dim {#1}
+ \dim_set:Nn \l_galley_total_right_margin_dim {#2}
+ \dim_set:Nn \l_galley_text_width_dim
+ {
+ \l_galley_width_dim
+ - \l_galley_total_left_margin_dim
+ - \l_galley_total_right_margin_dim
+ }
+ \@@_parshape_set:
+ }
+\cs_new_protected:Npn \galley_margins_set_relative:nn #1#2
+ {
+ \dim_add:Nn \l_galley_total_left_margin_dim {#1}
+ \dim_add:Nn \l_galley_total_right_margin_dim {#2}
+ \dim_set:Nn \l_galley_text_width_dim
+ {
+ \l_galley_width_dim
+ - \l_galley_total_left_margin_dim
+ - \l_galley_total_right_margin_dim
+ }
+ \@@_parshape_set:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Paragraph shape}
+%
+% \begin{macro}
+% {
+% \galley_parshape_set_multi:nnnN,
+% \galley_parshape_set_multi:nVVN,
+% \galley_parshape_set_single:nnnN,
+% \galley_parshape_set_single:nVVN
+% }
+% Setting the paragraph shape is mainly a question of converting the input.
+% First, though, a flag is set.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_parshape_set_multi:nnnN
+ {
+ \bool_set_true:N \l_@@_parshape_multipar_bool
+ \@@_parshape_set:nnnN
+ }
+\cs_new_protected:Npn \galley_parshape_set_single:nnnN
+ {
+ \bool_set_false:N \l_@@_parshape_multipar_bool
+ \@@_parshape_set:nnnN
+ }
+\cs_generate_variant:Nn \galley_parshape_set_multi:nnnN { nVV }
+\cs_generate_variant:Nn \galley_parshape_set_single:nnnN { nVV }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_parshape_set:nnnN}
+% Setting the paragraph shape starts by converting the two input lists into
+% sequences. The shape is then set using a mapping with allowance for the
+% unaltered lines and the possibility of resuming the measure. The unaltered
+% lines are added to the sequences as this information may be needed
+% elsewhere (if a cutout is active), and this is the most convenient way to
+% deal with it. Everything ends up stored in the two sequences for possible
+% re-application if there are cutouts.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set:nnnN #1#2#3#4
+ {
+ \seq_set_from_clist:Nn \l_@@_parshape_left_indent_seq {#2}
+ \seq_set_from_clist:Nn \l_@@_parshape_right_indent_seq {#3}
+ \prg_replicate:nn {#1}
+ {
+ \seq_put_left:Nn \l_@@_parshape_left_indent_seq { 0pt }
+ \seq_put_left:Nn \l_@@_parshape_right_indent_seq { 0pt }
+ }
+ \bool_if:NT #4
+ {
+ \seq_put_right:Nn \l_@@_parshape_left_indent_seq { 0pt }
+ \seq_put_right:Nn \l_@@_parshape_right_indent_seq { 0pt }
+ }
+ \@@_parshape_set:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Cutouts}
+%
+% Cutouts are another way of looking at paragraph shapes, but apply to
+% a fixed number of lines within a galley. As such, they require separate
+% handling from the measure and shape.
+%
+% \begin{macro}{\galley_cutout_left:nn, \galley_cutout_right:nn}
+% \begin{macro}{\@@_cutout:nnn}
+% \begin{macro}{\@@_cutout:NN, \@@_cutout:cN, \@@_cutout:Nc}
+% Setting up cutouts on the two sides of the paragraph is more or less
+% the same idea with one or two very specific points of difference. As such,
+% the two interface functions both use the same implementation.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_cutout_left:nn
+ { \@@_cutout:nnn { left } }
+\cs_new_protected:Npn \galley_cutout_right:nn
+ { \@@_cutout:nnn { right } }
+% \end{macrocode}
+% The cutout information needs to be saved in in the appropriate sequence.
+% First, the input is converted to a (temporary) sequence. If there is
+% no existing cutout defined, this is simply stored. On the other hand,
+% if there is a cutout already active, the new one needs to be added to
+% it. To achieve that, we iterate over whichever sequence is longer
+% (so that every relevant line is covered). At the end of this process,
+% the paragraph shape needs to be activated. Notice here that we use
+% a global temporary sequence: this allows the auxiliary \cs{@@_cutout:NN}
+% to apply \cs{seq_gpop_left:NNF } with whichever sequence is shorter.
+% The parshape is not set at this stage as cutouts need handling
+% on a per-paragraph level.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_cutout:nnn #1#2#3
+ {
+ \seq_gset_from_clist:Nn \g_@@_tmpa_seq {#3}
+ \prg_replicate:nn {#2}
+ { \seq_gput_left:Nn \g_@@_tmpa_seq { 0pt } }
+ \seq_if_empty:cTF { g_@@_cutout_ #1 _seq }
+ { \seq_gset_eq:cN { g_@@_cutout_ #1 _seq } \g_@@_tmpa_seq }
+ {
+ \int_compare:nNnTF
+ { \seq_count:c { g_@@_cutout_ #1 _seq } } >
+ { \seq_count:N \g_@@_tmpa_seq }
+ { \@@_cutout:cN { g_@@_cutout_ #1 _seq } \g_@@_tmpa_seq }
+ { \@@_cutout:Nc \g_@@_tmpa_seq { g_@@_cutout_ #1 _seq } }
+ \seq_gset_eq:cN { g_@@_cutout_ #1 _seq } \l_@@_tmp_seq
+ }
+ \seq_if_empty:cF { g_@@_cutout_ #1 _seq }
+ { \bool_gset_true:N \g_@@_cutout_active_bool }
+ }
+\cs_new_protected:Npn \@@_cutout:NN #1#2
+ {
+ \seq_clear:N \l_@@_tmp_seq
+ \seq_map_inline:Nn #1
+ {
+ \seq_gpop_left:NNF #2 \l_@@_tmp_tl
+ { \tl_set:Nn \l_@@_tmp_tl { 0pt } }
+ \seq_put_right:Nx \l_@@_tmp_seq
+ { \dim_eval:n { ##1 + \l_@@_tmp_tl } }
+ }
+ }
+\cs_generate_variant:Nn \@@_cutout:NN { c , Nc }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Setting the primitive paragraph shape}
+%
+% \begin{macro}{\@@_parshape_set:}
+% \begin{macro}
+% {
+% \@@_parshape_set_margins:,
+% \@@_parshape_set_indents:,
+% \@@_parshape_set_cutouts:
+% }
+% \begin{macro}{\@@_parshape_set_indents:NN}
+% \begin{macro}[rEXP]{\@@_parshape_set_indents:nn}
+% \begin{macro}{\@@_parshape_set_cutouts:n}
+% \begin{macro}{\@@_parshape_set_cutouts:N}
+% The real paragraph shape (\cs{tex_parshape:D}) is set whenever any
+% of the conceptual elements (margins, measure, cutouts) are set and
+% potentially at the end of a paragraph in the cases where a single-paragraph
+% measure or a cutout was active. Rather than try to track different
+% combinations of the elements, as was done in earlier versions of this
+% code, the approach taken here is always to reset the entire shape. This
+% is done in three stages, one for each conceptual element.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set:
+ {
+ \@@_parshape_set_margins:
+ \@@_parshape_set_indents:
+ \@@_parshape_set_cutouts:
+ }
+% \end{macrocode}
+% Setting the margins is simple. Either they are zero, using the full measure,
+% or they are not. This is always set even if there are other elements to
+% apply as these settings are very fast, so it is not worth doing a check
+% at this stage for further parts to apply.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set_margins:
+ {
+ \bool_lazy_and:nnTF
+ { \dim_compare_p:nNn \l_galley_total_left_margin_dim = \c_zero_dim }
+ { \dim_compare_p:nNn \l_galley_total_right_margin_dim = \c_zero_dim }
+ { \tex_parshape:D 0 \exp_stop_f: }
+ {
+ \tex_parshape:D
+ 1 \exp_stop_f:
+ \l_galley_total_left_margin_dim
+ \l_galley_text_width_dim
+ }
+ }
+% \end{macrocode}
+% If there is a measure (indent) to apply this simply overwrites the
+% settings for the margins. As there are two sequences to use it is
+% convenient to use a thread mapping, which means that the shorter sequence
+% will always apply. The margins are reapplied at this stage as part of
+% the auxiliary for the calculation of indentation.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set_indents:
+ {
+ \bool_lazy_and:nnF
+ { \seq_if_empty_p:N \l_@@_parshape_left_indent_seq }
+ { \seq_if_empty_p:N \l_@@_parshape_right_indent_seq }
+ {
+ \@@_parshape_set_indents:NN
+ \l_@@_parshape_left_indent_seq
+ \l_@@_parshape_right_indent_seq
+ }
+ }
+\cs_new_protected:Npn \@@_parshape_set_indents:NN #1#2
+ {
+ \tex_parshape:D
+ \int_min:nn { \seq_count:N #1 } { \seq_count:N #2 }
+ \exp_stop_f:
+ \seq_mapthread_function:NNN #1 #2
+ \@@_parshape_set_indents:nn
+ }
+\cs_new:Npn \@@_parshape_set_indents:nn #1#2
+ {
+ \dim_eval:n { \l_galley_total_left_margin_dim + #1 } \exp_stop_f:
+ \dim_eval:n { \l_galley_text_width_dim - ( #1 + #2 ) } \exp_stop_f:
+ }
+% \end{macrocode}
+% Calculating cutouts is by far the most complex operation here. The
+% first step is to establish if this needs to be done at all. If so,
+% the cutout list and the matching measure (indent) are stored in two
+% temporary sequences so that they can safely be \enquote{chomped} by
+% \cs{@@_cutout:NN}. We then construct the overall list of indents required
+% and store them in dedicated temporary storage. That is necessary as to get
+% the indents correct on both sides is to use a thread mapping. That also
+% means that the two sequences need to be of the same length. That is
+% done by finding the length difference then applying an auxiliary to
+% the shorter sequence.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set_cutouts:
+ {
+ \bool_lazy_and:nnF
+ { \seq_if_empty_p:N \g_@@_cutout_left_seq }
+ { \seq_if_empty_p:N \g_@@_cutout_right_seq }
+ {
+ \@@_parshape_set_cutouts:n { left }
+ \@@_parshape_set_cutouts:n { right }
+ \int_set:Nn \l_@@_tmp_int
+ {
+ \seq_count:N \l_@@_cutout_left_indent_seq
+ - \seq_count:N \l_@@_cutout_right_indent_seq
+ }
+ \int_compare:nNnTF \l_@@_tmp_int > 0
+ { \@@_parshape_set_cutouts:N \l_@@_cutout_right_indent_seq }
+ { \@@_parshape_set_cutouts:N \l_@@_cutout_left_indent_seq }
+ \@@_parshape_set_indents:NN
+ \l_@@_cutout_left_indent_seq
+ \l_@@_cutout_right_indent_seq
+ }
+ }
+% \end{macrocode}
+% To work out the offsets for a cutout, we first check that there is
+% anything to do at all. If there is, we need to ensure that there
+% are more entries in the measure list than in the cutout list, so that
+% any lines omitted by the cutout are correct and so that the normal measure
+% will resume after the cutout. That is done in a global temporary sequence,
+% which allows code sharing with the earlier path.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set_cutouts:n #1
+ {
+ \seq_if_empty:cTF { g_@@_cutout_ #1 _seq }
+ {
+ \seq_set_eq:cc { l_@@_cutout_ #1 _indent_seq }
+ { l_@@_parshape_ #1 _indent_seq }
+ }
+ {
+ \seq_gset_eq:Nc \g_@@_tmpa_seq { g_@@_cutout_ #1 _seq }
+ \seq_gset_eq:Nc \g_@@_tmpb_seq { l_@@_parshape_ #1 _indent_seq }
+ \seq_get_right:NNF \g_@@_tmpb_seq \l_@@_tmp_tl
+ { \tl_clear:N \l_@@_tmp_tl }
+ \tl_if_empty:NT \l_@@_tmp_tl
+ { \tl_set:Nn \l_@@_tmp_tl { 0pt } }
+ \int_set:Nn \l_@@_tmp_int
+ {
+ \seq_count:N \g_@@_tmpa_seq
+ - \seq_count:N \g_@@_tmpb_seq
+ + 1
+ }
+ \int_compare:nNnT \l_@@_tmp_int > 0
+ {
+ \prg_replicate:nn
+ { \l_@@_tmp_int }
+ { \seq_gput_right:NV \g_@@_tmpb_seq \l_@@_tmp_tl }
+ }
+ \@@_cutout:NN \g_@@_tmpb_seq \g_@@_tmpa_seq
+ \seq_set_eq:cN { l_@@_cutout_ #1 _indent_seq } \l_@@_tmp_seq
+ }
+ }
+% \end{macrocode}
+% Grab the last entry of the shorter sequence and repeat it to match
+% the length of the longer.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_parshape_set_cutouts:N #1
+ {
+ \seq_get_right:NNF #1 \l_@@_tmp_tl
+ { \tl_clear:N \l_@@_tmp_tl }
+ \tl_if_empty:NT \l_@@_tmp_tl
+ { \tl_set:Nn \l_@@_tmp_tl { 0pt } }
+ \prg_replicate:nn { \int_abs:n \l_@@_tmp_int }
+ { \seq_put_right:NV #1 \l_@@_tmp_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Between paragraphs}
+%
+% \begin{macro}
+% {
+% \galley_penalty_set_single:n,
+% \galley_penalty_add_single:n,
+% \galley_vspace_set_single:n,
+% \galley_vspace_add_single:n,
+% \galley_vspace_max_single:n
+% }
+% User supplied penalties and spaces only apply for a single paragraph.
+% In both cases, the input values need to be checked for the correct
+% form but are stored as token lists. The \texttt{x}-type expansion
+% deals with this nicely.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_penalty_set_single:n #1
+ { \tl_gset:Nx \g_@@_interpar_penalty_user_tl { \int_eval:n {#1} } }
+\cs_new_protected:Npn \galley_penalty_add_single:n #1
+ {
+ \tl_gset:Nx \g_@@_interpar_penalty_user_tl
+ {
+ \int_eval:n
+ {
+ \tl_if_empty:NF \g_@@_interpar_penalty_user_tl
+ { \g_@@_interpar_penalty_user_tl + }
+ #1
+ }
+ }
+ }
+\cs_new_protected:Npn \galley_vspace_set_single:n #1
+ { \tl_gset:Nx \g_@@_interpar_vspace_user_tl { \skip_eval:n {#1} } }
+\cs_new_protected:Npn \galley_vspace_add_single:n #1
+ {
+ \tl_gset:Nx \g_@@_interpar_vspace_user_tl
+ {
+ \skip_eval:n
+ {
+ \tl_if_empty:NF \g_@@_interpar_vspace_user_tl
+ { \g_@@_interpar_vspace_user_tl + }
+ #1
+ }
+ }
+ }
+\cs_new_protected:Npn \galley_vspace_max_single:n #1
+ {
+ \tl_if_empty:NTF \g_@@_interpar_vspace_user_tl
+ { \galley_vspace_set_single:n {#1} }
+ {
+ \dim_compare:nNnT
+ { \tex_glueexpr:D \g_@@_interpar_vspace_user_tl }
+ < { \tex_glueexpr:D (#1) \scan_stop: }
+ { \galley_vspace_set_single:n {#1} }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \begin{macro}{\parskip}
+% For the package, the \cs{parskip} primitive is moved out of the
+% way as the code above is handling things.
+% \begin{macrocode}
+%<*package>
+\dim_set:Nn \parskip \c_zero_dim
+\cs_undefine:N \parskip
+\skip_new:N \parskip
+%</package>
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Formatting inside the paragraph}
+%
+% Justification is more complex than is necessarily desirable as the various
+% \TeX{} parameters here interact in ways which mean that clear separation
+% between different areas is not so easy.
+%
+% \begin{variable}
+% {
+% \l_galley_line_left_skip,
+% \l_galley_line_right_skip,
+% \l_galley_par_begin_skip,
+% \l_galley_par_end_skip,
+% \l_galley_par_indent_dim
+% }
+% The variables for setting paragraph shape: essentially, these are
+% the \TeX{} set.
+% \begin{macrocode}
+\cs_new_eq:NN \l_galley_line_left_skip \tex_leftskip:D
+\cs_new_eq:NN \l_galley_line_right_skip \tex_rightskip:D
+\dim_new:N \l_galley_par_begin_skip
+\cs_new_eq:NN \l_galley_par_end_skip \tex_parfillskip:D
+\cs_new_eq:NN \l_galley_par_indent_dim \tex_parindent:D
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_galley_last_line_fit_int}
+% One from \eTeX{}.
+% \begin{macrocode}
+\cs_new_eq:NN \l_galley_last_line_fit_int \tex_lastlinefit:D
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Inter-word spacing}
+%
+% Setting the spacing between words and between sentences is important
+% for achieving the correct output from ragged and centered output. At
+% the same time, as far as possible the aim is to retain the spacing
+% specified by the font designer and not to use arbitrary values
+% (\emph{cf.}~the approach in \emph{The \TeX{}book}, p.~101).
+%
+% \begin{macro}{\galley_interword_spacing_set:N}
+% The approach taken to setting a fixed space is to use the information
+% from the current font to set the spacing. This means that only
+% \cs{tex_spacefactor:D} needs to be set, while \cs{tex_xspacefactor:D}
+% is left alone. However, this is only necessary for fonts which have
+% a stretch component to the inter-word spacing in the first place,
+% \emph{i.e.}~monospaced fonts require no changes. The code therefore
+% checks whether there is any stretch, and if there is uses the fixed
+% component to set \cs{tex_spaceskip:D}. If there is a stretch component
+% (non-zero \cs{tex_fontdimen:D} \texttt{3}), then the \cs{teX_spaceskip:D}
+% is set to the fixed component from the font.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_interword_spacing_set:N #1
+ {
+ \bool_if:NTF #1
+ { % TODO Hook for font changes required!
+ \dim_compare:nNnTF { \tex_fontdimen:D 3 \tex_font:D }
+ = \c_zero_dim
+ { \tex_spaceskip:D \c_zero_dim }
+ { \tex_spaceskip:D \tex_fontdimen:D 2 \tex_font:D }
+ }
+ { \tex_spaceskip:D \c_zero_dim }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Hyphenation}
+%
+% \begin{variable}{\l_galley_hyphen_left_int}
+% Currently something of a hack: this links in with language and fonts,
+% so is not so straight-forward to handle.
+% \begin{macrocode}
+\int_new:N \l_galley_hyphen_left_int
+%<*package>
+\int_set:Nn \l_galley_hyphen_left_int { \tex_lefthyphenmin:D }
+%</package>
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Line breaking}
+%
+% \begin{variable}
+% {
+% \l_galley_binop_penalty_int,
+% \l_galley_double_hyphen_demerits_int,
+% \l_galley_emergency_stretch_skip,
+% \l_galley_final_hyphen_demerits_int,
+% \l_galley_linebreak_badness_int,
+% \l_galley_linebreak_fuzz_dim,
+% \l_galley_linebreak_penalty_int,
+% \l_galley_linebreak_pretolerance_int,
+% \l_galley_linebreak_tolerance_int,
+% \l_galley_mismatch_demerits_int,
+% \l_galley_relation_penalty_int
+% }
+% All \TeX{} primitives renamed.
+% \begin{macrocode}
+\cs_new_eq:NN \l_galley_binop_penalty_int \tex_binoppenalty:D
+\cs_new_eq:NN \l_galley_double_hyphen_demerits_int \tex_doublehyphendemerits:D
+\cs_new_eq:NN \l_galley_emergency_stretch_skip \tex_emergencystretch:D
+\cs_new_eq:NN \l_galley_final_hyphen_demerits_int \tex_finalhyphendemerits:D
+\cs_new_eq:NN \l_galley_linebreak_badness_int \tex_hbadness:D
+\cs_new_eq:NN \l_galley_linebreak_fuzz_dim \tex_hfuzz:D
+\cs_new_eq:NN \l_galley_linebreak_penalty_int \tex_linepenalty:D
+\cs_new_eq:NN \l_galley_linebreak_pretolerance_int \tex_pretolerance:D
+\cs_new_eq:NN \l_galley_mismatch_demerits_int \tex_adjdemerits:D
+\cs_new_eq:NN \l_galley_relation_penalty_int \tex_relpenalty:D
+\cs_new_eq:NN \l_galley_linebreak_tolerance_int \tex_tolerance:D
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\galley_break_line:Nn}
+% Terminating a line early without a new paragraph requires a few steps.
+% First, any skips are removed, then any additional space to add is
+% places on the surrounding vertical list. Finally, the current line
+% is ended, using a penalty to prevents an overfull line ending |\\| giving
+% a totally-blank one in the output. The boolean argument is used to indicate
+% that a break is allowed after the blank line.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_break_line:Nn #1#2
+ {
+ \mode_if_vertical:TF
+ { \__kernel_msg_error:nn { galley } { no-line-to-end } }
+ {
+ \tex_unskip:D
+ \bool_if:NF #1
+ { \tex_vadjust:D { \tex_penalty:D \c_@@_max_penalty_int } }
+ \dim_compare:nNnF {#2} = \c_zero_dim
+ { \tex_vadjust:D { \skip_vertical:n {#2} } }
+ \tex_penalty:D \c_@@_max_penalty_int
+ \tex_hfil:D
+ \tex_penalty:D - \c_@@_max_penalty_int
+ }
+ }
+% \end{macrocode}
+%\end{macro}
+%
+% \subsection{Paragraph breaking}
+%
+% \begin{variable}
+% {
+% \l_galley_broken_penalty_int,
+% \l_galley_interline_penalty_int,
+% \l_galley_parbreak_badness_int,
+% \l_galley_parbreak_fuzz_dim,
+% \l_galley_post_display_penalty_int,
+% \l_galley_pre_display_penalty_int
+%
+% }
+% \TeX{} primitives renamed cover \emph{some} of this.
+% \begin{macrocode}
+\cs_new_eq:NN \l_galley_broken_penalty_int \tex_brokenpenalty:D
+\cs_new_eq:NN \l_galley_interline_penalty_int \tex_interlinepenalty:D
+\cs_new_eq:NN \l_galley_parbreak_badness_int \tex_vbadness:D
+\cs_new_eq:NN \l_galley_parbreak_fuzz_dim \tex_vfuzz:D
+\cs_new_eq:NN \l_galley_post_display_penalty_int \tex_postdisplaypenalty:D
+\cs_new_eq:NN \l_galley_pre_display_penalty_int \tex_predisplaypenalty:D
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\l_galley_club_penalties_clist, \l_galley_line_penalties_clist}
+% These are used to keep a track of information which cannot be
+% extracted out of the primitives due to the overlapping nature of
+% the meanings.
+% \begin{macrocode}
+\clist_new:N \l_galley_club_penalties_clist
+\clist_new:N \l_galley_line_penalties_clist
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \galley_display_widow_penalties_set:n,
+% \galley_display_widow_penalties_set:V,
+% \galley_display_widow_penalties_set:v,
+% \galley_widow_penalties_set:n,
+% \galley_widow_penalties_set:V,
+% \galley_widow_penalties_set:v
+% }
+% \begin{macro}{\@@_set_aux:n}
+% By far the easiest penalties to deal with are those for widows. These
+% work exactly as the names imply, with the display version only used
+% immediately before display math, and the standard penalty used at the end
+% of a paragraph. Thus there is only the need to convert the argument into
+% the correct form, and add a $0$ penalty at the end to nullify the effect of
+% repeating the last value.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_display_widow_penalties_set:n #1
+ {
+ \tex_displaywidowpenalties:D
+ \int_eval:n { \clist_count:n {#1} + 1 } \exp_stop_f:
+ \clist_map_function:nN {#1} \@@_set_aux:n
+ 0 \exp_stop_f:
+ }
+\cs_generate_variant:Nn \galley_display_widow_penalties_set:n { V , v }
+\cs_new_protected:Npn \galley_widow_penalties_set:n #1
+ {
+ \tex_widowpenalties:D
+ \int_eval:n { \clist_count:n {#1} + 1 } \exp_stop_f:
+ \clist_map_function:nN {#1} \@@_set_aux:n
+ 0 \exp_stop_f:
+ }
+\cs_generate_variant:Nn \galley_widow_penalties_set:n { V , v }
+\cs_new:Npn \@@_set_aux:n #1 { \int_eval:n {#1} ~ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \galley_club_penalties_set:n,
+% \galley_club_penalties_set:V,
+% \galley_club_penalties_set:v,
+% \galley_interline_penalties_set:n,
+% \galley_interline_penalties_set:V,
+% \galley_interline_penalties_set:v
+% }
+% Setting club or special line penalties is easy, as these are handled
+% mainly by the interline set up function. The two concepts are essentially
+% the same, but having two takes makes some special effects easier to
+% carry out.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_club_penalties_set:n #1
+ {
+ \clist_set:Nn \l_galley_club_penalties_clist {#1}
+ \@@_calc_interline_penalties:
+ }
+\cs_generate_variant:Nn \galley_club_penalties_set:n { V , v }
+\cs_new_protected:Npn \galley_interline_penalties_set:n #1
+ {
+ \clist_set:Nn \l_galley_line_penalties_clist {#1}
+ \@@_calc_interline_penalties:
+ }
+\cs_generate_variant:Nn \galley_interline_penalties_set:n { V , v }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \galley_display_club_penalties_set:n,
+% \galley_display_club_penalties_set:V,
+% \galley_display_club_penalties_set:v
+% }
+% Setting the display club penalties means first setting the primitive,
+% then recalculating the interline array to allow for these new values.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_display_club_penalties_set:n #1
+ {
+ \tex_clubpenalties:D
+ \int_eval:n { \clist_count:n {#1} + 1 } \exp_stop_f:
+ \clist_map_function:nN {#1} \@@_set_aux:n
+ 0 \exp_stop_f:
+ \@@_calc_interline_penalties:
+ }
+\cs_generate_variant:Nn \galley_display_club_penalties_set:n { V , v }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\galley_interline_penalty_set:n}
+% \begin{macro}{\@@_set_interline_penalty:nn}
+% \begin{macro}
+% {
+% \@@_set_interline_penalty_auxi:n,
+% \@@_set_interline_penalty_auxii:n
+% }
+% Dealing with the general interline penalty is handled in one shot.
+% The idea is that for lines with no special penalty, the old general
+% penalty is removed and the new one is added. If there is currently
+% no shape set, then after adding the general interline value the
+% generic build system is invoked (in case the
+% \cs{tex_interlinepenalties:D} has accidentally been cleared).
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_interline_penalty_set:n #1
+ {
+ \int_compare:nNnTF { \tex_interlinepenalties:D 0 } = 0
+ {
+ \tex_interlinepenalties:D 1 = \int_eval:n {#1} \exp_stop_f:
+ \@@_calc_interline_penalties:
+ }
+ {
+ \cs_set:Npn \@@_set_interline_penalty_auxii:n ##1
+ {
+ \int_eval:n
+ {
+ \tex_interlinepenalties:D ##1
+ - \tex_interlinepenalties:D \tex_interlinepenalties:D 0
+ + #1
+ }
+ \exp_stop_f:
+ }
+ \exp_args:Nf \@@_set_interline_penalty:nn
+ { \clist_count:N \l_galley_line_penalties_clist } {#1}
+ }
+ }
+\cs_new_protected:Npn \@@_set_interline_penalty:nn #1#2
+ {
+ \tex_interlinepenalties:D
+ \tex_interlinepenalties:D 0 \exp_stop_f:
+ \int_step_function:nN {#1}
+ \@@_set_interline_penalty_auxi:n
+ \int_step_function:nnN { #1 + 1 } { \tex_interlinepenalties:D 0 - 1 }
+ \@@_set_interline_penalty_auxii:n
+ \int_eval:n {#2} \exp_stop_f:
+ }
+\cs_new:Npn \@@_set_interline_penalty_auxi:n #1
+ { \tex_interlinepenalties:D \int_eval:n {#1} \exp_stop_f: }
+\cs_new:Npn \@@_set_interline_penalty_auxii:n #1 { }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_calc_interline_penalties:}
+% \begin{macro}{\@@_calc_interline_penalties:nn}
+% \begin{macro}
+% {
+% \@@_calc_interline_penalties_auxi:n,
+% \@@_calc_interline_penalties_auxii:n
+% }
+% The underlying interline penalty array has to deal with club penalties,
+% display club penalties and any special line penalties, and include
+% the general interline penalty. These requirements lead to a rather
+% complex requirement on how many lines to deal with. This is needed twice,
+% so an \texttt{f}-type expansion is used to make life a little less
+% complex.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_calc_interline_penalties:
+ {
+ \exp_args:Nff \@@_calc_interline_penalties:nn
+ {
+ \int_max:nn
+ { \clist_count:N \l_galley_club_penalties_clist + 1 }
+ {
+ \int_max:nn
+ { \clist_count:N \l_galley_line_penalties_clist + 1 }
+ { \tex_clubpenalties:D 0 }
+ }
+ }
+ { \clist_count:N \l_galley_line_penalties_clist }
+ }
+% \end{macrocode}
+% The idea is now to calculate the correct penalties. Two auxiliary functions
+% are used: one for any \enquote{special penalty} lines and a second for
+% normal lines. At the end of the process, the standard interline
+% penalty is always included.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_calc_interline_penalties:nn #1#2
+ {
+ \tex_interlinepenalties:D #1 ~
+ \int_step_function:nN {#2}
+ \@@_calc_interline_penalties_auxi:n
+ \int_step_function:nnN { #2 + 1 } { #1 - 1 }
+ \@@_calc_interline_penalties_auxii:n
+ \tex_interlinepenalties:D \tex_interlinepenalties:D 0 \exp_stop_f:
+ }
+\cs_new:Npn \@@_calc_interline_penalties_auxi:n #1
+ {
+ \int_eval:n
+ {
+ \clist_item:Nn \l_galley_line_penalties_clist {#1}
+ + 0 \clist_item:Nn \l_galley_club_penalties_clist {#1}
+ - \tex_clubpenalties:D #1 ~
+ }
+ \exp_stop_f:
+ }
+\cs_new:Npn \@@_calc_interline_penalties_auxii:n #1
+ {
+ \int_eval:n
+ {
+ \tex_interlinepenalties:D \tex_interlinepenalties:D 0 \exp_stop_f:
+ + 0 \clist_item:Nn \l_galley_club_penalties_clist {#1}
+ - \tex_clubpenalties:D #1 ~
+ }
+ \exp_stop_f:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}
+% {
+% \galley_save_club_penalties:N,
+% \galley_save_interline_penalties:N,
+% \galley_save_display_club_penalties:N,
+% \galley_save_display_widow_penalties:N,
+% \galley_save_widow_penalties:N
+% }
+% \begin{macro}
+% {
+% \@@_save_display_club_penalties:n,
+% \@@_save_display_widow_penalties:n,
+% \@@_save_widow_penalties:n
+% }
+% \begin{macro}{\galley_interline_penalty:}
+% Saving the array penalties varies in complexity depending on how they are
+% stored internally. The first two are easy: these are simply copies.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_save_club_penalties:N #1
+ { \clist_set_eq:NN #1 \l_galley_club_penalties_clist }
+\cs_new_protected:Npn \galley_save_interline_penalties:N #1
+ { \clist_set_eq:NN #1 \l_galley_line_penalties_clist }
+% \end{macrocode}
+% These all require appropriate mappings, using the fact that
+% \cs{clist_set:Nx} will tidy up the excess comma.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_save_display_club_penalties:N #1
+ {
+ \clist_set:Nx #1
+ {
+ \int_step_function:nN { \tex_clubpenalties:D 0 - 1 }
+ \@@_save_display_club_penalties:n
+ }
+ }
+\cs_new:Npn \@@_save_display_club_penalties:n #1
+ { \int_value:w \tex_clubpenalties:D \int_eval:n {#1} , }
+\cs_new_protected:Npn \galley_save_display_widow_penalties:N #1
+ {
+ \clist_set:Nx #1
+ {
+ \int_step_function:nN { \tex_displaywidowpenalties:D 0 - 1 }
+ \@@_save_display_widow_penalties:n
+ }
+ }
+\cs_new:Npn \@@_save_display_widow_penalties:n #1
+ {
+ \int_value:w \tex_displaywidowpenalties:D
+ \int_eval:n {#1} ,
+ }
+\cs_new_protected:Npn \galley_save_widow_penalties:N #1
+ {
+ \clist_set:Nx #1
+ {
+ \int_step_function:nN { \tex_widowpenalties:D 0 - 1 }
+ \@@_save_widow_penalties:n
+ }
+ }
+\cs_new:Npn \@@_save_widow_penalties:n #1
+ { \int_value:w \tex_widowpenalties:D \int_eval:n {#1} , }
+% \end{macrocode}
+% This one is not an array, but is stored in a primitive, so there is
+% a simple conversion. The general interline penalty is always the
+% last value in the primitive array.
+% \begin{macrocode}
+\cs_new_protected:Npn \galley_interline_penalty:
+ { \int_value:w \tex_interlinepenalties:D \tex_interlinepenalties:D 0 \exp_stop_f: }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+% \begin{macrocode}
+\__kernel_msg_new:nnn { galley } { no-line-to-end }
+ { There's~no~line~here~to~end. }
+% \end{macrocode}
+%
+% \subsection{\LaTeXe{} functions}
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% \begin{macro}{\clearpage}
+% The \tn{clearpage} macro needs to place material into the correct
+% structures rather than directly onto the main vertical list. Other
+% than that it is the same as the \LaTeXe{} version.
+% \begin{macrocode}
+\cs_set:Npn \clearpage
+ {
+ \mode_if_vertical:T
+ {
+ \int_compare:nNnT \@dbltopnum = { -1 }
+ {
+ \dim_compare:nNnT \tex_pagetotal:D < \topskip
+ { \tex_hbox:D { } }
+ }
+ }
+ \newpage
+ \galley_penalty_set_single:n { -\@Mi }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\nobreak}
+% \begin{macro}{\noindent}
+% \begin{macro}{\vspace}
+% In package mode, some of \LaTeXe{}'s functions are re-implemented using
+% the galley system. Not all of the optional arguments currently work!
+% \begin{macrocode}
+\cs_set_protected:Npn \nobreak
+ { \bool_gset_true:N \g_galley_no_break_next_bool }
+% \end{macrocode}
+% The \tn{noindent} primitive will causes problems, as it is used by
+% \LaTeXe{} documents to implicitly leave vertical mode as well as to
+% prevent indentation. Rather than patch \emph{every} place where we
+% need leave vertical mode, at the moment we stick with the primitive as
+% well as setting the galley flag.
+% \begin{macrocode}
+\cs_set_protected:Npn \noindent
+ {
+ \tex_noindent:D
+ \bool_gset_false:N \g_galley_omit_next_indent_bool
+ }
+% \begin{macrocode}
+% At present we don't have a proper handling for the starred version
+% of \tn{vspace}: just ignore it for the moment!
+% \begin{macrocode}
+\DeclareRobustCommand \vspace
+ {
+ \@ifstar
+ \@vspace
+ \@vspace
+ }
+\cs_set:Npn \@vspace #1 { \galley_vspace_add_single:n {#1} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\\}
+% \begin{macro}{\newline}
+% These functions pass their arguments straight through to the internal
+% implementation (which is currently just the \LaTeXe{} one recoded).
+% \begin{macrocode}
+\DeclareRobustCommand \\
+ {
+ \@ifstar
+ { \cs_set_eq:NN \reserved@e \c_true_bool }
+ { \cs_set_eq:NN \reserved@e \c_false_bool }
+ \@xnewline
+ }
+\cs_set:Npn \@xnewline
+ {
+ \@ifnextchar [ % ]
+ { \@newline }
+ { \@newline [ 0pt ] }
+ }
+\cs_set:Npn \@newline [ #1 ]
+ { \galley_break_line:Nn \reserved@e {#1} }
+\DeclareRobustCommand \newline
+ { \galley_break_line:Nn \c_true_bool { 0pt } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{\LaTeXe{} fixes}
+%
+% Purely for testing, some internal \LaTeXe{} functions are altered to work
+% with the mechanism here. This material is not comprehensive: additions are
+% made as-needed for test purposes.
+%
+% \begin{macro}{\@@par}
+% The primitive is moved as otherwise the clever skipping code will fail.
+% \begin{macrocode}
+\cs_set_eq:NN \@@@@par \galley_par:
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\addpenalty, \addvspace}
+% The mechanism here is entirely different, but at least this works!
+% \begin{macrocode}
+\cs_set_protected:Npn \addpenalty { \galley_penalty_add_single:n }
+\cs_set_protected:Npn \addvspace { \galley_vspace_max_single:n }
+% \end{macrocode}
+%\end{macro}
+%
+% \begin{macro}{\@afterheading}
+% Set some flags and hope for the best!
+% \begin{macrocode}
+\cs_set_protected:Npn \@afterheading
+ {
+ \bool_gset_true:N \g_galley_no_break_next_bool
+ \if@afterindent
+ \else
+ \bool_gset_true:N \g_galley_omit_next_indent_bool
+ \fi
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@hangfrom}
+% The \cs{tex_hangindent:D} primitive is no longer used, so the paragraph
+% shape is set in a different way. As a result, the label is part of the
+% same paragraph as the main body, hence the need to leave vertical mode.
+% \begin{macrocode}
+\cs_set_protected:Npn \@hangfrom #1
+ {
+ \bool_gset_true:N \g_galley_omit_next_indent_bool
+ \leavevmode
+ \setbox \@tempboxa = \hbox { {#1} }
+ \galley_parshape_set_single:nnnN
+ { 1 }
+ { \box_wd:N \@tempboxa }
+ \c_zero_dim
+ \c_false_bool
+ \bool_gset_true:N \g_galley_no_break_next_bool
+ \bool_gset_true:N \g_galley_omit_next_indent_bool
+ \box \@tempboxa
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@normalcr}
+% This is needed as \cs{@parboxrestore} sets |\\| equal to \cs{@normalcr},
+% and the new definition must be used
+% \begin{macrocode}
+\cs_set_eq:Nc \@normalcr { \cs_to_str:N \\ }
+% \end{macrocode}
+%\end{macro}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3galley.pdf b/macros/latex/contrib/l3experimental/l3galley.pdf
new file mode 100644
index 0000000000..09277e5949
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3galley.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3graphics.dtx b/macros/latex/contrib/l3experimental/l3graphics.dtx
new file mode 100644
index 0000000000..d65bce66d4
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3graphics.dtx
@@ -0,0 +1,716 @@
+% \iffalse meta-comment
+%
+%% File: l3graphics.dtx
+%
+% Copyright (C) 2017-2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3graphics} package\\ Graphics inclusion support^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{\pkg{l3graphics} documentation}
+%
+% \subsection{Driver functions}
+%
+% Inclusion of graphic files requires a range of low-level data be passed to
+% the driver layer. These functions are primarily aimed at supporting this
+% work.
+%
+% \begin{variable}{\l_graphics_decodearray_tl}
+% 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}{\l_graphics_interpolate_bool}
+% Indicates whether interpolation should be applied to bitmap graphic files.
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_page_int}
+% The page to extract from a multi-page graphic file: used for |.pdf| files
+% which may contain multiple pages.
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_pagebox_tl}
+% 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.
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_draft_bool}
+% Switch to enable draft mode: graphics are read but not included when this is
+% true.
+% \end{variable}
+%
+% \begin{variable}
+% {\l_graphics_llx_dim, \l_graphics_lly_dim, \l_graphics_urx_dim, \l_graphics_ury_dim}
+% Dimensions which return the points $(\meta{llx},\meta{lly})$ and
+% $(\meta{urx},\meta{ury})$ for the graphic. For many graphics only the resulting
+% height and width are significant, but this is driver-dependent.
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_name_bool}
+% The name of a graphics file being loaded: usually the same as the
+% input file name, but may be altered by some drivers.
+% \end{variable}
+%
+% \begin{function}
+% {
+% \graphics_bb_save:n, \graphics_bb_save:x,
+% \graphics_bb_restore:nF, \graphics_bb_restore:xF
+% }
+% \begin{syntax}
+% \cs{graphics_bb_save:n} \Arg{graphic}
+% \cs{graphics_bb_restore:nF} \Arg{graphic} \Arg{false code}
+% \end{syntax}
+% This pair of functions are used to cache the bounding box of an \meta{graphic}
+% so that extraction/reading is only required once. The \texttt{save}
+% function stores the values from \cs{l_graphics_llx_dim},
+% \cs{l_graphics_lly_dim}, \cs{l_graphics_urx_dim} and
+% \cs{l_graphics_ury_dim} as constants. The \texttt{restore} function will then
+% look up values for the bounding box of an \meta{graphic} and set the four
+% dimensions appropriately. For any one \meta{graphic} the bounding box will
+% be constant, so the \texttt{save} function should only be called once. Thus
+% a typical use case is
+% \begin{verbatim}
+% \graphics_bb_restore:nF { <name> }
+% {
+% % Code to read the bb
+% \graphics_bb_save:n { <name> }
+% }
+% \end{verbatim}
+% \emph{i.e.}~every use of a bounding box will attempt to restore saved data,
+% and saving will only take place where that is not possible.
+%
+% Note that the \meta{graphic} may not be a simple file name: a multi-page PDF,
+% for example, will need to have the bounding box cached for each page used.
+% \end{function}
+%
+% \begin{function}{\graphics_extract_bb:n}
+% \begin{syntax}
+% \cs{graphics_extract_bb:n} \Arg{file}
+% \end{syntax}
+% Extracts bounding box data for the graphic \meta{file} using the |extractbb|
+% utility, and stores the bounding box of the graphic file in
+% \cs{l_graphics_llx_dim}, \cs{l_graphics_lly_dim}, \cs{l_graphics_urx_dim} and
+% \cs{l_graphics_ury_dim}.
+%
+% The \meta{file} name should be fully-qualified and sanitized: no search
+% or other manipulation is carried out at this level. No check is made on
+% the file \emph{type} at this stage: it is assumed that the driver code
+% using this function has made such a check. File types such as |.pdf| and
+% |.jpg| are appropriate for parsing using this function.
+%
+% When \cs{l_graphics_page_int} is positive the appropriate page will be
+% queried from the graphic file.
+%
+% Note that this function requires pipe shell calls to be enabled: this is
+% generally true but may require the option |--enable-pipes| to be enabled
+% when running the \TeX{} job.
+% \end{function}
+%
+% \begin{function}{\graphics_read_bb:n}
+% \begin{syntax}
+% \cs{graphics_read_bb:n} \Arg{file}
+% \end{syntax}
+% Parses the graphic \meta{file} to find a PostScript-style bounding box
+% line of the form
+% \begin{verbatim}
+% %%BoundingBox: llx lly urx urx
+% \end{verbatim}
+% where \meta{llx}, \meta{lly}, \meta{urx} and \meta{ury} are the corners
+% of the bounding box expressed in PostScript (\enquote{big}) points. The
+% values are stored in \cs{l_graphics_llx_dim}, \cs{l_graphics_lly_dim},
+% \cs{l_graphics_urx_dim} and \cs{l_graphics_ury_dim}.
+%
+% The \meta{file} name should be fully-qualified and sanitized: no search
+% or other manipulation is carried out at this level. No check is made on
+% the file \emph{type} at this stage: it is assumed that the driver code
+% using this function has made such a check. File types such as |.eps| and
+% |.bb|/|.xbb| are appropriate for parsing using this function.
+% \end{function}
+%
+% \begin{function}{\graphics_include:n, \graphics_include:nn}
+% \begin{syntax}
+% \cs{graphics_include:n} \Arg{file}
+% \cs{graphics_include:nn} \Arg{file} \Arg{type}
+% \end{syntax}
+% Horizontal-mode commands which include the \meta{file} as an graphic
+% at the current location. The file \meta{type} is given explicitly in
+% the two-argument version, or is inferred from the file extension extracted
+% in the single-argument form. The exact graphic types supported depend upon
+% the driver in use.
+%
+% Where the \meta{file} is not found and the \meta{type} is \emph{not}
+% given, a search for possible graphic files is undertaken using the extensions
+% stored in \cs{l_graphics_search_ext_seq}.
+% \end{function}
+%
+% \begin{variable}{\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}{\l_graphics_search_ext_seq}
+% Extensions to use for graphic searching when the given \meta{file} name is not
+% found by \cs{graphics_include:n}.
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_search_path_seq}
+% Each entry is the path to a directory which should be searched when
+% seeking an 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}
+%
+% \begin{function}{\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 by 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}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=graphics>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3graphics}{2019-08-25}{}
+ {L3 Experimental graphics inclusion support}
+%</package>
+% \end{macrocode}
+%
+% \begin{variable}{\l_@@_tmp_ior, \l_@@_tmp_tl}
+% Scratch space.
+% \begin{macrocode}
+\ior_new:N \l_@@_tmp_ior
+\tl_new:N \l_@@_tmp_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Image feature support}
+%
+% \begin{macro}
+% {
+% \l_graphics_decodearray_tl ,
+% \l_graphics_interpolate_bool,
+% \l_graphics_page_int ,
+% \l_graphics_pagebox_tl
+% }
+% Keys which control features of graphics. The standard value of |pagebox|
+% set up here should match the default for the drivers themselves: in
+% the absence of any other setting the |cropbox| should be used.
+% \begin{macrocode}
+\tl_new:N \l_graphics_decodearray_tl
+\tl_new:N \l_graphics_pagebox_tl
+\bool_new:N \l_graphics_interpolate_bool
+\tl_set:Nn \l_graphics_pagebox_tl { cropbox }
+\int_new:N \l_graphics_page_int
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Obtaining bounding box data}
+%
+% \begin{variable}
+% {
+% \l_graphics_llx_dim , \l_graphics_lly_dim,
+% \l_graphics_urx_dim , \l_graphics_ury_dim
+% }
+% Storage for the return of bounding box.
+% \begin{macrocode}
+\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
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\graphics_bb_save:n, \graphics_bb_save:x}
+% \begin{macro}{\graphics_bb_restore:nF, \graphics_bb_restore:xF}
+% 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 \graphics_bb_save:n #1
+ {
+ \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
+ { \__kernel_msg_error:nnn { graphic } { bb-already-cached } {#1} }
+ {
+ \dim_compare:nNnF \l_graphics_llx_dim = { 0pt }
+ { \dim_const:cn { c_@@_ #1 _llx_dim } { \l_graphics_llx_dim } }
+ \dim_compare:nNnF \l_graphics_lly_dim = { 0pt }
+ { \dim_const:cn { c_@@_ #1 _lly_dim } { \l_graphics_lly_dim } }
+ \dim_const:cn { c_@@_ #1 _urx_dim } { \l_graphics_urx_dim }
+ \dim_const:cn { c_@@_ #1 _ury_dim } { \l_graphics_ury_dim }
+ }
+ }
+\cs_generate_variant:Nn \graphics_bb_save:n { x }
+\cs_new_protected:Npn \graphics_bb_restore:nF #1#2
+ {
+ \dim_if_exist:cTF { c_@@_ #1 _urx_dim }
+ {
+ \dim_set_eq:Nc \l_graphics_urx_dim { c_@@_ #1 _urx_dim }
+ \dim_set_eq:Nc \l_graphics_ury_dim { c_@@_ #1 _ury_dim }
+ \dim_if_exist:cTF { c_@@_ #1 _llx_dim }
+ { \dim_set_eq:Nc \l_graphics_llx_dim { c_@@_ #1 _llx_dim } }
+ { \dim_zero:N \l_graphics_llx_dim }
+ \dim_if_exist:cTF { c_@@_ #1 _lly_dim }
+ { \dim_set_eq:Nc \l_graphics_lly_dim { c_@@_ #1 _lly_dim } }
+ { \dim_zero:N \l_graphics_lly_dim }
+ }
+ {#2}
+ }
+\cs_generate_variant:Nn \graphics_bb_restore:nF { x }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\graphics_extract_bb:n, \graphics_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 \graphics_extract_bb:n #1
+ {
+ \int_compare:nNnTF \l_graphics_page_int > 0
+ { \@@_extract_bb_auxi:Vn \l_graphics_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_graphics_pagebox_tl
+ { \@@_extract_bb_auxiv:nnn }
+ { \@@_extract_bb_auxiii:Vnnn \l_graphics_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_@@_tmp_ior { extractbb~#3-O~#1 } }
+ { pipe-failed }
+ }
+\cs_new_protected:Npn \graphics_read_bb:n #1
+ {
+ \@@_read_bb_auxi:nnnn {#1} { }
+ { \ior_open:Nn \l_@@_tmp_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
+ {
+ \graphics_bb_restore:nF {#1#2}
+ { \@@_read_bb_auxii:nnnn {#3} {#4} {#1} {#2} }
+ }
+\cs_new_protected:Npx \@@_read_bb_auxii:nnnn #1#2#3#4
+ {
+ #1
+ \exp_not:N \ior_if_eof:NTF \exp_not:N \l_@@_tmp_ior
+ { \__kernel_msg_error:nnn { graphics } {#2} {#3} }
+ {
+ \ior_str_map_inline:Nn \exp_not:N \l_@@_tmp_ior
+ {
+ \exp_not:N \@@_read_bb_auxiii:w
+ ##1 ~ \c_colon_str \exp_not:N \q_stop
+ }
+ }
+ \exp_not:n
+ {
+ \ior_close:N \l_@@_tmp_ior
+ \dim_compare:nNnF \l_graphics_llx_dim = { 0pt }
+ { \dim_const:cn { c_@@_ #3#4 _llx_dim } { \l_graphics_llx_dim } }
+ \dim_compare:nNnF \l_graphics_lly_dim = { 0pt }
+ { \dim_const:cn { c_@@_ #3#4 _lly_dim } { \l_graphics_lly_dim } }
+ \dim_const:cn { c_@@_ #3#4 _urx_dim } { \l_graphics_urx_dim }
+ \dim_const:cn { c_@@_ #3#4 _ury_dim } { \l_graphics_ury_dim }
+ }
+ }
+\use:x
+ {
+ \cs_new_protected:Npn \exp_not:N \@@_read_bb_auxiii:w
+ ##1 \c_colon_str ##2 \exp_not:N \q_stop
+ {
+ \exp_not:N \str_if_eq:nnT
+ { \c_percent_str \c_percent_str BoundingBox }
+ {##1}
+ { \exp_not:N \@@_read_bb_auxiv:w ##2 ( ) \exp_not:N \q_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 \q_stop
+ {
+ \str_if_eq:nnF {#2} { atend }
+ {
+ \tl_set_rescan:Nnx \l_@@_tmp_tl
+ {
+ \char_set_catcode_space:n { 9 }
+ \char_set_catcode_space:n { 32 }
+ }
+ { \use:n #1 }
+ \exp_after:wN \@@_read_bb_auxv:w \l_@@_tmp_tl \q_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 \q_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:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{variable}{\l_graphics_draft_bool}
+% Relevant to including graphics but not to reading them.
+% \begin{macrocode}
+\bool_new:N \l_graphics_draft_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_graphics_name_tl, \l_@@_name_tl}
+% \begin{macrocode}
+\tl_new:N \l_graphics_name_tl
+\tl_new:N \l_@@_name_tl
+% \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-driver 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:n}
+% \begin{macro}{\@@_include_search:n}
+% \begin{macro}{\@@_include:}
+% \begin{macro}{\graphics_include:nn}
+% \begin{macro}
+% {
+% \@@_include_auxi:n, \@@_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 driver. 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:n #1
+ {
+ \group_begin:
+ \seq_set_eq:NN \l_file_search_path_seq \l_graphics_search_path_seq
+ \file_get_full_name:nNTF {#1} \l_@@_name_tl
+ {
+ \str_if_eq:eeTF { \l_@@_name_tl } { #1 .tex }
+ { \@@_include_search:n {#1} }
+ { \@@_include: }
+ }
+ { \@@_include_search:n {#1} }
+ \group_end:
+ }
+\cs_new_protected:Npn \@@_include_search:n #1
+ {
+ \seq_map_inline:Nn \l_graphics_search_ext_seq
+ {
+ \file_get_full_name:nNT { #1 . ##1 } \l_@@_name_tl
+ { \seq_map_break:n { \use_i:nnn \@@_include: } }
+ }
+ \use:n
+ { \__kernel_msg_error:nnn { graphics } { graphic-not-found } {#1} }
+ }
+\cs_new_protected:Npn \@@_include:
+ {
+ \file_parse_full_name:VNNN \l_@@_name_tl
+ \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
+ \exp_args:Ne \@@_include_auxi:n
+ {
+ \exp_args:Ne \str_tail:n
+ { \str_fold_case:V \l_@@_ext_str }
+ }
+ }
+\cs_new_protected:Npn \graphics_include: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_@@_name_tl
+ {
+ \exp_args:Ne \@@_include_auxi:n
+ { \str_fold_case:n {#1} }
+ }
+ { \__kernel_msg_error:nnn { graphics } { graphic-not-found } {#1} }
+ \group_end:
+ }
+\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_new_protected:Npn \@@_include_auxii:n #1
+ {
+ \mode_leave_vertical:
+ \cs_if_exist:cTF { @@_backend_include_ #1 :n }
+ {
+ \tl_set_eq:NN \l_graphics_name_tl \l_@@_name_tl
+ \exp_args:NnV \use:c { @@_backend_getbb_ #1 :n }
+ \l_@@_name_tl
+ \seq_gput_right:NV \g_@@_record_seq \l_graphics_name_tl
+%<*package>
+ \clist_if_exist:NT \@filelist
+ { \exp_args:NV \@addtofilelist \l_graphics_name_tl }
+%</package>
+ \bool_if:NTF \l_graphics_draft_bool
+ { \@@_include_auxiii:n }
+ { \@@_include_auxiv:n }
+ {#1}
+ }
+ { \__kernel_msg_error:nnn { graphics } { unsupported-graphic-type } {#1} }
+ }
+\cs_new_protected:Npn \@@_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 }
+ {
+%<*package>
+ \ttfamily
+%</package>
+ \tex_hss:D \l_@@_name_tl \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_@@_name_tl
+ }
+ \box_set_dp:Nn \l_@@_internal_box { 0pt }
+ \box_set_ht:Nn \l_@@_internal_box
+ { \l_graphics_ury_dim - \l_graphics_lly_dim }
+ \box_set_wd:Nn \l_@@_internal_box
+ { \l_graphics_urx_dim - \l_graphics_llx_dim }
+ \box_use_drop:N \l_@@_internal_box
+ }
+% \end{macrocode}
+% \end{macro}
+% \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:nnxxxx }
+\cs_new_protected:Npn \graphics_log_list: { \@@_list:N \msg_log:nnxxxx }
+\cs_new_protected:Npn \@@_list:N #1
+ {
+ \seq_remove_duplicates:N \g_@@_record_seq
+ #1 { LaTeX/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{Messages}
+%
+% \begin{macrocode}
+\__kernel_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.
+ }
+\__kernel_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.
+ }
+\__kernel_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}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3graphics.ins b/macros/latex/contrib/l3experimental/l3graphics.ins
new file mode 100644
index 0000000000..815535a352
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3graphics.ins
@@ -0,0 +1,56 @@
+\iffalse meta-comment
+
+File l3graphics.ins Copyright (C) 2017,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3graphics.sty}{\from{l3graphics.dtx}{package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3graphics.pdf b/macros/latex/contrib/l3experimental/l3graphics.pdf
new file mode 100644
index 0000000000..3a904d5f29
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3graphics.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3pdf-code.pdf b/macros/latex/contrib/l3experimental/l3pdf-code.pdf
new file mode 100644
index 0000000000..0b2e370f8d
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3pdf-code.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3pdf-code.tex b/macros/latex/contrib/l3experimental/l3pdf-code.tex
new file mode 100644
index 0000000000..a93342f1f3
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3pdf-code.tex
@@ -0,0 +1,56 @@
+\iffalse meta-comment
+
+File: l3pdf-code.tex
+
+Copyright (C) 2019 The LaTeX3 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
+
+ https://www.latex-project.org/lppl.txt
+
+This file is part of the "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+The released version of this bundle is available from CTAN.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+ https://github.com/latex3/latex3
+
+for those people who are interested.
+
+\fi
+
+\documentclass{l3doc}
+
+\begin{document}
+
+\makeatletter
+\let\DelayPrintIndex\PrintIndex
+\let\PrintIndex\@empty
+\makeatother
+
+\ExplSyntaxOn
+\clist_gput_right:Nn \g_docinput_clist
+ {
+ l3pdf.dtx ,
+ }
+\ExplSyntaxOff
+
+\part{Implementation}
+
+\def\maketitle{}
+\EnableImplementation
+\DisableDocumentation
+\DocInputAgain
+
+\clearpage
+
+\DelayPrintIndex
+
+\end{document}
diff --git a/macros/latex/contrib/l3experimental/l3pdf.dtx b/macros/latex/contrib/l3experimental/l3pdf.dtx
new file mode 100644
index 0000000000..413122a996
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3pdf.dtx
@@ -0,0 +1,337 @@
+% \iffalse meta-comment
+%
+%% File: l3pdf.dtx
+%
+% Copyright(C) 2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3pdf} package\\ Core PDF support^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{\pkg{l3pdf} documentation}
+%
+% \subsection{Objects}
+%
+% \begin{function}[added = 2019-06-02]{\pdf_object_new:nn}
+% \begin{syntax}
+% \cs{pdf_object_new:nn} \Arg{object} \Arg{type}
+% \end{syntax}
+% Declares \meta{object} as a PDF object of \meta{type}, which should be
+% one of
+% \begin{itemize}
+% \item \texttt{array}
+% \item \texttt{dict}
+% \item \texttt{fstream}
+% \item \texttt{stream}
+% \end{itemize}
+% The object may be referenced from this point on, and written later
+% using \cs{pdf_object_write:nn}.
+% \end{function}
+%
+% \begin{function}[added = 2019-06-02]
+% {\pdf_object_write:nn, \pdf_object_write:nx}
+% \begin{syntax}
+% \cs{pdf_object_write:nn} \Arg{object} \Arg{content}
+% \end{syntax}
+% Writes the \meta{content} as content of the \meta{object}. Depending on the
+% \meta{type} declared for the object, the format required for the
+% \meta{data} will vary
+% \begin{itemize}
+% \item[\texttt{array}] A space-separated list of values
+% \item[\texttt{dict}] Key--value pairs in the form
+% \texttt{/\meta{key} \meta{value}}
+% \item[\texttt{fstream}] Two brace groups: \meta{file name} and
+% \meta{file content}
+% \item[\texttt{stream}] Two brace groups: \meta{attributes (dictionary)}
+% and \meta{stream contents}
+% \end{itemize}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2019-06-02]{\pdf_object_ref:n}
+% \begin{syntax}
+% \cs{pdf_object_ref:n} \Arg{object}
+% \end{syntax}
+% Inserts the appropriate information to reference the \meta{object}
+% in for example page resource allocation
+% \end{function}
+%
+% \begin{function}[added = 2019-06-02]
+% {\pdf_object_now:nn, \pdf_object_now:nx}
+% \begin{syntax}
+% \cs{pdf_object_now:nn} \Arg{type} \Arg{content}
+% \end{syntax}
+% Writes the \meta{content} as content of an anonymous object. Depending on the
+% \meta{type}, the format required for the \meta{data} will vary
+% \begin{itemize}
+% \item[\texttt{array}] A space-separated list of values
+% \item[\texttt{dict}] Key--value pairs in the form
+% \texttt{/\meta{key} \meta{value}}
+% \item[\texttt{fstream}] Two brace groups: \meta{file name} and
+% \meta{file content}
+% \item[\texttt{stream}] Two brace groups: \meta{attributes (dictionary)}
+% and \meta{stream contents}
+% \end{itemize}
+% \end{function}
+%
+% \begin{function}[EXP, added = 2019-06-02]{\pdf_object_last:}
+% \begin{syntax}
+% \cs{pdf_object_last:}
+% \end{syntax}
+% Inserts the appropriate information to reference the last \meta{object}
+% created. This is particularly useful for anonymous objects.
+% \end{function}
+%
+% \subsection{Version}
+%
+% \begin{function}[pTF, EXP, added = 2019-06-02]{\pdf_version_compare:Nn}
+% \begin{syntax}
+% \cs{pdf_version_compare:NnTF} \meta{comparator} \Arg{version} \Arg{true code} \Arg{false code}
+% \end{syntax}
+% Compares the version of the PDF being created with the \meta{version}
+% string specified, using the \meta{comparator}. Either the \meta{true code}
+% or \meta{false code} will be left in the output stream.
+% \end{function}
+%
+% \begin{function}[added = 2019-06-02]
+% {\pdf_version_gset:n, \pdf_version_min_gset:n}
+% \begin{syntax}
+% \cs{pdf_version_gset:n} \Arg{version}
+% \end{syntax}
+% Sets the \meta{version} of the PDF being created. The |min| version will
+% not alter the output version unless it is currently lower than the
+% \meta{version} requested.
+%
+% This function may only be used up to the point where the PDF file is
+% initialised.
+% \end{function}
+%
+% \begin{function}[EXP, added = 2019-06-02]
+% {\pdf_version:, \pdf_version_major:, \pdf_version_minor:}
+% \begin{syntax}
+% \cs{pdf_version:}
+% \end{syntax}
+% Expands to the currently-active PDF version.
+% \end{function}
+%
+% \subsection{Compression}
+%
+% \begin{function}[added = 2019-06-02]{\pdf_uncompress:}
+% \begin{syntax}
+% \cs{pdf_uncompress:}
+% \end{syntax}
+% Disables any compression of the PDF, where possible.
+%
+% This function may only be used up to the point where the PDF file is
+% initialised.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3pdf} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=pdf>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3pdf}{2019-08-25}{}
+ {L3 Experimental core PDF support}
+%</package>
+% \end{macrocode}
+%
+% \begin{variable}{\g_@@_init_bool}
+% A flag so we have some chance of avoiding setting things we are not
+% allowed to.
+% \begin{macrocode}
+\bool_new:N \g_@@_init_bool
+%<*package>
+\cs_if_exist:NT \documentclass
+ {
+ \AtBeginDocument
+ { \bool_gset_true:N \g_@@_init_bool }
+ }
+%</package>
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Compression}
+%
+% \begin{macro}{\pdf_uncompress:}
+% Simple to do.
+% \begin{macrocode}
+\cs_new_protected:Npn \pdf_uncompress:
+ {
+ \bool_if:NF \g_@@_init_bool
+ {
+ \@@_backend_compresslevel:n { 0 }
+ \@@_backend_compress_objects:n { \c_false_bool }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Objects}
+%
+% \begin{macro}{\pdf_object_new:nn, \pdf_object_write:nn, \pdf_object_write:nx}
+% \begin{macro}{\pdf_object_ref:n}
+% \begin{macro}{\pdf_object_now:nn, \pdf_object_now:nx}
+% \begin{macro}{\pdf_object_last:}
+% Simple to do.
+% \begin{macrocode}
+\cs_new_protected:Npn \pdf_object_new:nn #1#2
+ { \@@_backend_object_new:nn {#1} {#2} }
+\cs_new_protected:Npn \pdf_object_write:nn #1#2
+ { \@@_backend_object_write:nn {#1} {#2} }
+\cs_generate_variant:Nn \pdf_object_write:nn { nx }
+\cs_new:Npn \pdf_object_ref:n #1 { \@@_backend_object_ref:n {#1} }
+\cs_new_protected:Npn \pdf_object_now:nn #1#2
+ { \@@_backend_object_now:nn {#1} {#2} }
+\cs_generate_variant:Nn \pdf_object_now:nn { nx }
+\cs_new:Npn \pdf_object_last: { \@@_backend_object_last: }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Version}
+%
+% \begin{macro}{\pdf_version_compare:Nn}
+% \begin{macro}
+% {
+% @@_version_compare_=:w ,
+% @@_version_compare_<:w ,
+% @@_version_compare_>:w
+% }
+% \begin{macrocode}
+% To compare version, we need to split the given value then deal with both
+% major and minor version
+\prg_new_conditional:Npnn \pdf_version_compare:Nn #1#2 { p , T , F , TF }
+ { \use:c { @@_version_compare_ #1 :w } #2 . . \q_stop }
+\cs_new:cpn { @@_version_compare_=:w } #1 . #2 . #3 \q_stop
+ {
+ \bool_lazy_and:nnTF
+ { \int_compare_p:nNn \@@_backend_version_major: = {#1} }
+ { \int_compare_p:nNn \@@_backend_version_minor: = {#2} }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+\cs_new:cpn { @@_version_compare_<:w } #1 . #2 . #3 \q_stop
+ {
+ \bool_lazy_or:nnTF
+ { \int_compare_p:nNn \@@_backend_version_major: < {#1} }
+ {
+ \bool_lazy_and_p:nn
+ { \int_compare_p:nNn \@@_backend_version_major: = {#1} }
+ { \int_compare_p:nNn \@@_backend_version_minor: < {#2} }
+ }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+\cs_new:cpn { @@_version_compare_>:w } #1 . #2 . #3 \q_stop
+ {
+ \bool_lazy_or:nnTF
+ { \int_compare_p:nNn \@@_backend_version_major: > {#1} }
+ {
+ \bool_lazy_and_p:nn
+ { \int_compare_p:nNn \@@_backend_version_major: = {#1} }
+ { \int_compare_p:nNn \@@_backend_version_minor: > {#2} }
+ }
+ { \prg_return_true: }
+ { \prg_return_false: }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\pdf_version_gset:n, \pdf_version_min_gset:n}
+% \begin{macro}{\@@_version_gset:w}
+% Split the version and set.
+% \begin{macrocode}
+\cs_new_protected:Npn \pdf_version_gset:n #1
+ { \@@_version_gset:w #1 . . \q_stop }
+\cs_new_protected:Npn \pdf_version_min_gset:n #1
+ {
+ \pdf_version_compare:NnT < {#1}
+ { \@@_version_gset:w #1 . . \q_stop }
+ }
+\cs_new_protected:Npn \@@_version_gset:w #1 . #2 . #3\q_stop
+ {
+ \bool_if:NF \g_@@_init_bool
+ {
+ \@@_backend_version_major_gset:n {#1}
+ \@@_backend_version_minor_gset:n {#2}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\pdf_version:, \pdf_version_major:, \pdf_version_minor:}
+% Wrappers.
+% \begin{macrocode}
+\cs_new:Npn \pdf_version:
+ { \@@_backend_version_major: . \@@_backend_version_minor: }
+\cs_new:Npn \pdf_version_major: { \@@_backend_version_major: }
+\cs_new:Npn \pdf_version_minor: { \@@_backend_version_minor: }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3pdf.ins b/macros/latex/contrib/l3experimental/l3pdf.ins
new file mode 100644
index 0000000000..d39eefa114
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3pdf.ins
@@ -0,0 +1,60 @@
+\iffalse meta-comment
+
+File: l3pdf.ins
+
+Copyright (C) 2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2019 The LaTeX3 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 "l3pdf bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3pdf.sty}
+ {\from{l3pdf.dtx}{package}}
+}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3pdf.pdf b/macros/latex/contrib/l3experimental/l3pdf.pdf
new file mode 100644
index 0000000000..55200d2c56
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3pdf.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3str-format.dtx b/macros/latex/contrib/l3experimental/l3str-format.dtx
new file mode 100644
index 0000000000..17c2dad4a0
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3str-format.dtx
@@ -0,0 +1,982 @@
+% \iffalse meta-comment
+%
+%% File: l3str-format.dtx
+%
+% Copyright (C) 2012-2019 The LaTeX3 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
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\usepackage{amsmath}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+%
+% \title{^^A
+% The \textsf{l3str-format} package: formatting strings of characters^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Format specifications}
+%
+% In this module, we introduce the notion of a string \meta{format}.
+% The syntax follows that of Python's \texttt{format} built-in function.
+% A \meta{format specification} is a string of the form
+% \begin{equation*}
+% \meta{format specification} = [[\meta{fill}]\meta{alignment}]
+% [\meta{sign}] [\meta{width}] [.\meta{precision}] [\meta{style}]
+% \end{equation*}
+% where each $[\ldots]$ denotes an independent optional part.
+% \begin{itemize}
+% \item \meta{fill} can be any character: it is assumed to be present
+% whenever the second character of the \meta{format specification}
+% is a valid \meta{alignment} character.
+% \item \meta{alignment} can be |<|~(left alignment), |>|~(right
+% alignment), |^|~(centering), or |=|~(for numeric types only).
+% \item \meta{sign} is allowed for numeric types; it can be |+|~(show
+% a sign for positive and negative numbers), |-|~(only put a sign
+% for negative numbers), or a space~(show a space or a~|-|).
+% \item \meta{width} is the minimum number of characters of the
+% result: if the result is naturally shorter than this \meta{width},
+% then it is padded with copies of the character \meta{fill}, with a
+% position depending on the choice of \meta{alignment}. If the
+% result is naturally longer, it is not truncated.
+% \item \meta{precision}, whose presence is indicated by a period,
+% can have different meanings depending on the type.
+% \item \meta{style} is one character, which controls how the given
+% data should be formatted. The list of allowed \meta{styles}
+% depends on the type.
+% \end{itemize}
+% The choice of \meta{alignment} |=| is only valid for numeric types: in
+% this case the padding is inserted between the sign and the rest of the
+% number.
+%
+% \section{Formatting various data-types}
+%
+% \begin{function}[EXP]{\tl_format:Nn, \tl_format:cn, \tl_format:nn}
+% \begin{syntax}
+% \cs{tl_format:nn} \Arg{token list} \Arg{format specification}
+% \end{syntax}
+% Converts the \meta{token list} to a string according to the
+% \meta{format specification}. The \meta{style}, if present, must
+% be~|s|. If \meta{precision} is given, all characters of the string
+% representation of the \meta{token list} beyond the first
+% \meta{precision} characters are discarded.
+% \end{function}
+%
+% \begin{function}[EXP]{\seq_format:Nn, \seq_format:cn}
+% \begin{syntax}
+% \cs{seq_format:Nn} \Arg{sequence} \Arg{format specification}
+% \end{syntax}
+% Converts each item in the \meta{sequence} to a string according to
+% the \meta{format specification}, and concatenates the results.
+% \end{function}
+%
+% \begin{function}[EXP]{\int_format:nn}
+% \begin{syntax}
+% \cs{int_format:nn} \Arg{intexpr} \Arg{format specification}
+% \end{syntax}
+% Evaluates the \meta{integer expression} and converts the result to a
+% string according to the \meta{format specification}. The
+% \meta{precision} argument is not allowed. The \meta{style} can be
+% |b| for binary output, |d| for decimal output (this is the default),
+% |o| for octal output, |X| for hexadecimal output (using capital
+% letters).
+% \end{function}
+%
+% \begin{function}[EXP]{\fp_format:nn}
+% \begin{syntax}
+% \cs{fp_format:nn} \Arg{fpexpr} \Arg{format specification}
+% \end{syntax}
+% Evaluates the \meta{floating point expression} and converts the
+% result to a string according to the \meta{format specification}.
+% The \meta{style} can be
+% \begin{itemize}
+% \item |e| for scientific notation, with one digit before and
+% \meta{precision} digits after the decimal separator, and an
+% integer exponent, following |e|;
+% \item |f| for a fixed point notation, with \meta{precision} digits
+% after the decimal separator and no exponent;
+% \item |g| for a general format, which uses style |f| for numbers
+% in the range $[10^{-4}, 10^{\meta{precision}})$ and style |e|
+% otherwise.
+% \end{itemize}
+% When there is no \meta{style} specifier nor \meta{precision} the
+% number is displayed without rounding. Otherwise the
+% \meta{precision} defaults to~$6$.
+% \end{function}
+%
+% \section{Possibilities, and things to do}
+%
+% \begin{itemize}
+% \item Provide a token list formatting \meta{style} which keeps the
+% last \meta{precision} characters rather than the first
+% \meta{precision}.
+% \end{itemize}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3str-format} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=str>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3str-format}{2019-08-25}{}
+ {L3 Experimental string formatting}
+%</package>
+% \end{macrocode}
+%
+% \subsection{Helpers}
+%
+% \begin{macro}[EXP]{\use:nf, \use:fnf}
+% A simple variant.
+% \begin{macrocode}
+\cs_generate_variant:Nn \use:nn { nf }
+\cs_generate_variant:Nn \use:nnn { fnf }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\tl_to_str:f}
+% A simple variant.
+% \begin{macrocode}
+\cs_generate_variant:Nn \tl_to_str:n { f }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_if_digit:NTF}
+% Here we expect |#1| to be a character with category other, or
+% \cs{s_stop}.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_format_if_digit:N #1 { TF }
+ {
+ \if_int_compare:w 9 < 1 #1 \exp_stop_f:
+ \prg_return_true: \else: \prg_return_false: \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]
+% {\@@_format_put:nw, \@@_format_put:ow, \@@_format_put:fw}
+% Put |#1| after an \cs{s_stop} delimiter.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_put:nw #1 #2 \s_stop { #2 \s_stop #1 }
+\cs_generate_variant:Nn \@@_format_put:nw { o , f }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP, TF]{\@@_format_if_in:nN}
+% \begin{macro}[EXP]{\@@_format_if_in_aux:NN}
+% A copy of \cs{@@_if_contains_char:nNTF} to avoid relying on
+% this weird internal string function.
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_format_if_in:nN #1#2 { TF }
+ {
+ \@@_format_if_in_aux:NN #2 #1
+ { #2 \prg_return_false: \exp_after:wN \prg_break: \else: }
+ \prg_break_point:
+ }
+\cs_new:Npn \@@_format_if_in_aux:NN #1#2
+ {
+ \if_charcode:w #1 #2
+ \prg_return_true:
+ \exp_after:wN \prg_break:
+ \fi:
+ \@@_format_if_in_aux:NN #1
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Parsing a format specification}
+%
+% \begin{macro}[EXP]{\@@_format_parse:n}
+% \begin{macro}[EXP]
+% {
+% \@@_format_parse_auxi:NN,
+% \@@_format_parse_auxii:nN,
+% \@@_format_parse_auxiii:nN,
+% \@@_format_parse_auxiv:nwN,
+% \@@_format_parse_auxv:nN,
+% \@@_format_parse_auxvi:nwN,
+% \@@_format_parse_auxvii:nN,
+% \@@_format_parse_end:nwn,
+% }
+% The goal is to parse
+% \begin{equation*}
+% \meta{format specification} = [[\meta{fill}]\meta{alignment}]
+% [\meta{sign}] [\meta{width}] [.\meta{precision}] [\meta{style}]
+% \end{equation*}
+% \begin{macrocode}
+\cs_new:Npn \@@_format_parse:n #1
+ {
+ \exp_last_unbraced:Nf \@@_format_parse_auxi:NN
+ { \__kernel_str_to_other:n {#1} } \s_stop \s_stop {#1}
+ }
+\cs_new:Npx \@@_format_parse_auxi:NN #1#2
+ {
+ \exp_not:N \@@_format_if_in:nNTF { < > = ^ } #2
+ { \exp_not:N \@@_format_parse_auxiii:nN { #1 #2 } }
+ {
+ \exp_not:N \@@_format_parse_auxii:nN
+ { \c_catcode_other_space_tl } #1 #2
+ }
+ }
+\cs_new:Npn \@@_format_parse_auxii:nN #1#2
+ {
+ \@@_format_if_in:nNTF { < > = ^ } #2
+ { \@@_format_parse_auxiii:nN { #1 #2 } }
+ { \@@_format_parse_auxiii:nN { #1 ? } #2 }
+ }
+\cs_new:Npx \@@_format_parse_auxiii:nN #1#2
+ {
+ \exp_not:N \@@_format_if_in:nNTF
+ { + - \c_catcode_other_space_tl }
+ #2
+ { \exp_not:N \@@_format_parse_auxiv:nwN { #1 #2 } ; }
+ { \exp_not:N \@@_format_parse_auxiv:nwN { #1 ? } ; #2 }
+ }
+\cs_new:Npn \@@_format_parse_auxiv:nwN #1#2; #3
+ {
+ \@@_format_if_digit:NTF #3
+ { \@@_format_parse_auxiv:nwN {#1} #2 #3 ; }
+ { \@@_format_parse_auxv:nN { #1 {#2} } #3 }
+ }
+\cs_new:Npn \@@_format_parse_auxv:nN #1#2
+ {
+ \token_if_eq_charcode:NNTF . #2
+ { \@@_format_parse_auxvi:nwN {#1} 0 ; }
+ { \@@_format_parse_auxvii:nN { #1 { } } #2 }
+ }
+\cs_new:Npn \@@_format_parse_auxvi:nwN #1#2; #3
+ {
+ \@@_format_if_digit:NTF #3
+ { \@@_format_parse_auxvi:nwN {#1} #2 #3 ; }
+ { \@@_format_parse_auxvii:nN { #1 {#2} } #3 }
+ }
+\cs_new:Npn \@@_format_parse_auxvii:nN #1#2
+ {
+ \token_if_eq_meaning:NNTF \s_stop #2
+ { \@@_format_parse_end:nwn { #1 ? } #2 }
+ { \@@_format_parse_end:nwn { #1 #2 } }
+ }
+\cs_new:Npn \@@_format_parse_end:nwn #1 #2 \s_stop \s_stop #3
+ {
+ \tl_if_empty:nF {#2}
+ { \__kernel_msg_expandable_error:nnn { str } { invalid-format } {#3} }
+ #1
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Alignment}
+%
+% The $4$ functions in this section receive an \meta{body}, a
+% \meta{sign}, a \meta{width} and a \meta{fill} character (exactly one
+% character). For non-numeric types, the \meta{sign} is empty and the
+% \meta{body} is the (other) string we want to format. For numeric
+% types, we wish to format \meta{sign} \meta{body} (both are other
+% strings). The alignment types |<|, |>| and |^| keep \meta{sign} and
+% \meta{body} together. The |=| alignment type, however, inserts the
+% padding between the \meta{sign} and the \meta{body}, hence the need to
+% keep those separate.
+%
+% \begin{macro}[EXP]{\@@_format_align_<:nnnN}
+% \begin{quote}
+% \cs{@@_format_align_<:nnnN} \Arg{body} \Arg{sign} \Arg{width}
+% \meta{fill}
+% \end{quote}
+% Aligning \enquote{\meta{sign} \meta{body}} to the left
+% entails appending |#4| the correct number of times. Then convert
+% the result to a string.
+% \begin{macrocode}
+\cs_new:cpn { @@_format_align_<:nnnN } #1#2#3#4
+ {
+ \use:nf { #2 #1 }
+ {
+ \prg_replicate:nn
+ { \int_max:nn { #3 - \@@_count:n { #2 #1 } } { 0 } }
+ {#4}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_align_>:nnnN}
+% \begin{quote}
+% \cs{@@_format_align_>:nnnN} \Arg{body} \Arg{sign} \Arg{width}
+% \meta{fill}
+% \end{quote}
+% Aligning an \enquote{\meta{sign} \meta{body}} to the right
+% entails prepending |#4| the correct number of times. Then convert
+% the result to a string.
+% \begin{macrocode}
+\cs_new:cpn { @@_format_align_>:nnnN } #1#2#3#4
+ {
+ \prg_replicate:nn
+ { \int_max:nn { #3 - \@@_count:n { #2 #1 } } { 0 } }
+ {#4}
+ #2 #1
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begingroup\catcode`\^=12
+% \begin{macro}[EXP]{\@@_format_align_^:nnnN}
+% \begin{quote}
+% \cs{@@_format_align_^:nnnN} \Arg{body} \Arg{sign} \Arg{width}
+% \meta{fill}
+% \end{quote}
+% Centering \enquote{\meta{sign} \meta{body}} entails
+% prepending and appending |#4| the correct number of times. If the
+% number of |#4| to be added is odd, we add one more after than
+% before.
+% \begin{macrocode}
+\cs_new:cpn { @@_format_align_^:nnnN } #1#2#3#4
+ {
+ \use:fnf
+ {
+ \prg_replicate:nn
+ {
+ \int_max:nn { 0 }
+ { #3 - \@@_count:n { #2 #1 } - 1 }
+ / 2
+ }
+ {#4}
+ }
+ { #2 #1 }
+ {
+ \prg_replicate:nn
+ {
+ \int_max:nn { 0 }
+ { #3 - \@@_count:n { #2 #1 } }
+ / 2
+ }
+ {#4}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \endgroup
+%
+% \begin{macro}[EXP]{\@@_format_align_=:nnnN}
+% \begin{quote}
+% \cs{@@_format_align_=:nnnN} \Arg{body} \Arg{sign} \Arg{width}
+% \meta{fill}
+% \end{quote}
+% The special numeric alignment |=| means that we insert the
+% appropriate number of copies of |#4| between the \meta{sign} and the
+% \meta{body}. Then convert the result to a string.
+% \begin{macrocode}
+\cs_new:cpn { @@_format_align_=:nnnN } #1#2#3#4
+ {
+ \use:nf {#2}
+ {
+ \prg_replicate:nn
+ { \int_max:nn { #3 - \@@_count:n { #2 #1 } } { 0 } }
+ {#4}
+ }
+ #1
+ }
+% \end{macrocode}
+% \end{macro}
+%
+%
+% \subsection{Formatting token lists}
+%
+% \begin{macro}[EXP]{\tl_format:Nn, \tl_format:cn, \tl_format:nn}
+% Call \cs{@@_format_tl:NNNnnNn} to read the parsed \meta{format
+% specification}. Then convert the result to a string.
+% \begin{macrocode}
+\cs_new:Npn \tl_format:Nn { \exp_args:No \tl_format:nn }
+\cs_generate_variant:Nn \tl_format:Nn { c }
+\cs_new:Npn \tl_format:nn #1#2
+ {
+ \tl_to_str:f
+ {
+ \exp_last_unbraced:Nf \@@_format_tl:NNNnnNn
+ { \@@_format_parse:n {#2} }
+ {#1}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_tl:NNNnnNn}
+% \begin{quote}
+% \cs{@@_format_tl:NNNnnNn} \meta{fill} \meta{alignment} \meta{sign}
+% \Arg{width} \Arg{precision} \meta{style} \Arg{token list}
+% \end{quote}
+% First check that the \meta{alignment} is not |=|, and set the
+% default alignment |?| to |<|. Place the modified information after
+% a trailing \cs{s_stop} for later retrieval. Then check that there
+% was no \meta{sign}. The width will be useful later, store it after
+% \cs{s_stop}. Afterwards, store the precision, and the function
+% \cs{@@_range:nnn} that will be used to extract the first
+% |#5| characters of the string.
+% There is a need to use the internal function, as otherwise
+% leading spaces would get stripped by |f|-expansion. Finally, check
+% that the \meta{style} is |?| or |s|.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_tl:NNNnnNn #1#2#3#4#5#6
+ {
+ \token_if_eq_charcode:NNTF #2 =
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-align-format } {#2} {tl}
+ \@@_format_put:nw { #1 < }
+ }
+ {
+ \token_if_eq_charcode:NNTF #2 ?
+ { \@@_format_put:nw { #1 < } }
+ { \@@_format_put:nw { #1 #2 } }
+ }
+ \token_if_eq_charcode:NNF #3 ?
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-sign-format } {#3} {tl}
+ }
+ \@@_format_put:nw { {#4} }
+ \tl_if_empty:nTF {#5}
+ { \@@_format_put:nw { \@@_range:nnn { {1} {-1} } } }
+ { \@@_format_put:nw { \@@_range:nnn { {1} {#5} } } }
+ \token_if_eq_charcode:NNF #6 s
+ {
+ \token_if_eq_charcode:NNF #6 ?
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-style-format } {#6} {tl}
+ }
+ }
+ \@@_format_tl_s:NNnnNNn
+ \s_stop
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_tl_s:NNnnNNn}
+% \begin{quote}
+% \cs{@@_format_tl_s:NNnnNNn} \cs{s_stop} \meta{function}
+% \Arg{arguments} \Arg{width} \meta{fill} \meta{alignment}
+% \Arg{token list}
+% \end{quote}
+% The \meta{function} and \meta{arguments} are built in such a way
+% that |f|-expanding \meta{function} \Arg{other string}
+% \meta{arguments} yields the piece of the \meta{other string} that we
+% want to output. The \meta{other string} is built from the
+% \meta{token list} by |f|-expanding \cs{__kernel_str_to_other:n}.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_tl_s:NNnnNNn #1#2#3#4#5#6#7
+ {
+ \exp_args:Nc \exp_args:Nf
+ { @@_format_align_#6:nnnN }
+ { \exp_args:Nf #2 { \__kernel_str_to_other:n {#7} } #3 }
+ { }
+ {#4} #5
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Formatting sequences}
+%
+% \begin{macro}[EXP]{\seq_format:Nn, \seq_format:cn}
+% Each item is formatted as a token list according to the
+% specification. First parse the format and expand the sequence, then
+% loop through the items. Eventually, convert to a string.
+% \begin{macrocode}
+\cs_new:Npn \seq_format:Nn #1#2
+ {
+ \tl_to_str:f
+ {
+ \@@_format_seq:ff
+ { \exp_after:wN \use_i:nn \exp_after:wN \exp_stop_f: #1 }
+ { \@@_format_parse:n {#2} }
+ }
+ }
+\cs_generate_variant:Nn \seq_format:Nn { c }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_seq:nn, \@@_format_seq:ff}
+% The first argument is the contents of a \texttt{seq} variable. The
+% second is a parsed \meta{format specification}. Set up the loop.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_seq:nn #1#2
+ {
+ \@@_format_seq_loop:nnNn { } {#2}
+ #1
+ { ? \@@_format_seq_end:w } { }
+ }
+\cs_generate_variant:Nn \@@_format_seq:nn { ff }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_seq_loop:nnNn}
+% \begin{quote}
+% \cs{@@_format_seq_loop:nnNn} \Arg{done} \Arg{parsed format}
+% \cs{__seq_item:n} \Arg{item}
+% \end{quote}
+% The first argument is the result of formatting the items read so
+% far. The third argument is a single token (\cs{__seq_item:n}),
+% until we reach the end of the sequence, where |\use_none:n #3| ends
+% the loop.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_seq_loop:nnNn #1#2#3#4
+ {
+ \use_none:n #3
+ \exp_args:Nf \@@_format_seq_loop:nnNn
+ { \use:nf {#1} { \@@_format_tl:NNNnnNn #2 {#4} } }
+ {#2}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_seq_end:w}
+% Pick the right piece in the loop above.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_seq_end:w #1#2#3#4 { \use_ii:nnn #3 }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Formatting integers}
+%
+% \begin{macro}[EXP]{\int_format:nn}
+% Evaluate the first argument and feed it to \cs{@@_format_int:nn}.
+% \begin{macrocode}
+\cs_new:Npn \int_format:nn #1
+ { \exp_args:Nf \@@_format_int:nn { \int_eval:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_int:nn}
+% Parse the \meta{format specification} and feed it to
+% \cs{@@_format_int:NNNnnNn}. Then convert the result to a string
+% \begin{macrocode}
+\cs_new:Npn \@@_format_int:nn #1#2
+ {
+ \tl_to_str:f
+ {
+ \exp_last_unbraced:Nf \@@_format_int:NNNnnNn
+ { \@@_format_parse:n {#2} }
+ {#1}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_int:NNNnnNn}
+% \begin{quote}
+% \cs{@@_format_int:NNNnnNn} \meta{fill} \meta{alignment}
+% \meta{sign} \Arg{width} \Arg{precision} \meta{style} \Arg{integer}
+% \end{quote}
+% First set the
+% default alignment |?| to |>|. Place the modified information after
+% a trailing \cs{s_stop} for later retrieval. Then check the
+% \meta{sign}: if the integer is negative, always put~|-|. Otherwise,
+% if the format's \meta{sign} is |~|, put a space (with category
+% \enquote{other}); if it is~|+| put |+|; if it is |-| (default), put
+% nothing, represented as a brace group. The width |#4| will be
+% useful later, store it after \cs{s_stop}. Afterwards, check that
+% the \meta{precision} was absent. Finally, dispatch depending on the
+% \meta{style}.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_int:NNNnnNn #1#2#3#4#5#6#7
+ {
+ \token_if_eq_charcode:NNTF #2 ?
+ { \@@_format_put:nw { #1 > } }
+ { \@@_format_put:nw { #1 #2 } }
+ \int_compare:nNnTF {#7} < 0
+ { \@@_format_put:nw { - } }
+ {
+ \str_case:nnF {#3}
+ {
+ { ~ } { \@@_format_put:ow { \c_catcode_other_space_tl } }
+ { + } { \@@_format_put:nw { + } }
+ }
+ { \@@_format_put:nw { { } } }
+ }
+ \@@_format_put:nw { {#4} }
+ \tl_if_empty:nF {#5}
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-precision-format } {#5} {int}
+ }
+ \str_case:nnF {#6}
+ {
+ { ? } { \@@_format_int:NwnnNNn \use:n }
+ { d } { \@@_format_int:NwnnNNn \use:n }
+ { b } { \@@_format_int:NwnnNNn \int_to_bin:n }
+ { o } { \@@_format_int:NwnnNNn \int_to_oct:n }
+ { X } { \@@_format_int:NwnnNNn \int_to_Hex:n }
+ }
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-style-format } {#6} { int }
+ \@@_format_int:NwnnNNn \use:n
+ }
+ \s_stop {#7}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_int:NwnnNNn}
+% \begin{quote}
+% \cs{@@_format_int:NwnnNNn} \meta{function} \cs{s_stop}
+% \Arg{width} \Arg{sign} \meta{fill} \meta{alignment} \Arg{integer}
+% \end{quote}
+% Use the |format_align| function corresponding to the
+% \meta{alignment}, with the following arguments:
+% \begin{itemize}
+% \item the string formed by combining the sign |#4| with the result
+% of converting the absolute value of the \meta{integer} |#7|
+% according to the conversion function |#1|;
+% \item the \meta{width};
+% \item the \meta{fill} character.
+% \end{itemize}
+% \begin{macrocode}
+\cs_new:Npn \@@_format_int:NwnnNNn #1#2 \s_stop #3#4#5#6#7
+ {
+ \exp_args:Nc \exp_args:Nf
+ { @@_format_align_#6:nnnN }
+ { #1 { \int_abs:n {#7} } }
+ {#4}
+ {#3} #5
+ }
+% \end{macrocode}
+% ^^A todo: note similarity with \@@_format_tl_s:NNnnNNn
+% \end{macro}
+%
+% \subsection{Formatting floating points}
+%
+% \begin{macro}[EXP]{\fp_format:nn}
+% Evaluate the first argument to a floating point number, and feed it
+% to \cs{@@_format_fp:nn}. It would be more efficient to use internal
+% floating point numbers but efficiency is not essential and the code
+% is cleaner this way.
+% \begin{macrocode}
+\cs_new:Npn \fp_format:nn #1
+ { \exp_args:Nf \@@_format_fp:nn { \fp_to_tl:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp:nn}
+% Parse the \meta{format specification} and feed it to
+% \cs{@@_format_fp:NNNnnNn}. Then convert the result to a string
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp:nn #1#2
+ {
+ \tl_to_str:f
+ {
+ \exp_last_unbraced:Nf \@@_format_fp:NNNnnNn
+ { \@@_format_parse:n {#2} }
+ {#1}
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp:NNNnnNn}
+% \begin{quote}
+% \cs{@@_format_fp:NNNnnNn} \meta{fill} \meta{alignment}
+% \meta{format sign} \Arg{width} \Arg{precision} \meta{style}
+% \Arg{floating point}
+% \end{quote}
+% First set the default alignment |?| to |>|. Place the modified
+% information after a trailing \cs{s_stop} for later retrieval. Then
+% check the \meta{format sign} and the \meta{fp sign}: if the floating
+% point is negative, always put~|-|. Otherwise (including
+% \texttt{nan}), if the format's \meta{sign} is |~|, put a space (with
+% category \enquote{other}); if it is~|+| put |+|; if it is |-|
+% (default), put nothing, represented as a brace group. The width
+% |#4| will be useful later, store it after \cs{s_stop}. Afterwards,
+% check the \meta{precision}: if it was not given, replace it by $6$
+% (default precision) unless no \meta{style} was given: in that case
+% we want to use whatever precision is needed to fully describe the
+% number. Finally, dispatch depending on the \meta{style}.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp:NNNnnNn
+ #1#2#3#4#5#6#7
+ {
+ \token_if_eq_charcode:NNTF #2 ?
+ { \@@_format_put:nw { #1 > } }
+ { \@@_format_put:nw { #1 #2 } }
+ \tl_if_head_eq_charcode:nNTF {#7} -
+ { \@@_format_put:nw { - } }
+ {
+ \str_case:nnF {#3}
+ {
+ { ~ } { \@@_format_put:ow { \c_catcode_other_space_tl } }
+ { + } { \@@_format_put:nw { + } }
+ }
+ { \@@_format_put:nw { { } } }
+ }
+ \@@_format_put:nw { {#4} }
+ \tl_if_empty:nTF {#5}
+ {
+ \token_if_eq_meaning:NNTF #6 ?
+ { \@@_format_put:nw { { } } }
+ { \@@_format_put:nw { { 6} } }
+ }
+ { \@@_format_put:nw { {#5} } }
+ \str_case:nnF {#6}
+ {
+ { e } { \@@_format_fp:wnnnNNn \@@_format_fp_e:nn }
+ { f } { \@@_format_fp:wnnnNNn \@@_format_fp_f:nn }
+ { g } { \@@_format_fp:wnnnNNn \@@_format_fp_g:nn }
+ { ? } { \@@_format_fp:wnnnNNn \@@_format_fp_g:nn }
+ }
+ {
+ \__kernel_msg_expandable_error:nnnn
+ { str } { invalid-style-format } {#6} { fp }
+ \@@_format_fp:wnnnNNn \@@_format_fp_g:nn
+ }
+ \s_stop {#7}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp:wnnnNNn}
+% \begin{quote}
+% \cs{@@_format_fp:wnnnNNn} \meta{formatting function} \cs{s_stop}
+% \Arg{precision} \Arg{width} \Arg{sign} \meta{fill}
+% \meta{alignment} \Arg{floating point}
+% \end{quote}
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp:wnnnNNn
+ #1 \s_stop #2 #3 #4 #5#6 #7
+ {
+ \exp_args:Nc \exp_args:Nf
+ { @@_format_align_#6:nnnN }
+ { #1 {#7} {#2} }
+ {#4}
+ {#3} #5
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp_round:nn}
+% Round the given floating point and take the absolute value (in this
+% order, to play nicely with unusual rounding modes if we ever
+% implement these).
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp_round:nn #1 #2
+ { \fp_to_tl:n { abs ( round ( #1 , #2 - logb(#1) - 1 ) ) } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp_e:nn}
+% \begin{macro}[EXP]{\@@_format_fp_e_aux:nn, \@@_format_fp_e_aux:wwn}
+% With the |e| type, first round to |#4+1| significant figures (one
+% before the decimal separator, |#4| after), then filter out special
+% cases, then convert to scientific notations. This order is
+% important because rounding can produce infinities or zeros and
+% because \cs{fp_to_scientific:n} does not accept |nan| nor |inf|.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp_e:nn #1#2
+ {
+ \exp_args:Nf \@@_format_fp_e_aux:nn
+ { \@@_format_fp_round:nn {#1} { #2 + 1 } }
+ {#2}
+ }
+\cs_new:Npn \@@_format_fp_e_aux:nn #1#2
+ {
+ \str_case:nnF {#1}
+ {
+ { inf } { inf }
+ { nan } { nan }
+ }
+ {
+ \exp_last_unbraced:Nf \@@_format_fp_e_aux:wwn
+ { \fp_to_scientific:n {#1} } ;
+ {#2}
+ }
+ }
+\cs_new:Npn \@@_format_fp_e_aux:wwn #1 . #2 e #3 ; #4
+ {
+ \@@_format_put:nw { e #3 }
+ \int_compare:nNnTF {#4} < \c__fp_prec_int
+ {
+ \@@_format_put:fw { \str_range:nnn { #2 } { 1 } { #4 } }
+ \@@_format_put:nw { #1 . }
+ }
+ {
+ \@@_format_put:fw
+ { \prg_replicate:nn { #4 - \c__fp_prec_int + 1 } { 0 } }
+ \@@_format_put:nw { #1 . #2 }
+ }
+ \use_none:n \s_stop
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp_f:nn}
+% \begin{macro}[EXP]{\@@_format_fp_f_aux:wwwn}
+% With the |f| type, first round to |#4| (absolute) decimal places
+% then filter out special cases, then in normal cases pad with zeros.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp_f:nn #1#2
+ {
+ \exp_args:Nf \@@_format_fp_f_aux:nn
+ { \fp_to_tl:n { abs ( round ( #1 , #2 ) ) } }
+ {#2}
+ }
+\cs_new:Npn \@@_format_fp_f_aux:nn #1#2
+ {
+ \str_case:nnF {#1}
+ {
+ { inf } { inf }
+ { nan } { nan }
+ }
+ {
+ \exp_last_unbraced:Nf \@@_format_fp_f_aux:wwwn
+ { \fp_to_decimal:n {#1} } . . ; {#2}
+ }
+ }
+\cs_new:Npn \@@_format_fp_f_aux:wwwn #1 . #2 . #3 ; #4
+ {
+ \use:nf { #1 . #2 }
+ { \prg_replicate:nn { #4 - \@@_count:n {#2} } {0} }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp_g:nn}
+% \begin{macro}[EXP]{\@@_format_fp_g_aux:wn}
+% \begin{macro}[EXP]{\@@_format_fp_to_scientific:n, \@@_format_fp_trim:w}
+% With the |g| type, a special case is when |#2| is empty (no style
+% nor precision in the original specification): then we output the
+% number without rounding (and without its sign). Otherwise round to
+% |#2| significant figures before filtering out special cases. (A
+% \meta{precision} of $0$ is treated like a precision of $1$.)
+% Distinguish exponents $-4\leq \meta{exponent} < \meta{precision}$
+% from others and use essentially the |f| or |e| presentations in
+% these two cases, but trimming trailing zeros. Because we don't need
+% to keep a fixed number of digits after the decimal point we can
+% simply use \cs{fp_to_decimal:n} and \cs{fp_to_scientific:n}, and in
+% the second case post-process the result by trimming zeros and a
+% period.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp_g:nn #1#2
+ {
+ \tl_if_empty:nTF {#2} { \fp_to_tl:n { abs(#1) } }
+ {
+ \exp_args:Nf \@@_format_fp_g_aux:nn
+ { \@@_format_fp_round:nn {#1} { \int_max:nn {1} {#2} } }
+ { \int_max:nn {1} {#2} }
+ }
+ }
+\cs_new:Npn \@@_format_fp_g_aux:nn #1#2
+ {
+ \str_case:nnF {#1}
+ {
+ { 0 } { 0 }
+ { inf } { inf }
+ { nan } { nan }
+ }
+ {
+ \fp_compare:nTF { 1e-4 <= #1 < 1e#2 }
+ { \fp_to_decimal:n {#1} }
+ {
+ \exp_last_unbraced:Nf \@@_format_fp_trim:w
+ { \fp_to_scientific:n {#1} }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[EXP]{\@@_format_fp_trim:w}
+% \begin{macro}[EXP]
+% {\@@_format_fp_trim_loop:w, \@@_format_fp_trim_dot:w, \@@_format_fp_trim_end:w}
+% If |#1| ends with a $0$, the \texttt{loop} auxiliary takes that zero
+% as an end-delimiter for its first argument, and the second argument
+% is the same \texttt{loop} auxiliary. Once the last trailing zero is
+% reached, the second argument is the \texttt{dot} auxiliary,
+% which removes a trailing dot if any. We then clean-up with the
+% \texttt{end} auxiliary, keeping only the number.
+% \begin{macrocode}
+\cs_new:Npn \@@_format_fp_trim:w #1 e
+ {
+ \@@_format_fp_trim_loop:w #1
+ ; \@@_format_fp_trim_loop:w 0; \@@_format_fp_trim_dot:w .; \s_stop e
+ }
+\cs_new:Npn \@@_format_fp_trim_loop:w #1 0; #2 { #2 #1 ; #2 }
+\cs_new:Npn \@@_format_fp_trim_dot:w #1 .; { \@@_format_fp_trim_end:w #1 ; }
+\cs_new:Npn \@@_format_fp_trim_end:w #1 ; #2 \s_stop { #1 }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Messages}
+%
+% All of the messages are produced expandably, so there is no need for
+% an extra-text.
+% \begin{macrocode}
+\__kernel_msg_new:nnn { str } { invalid-format }
+ { Invalid~format~'#1'. }
+\__kernel_msg_new:nnn { str } { invalid-align-format }
+ { Invalid~alignment~'#1'~for~type~'#2'. }
+\__kernel_msg_new:nnn { str } { invalid-sign-format }
+ { Invalid~sign~'#1'~for~type~'#2'. }
+\__kernel_msg_new:nnn { str } { invalid-precision-format }
+ { Invalid~precision~'#1'~for~type~'#2'. }
+\__kernel_msg_new:nnn { str } { invalid-style-format }
+ { Invalid~style~'#1'~for~type~'#2'. }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3str-format.pdf b/macros/latex/contrib/l3experimental/l3str-format.pdf
new file mode 100644
index 0000000000..5ccd8f3762
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3str-format.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/l3str.ins b/macros/latex/contrib/l3experimental/l3str.ins
new file mode 100644
index 0000000000..d3f00364e1
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3str.ins
@@ -0,0 +1,58 @@
+\iffalse meta-comment
+
+File l3str.ins
+
+Copyright (C) 2011-2017,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2011-2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3str-format.sty} {\from{l3str-format.dtx} {package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3sys-shell.dtx b/macros/latex/contrib/l3experimental/l3sys-shell.dtx
new file mode 100644
index 0000000000..4f8c3bee40
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3sys-shell.dtx
@@ -0,0 +1,372 @@
+% \iffalse meta-comment
+%
+%% File: l3sys-shell.dtx
+%
+% Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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|package>
+\RequirePackage{expl3}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \pkg{l3sys-shell} package\\ System shell functions^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \begin{function}[added = 2018-07-28]{\sys_shell_cp:nn}
+% \begin{syntax}
+% \cs{sys_shell_cp:nn} \Arg{source} \Arg{dest}
+% \end{syntax}
+% Copies the files specified in the \meta{source} (which may include
+% wildcards) to the \meta{dest}. The file paths should be specified using
+% |/| as a path separator. Copying is \emph{not} recursive: only files at
+% the path level given are copied. If unrestricted shell escape is not
+% enabled, no action is attempted.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-27]{\sys_shell_mkdir:n}
+% \begin{syntax}
+% \cs{sys_shell_mkdir:n} \Arg{directory}
+% \end{syntax}
+% Creates the \meta{directory}, which should should be specified using |/| as
+% a path separator. If unrestricted shell escape is not enabled, no action is
+% attempted.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-28]{\sys_shell_mv:nn}
+% \begin{syntax}
+% \cs{sys_shell_mv:nn} \Arg{old} \Arg{new}
+% \end{syntax}
+% Moves the files from the \meta{old} to \meta{new} names/locations: the
+% \meta{old} names may include wildcards. In both arguments, |/| should be
+% used as the path separator. If unrestricted shell escape is not enabled, no
+% action is attempted.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-27]{\sys_shell_rm:n}
+% \begin{syntax}
+% \cs{sys_shell_rm:n} \Arg{files}
+% \end{syntax}
+% Removes the \meta{files} (which may include wildcards). The file path should
+% be specified using |/| as a path separator. If unrestricted shell escape is
+% not enabled, no action is attempted.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-27]{\sys_shell_rmdir:n}
+% \begin{syntax}
+% \cs{sys_shell_rmdir:n} \Arg{directory}
+% \end{syntax}
+% Removes the \meta{directory}, which should should be specified using |/| as
+% a path separator. If unrestricted shell escape is not enabled, no action is
+% attempted.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-28]{\sys_shell_get_pwd:N}
+% \begin{syntax}
+% \cs{sys_shell_get_pwd:N} \meta{str var}
+% \end{syntax}
+% Sets the \meta{str var} to the present working directory (the output of
+% |pwd| on Unix or |cd| on Windows). Note that on Windows this will contain
+% string backslash chars (|\|). If unrestricted shell escape is not
+% enabled, no action is taken.
+% \end{function}
+%
+% \begin{function}[added = 2018-07-28]{\sys_shell_split_ls:nN}
+% \begin{syntax}
+% \cs{sys_shell_split_ls:N} \Arg{glob} \meta{seq}
+% \end{syntax}
+% Sets the \meta{seq var} to contain one entry per directory listing
+% (equivalent to |ls -1| or |dir /b|) as specified by the \meta{glob}: the
+% entries are strings. If unrestricted shell is not enabled, no action is
+% taken.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{l3sys-shell} implementation}
+%
+% \begin{macrocode}
+%<*initex|package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=sys>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<*package>
+\ProvidesExplPackage{l3sys-shell}{2019-08-25}{}
+ {L3 Experimental system shell functions}
+%</package>
+% \end{macrocode}
+%
+% \begin{macro}[EXP]{\@@_path_to_win:n}
+% \begin{macro}[EXP]{\@@_path_to_win:w}
+% \begin{macro}[EXP]{\@@_path_to_win:N}
+% A simple expandable search-and-replace for providing Windows-style paths.
+% \begin{macrocode}
+\cs_new:Npn \@@_path_to_win:n #1
+ {
+ \exp_after:wN \@@_path_to_win:w \tl_to_str:n {#1} ~ \q_stop
+ }
+\cs_new:Npn \@@_path_to_win:w #1 ~ #2 \q_stop
+ {
+ \@@_path_to_win:N #1 \q_nil
+ \tl_if_empty:nF {#2}
+ {
+ \c_space_tl
+ \@@_path_to_win:w #2 \q_stop
+ }
+ }
+\cs_new:Npn \@@_path_to_win:N #1
+ {
+ \quark_if_nil:NF #1
+ {
+ \token_if_eq_meaning:NNTF #1 /
+ { \c_backslash_str }
+ {#1}
+ \@@_path_to_win:N
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_cp:nn}
+% Simple Unix-like file copying: at some stage we may need a directory-only
+% version as Windows and Unix have different requirements here.
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_cp:nn #1#2
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \sys_shell_now:x
+ {
+ \sys_if_platform_unix:T
+ {
+ cp~-f~ \exp_not:N \tl_to_str:n {#1} ~
+ \exp_not:N \tl_to_str:n {#2}
+ }
+ \sys_if_platform_windows:T
+ {
+ copy~/y~ \exp_not:N \@@_path_to_win:n {#1} ~
+ \exp_not:N \@@_path_to_win:n {#2}
+ }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_mkdir:n}
+% Windows (with the extensions) will automatically make directory trees but
+% issues a warning if the directory already exists: avoid by including a
+% test.
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_mkdir:n #1
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \sys_shell_now:x
+ {
+ \sys_if_platform_unix:T
+ { mkdir~-p~ \exp_not:N \tl_to_str:n {#1} }
+ \sys_if_platform_windows:T
+ {
+ if~not~exist~
+ \exp_not:N \@@_path_to_win:n { #1 / nul } ~
+ mkdir~ \exp_not:N \@@_path_to_win:n {#1}
+ }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_mv:nn}
+% On Windows we do not have a single |mv| operation, so copy-and-delete
+% instead.
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_mv:nn #1#2
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \sys_shell_now:x
+ {
+ \sys_if_platform_unix:T
+ {
+ mv~ \exp_not:N \tl_to_str:n {#1} ~
+ \exp_not:N \tl_to_str:n {#2}
+ }
+ \sys_if_platform_windows:T
+ {
+ copy~/y~ \exp_not:N \@@_path_to_win:n {#1} ~
+ \exp_not:N \@@_path_to_win:n {#2}
+ \token_to_str:N & \token_to_str:N &
+ del~/f~/q~\exp_not:N \@@_path_to_win:n {#1}
+ }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_rm:n}
+% Deletion: obviously a big health warning here!
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_rm:n #1
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \sys_shell_now:x
+ {
+ \sys_if_platform_unix:T
+ { rm~-f~ \exp_not:N \tl_to_str:n {#1} }
+ \sys_if_platform_windows:T
+ { del~/f~/q~ \exp_not:N \@@_path_to_win:n {#1} }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_rmdir:n}
+% When removing a directory, we create it first as that avoids errors in
+% the Windows case.
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_rmdir:n #1
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \sys_shell_mkdir:n {#1}
+ \sys_shell_now:x
+ {
+ \sys_if_platform_unix:T
+ { rm~-rf~ \exp_not:N \tl_to_str:n {#1} }
+ \sys_if_platform_windows:T
+ { rmdir~/s~/q~ \exp_not:N \@@_path_to_win:n {#1} }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_tmp_tl}
+% Scratch space.
+% \begin{macrocode}
+\tl_new:N \l_@@_tmp_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\sys_shell_get_pwd:N}
+% Getting the path is easy: the main work is avoiding loosing any
+% information. (This information can be obtained using the recorder file:
+% that does not require shell escape. However, it's hard to see how it might
+% be useful without the other file functions.)
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_get_pwd:N #1
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \exp_not:N \sys_shell_get:nnN
+ {
+ \sys_if_platform_unix:T { pwd }
+ \sys_if_platform_windows:T { cd }
+ }
+ {
+ \char_set_catcode_other:N \exp_not:N \\
+ \char_set_catcode_other:N \exp_not:N \#
+ \char_set_catcode_other:N \exp_not:N \~
+ \char_set_catcode_other:N \exp_not:N \%
+ \char_set_catcode_space:N \exp_not:N \ %
+ \tex_endlinechar:D -1 \scan_stop:
+ }
+ \exp_not:N \l_@@_tmp_tl
+ \str_set:NV #1 \exp_not:N \l_@@_tmp_tl
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\sys_shell_split_ls:nN}
+% Getting a one-per-line listing is easy enough. We need to set
+% \cs{ExplSyntaxOff} as that deals with the end-of-line character. After that,
+% just a case of tidying up. The listing always ends in |^^M| so there is an
+% extra entry to trim.
+% \begin{macrocode}
+\cs_new_protected:Npx \sys_shell_split_ls:nN #1#2
+ {
+ \sys_if_shell_unrestricted:T
+ {
+ \exp_not:N \sys_shell_get:nnN
+ {
+ \sys_if_platform_unix:T { ls~-1~ #1 }
+ \sys_if_platform_windows:T { dir~/b~ #1 }
+ }
+ {
+ \ExplSyntaxOff
+ \char_set_catcode_other:N \exp_not:N \\
+ \char_set_catcode_other:N \exp_not:N \#
+ \char_set_catcode_other:N \exp_not:N \~
+ \char_set_catcode_other:N \exp_not:N \%
+ \char_set_catcode_other:n { 13 }
+ }
+ \exp_not:N \l_@@_tmp_tl
+ \str_set:NV \exp_not:N \l_@@_tmp_tl \exp_not:N \l_@@_tmp_tl
+ \seq_set_split:NnV #2 { \char_generate:nn { `\^^M } { 12 } }
+ \exp_not:N \l_@@_tmp_tl
+ \seq_pop_right:NN #2 \exp_not:N \l_@@_tmp_tl
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</initex|package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/l3sys-shell.ins b/macros/latex/contrib/l3experimental/l3sys-shell.ins
new file mode 100644
index 0000000000..acf3c1d710
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3sys-shell.ins
@@ -0,0 +1,62 @@
+\iffalse meta-comment
+
+File: l3sys-shell.ins
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2018,2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3sys-shell.sty}
+ {
+ \from{l3sys-shell.dtx}{package}
+ }
+}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/l3sys-shell.pdf b/macros/latex/contrib/l3experimental/l3sys-shell.pdf
new file mode 100644
index 0000000000..6b890dcc85
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/l3sys-shell.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/xcoffins.dtx b/macros/latex/contrib/l3experimental/xcoffins.dtx
new file mode 100644
index 0000000000..3e9f723488
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xcoffins.dtx
@@ -0,0 +1,938 @@
+% \iffalse meta-comment
+%
+%% File: xcoffins.dtx
+%
+% Copyright (C) 2010-2012,2014,2016-2019 The LaTeX3 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
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3experimental 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|package>
+\RequirePackage{xparse}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\usepackage{xcoffins} % Not yet part of expl3, so not in l3doc
+\usepackage{xcolor} % As coffins only loads basic color support
+\NewCoffin \ExampleCoffin
+\NewCoffin \SmallCoffin
+\NewCoffin \OutputCoffin
+\NewCoffin \RedCoffin
+\NewCoffin \BlueCoffin
+\NewCoffin \GreenCoffin
+\NewCoffin \YellowCoffin
+\NewCoffin \OrangeCoffin
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \textsf{xcoffins} package\\ Design-level coffins^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Introduction: the coffin concept}
+%
+% In \LaTeX3 terminology, a \enquote{coffin} is a box containing
+% typeset material. Along with the box itself, the coffin structure
+% includes information on the size and shape of the box, which makes
+% it possible to align two or more coffins easily. This is achieved
+% by providing a series of `poles' for each coffin. These
+% are horizontal and vertical lines through the coffin at defined
+% positions, for example the top or horizontal centre. The points
+% where these poles intersect are called \enquote{handles}. Two
+% coffins can then be aligned by describing the relationship between
+% a handle on one coffin with a handle on the second. In words, an
+% example might then read
+% \begin{quote}
+% Align the top-left handle of coffin A with the bottom-right
+% handle of coffin B.
+% \end{quote}
+%
+% The locations of coffin handles are much easier to understand
+% visually. Figure~\ref{fgr:handles} shows the standard handle
+% positions for a coffin typeset in horizontal mode (left) and in
+% vertical mode (right). Notice that the later case results in a greater
+% number of handles being available. As illustrated, each handle
+% results from the intersection of two poles. For example, the centre
+% of the coffin is marked |(hc,vc)|, \emph{i.e.}~it is the
+% point of intersection of the horizontal centre pole with the
+% vertical centre pole. New handles are generated automatically when
+% poles are added to a coffin: handles are \enquote{dynamic} entities.
+%
+% \begin{figure}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \SetHorizontalCoffin\ExampleCoffin
+% {\color{black!10!white}\rule{1 in}{1 in}}
+% \DisplayCoffinHandles\ExampleCoffin{blue}
+% \end{minipage}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \SetVerticalCoffin\ExampleCoffin{1 in}
+% {\color{black!10!white}\rule{1 in}{1 in}}
+% \DisplayCoffinHandles\ExampleCoffin{blue}
+% \end{minipage}
+% \hfil
+% \caption{Standard coffin handles: left, horizontal coffin; right,
+% vertical coffin}
+% \label{fgr:handles}
+% \end{figure}
+%
+%\section{Creating and setting coffins}
+%
+% Before any alignment can take place, coffins must be created and
+% their contents must be created. All coffin operations are local
+% to the current \TeX{} group with the exception of coffin creation.
+% \begin{function}{\NewCoffin}
+% \begin{syntax}
+% \cs{NewCoffin} \meta{coffin}
+% \end{syntax}
+% Before a \meta{coffin} can be used, it must be allocated using
+% \cs{NewCoffin}. The name of the \meta{coffin} should be a
+% control sequence (starting with the escape character, usually
+% |\|), for example
+% \begin{verbatim}
+% \NewCoffin\MyCoffin
+% \end{verbatim}
+% Coffins are allocated globally, and an error will be raised if the
+% name of the \meta{coffin} is not globally-unique.
+% \end{function}
+%
+% \begin{function}{\SetHorizontalCoffin}
+% \begin{syntax}
+% \cs{SetHorizontalCoffin} \meta{coffin} \Arg{material}
+% \end{syntax}
+% Typesets the \meta{material} in horizontal mode, storing the result
+% in the \meta{coffin}. The standard poles for the \meta{coffin} are
+% then set up based on the size of the typeset material.
+% \end{function}
+%
+% \begin{function}{\SetVerticalCoffin}
+% \begin{syntax}
+% \cs{SetVerticalCoffin} \meta{coffin} \Arg{width} \Arg{material}
+% \end{syntax}
+% Typesets the \meta{material} in vertical mode constrained to the
+% given \meta{width} and stores the result in the \meta{coffin}. The
+% standard poles for the \meta{coffin} are then set up based on the
+% size of the typeset material.
+% \end{function}
+%
+% \section{Controlling coffin poles}
+%
+% A number of standard poles are automatically generated when the coffin
+% is set or an alignment takes place. The standard poles for all coffins
+% are:
+% \begin{itemize}[font = \ttfamily]
+% \item[l] a pole running along the left-hand edge of the bounding
+% box of the coffin;
+% \item[hc] a pole running vertically through the centre of the coffin
+% half-way between the left- and right-hand edges of the bounding
+% box (\emph{i.e.}~the \enquote{horizontal centre});
+% \item[r] a pole running along the right-hand edge of the bounding
+% box of the coffin;
+% \item[b] a pole running along the bottom edge of the bounding
+% box of the coffin;
+% \item[vc] a pole running horizontally through the centre of the
+% coffin half-way between the bottom and top edges of the bounding
+% box (\emph{i.e.}~the \enquote{vertical centre});
+% \item[t] a pole running along the top edge of the bounding
+% box of the coffin;
+% \item[H] a pole running along the baseline of the typeset material
+% contained in the coffin.
+% \end{itemize}
+% In addition, coffins containing vertical-mode material also
+% feature poles which reflect the richer nature of these systems:
+% \begin{itemize}
+% \item[B] a pole running along the baseline of the material at the
+% bottom of the coffin.
+% \item[T] a pole running along the baseline of the material at the top
+% of the coffin.
+% \end{itemize}
+%
+% \begin{function}{\SetHorizontalPole}
+% \begin{syntax}
+% \cs{SetHorizontalPole} \meta{coffin} \Arg{pole} \Arg{offset}
+% \end{syntax}
+% Sets the \meta{pole} to run horizontally through the \meta{coffin}.
+% The \meta{pole} will be located at the \meta{offset} from the
+% bottom edge of the bounding box of the \meta{coffin}. The
+% \meta{offset} should be given as a dimension expression; this may
+% include the terms \cs{TotalHeight}, \cs{Height}, \cs{Depth} and
+% \cs{Width}, which will evaluate to the appropriate dimensions of
+% the \meta{coffin}. For example, to create a pole running
+% horizontally through the coffin at one third of the distance from
+% the base of the coffin to the top, the appropriate instruction would
+% be
+% \begin{verbatim}
+% \SetHorizontalPole \MyCoffin {height/3} {\TotalHeight/3}
+% \end{verbatim}
+% Note that poles which run \emph{horizontally} are described in terms
+% of their \emph{vertical} location in the coffin. Also notice that
+% the total height of the coffin is described by the sum of
+% \cs{Height} and \cs{Depth}: these are both measured from the
+% horizontal baseline of the material in the coffin.
+% \end{function}
+%
+% \begin{function}{\SetVerticalPole}
+% \begin{syntax}
+% \cs{SetVerticalPole} \meta{coffin} \Arg{pole} \Arg{offset}
+% \end{syntax}
+% Sets the \meta{pole} to run vertically through the \meta{coffin}.
+% The \meta{pole} will be located at the \meta{offset} from the
+% left-hand edge of the bounding box of the \meta{coffin}. The
+% \meta{offset} should be given as a dimension expression; this may
+% include the terms \cs{TotalHeight}, \cs{Height}, \cs{Depth} and
+% \cs{Width}, which will evaluate to the appropriate dimensions of
+% the \meta{coffin}. For example, to create a pole running vertically
+% through the coffin at one third of the distance from the left-hand
+% edge, the appropriate instruction would be
+% \begin{verbatim}
+% \SetVerticalPole \MyCoffin {width/3} {\Width/3}
+% \end{verbatim}
+% Note that poles which run \emph{vertically} are described in terms
+% of their \emph{horizontal} location in the coffin.
+% \end{function}
+%
+% \begin{function}{\TotalHeight}
+% \begin{syntax}
+% \cs{TotalHeight}
+% \end{syntax}
+% Within the \meta{offset} argument of \cs{SetHorizontalPole} and
+% \cs{SetVerticalPole}, \cs{TotalHeight} will give the distance from
+% the base to the top of the bounding box of the relevant coffin.
+% \end{function}
+%
+% \begin{function}{\Height}
+% \begin{syntax}
+% \cs{Height}
+% \end{syntax}
+% Within the \meta{offset} argument of \cs{SetHorizontalPole} and
+% \cs{SetVerticalPole}, \cs{Height} will give the distance from the
+% baseline to the top of the bounding box of the relevant coffin.
+% \end{function}
+%
+% \begin{function}{\Depth}
+% \begin{syntax}
+% \cs{Depth}
+% \end{syntax}
+% Within the \meta{offset} argument of \cs{SetHorizontalPole} and
+% \cs{SetVerticalPole}, \cs{Depth} will give the distance from the
+% baseline to the bottom of the bounding box of the relevant coffin.
+% \end{function}
+%
+% \begin{function}{\Width}
+% \begin{syntax}
+% \cs{Width}
+% \end{syntax}
+% Within the \meta{offset} argument of \cs{SetHorizontalPole} and
+% \cs{SetVerticalPole}, \cs{Width} will give the distance from the
+% right edge to the left edge of the bounding box of the relevant
+% coffin.
+% \end{function}
+%
+% \section{Rotating coffins}
+%
+% \begin{function}{\RotateCoffin}
+% \begin{syntax}
+% \cs{RotateCoffin} \meta{coffin} \Arg{angle}
+% \end{syntax}
+% Rotates the \meta{coffin} by the given \meta{angle} about its reference
+% point (given in degrees counter-clockwise) . This process will rotate both
+% the coffin content and poles. Multiple rotations will not result in
+% the bounding box of the coffin growing unnecessarily.
+%
+% The effect of rotation on a coffin is illustrated in
+% Figure~\ref{fgr:rotation}. As is shown, the coffin handles will
+% remain correctly positioned relative to the content of the coffin.
+% The \enquote{top} of a rotated coffin may of course no longer be the
+% edge closest to the top of the physical page.
+% \end{function}
+%
+% \begin{figure}
+% \hfil
+% \SetHorizontalCoffin\ExampleCoffin
+% {^^A
+% \color{black!10!white}\rule{0.5 in}{1 in}^^A
+% \color{black!20!white}\rule{0.5 in}{1 in}^^A
+% }
+% \begin{minipage}{0.4\textwidth}
+% \DisplayCoffinHandles\ExampleCoffin{blue}
+% \end{minipage}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \RotateCoffin\ExampleCoffin{45}
+% \DisplayCoffinHandles\ExampleCoffin{blue}
+% \end{minipage}
+% \hfil
+% \caption{Coffin rotation: left, unrotated; right, rotated by
+% $45$\textdegree.}
+% \label{fgr:rotation}
+% \end{figure}
+%
+% \section{Resizing coffins}
+%
+% \begin{function}{\ResizeCoffin}
+% \begin{syntax}
+% \cs{ResizeCoffin} \meta{coffin} \Arg{width} \Arg{total-height}
+% \end{syntax}
+% Resized the \meta{coffin} to \meta{width} and \meta{total-height},
+% both of which should be given as dimension expressions.
+% \end{function}
+%
+% \begin{function}{\ScaleCoffin}
+% \begin{syntax}
+% \cs{ScaleCoffin} \meta{coffin} \Arg{x-scale} \Arg{y-scale}
+% \end{syntax}
+% Scales the \meta{coffin} by a factors \meta{x-scale} and
+% \meta{y-scale} in the horizontal and vertical directions,
+% respectively. The two scale factors should be given as real numbers.
+% \end{function}
+%
+% \cs{ResizeCoffin} and \cs{ScaleCoffin} can be used interchangeably:
+% whether scale factors or absolute values are the best form for the
+% resizing will depend upon the context (Figure~\ref{fgr:resizing}).
+%
+% \begin{figure}
+% \hfil
+% \SetHorizontalCoffin\ExampleCoffin
+% {^^A
+% \color{black!10!white}\rule{0.5 in}{1 in}^^A
+% \color{black!20!white}\rule{0.5 in}{1 in}^^A
+% }
+% \begin{minipage}{0.4\textwidth}
+% \ResizeCoffin \ExampleCoffin {4 cm} {3 cm}
+% \DisplayCoffinHandles \ExampleCoffin {blue}
+% \end{minipage}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \ScaleCoffin \ExampleCoffin {2.0} {0.5}
+% \DisplayCoffinHandles \ExampleCoffin {blue}
+% \end{minipage}
+% \hfil
+% \caption{Coffin resizing: left, resized to exactly $4$\,cm by
+% $6$\,cm; right, scaled a factors of $2$ and $0.5$ in
+% $x$ and $y$, respectively (example coffin as in
+% Figure~\ref{fgr:rotation}).}
+% \label{fgr:resizing}
+% \end{figure}
+%
+% \section{Joining coffins}
+%
+% The key operation for coffins is joining coffins to each other. This
+% is always carried out such that the first coffin is the
+% \enquote{parent}, and is updated by the alignment. The second
+% \enquote{child} coffin is not altered by the alignment process.
+%
+% \begin{function}{\JoinCoffins}
+% \begin{syntax}
+% \cs{JoinCoffins} *
+% ~~\meta{coffin1} [ \meta{coffin1-pole1} , \meta{coffin1-pole2} ]
+% ~~\meta{coffin2} [ \meta{coffin2-pole1} , \meta{coffin2-pole2} ]
+% ~~( \meta{x-offset} , \meta{y-offset} )
+% \end{syntax}
+% Joining of two coffins is carried out by the \cs{JoinCoffins}
+% function, which takes two mandatory arguments: the \enquote{parent}
+% \meta{coffin1} and the \enquote{child} \meta{coffin2}. All of the
+% other arguments shown are optional.
+% \end{function}
+%
+% The standard \cs{JoinCoffins} functions joins \meta{coffin2} to
+% \meta{coffin1} such that the bounding box of \meta{coffin1} after the
+% process will expand. The new bounding box will be the smallest
+% rectangle covering the bounding boxes of the two input coffins.
+% When the starred variant of \cs{JoinCoffins} is used, the bounding
+% box of \meta{coffin1} is not altered, \emph{i.e.}~\meta{coffin2} may
+% protrude outside of the bounding box of the updated \meta{coffin1}.
+% The difference between the two forms of alignment is best illustrated
+% using a visual example. In Figure~\ref{fgr:alignment}, the two
+% processes are contrasted. In both cases, the small red coffin has been
+% aligned with the large grey coffin. In the left-hand illustration,
+% the \cs{JoinCoffins} function was used, resulting in an expanded
+% bounding box. In contrast, on the right \cs{AttachCoffin} was used,
+% meaning that the bounding box does not include the area of the
+% smaller coffin.
+%
+% \begin{figure}
+% \fboxsep 0 pt\relax
+% \SetHorizontalCoffin\ExampleCoffin
+% {\color{black!20!white}\rule{1 in}{1 in}}
+% \SetHorizontalCoffin \SmallCoffin
+% {\color{red!20!white}\rule{0.1 in}{0.1 in}}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \centering
+% \JoinCoffins\ExampleCoffin[vc,r]\SmallCoffin[vc,l]
+% \fbox{\TypesetCoffin\ExampleCoffin}
+% \end{minipage}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \centering
+% \JoinCoffins*\ExampleCoffin[vc,r]\SmallCoffin[vc,l]
+% \fbox{\TypesetCoffin\ExampleCoffin}%
+% \end{minipage}
+% \hfil
+% \caption{Contrast between \cs{JoinCoffins} (left) and
+% \cs{JoinCoffins*} (right); the bounding box of the coffin is show
+% in black.}
+% \label{fgr:alignment}
+% \end{figure}
+%
+% The alignment is carried out by first calculating \meta{handle1}, the
+% point of intersection of \meta{coffin1-pole1} and
+% \meta{coffin1-pole2}, and \meta{handle2}, the point of intersection
+% of \meta{coffin2-pole1} and \meta{coffin2-pole2}. If the two
+% \meta{poles} are not specified, \cs{JoinCoffins} will use the
+% default value |(H,l)|, \emph{i.e.}~the reference point used by \TeX\
+% for the underlying box. Once the two \meta{handles} have been
+% located, \meta{coffin2} is then attached to \meta{coffin1} such that
+% the relationship between \meta{handle1} and \meta{handle2} is
+% described by the \meta{x-offset} and \meta{y-offset}. This
+% \meta{offset} is an optional argument, and if it is not given then
+% |(0 pt, 0 pt)| is used.
+%
+% Notice that when \cs{JoinCoffins} is used the new bounding box is
+% the smallest rectangle containing the bounding boxes of the two input
+% coffins. As a result, it will include additional white space unless
+% one coffin entirely overlaps the other (Figure~\ref{fgr:bounding},
+% left). Rotation of coffins will take account of the extent of the
+% material after rotation when re-calculating the bounding box. This
+% means that no \emph{unnecessary} white space will be added on
+% rotation (Figure~\ref{fgr:bounding}, right).
+%
+% \begin{figure}
+% \fboxsep 0 pt\relax
+% \SetHorizontalCoffin\ExampleCoffin
+% {\color{black!20!white}\rule{1 in}{1 in}}
+% \SetHorizontalCoffin\SmallCoffin
+% {\color{red!20!white}\rule{0.1 in}{0.1 in}}
+% \JoinCoffins\ExampleCoffin[vc,r]\SmallCoffin[vc,l]
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \centering
+% \fbox{\copy\ExampleCoffin}
+% \end{minipage}
+% \hfil
+% \begin{minipage}{0.4\textwidth}
+% \centering
+% \RotateCoffin\ExampleCoffin{135}
+% \fbox{\copy\ExampleCoffin}
+% \end{minipage}
+% \hfil
+% \caption{The effect of rotation of a joined coffin: the black line
+% shows the coffin bounding box.}
+% \label{fgr:bounding}
+% \end{figure}
+%
+% As part of the joining procedure, the poles of the two input coffins
+% are preserved within the structure of the updated coffin. In this way
+% it is possible to carry out complex alignment procedures. The poles of
+% a coffin after alignment may therefore be divided into three groups:
+% \begin{enumerate}
+% \item The \enquote{native} poles of the updated coffin, such as
+% \texttt{l}, \texttt{r}, \texttt{hc}, \emph{etc}.
+% \item Poles derived from \meta{coffin1}, such as
+% \texttt{\meta{coffin1}-l}, \texttt{\meta{coffin1}-r},
+% \texttt{\meta{coffin1}-hc}, \emph{etc.}
+% \item Poles derived from \meta{coffin2}, such as
+% \texttt{\meta{coffin2}-l}, \texttt{\meta{coffin2}-r},
+% \texttt{\meta{coffin2}-hc}, \emph{etc.}
+% \end{enumerate}
+%
+% Applying this ability allows a series of joining operations to
+% take place, as illustrated in Figure~\ref{fgr:nested}. In this
+% example, the scheme used for alignment was as follows:
+% \begin{verbatim}
+% \SetHorizontalCoffin\OutputCoffin{}
+% \SetHorizontalCoffin\RedCoffin
+% {\color{red!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[vc,hc]\RedCoffin[vc,hc]
+% \SetHorizontalCoffin\BlueCoffin
+% {\color{blue!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\RedCoffin-vc,\RedCoffin-hc]
+% \BlueCoffin[b,l]
+% \SetHorizontalCoffin\GreenCoffin
+% {\color{green!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\BlueCoffin-vc,\BlueCoffin-hc]
+% \GreenCoffin[b,l]
+% \SetHorizontalCoffin\YellowCoffin
+% {\color{yellow!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\GreenCoffin-vc,\GreenCoffin-hc]
+% \YellowCoffin[b,l]
+% \SetHorizontalCoffin \OrangeCoffin
+% {\color{orange!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\BlueCoffin-t,\BlueCoffin-l]
+% \OrangeCoffin[b,r]
+% \TypesetCoffin\OutputCoffin
+% \end{verbatim}
+% This process begins by setting up \cs{OutputCoffin} to hold the joined
+% output. Each join then takes place placing the new addition relative
+% to the previous one. As each coffin joined has a unique name it is
+% possible to align relative to each one of the component parts of the
+% assembly. This is illustrated by the addition of the final
+% \cs{OrangeCoffin} based on the earlier placement of the
+% \cs{BlueCoffin}.
+%
+% \begin{figure}
+% \centering
+% \SetHorizontalCoffin\OutputCoffin{}
+% \SetHorizontalCoffin\RedCoffin
+% {\color{red!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[vc,hc]\RedCoffin[vc,hc]
+% \SetHorizontalCoffin\BlueCoffin
+% {\color{blue!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\RedCoffin-vc,\RedCoffin-hc]
+% \BlueCoffin[b,l]
+% \SetHorizontalCoffin\GreenCoffin
+% {\color{green!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\BlueCoffin-vc,\BlueCoffin-hc]
+% \GreenCoffin[b,l]
+% \SetHorizontalCoffin\YellowCoffin
+% {\color{yellow!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\GreenCoffin-vc,\GreenCoffin-hc]
+% \YellowCoffin[b,l]
+% \SetHorizontalCoffin \OrangeCoffin
+% {\color{orange!20!white}\rule{0.2 in}{0.2 in}}
+% \JoinCoffins\OutputCoffin[\BlueCoffin-t,\BlueCoffin-l]
+% \OrangeCoffin[b,r]
+% \TypesetCoffin\OutputCoffin
+% \caption{Aligning coffins using poles from previous operations.}
+% \label{fgr:nested}
+% \end{figure}
+%
+% \section{Typesetting coffins}
+%
+% \begin{function}{\TypesetCoffin}
+% \begin{syntax}
+% \cs{TypesetCoffin}
+% ~~\meta{coffin} [ \meta{pole1} , \meta{pole2} ]
+% ~~( \meta{x-offset} , \meta{y-offset} )
+% \end{syntax}
+% Typesetting is carried out by first calculating \meta{handle}, the
+% point of intersection of \meta{pole1} and \meta{pole2}. This is an
+% optional argument, and if not given then |(H,l)|, the \TeX{}
+% reference point of the underlying box, is used. The coffin
+% is then typeset such that the relationship between the current
+% reference point in the document and the \meta{handle} is described
+% by the \meta{x-offset} and \meta{y-offset}. This \meta{offset} is
+% optional, and if not given |(0 pt, 0 pt)| is used. Typesetting a
+% coffin is therefore analogous to carrying out an alignment where the
+% \enquote{parent} coffin is the current insertion point.
+% \end{function}
+%
+% \section{Measuring coffins}
+%
+% There are places in the design process where it is useful to be able to
+% measure coffins outside of pole-setting procedures.
+%
+% \begin{function}{\CoffinDepth}
+% \begin{syntax}
+% \cs{CoffinDepth} \meta{coffin}
+% \end{syntax}
+% Calculates the depth (below the baseline) of the \meta{coffin}
+% in a form suitable for use in a \meta{dimension expression}, for example
+% |\setlength{\mylength}{\CoffinDepth\ExampleCoffin}|.
+% \end{function}
+%
+% \begin{function}{\CoffinHeight}
+% \begin{syntax}
+% \cs{CoffinHeight} \meta{coffin}
+% \end{syntax}
+% Calculates the height (above the baseline) of the \meta{coffin}
+% in a form suitable for use in a \meta{dimension expression}, for example
+% |\setlength{\mylength}{\CoffinHeight\ExampleCoffin}|.
+% \end{function}
+%
+% \begin{function}{\CoffinTotalHeight}
+% \begin{syntax}
+% \cs{CoffinTotalHeight} \meta{coffin}
+% \end{syntax}
+% Calculates the total height of the \meta{coffin}
+% in a form suitable for use in a \meta{dimension expression}, for example
+% |\setlength{\mylength}{\CoffinTotalHeight\ExampleCoffin}|.
+% \end{function}
+%
+% \begin{function}{\CoffinWidth}
+% \begin{syntax}
+% \cs{CoffinWidth} \meta{coffin}
+% \end{syntax}
+% Calculates the width of the \meta{coffin} in a form
+% suitable for use in a \meta{dimension expression}, for example
+% |\setlength{\mylength}{\CoffinWidth\ExampleCoffin}|.
+% \end{function}
+%
+% \section{Diagnostic functions}
+%
+% Diagnostic data for following the coffin-building process is
+% available both graphically and at the terminal. This reflects the
+% fact that coffins are visual constructs.
+%
+% \begin{function}{\DisplayCoffinHandles}
+% \begin{syntax}
+% \cs{DisplayCoffinHandles} \meta{coffin} \Arg{color}
+% \end{syntax}
+% This function first calculates the intersections between all of
+% the \meta{poles} of the \meta{coffin} to give a set of
+% \meta{handles}. It then prints the \meta{coffin} at the current
+% location in the source, with the position of the \meta{handles}
+% marked on the coffin. The \meta{handles} will be labelled as part
+% of this process: the locations of the \meta{handles} and the labels
+% are both printed in the \meta{color} specified.
+% \end{function}
+%
+% \begin{function}{\MarkCoffinHandle}
+% \begin{syntax}
+% \cs{MarkCoffinHandle} \meta{coffin}
+% ~~[ \meta{pole1} , \meta{pole2} ] \Arg{color}
+% \end{syntax}
+% This function first calculates the \meta{handle} for the
+% \meta{coffin} as defined by the intersection of \meta{pole1} and
+% \meta{pole2}. It then marks the position of the \meta{handle}
+% on the \meta{coffin}. The \meta{handle} will be labelled as part of
+% this process: the location of the \meta{handle} and the label are
+% both printed in the \meta{color} specified. If no \meta{poles} are
+% give, the default |(H,l)| is used.
+% \end{function}
+%
+% \begin{function}{\ShowCoffinStructure}
+% \begin{syntax}
+% \cs{ShowCoffinStructure} \meta{coffin}
+% \end{syntax}
+% This function shows the structural information about the
+% \meta{coffin} in the terminal. The width, height and depth of the
+% typeset material are given, along with the location of all of the
+% poles of the coffin. For example, for the rotated coffin in
+% Figure~\ref{fgr:rotation}, the output of \cs{ShowCoffinStructure}
+% is:
+% \begin{verbatim}
+% Size of coffin \ExampleCoffin:
+% > ht = 72.26999pt
+% > dp = 0.0pt
+% > wd = 72.26999pt
+% Poles of coffin \ExampleCoffin:
+% > l => {0pt}{0pt}{0pt}{1000pt}
+% > B => {0pt}{0pt}{1000pt}{0pt}
+% > H => {0pt}{0pt}{1000pt}{0pt}
+% > T => {0pt}{0pt}{1000pt}{0pt}
+% > hc => {36.135pt}{0pt}{0pt}{1000pt}
+% > r => {72.26999pt}{0pt}{0pt}{1000pt}
+% > vc => {0pt}{36.135pt}{1000pt}{0pt}
+% > t => {0pt}{72.26999pt}{1000pt}{0pt}
+% > b => {0pt}{0.0pt}{1000pt}{0pt}.
+% <recently read> }
+% \end{verbatim}
+% Notice that the poles of a coffin are defined by four values:
+% the $x$ and $y$ co-ordinates of a point that the pole
+% passes through and the $x$- and $y$-components of a
+% vector denoting the direction of the pole. It is the ratio between
+% the later, rather than the absolute values, which determines the
+% direction of the pole.
+% \end{function}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{Implementation}
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=coffin>
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ProvidesExplPackage{xcoffins}{2019-08-25}{}
+ {L3 Experimental design level coffins}
+% \end{macrocode}
+%
+% \begin{variable}
+% {
+% \l_@@_A_hpole_tl ,
+% \l_@@_A_vpole_tl ,
+% \l_@@_B_hpole_tl ,
+% \l_@@_B_vpole_tl ,
+% \l_@@_bound_box_grow_bool ,
+% \l_@@_hoffset_dim ,
+% \l_@@_voffset_dim
+% }
+% Key--value definitions for the alignment system. With the exception
+% of \texttt{grow-bounding-box}, all of these have to be given with a
+% value.
+% \begin{macrocode}
+\keys_define:nn { coffin }
+ {
+ coffin1-hpole .tl_set:N = \l_@@_A_hpole_tl ,
+ coffin1-hpole .value_required:n = true ,
+ coffin1-vpole .tl_set:N = \l_@@_A_vpole_tl ,
+ coffin1-vpole .value_required:n = true ,
+ coffin2-hpole .tl_set:N = \l_@@_B_hpole_tl ,
+ coffin2-hpole .value_required:n = true ,
+ coffin2-vpole .tl_set:N = \l_@@_B_vpole_tl ,
+ coffin2-vpole .value_required:n = true ,
+ grow-bounding-box .bool_set:N = \l_@@_bound_box_grow_bool ,
+ grow-bounding-box .default:n = true ,
+ hoffset .dim_set:N = \l_@@_hoffset_dim ,
+ hoffset .value_required:n = true ,
+ voffset .dim_set:N = \l_@@_voffset_dim ,
+ voffset .value_required:n = true
+ }
+\keys_set:nn { coffin }
+ {
+ coffin1-hpole = H ,
+ coffin1-vpole = l ,
+ coffin2-hpole = H ,
+ coffin2-vpole = l ,
+ grow-bounding-box = true ,
+ hoffset = 0 pt ,
+ voffset = 0 pt
+ }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_design_names:N}
+% \begin{variable}{\Height, \Depth, \Width, \TotalHeight}
+% \begin{variable}
+% {\l_@@_height_dim, \l_@@_depth_dim, \l_@@_width_dim, \l_@@_totalheight_dim}
+% Sets up design-level names for the various coffin dimensions. These are
+% not defined outside of this scope, and are dimensions so that they work
+% correctly inside for example \cs{fp_eval:n}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_design_names:N #1
+ {
+ \dim_set:Nn \l_@@_height_dim { \coffin_ht:N #1 }
+ \dim_set:Nn \l_@@_depth_dim { \coffin_dp:N #1 }
+ \dim_set:Nn \l_@@_width_dim { \coffin_wd:N #1 }
+ \dim_set:Nn \l_@@_totalheight_dim
+ { \l_@@_height_dim + \l_@@_depth_dim }
+ \cs_set_eq:NN \Height \l_@@_height_dim
+ \cs_set_eq:NN \Depth \l_@@_depth_dim
+ \cs_set_eq:NN \Width \l_@@_width_dim
+ \cs_set_eq:NN \TotalHeight \l_@@_totalheight_dim
+ }
+\dim_new:N \l_@@_height_dim
+\dim_new:N \l_@@_depth_dim
+\dim_new:N \l_@@_width_dim
+\dim_new:N \l_@@_totalheight_dim
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{macro}
+%
+% A lot of this is more-or-less just passing data straight through.
+%
+% \begin{macro}{\NewCoffin}
+% This is a very easy conversion.
+% \begin{macrocode}
+\NewDocumentCommand \NewCoffin { m }
+ { \coffin_new:N #1 }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\SetHorizontalCoffin}
+% \begin{macro}{\SetVerticalCoffin}
+% These are again straight-forward translations.
+% \begin{macrocode}
+\NewDocumentCommand \SetHorizontalCoffin { m +m }
+ { \hcoffin_set:Nn #1 {#2} }
+\NewDocumentCommand \SetVerticalCoffin { m m +m }
+ { \vcoffin_set:Nnn #1 {#2} {#3} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\SetHorizontalPole}
+% \begin{macro}{\SetVerticalPole}
+% Here, there is a need to set up the design-level names for coffin
+% dimensions. This requires grouping, but the coffin work has to occur
+% outside of the group. Hence there is a bit of expansion trickery.
+% \begin{macrocode}
+\NewDocumentCommand \SetHorizontalPole { m m m }
+ {
+ \group_begin:
+ \@@_design_names:N #1
+ \use:x
+ {
+ \group_end:
+ \coffin_set_horizontal_pole:Nnn #1
+ { \exp_not:n {#2} } { \dim_eval:n {#3} }
+ }
+ }
+\NewDocumentCommand \SetVerticalPole { m m m }
+ {
+ \group_begin:
+ \@@_design_names:N #1
+ \use:x
+ {
+ \group_end:
+ \coffin_set_vertical_pole:Nnn #1
+ { \exp_not:n {#2} } { \dim_eval:n {#3} }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\JoinCoffins}
+% The \cs{JoinCoffins} function needs to do a bit of work on the input
+% syntax, as there are a number of optional arguments to worry about.
+% The idea here is that \cs{JoinCoffins} can be used to either expand
+% the bounding box of \meta{coffin1} or add \meta{coffin2} without any
+% expansion of the bounding box. There are also the two handle positions
+% and the offset to sort out.
+% \begin{macrocode}
+\NewDocumentCommand \JoinCoffins
+ {
+ o
+ s
+ m
+ > { \SplitArgument { 1 } { , } } O { H , l }
+ m
+ > { \SplitArgument { 1 } { , } } O { H , l }
+ > { \SplitArgument { 1 } { , } } D ( ) { 0 pt , 0 pt }
+ }
+ {
+ \IfNoValueTF {#1}
+ {
+ \IfBooleanTF #2
+ { \coffin_attach:NnnNnnnn #3 #4 #5 #6 #7 }
+ { \coffin_join:NnnNnnnn #3 #4 #5 #6 #7 }
+ }
+ {
+ \group_begin:
+ \keys_set:nn { coffin } {#1}
+ \tl_set:Nx \l_@@_tmp_tl
+ {
+ \group_end:
+ \bool_if:NTF \l_@@_bound_box_grow_bool
+ { \coffin_join:NnnNnnnn }
+ { \coffin_attach:NnnNnnnn }
+ \exp_not:N #3
+ { \exp_not:o { \l_@@_A_hpole_tl } }
+ { \exp_not:o { \l_@@_A_vpole_tl } }
+ \exp_not:N #5
+ { \exp_not:o { \l_@@_B_hpole_tl } }
+ { \exp_not:o { \l_@@_B_vpole_tl } }
+ { \dim_use:N \l_@@_hoffset_dim }
+ { \dim_use:N \l_@@_voffset_dim }
+ }
+ \l_@@_tmp_tl
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\TypesetCoffin}
+% For typesetting coffins there are two optional arguments, both of
+% which need to be split. This is a simpler case of the code needed for
+% \cs{JoinCoffins}.
+% \begin{macrocode}
+\NewDocumentCommand \TypesetCoffin
+ {
+ m
+ > { \SplitArgument { 1 } { , } } O { H , l }
+ > { \SplitArgument { 1 } { , } } D ( ) { 0 pt , 0 pt }
+ }
+ { \coffin_typeset:Nnnnn #1 #2 #3 }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\RotateCoffin}
+% \begin{macro}{\ResizeCoffin}
+% \begin{macro}{\ScaleCoffin}
+% Mores straight-forward copies.
+% \begin{macrocode}
+\NewDocumentCommand \RotateCoffin { m m }
+ { \coffin_rotate:Nn #1 {#2} }
+\NewDocumentCommand \ResizeCoffin { m m m }
+ { \coffin_resize:Nnn #1 {#2} {#3} }
+\NewDocumentCommand \ScaleCoffin { m m m }
+ { \coffin_scale:Nnn #1 {#2} {#3} }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\CoffinDepth, \CoffinHeight, \CoffinTotalHeigth, \CoffinWidth}
+% Nothing too complex, except that the total height is set up as an
+% expression so that it will act correctly if prefixed with a negative
+% sign, \emph{etc.}
+% \begin{macrocode}
+\NewDocumentCommand \CoffinDepth { m }
+ { \dim_eval:n { \coffin_dp:N #1 } }
+\NewDocumentCommand \CoffinHeight { m }
+ { \dim_eval:n { \coffin_ht:N #1 } }
+\NewDocumentCommand \CoffinTotalHeight { m }
+ { \dim_eval:n { \coffin_ht:N #1 + \coffin_dp:N #1 } }
+\NewDocumentCommand \CoffinWidth { m }
+ { \dim_eval:n { \coffin_wd:N #1 } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\DisplayCoffinHandles}
+% Displaying all of the handles is a bit easier, as there is no need
+% to worry about the handle.
+% \begin{macrocode}
+\NewDocumentCommand \DisplayCoffinHandles { m m }
+ { \coffin_if_exist:NT #1 { \coffin_display_handles:Nn #1 {#2} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\MarkCoffinHandle}
+% Marking a handle requires a bit of work with the input, so that
+% the design-level interface is \enquote{nice}.
+% \begin{macrocode}
+\NewDocumentCommand \MarkCoffinHandle
+ { m > { \SplitArgument { 1 } { , } } O { H , l } m }
+ { \coffin_if_exist:NT #1 { \coffin_mark_handle:Nnnn #1 #2 {#3} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\ShowCoffinStructure}
+% Back again to easy-to-implement functions.
+% \begin{macrocode}
+\NewDocumentCommand \ShowCoffinStructure { m }
+ { \coffin_show_structure:N #1 }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/xcoffins.ins b/macros/latex/contrib/l3experimental/xcoffins.ins
new file mode 100644
index 0000000000..065cb5eae7
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xcoffins.ins
@@ -0,0 +1,58 @@
+\iffalse meta-comment
+
+File: xcoffins.ins
+
+Copyright (C) 2010,2011,2012,2016,2017,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2010-2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{xcoffins.sty}{\from{xcoffins.dtx}{package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/xcoffins.pdf b/macros/latex/contrib/l3experimental/xcoffins.pdf
new file mode 100644
index 0000000000..61bd99bb58
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xcoffins.pdf
Binary files differ
diff --git a/macros/latex/contrib/l3experimental/xgalley.dtx b/macros/latex/contrib/l3experimental/xgalley.dtx
new file mode 100644
index 0000000000..5544223ad7
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xgalley.dtx
@@ -0,0 +1,1559 @@
+% \iffalse
+%
+%% File: xgalley.dtx
+%
+% Copyright (C) 1999-2001,2004-2009 Frank Mittelbach
+% (C) 2010-2012,2014,2016-2019 The LaTeX3 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
+%
+% https://www.latex-project.org/lppl.txt
+%
+% This file is part of the "l3experimental 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|package>
+\RequirePackage{xparse}
+%</driver|package>
+%<*driver>
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{^^A
+% The \textsf{xgalley} package\\ Galley^^A
+% }
+%
+% \author{^^A
+% The \LaTeX3 Project\thanks
+% {^^A
+% E-mail:
+% \href{mailto:latex-team@latex-project.org}
+% {latex-team@latex-project.org}^^A
+% }^^A
+% }
+%
+% \date{Released 2019-08-25}
+%
+% \maketitle
+%
+% \begin{documentation}
+%
+% \section{Introduction}
+%
+% In \LaTeX3 terminology a galley is a rectangular area which receives
+% text and other material filling it from top. The vertically extend of
+% a galley is normally not restricted: instead certain chunks are taken
+% off the top of an already partially filled galley to form columns or
+% similar areas on a page. This process is typically asynchronous but
+% there are ways to control or change its behaviour.
+%
+% Examples for galleys are \enquote{the main galley}, where the
+% continuous document data gets formatted into and from which columns
+% and pages are constructed, and \enquote{vertical box galleys}, such
+% as the body of a minipage environment. The latter galleys are
+% typically not split after formatting, though there can be exceptions.
+%
+% \section{Formatting layers}
+%
+% The present module is mainly concerned with the formatting of text
+% in galleys. The mechanism by which this is achieved uses four
+% (somewhat) distinct layers, some of which can be addressed using the
+% templates provided here.
+%
+% \subsection{Layer one: external dimensions}
+%
+% The bottom layer of the system is the external dimensions of the
+% galley. Normally only the horizontal dimension is fixed externally,
+% while the vertical (filling) dimension is unspecified. The external
+% dimensions are fixed when starting a new galley, and are therefore
+% not modifiable within the galley.
+%
+% There are no templates for setting this layer directly, although the
+% external values are influenced by other parts of the system (for
+% example when creating minipage environments).
+%
+% \subsection{Layer two: internal dimensions}
+%
+% The second layer is the internal dimensions of the galley: the
+% \emph{measure} used for paragraph text and the position of the
+% paragraph relative to the edges of the galley.
+%
+% This layer is normally accessed by higher-level templates
+% \emph{via} the object type \texttt{measure}. Changes made using
+% level two templates will often extend for large parts of a document
+% (up to and including the entire document).
+%
+% \subsection{Layer three: paragraph shape}
+%
+% The third layer defines the paragraph shape within the measure as
+% provided by the second layer. In the absence of any specification
+% for that layer the paragraph shape used will be that of a
+% rectangular area of the width of the current measure.
+%
+% There are some restrictions imposed on the shape of a paragraph by the
+% underlying \TeX{} mechanisms. For example, cut out sections in
+% paragraphs can be specified from the top of the paragraph but not from
+% the bottom.
+%
+% \subsection{Layer four: formatting inside the paragraph}
+%
+% The forth layer deals with the paragraph formatting aspects such as
+% hyphenation and justification within the paragraph (this is sometimes
+% referred to as \enquote{\texttt{h\&j}} or \enquote{\texttt{hj}}).
+%
+% \section{Templates}
+%
+% \subsection{Layer two: internal dimensions}
+%
+% \begin{TemplateInterfaceDescription}{measure}
+% \TemplateArgument{}{} ^^A A hack
+% \TemplateSemantics
+% Sets the width available to typeset material within the galley.
+% The \meta{left margin} and \meta{right margin} values are used in the
+% adjustment to over-ride any given in the template. Depending upon
+% the template in use, the margins may be absolute (relative only to the
+% edges of the galley) or relative (taking account of |measure| adjustments
+% already made). The template applies to the galley from the point of
+% us forward, unless over-ridden by another use of the |measure|
+% object type.
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{measure}{absolute}
+% \TemplateKey{left-margin}{length}
+% {^^A
+% The distance from the left edge of the galley to the left edge of
+% the area for typeset material. A negative value will cause the
+% typeset material to extend beyond the edge of the galley.^^A
+% }
+% {0pt}
+% \TemplateKey{right-margin}{length}
+% {^^A
+% The distance from the right edge of the galley to the right edge of
+% the area for typeset material. A negative value will cause the
+% typeset material to extend beyond the edge of the galley.^^A
+% }
+% {0pt}
+% \TemplateSemantics
+% This template sets up the typesetting area such that typeset material
+% runs from |left-margin| away from the left edge of the galley to
+% |right-margin| away from the right edge of the galley. Both of these
+% distances are absolute, \emph{i.e.}~no account is taken of previous
+% |measure| settings. Either on or both values may be negative, in which
+% case the typeset material will protrude outside of the edges of the
+% galley.
+% \end{TemplateDescription}
+%
+% \begin{TemplateDescription}{measure}{relative}
+% \TemplateKey{left-margin}{length}
+% {^^A
+% The distance from the previous left margin of the typeset material
+% within the galley to the new position of the left margin. A negative
+% value will cause the new margin to be \enquote{outside} of the
+% previous one, and \emph{may} cause the typeset material to
+% protrude outside of the edge of the galley.
+% }
+% {0pt}
+% \TemplateKey{right-margin}{length}
+% {^^A
+% The distance from the previous right margin of the typeset material
+% within the galley to the new position of the right margin. A negative
+% value will cause the new margin to be \enquote{outside} of the
+% previous one, and \emph{may} cause the typeset material to
+% protrude outside of the edge of the galley.
+% }
+% {0pt}
+% \TemplateSemantics
+% This template sets up the typesetting area such that it has margins
+% |left-margin| and |right-margin| within those previously set. For a
+% galley within no previous margins, this will result in margins relative
+% to the edges of the galley. Within a galley in which the |measure| has
+% already been set, using the |relative| template will indent the typeset
+% material relative to the existing margins.
+% Either on or both values may be negative, in which
+% case the typeset material may protrude outside of the edges of the
+% galley.
+% \end{TemplateDescription}
+%
+% \subsection{Layer three: paragraph shape}
+%
+% \begin{TemplateInterfaceDescription}{parshape}
+% \TemplateArgument{}{}^^A A hack
+% \TemplateSemantics
+% Template of this type define any shaping of the paragraph within the
+% current measure of the galley. Thus they are used to generate
+% \enquote{special} paragraph shapes, for example placing a cutout
+% in one side of the paragraph. Typically, \texttt{parshape} templates
+% will apply in a limited sense (to a single paragraph or a defined number of
+% lines). However, \texttt{parshape} templates may also apply in an
+% \enquote{ongoing} manner.
+%
+% Note that \texttt{parshape} templates do not alter any first-line
+% indent for paragraphs (or any other \enquote{in paragraph} setting).
+% Instead, they define a shape inside which the paragraph material will be
+% placed.
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{parshape}{hang}
+% \TemplateKey{indent}{length}
+% {^^A
+% The hanging indent from either the left- or right-hand margin
+% (as determined by \texttt{on-left-side}).^^A
+% }
+% {0pt}
+% \TemplateKey{on-left-side}{boolean}
+% {^^A
+% If \texttt{true}, causes the hanging indent to be on the left-hand
+% side of the paragraph.^^A
+% }
+% {true}
+% \TemplateKey{lines}{integer}
+% {The number of lines of full width before hanging begins.}
+% {1}
+% \TemplateSemantics
+% Sets the paragraph shape such that the after a number of full-width
+% lines, specified by |lines|, the paragraph is indented by the
+% |indent| from a margin. If |on-left-side| is |true| this indent will be
+% from the left-hand margin, otherwise it will be from the right. In either
+% case, the indent is relative to the edge of the current |measure| and
+% may be negative (in which case an outdent will result).
+% This template type applies only to a single paragraph.
+% \end{TemplateDescription}
+%
+% \begin{TemplateDescription}{parshape}{initial}
+% \TemplateKey{indent}{length}
+% {^^A
+% The indent for the initial lines from either the left- or right-hand
+% margin (as determined by \texttt{on-left-side}).^^A
+% }
+% {0pt}
+% \TemplateKey{on-left-side}{boolean}
+% {^^A
+% If \texttt{true}, causes the indent to be on the left-hand
+% side of the paragraph.^^A
+% }
+% {true}
+% \TemplateKey{lines}{integer}
+% {The number of lines of indented lines before full-width line begins.}
+% {2}
+% \TemplateSemantics
+% Sets the paragraph shape such that the first |lines| lines
+% are indented by the |indent| given, before lines of full width begin.
+% If |on-left-side| is |true| this indent will be
+% from the left-hand margin, otherwise it will be from the right. In either
+% case, the indent is relative to the edge of the current |measure| and
+% may be negative (in which case an outdent will result).
+% This template type applies only to a single paragraph.
+% \end{TemplateDescription}
+%
+% \begin{TemplateDescription}{parshape}{std}
+% \TemplateKey{}{}{}{} ^^A hack
+% \TemplateSemantics
+% Sets a rectangular paragraph shape which occupies the full width
+% specified by the |measure|. It is therefore intended as a
+% \enquote{do nothing} template for use where a paragraph shape is required
+% but where no special formatting is needed. This template type applies only
+% to a single paragraph.
+% \end{TemplateDescription}
+%
+% \subsection{Layer four: formatting inside the paragraph}
+%
+% \begin{TemplateInterfaceDescription}{hyphenation}
+% \TemplateArgument{}{} ^^A Hack
+% \TemplateSemantics
+% Controls whether hyphenation is attempted within the current
+% galley. This object type may also alter the degree to which hyphenation
+% is encouraged by manipulating the underlying \TeX{} parameters. This
+% object type applies to the galley from the point of use forward.
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{hyphenation}{std}
+% \TemplateKey{enable}{boolean}
+% {Switches all hyphenation on or off.}
+% {true}
+% \TemplateKey{enable-upper-case}
+% {boolean}
+% {^^A
+% Switches hyphenation on or off for words beginning with upper case
+% letters.^^A
+% }
+% {true}
+% \TemplateKey{penalty}{choice}
+% {^^A
+% Sets the degree to which \TeX{} is discouraged from undertaking
+% hyphenation, from the choices |low|, |medium| and |high|.^^A
+% }
+% {low}
+% \TemplateSemantics
+% Determines both whether hyphenation is allowed at all, and if so to
+% what degree it is discouraged. Setting |penalty| to |high| does not
+% prevent hyphenation: this is only done if |enable| is set |false|.
+% \end{TemplateDescription}
+%
+% \begin{TemplateInterfaceDescription}{justification}
+% \TemplateArgument{}{} ^^A Hack
+% \TemplateSemantics
+% Controls the nature of justification undertaken within the galley.
+% The template applies from the point of use forward.
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{justification}{std}
+% \TemplateKey{end-skip}{skip}
+% {The skip inserted to fill the last line of a paragraph.}
+% {0pt plus 1fil}
+% \TemplateKey{fixed-word-spacing}{boolean}
+% {^^A
+% Determines whether inter-word spacing has a stretch component (for
+% non-monospaced fonts.^^A
+% }
+% {false}
+% \TemplateKey{indent-width}{length}
+% {^^A
+% The length of the indent inserted at the start of the first line of a
+% new paragraph.^^A
+% }
+% {}
+% \TemplateKey{left-skip}{skip}
+% {^^A
+% The skip between the left margin of the galley and the left edge of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateKey{right-skip}{skip}
+% {^^A
+% The skip between the right margin of the galley and the right edge of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateKey{start-skip}{skip}
+% {^^A
+% The skip inserted in addition to |indent-width| at the start of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateSemantics
+% The |std| template for justification provides rubber lengths
+% at the start and end of the paragraph and at each side of the paragraph.
+% It also allows for both flexible and fixed inter-word spacing. The
+% interaction between the settings is demonstrated in the selection of
+% standard instances provided.
+% \end{TemplateDescription}
+%
+% \begin{InstanceDescription}{justification}{justified}{std}
+% \InstanceKey{indent-width}{15pt}
+% \InstanceSemantics
+% Sets paragraphs fully-justified with the first line indented by
+% |15pt|.
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}{justification}{noindent}{std}
+% \InstanceKey{end-skip}{15pt plus 1fil}
+% \InstanceKey{indent-width}{0pt}
+% \InstanceSemantics
+% Sets paragraphs fully-justified with no indent for the first line. To
+% ensure that paragraphs have some visual distinction, the |end-skip| is
+% set to insert some space in all cases.
+% \end{InstanceDescription}
+%
+% \begin{TemplateDescription}{justification}{single}
+% \TemplateKey{end-skip}{skip}
+% {The skip inserted to fill the last line of a paragraph.}
+% {0pt plus 1fil}
+% \TemplateKey{fixed-word-spacing}{boolean}
+% {^^A
+% Determines whether inter-word spacing has a stretch component (for
+% non-monospaced fonts.^^A
+% }
+% {false}
+% \TemplateKey{indent-width}{length}
+% {^^A
+% The length of the indent inserted at the start of the first line of a
+% new paragraph.^^A
+% }
+% {}
+% \TemplateKey{left-skip}{skip}
+% {^^A
+% The skip between the left margin of the galley and the left edge of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateKey{right-skip}{skip}
+% {^^A
+% The skip between the right margin of the galley and the right edge of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateKey{start-skip}{skip}
+% {^^A
+% The skip inserted in addition to |indent-width| at the start of a
+% paragraph.^^A
+% }
+% {0pt}
+% \TemplateKey{stretch-last-line}{boolean}
+% {
+% Determines whether inter-word spacing in the last line is stretched.
+% If \texttt{true}, the spacing in the last line is stretched in the
+% by the same factor as that in the penultimate line.^^A
+% }
+% {false}
+% \TemplateSemantics
+% The |single| template for justification provides rubber lengths
+% at the start and end of the paragraph and at each side of the paragraph.
+% It also allows for both flexible and fixed inter-word spacing. The
+% interaction between the settings is demonstrated in the selection of
+% standard instances provided. The template applies only to a single
+% paragraph.
+% \end{TemplateDescription}
+%
+% \begin{InstanceDescription}[fixed-word-spacing-xxx]{justification}{ragged-left}{std}
+% \InstanceKey{end-skip}{0pt}
+% \InstanceKey{fixed-word-spacing}{true}
+% \InstanceKey{indent-width}{0pt}
+% \InstanceKey{left-skip}{0pt plus 2em}
+% \InstanceKey{right-skip}{0pt}
+% \InstanceSemantics
+% Typesets material with a ragged left margin such that hyphenation will
+% still occur and such that very short lines are discouraged. This is
+% similar to the \LaTeXe{} \pkg{ragged2e} \env{RaggedLeft} environment.
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[fixed-word-spacing-xxx]{justification}{ragged-right}{std}
+% \InstanceKey{end-skip}{0pt}
+% \InstanceKey{fixed-word-spacing}{true}
+% \InstanceKey{indent-width}{0pt}
+% \InstanceKey{left-skip}{0pt}
+% \InstanceKey{right-skip}{0pt plus 2em}
+% \InstanceSemantics
+% Typesets material with a ragged right margin such that hyphenation will
+% still occur and such that very short lines are discouraged. This is
+% similar to the \LaTeXe{} \pkg{ragged2e} \env{RaggedLeft} environment.
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[fixed-word-spacing-xxx]{justification}{center}{std}
+% \InstanceKey{end-skip}{0pt}
+% \InstanceKey{fixed-word-spacing}{true}
+% \InstanceKey{indent-width}{0pt}
+% \InstanceKey{left-skip}{0pt plus 1fil}
+% \InstanceKey{right-skip}{0pt plus 1fil}
+% \InstanceSemantics
+% Centres typeset material such that hyphenation is discouraged and short
+% lines are allowed.
+% \end{InstanceDescription}
+%
+% \begin{TemplateDescription}{justification}{compound}
+% \TemplateKey{first-paragraph}{instance}
+% {Justification for the first paragraph.}
+% {}
+% \TemplateKey{other-paragraphs}{instance}
+% {Justification for the remaining paragraphs.}
+% {}
+% \TemplateSemantics
+% Here, both keys should themselves be instances of the |justification|
+% template. The |compound| template is used to set up a single
+% \enquote{non-standard} paragraph followed by \enquote{standard} ones.
+% For example, it can be used to ensure that one |noindent| paragraph is
+% then followed by |std| justification.
+% \end{TemplateDescription}
+%
+% \begin{TemplateInterfaceDescription}{line-breaking}
+% \TemplateArgument{}{} ^^A Hack
+% \TemplateSemantics
+% Controls the line breaking attempted by \TeX{} when typesetting
+% material for the galley. This does not include whether words are
+% hyphenated, which is handled separately.
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{line-breaking}{std}
+% \TemplateKey{badness}{integer}
+% {^^A
+% Boundary that if exceeded will cause \TeX{} to report an
+% underfull line.^^A
+% }
+% {1000}
+% \TemplateKey{binop-penalty}{integer}
+% {^^A
+% Penalty charged if an inline math formula is broken at a
+% binary operator.^^A
+% }
+% {700}
+% \TemplateKey{double-hyphen-demerits}{integer}
+% {^^A
+% Extra demerit charge of two (or more) lines in succession end
+% in a hyphen.^^A
+% }
+% {10000}
+% \TemplateKey{emergency-stretch}{skip}
+% {^^A
+% Additional stretch assumed for each line if no better line breaking
+% can be found without it. This stretch is not actually added to lines,
+% so its use may result in underfull box warnings.^^A
+% }
+% {0pt}
+% \TemplateKey{final-hyphen-demerits}{integer}
+% {Extra demerit charge if the second last line is hyphenated.}
+% {5000}
+% \TemplateKey{fuzz}{length}
+% {Boundary below overfull lines are not reported.}
+% {0.1pt}
+% \TemplateKey{mismatch-demerits}{integer}
+% {^^A
+% Extra demerit charge if two visually incompatible lines follow
+% each other.^^A
+% }
+% {10000}
+% \TemplateKey{line-penalty}{integer}
+% {^^A
+% Extra penalty charged per line in the paragraph. By making
+% this penalty higher \TeX{} will try harder to produce compact
+% paragraphs.^^A
+% }
+% {10}
+% \TemplateKey{pretolerance}{integer}
+% {^^A
+% Maximum tolerance allowed for individual lines to break the
+% paragraph without attempting hyphenation.^^A
+% }
+% {100}
+% \TemplateKey{relation-penalty}{integer}
+% {^^A
+% Penalty charged if an inline math formula is broken at a
+% relational symbol.^^A
+% }
+% {500}
+% \TemplateKey{tolerance}{integer}
+% {^^A
+% Maximum tolerance allowed for individual lines when breaking a
+% paragraph while attempting hyphenation (if this limit can't be
+% met \texttt{emergency-stretch} comes into play).^^A
+% }
+% {200}
+% \TemplateSemantics
+% This is an interface to the underlying \TeX{} system for determining
+% line breaking.
+% \end{TemplateDescription}
+%
+% \subsection{Between paragraphs}
+%
+% \begin{TemplateInterfaceDescription}{paragraph-breaking}
+% \TemplateArgument{}{} ^^A Hack
+% \TemplateSemantics
+% This object type determines how \TeX{} determines the behaviour when
+% the paragraph-breaking algorithm is calculating whether to break up a
+% paragraph. Thus for example an instance of this object type may prevent
+% breaks within a paragraph, forbid widows or orphans, \emph{etc.}
+% \end{TemplateInterfaceDescription}
+%
+% \begin{TemplateDescription}{paragraph-breaking}{std}
+% \TemplateKey{badness}{integer}
+% {^^A
+% Boundary that if exceeded will cause \TeX{} to report an
+% underfull vertical box.^^A
+% }
+% {1000}
+% \TemplateKey{broken-penalty}{integer}
+% {Penalty for page breaking after a hyphenated line.}
+% {100}
+% \TemplateKey{club-penalty}{integer}
+% {Penalty for generating a club line when page breaking.}
+% {150}
+% \TemplateKey{display-club-penalty}{integer}
+% {^^A
+% Penalty for breaking between to leave a club line after display
+% math.^^A
+% }
+% {150}
+% \TemplateKey{display-widow-penalty}{integer}
+% {^^A
+% Penalty for breaking between to leave a widow line before display
+% math.^^A
+% }
+% {150}
+% \TemplateKey{fuzz}{length}
+% {Boundary below which overfull vertical boxes are not reported.}
+% {0.1pt}
+% \TemplateKey{interline-penalty}{integer}
+% {Penalty for breaking between lines in a paragraph.}
+% {0}
+% \TemplateKey{pre-display-penalty}{integer}
+% {Penalty for breaking between immediately before display math material.}
+% {10000}
+% \TemplateKey{post-display-penalty}{integer}
+% {Penalty for breaking between immediately after display math material.}
+% {0}
+% \TemplateKey{widow-penalty}{integer}
+% {Penalty for generating a widow line when page breaking.}
+% {150}
+% \TemplateSemantics
+% This template provides an interface to the underlying \TeX{} mechanism for
+% controlling page breaking. The template applies on an ongoing basis to all
+% paragraphs after the template is used.
+% \end{TemplateDescription}
+%
+% \begin{InstanceDescription}{paragraph-breaking}{std}{std}
+% \InstanceSemantics
+% Sets paragraphs such that they can break with widows and orphans
+% discouraged but not prevented. Breaks are possible after display math
+% material but no immediately before it.
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[post-display-penalty-xxxx]{paragraph-breaking}{nobreak}{std}
+% \InstanceKey{interline-penalty}{10000}
+% \InstanceKey{post-display-penalty}{10000}
+% \InstanceSemantics
+% Sets paragraphs such that they cannot be broken at all (as far as is
+% possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[post-display-penalty-xxxx]{paragraph-breaking}{nolone}{std}
+% \InstanceKey{club-penalty}{10000}
+% \InstanceKey{display-widow-penalty}{10000}
+% \InstanceKey{widow-penalty}{10000}
+% \InstanceSemantics
+% Sets paragraphs such that they cannot be broken to leave a club or
+% widow line (as far as is possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \begin{TemplateDescription}{paragraph-breaking}{single}
+% \TemplateKey{badness}{integer}
+% {^^A
+% Boundary that if exceeded will cause \TeX{} to report an
+% underfull vertical box.^^A
+% }
+% {\meta{none}}
+% \TemplateKey{broken-penalty}{integer}
+% {Penalty for page breaking after a hyphenated line.}
+% {\meta{none}}
+% \TemplateKey{club-penalty}{integer}
+% {Penalty for generating a club line when page breaking.}
+% {\meta{none}}
+% \TemplateKey{display-club-penalty}{integer}
+% {^^A
+% Penalty for breaking between to leave a club line after display
+% math.^^A
+% }
+% {\meta{none}}
+% \TemplateKey{display-widow-penalty}{integer}
+% {^^A
+% Penalty for breaking between to leave a widow line before display
+% math.^^A
+% }
+% {\meta{none}}
+% \TemplateKey{fuzz}{length}
+% {Boundary below which overfull vertical boxes are not reported.}
+% {\meta{none}}
+% \TemplateKey{interline-penalty}{integer}
+% {Penalty for breaking between lines in a paragraph.}
+% {\meta{none}}
+% \TemplateKey{pre-display-penalty}{integer}
+% {Penalty for breaking between immediately before display math material.}
+% {\meta{none}}
+% \TemplateKey{post-display-penalty}{integer}
+% {Penalty for breaking between immediately after display math material.}
+% {\meta{none}}
+% \TemplateKey{widow-penalty}{integer}
+% {Penalty for generating a widow line when page breaking.}
+% {\meta{none}}
+% \TemplateSemantics
+% This template provides an interface to the underlying \TeX{} mechanism for
+% controlling page breaking. The template applies only to the next paragraph,
+% and can thus be used to achieve effects such as non-breaking paragraphs.
+% \end{TemplateDescription}
+%
+% \begin{InstanceDescription}{paragraph-breaking}{single-std}{single}
+% \InstanceSemantics
+% Sets the next paragraph such that it can break with widows and orphans
+% discouraged but not prevented. Breaks are possible after display math
+% material but no immediately before it.
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[post-display-penalty-xxx]{paragraph-breaking}{single-nobreak}{single}
+% \InstanceKey{interline-penalty}{10000}
+% \InstanceKey{post-display-penalty}{10000}
+% \InstanceSemantics
+% Sets the next paragraph such that it cannot be broken at all (as far as is
+% possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[display-club-penalty-xxx]{paragraph-breaking}{single-noclub}{single}
+% \InstanceKey{club-penalty}{10000}
+% \InstanceKey{display-club-penalty}{10000}
+% \InstanceSemantics
+% Sets the next paragraph such that it cannot be broken to leave a club
+% line (as far as is possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[display-widow-penalty-xxx]{paragraph-breaking}{single-nolone}{single}
+% \InstanceKey{club-penalty}{10000}
+% \InstanceKey{display-club-penalty}{10000}
+% \InstanceKey{display-widow-penalty}{10000}
+% \InstanceKey{widow-penalty}{10000}
+% \InstanceSemantics
+% Sets the next paragraph such that it cannot be broken to leave a club or
+% widow line (as far as is possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \begin{InstanceDescription}[display-widow-penalty-xxx]{paragraph-breaking}{single-nowidow}{single}
+% \InstanceKey{display-widow-penalty}{10000}
+% \InstanceKey{widow-penalty}{10000}
+% \InstanceSemantics
+% Sets the next paragraph such that it cannot be broken to leave a
+% widow line (as far as is possible in \TeX{}).
+% \end{InstanceDescription}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{xgalley} Implementation}
+%
+% This module provided a template-level interface for the \LaTeX3 galley. As
+% such, the code here is intended for design-level changes which apply to
+% large blocks. The variables provided are therefore used only for supporting
+% the templates, while any documented interfaces are in \pkg{l3galley}.
+%
+% \begin{macrocode}
+%<*package>
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=galley>
+% \end{macrocode}
+%
+% \begin{macrocode}
+\ProvidesExplPackage{xgalley}{2019-08-25}{}
+ {L3 Experimental galley}
+\RequirePackage{xparse,xtemplate,l3galley}
+% \end{macrocode}
+%
+% \subsection{Variables}
+%
+% \begin{variable}{\l_@@_tmpa_clist, \l_@@_tmpb_clist}
+% Scratch space.
+% \begin{macrocode}
+\clist_new:N \l_@@_tmpa_clist
+\clist_new:N \l_@@_tmpb_clist
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Layer two: internal dimensions}
+%
+% There is a single object type for level two, the \texttt{measure} for
+% the text in the galley. There are no arguments, as the measure is a design
+% concept.
+% \begin{macrocode}
+\DeclareObjectType { measure } { 0 }
+% \end{macrocode}
+%
+% There are two templates for galley measures: absolute and relative.
+% Both use the same interface.
+% \begin{macrocode}
+\DeclareTemplateInterface { measure } { absolute } { 0 }
+ {
+ left-margin : length = 0pt ,
+ right-margin : length = 0pt
+ }
+\DeclareTemplateInterface { measure } { relative } { 0 }
+ {
+ left-margin : length = 0pt ,
+ right-margin : length = 0pt
+ }
+% \end{macrocode}
+%
+% \begin{variable}{\l_@@_left_margin_dim, \l_@@_right_margin_dim}
+% In the \texttt{absolute} template, the two margin values are relative
+% to the edges of the galley. This means that any existing offset or
+% line-length adjustment are ignored.
+% \begin{macrocode}
+%<*package>
+\cs_new_eq:NN \l_@@_left_margin_dim \leftmargin
+%</package>
+%<*package>
+\cs_new_eq:NN \l_@@_right_margin_dim \rightmargin
+%</package>
+\DeclareTemplateCode { measure } { absolute } { 0 }
+ {
+ left-margin = \l_@@_left_margin_dim ,
+ right-margin = \l_@@_right_margin_dim
+ }
+ {
+ \AssignTemplateKeys
+ \galley_margins_set_absolute:nn \l_@@_left_margin_dim
+ \l_@@_right_margin_dim
+ }
+% \end{macrocode}
+% On the other hand, the \texttt{relative} template works relative to
+% the current indentation at both sides.
+% \begin{macrocode}
+\DeclareTemplateCode { measure } { relative } { 0 }
+ {
+ left-margin = \l_@@_left_margin_dim ,
+ right-margin = \l_@@_right_margin_dim
+ }
+ {
+ \AssignTemplateKeys
+ \galley_margins_set_relative:nn \l_@@_left_margin_dim
+ \l_@@_right_margin_dim
+ }
+% \end{macrocode}
+% \end{variable}
+%
+% \subsection{Layer three: paragraph shape}
+%
+% The object type \texttt{parshape} is a somewhat extended interface
+% to the \TeX{} \cs{tex_parshape:D} primitive. As with the \texttt{measure},
+% the \texttt{parshape} template has no arguments as it is essentially a
+% design-oriented concept.
+% \begin{macrocode}
+\DeclareObjectType { parshape } { 0 }
+% \end{macrocode}
+%
+% There are two standard templates for paragraph shapes which do
+% something, both with the same interface. The \texttt{hang} template
+% provides one or more standard lines followed by a hanging paragraph,
+% while the \texttt{initial} template cuts out a space at the start of
+% the paragraph.
+% \begin{macrocode}
+\DeclareTemplateInterface { parshape } { hang } { 0 }
+ {
+ indent : length = 0pt ,
+ on-left-side : boolean = true ,
+ lines : integer = 1
+ }
+\DeclareTemplateInterface { parshape } { initial } { 0 }
+ {
+ indent : length = 0pt ,
+ on-left-side : boolean = true ,
+ lines : integer = 2
+ }
+% \end{macrocode}
+% \begin{variable}{\l_@@_parshape_indent_dim}
+% \begin{variable}{\l_@@_parshape_on_left_bool}
+% \begin{variable}{\l_@@_parshape_lines_int}
+% Both of the templates are implemented as special cases of the more
+% general function defined earlier.
+% \begin{macrocode}
+\DeclareTemplateCode { parshape } { hang } { 0 }
+ {
+ indent = \l_@@_parshape_indent_dim ,
+ on-left-side = \l_@@_parshape_on_left_bool ,
+ lines = \l_@@_parshape_lines_int
+ }
+ {
+ \AssignTemplateKeys
+ \bool_if:NTF \l_@@_parshape_on_left_bool
+ {
+ \galley_parshape_set_single:nVVN
+ \l_@@_parshape_lines_int
+ \l_@@_parshape_indent_dim
+ \c_zero_dim
+ \c_false_bool
+ }
+ {
+ \galley_parshape_set_single:nVVN
+ \l_@@_parshape_lines_int
+ \c_zero_dim
+ \l_@@_parshape_indent_dim
+ \c_false_bool
+ }
+ }
+\DeclareTemplateCode { parshape } { initial } { 0 }
+ {
+ indent = \l_@@_parshape_indent_dim ,
+ on-left-side = \l_@@_parshape_on_left_bool ,
+ lines = \l_@@_parshape_lines_int
+ }
+ {
+ \AssignTemplateKeys
+ \clist_clear:N \l_@@_tmpa_clist
+ \clist_clear:N \l_@@_tmpb_clist
+ \prg_replicate:nn { \l_@@_parshape_lines_int }
+ {
+ \clist_put_right:Nn \l_@@_tmpa_clist
+ { \l_@@_parshape_indent_dim }
+ \clist_put_right:Nn \l_@@_tmpb_clist
+ { \c_zero_dim }
+ }
+ \bool_if:NTF \l_@@_parshape_on_left_bool
+ {
+ \galley_parshape_set_single:nVVN
+ { 0 }
+ \l_@@_tmpa_clist
+ \l_@@_tmpb_clist
+ \c_true_bool
+ }
+ {
+ \galley_parshape_set_single:nVVN
+ { 0 }
+ \l_@@_tmpb_clist
+ \l_@@_tmpa_clist
+ \c_true_bool
+ }
+ }
+% \end{macrocode}
+% \end{variable}
+% \end{variable}
+% \end{variable}
+%
+% There is also a \enquote{do nothing} paragraph shape for cases where
+% a template is needed but no action is desirable.
+% \begin{macrocode}
+\DeclareTemplateInterface { parshape } { std } { 0 } { }
+\DeclareTemplateCode { parshape } { std } { 0 } { } { }
+% \end{macrocode}
+%
+% \subsection{Layer four: formatting inside the paragraph}
+%
+% The first type of object within a paragraph is the hyphenation. This
+% object needs no arguments.
+% \begin{macrocode}
+\DeclareObjectType { hyphenation } { 0 }
+% \end{macrocode}
+%
+% There is only hyphenation template as standard. This provides a
+% semi-flexible interface to the underlying \TeX{} methods. (The detail
+% is therefore hidden within the implementation phase.)
+% \begin{macrocode}
+\DeclareTemplateInterface { hyphenation } { std } { 0 }
+ {
+ enable : boolean = true ,
+ enable-upper-case : boolean = true ,
+ penalty : choice { low , medium , high } = low
+ }
+% \end{macrocode}
+% The implementation for hyphenation mainly sets low-level values.
+% The minimum number of characters after a hyphen is set directly,
+% whereas the number before is not. This is so that
+% \cs{tex_lefthyphenmin:D} can also be used to completely prevent
+% hyphenation.
+% \begin{macrocode}
+\DeclareTemplateCode { hyphenation } { std } { 0 }
+ {
+ enable = \l_galley_hyphen_enable_bool ,
+ enable-upper-case = \l_galley_hyphen_uppercase_bool ,
+ penalty =
+ {
+ low =
+ {
+ \int_set:Nn \tex_hyphenpenalty:D { 51 }
+ \int_set:Nn \tex_exhyphenpenalty:D { 51 }
+ } ,
+ medium =
+ {
+ \int_set:Nn \tex_hyphenpenalty:D { 151 }
+ \int_set:Nn \tex_exhyphenpenalty:D { 151 }
+ } ,
+ high =
+ {
+ \int_set:Nn \tex_hyphenpenalty:D { 301 }
+ \int_set:Nn \tex_exhyphenpenalty:D { 301 }
+ } ,
+ }
+ }
+ {
+ \AssignTemplateKeys
+ \int_set:Nn \tex_lefthyphenmin:D
+ {
+ \bool_if:NTF \l_galley_hyphen_enable_bool
+ { \l_galley_hyphen_left_int }
+ { 63 }
+ }
+ \int_set:Nn \tex_uchyph:D
+ {
+ \bool_if:NTF \l_galley_hyphen_uppercase_bool
+ { 1 }
+ { 0 }
+ }
+ }
+% \end{macrocode}
+% At this stage, the default hyphenation character should be set and
+% hyphenation should be enabled.
+% \begin{macrocode}
+\UseTemplate { hyphenation } { std } { }
+\tex_defaulthyphenchar:D 45 \scan_stop:
+% \end{macrocode}
+%
+% \begin{variable}{\l_@@_justification_other_tl}
+% Used for the reset system for justification: using this token list means
+% that there is no need to remove anything from
+% \cs{g_galley_restore_running_tl}.
+% \begin{macrocode}
+\tl_new:N \l_@@_justification_other_tl
+% \end{macrocode}
+% \end{variable}
+%
+% The second level four object is the justification, which again
+% takes no arguments.
+% \begin{macrocode}
+\DeclareObjectType { justification } { 0 }
+% \end{macrocode}
+% There are two templates here with the same interface: the standard one
+% to apply from this point onward, and one which applies only to a single
+% paragraph.
+% \begin{macrocode}
+\DeclareTemplateInterface { justification } { std } { 0 }
+ {
+ end-skip : skip = 0pt plus 1fil ,
+ fixed-word-spacing : boolean = false ,
+ indent-width : length ,
+ left-skip : skip = 0pt ,
+ right-skip : skip = 0pt ,
+ start-skip : skip = 0pt ,
+ stretch-last-line : boolean = false
+ }
+\DeclareTemplateInterface { justification } { single } { 0 }
+ {
+ end-skip : skip = 0pt plus 1fil ,
+ fixed-word-spacing : boolean = false ,
+ indent-width : length ,
+ left-skip : skip = 0pt ,
+ right-skip : skip = 0pt ,
+ start-skip : skip = 0pt ,
+ stretch-last-line : boolean = false
+ }
+% \end{macrocode}
+% \begin{variable}{\l_galley_fixed_spacing_bool}
+% The implementation here is pretty simple as almost everything that
+% goes on is a simple case of saving the settings, which are then
+% applied either by \TeX{} itself or the rest of the galley system.
+% \begin{macrocode}
+\DeclareTemplateCode { justification } { std } { 0 }
+ {
+ end-skip = \l_galley_par_end_skip ,
+ fixed-word-spacing = \l_galley_fixed_spacing_bool ,
+ indent-width = \l_galley_par_indent_dim ,
+ left-skip = \l_galley_line_left_skip ,
+ right-skip = \l_galley_line_right_skip ,
+ start-skip = \l_galley_par_begin_skip ,
+ stretch-last-line = \l_galley_par_stretch_last_bool
+ }
+ {
+ \AssignTemplateKeys
+ \tl_clear:N \l_@@_justification_other_tl
+ \galley_interword_spacing_set:N \l_galley_fixed_spacing_bool
+ \bool_if:NTF \l_galley_par_stretch_last_bool
+ { \int_set:Nn \l_galley_last_line_fit_int { 1000 } }
+ { \int_zero:N \l_galley_last_line_fit_int }
+ \skip_set:Nn \@rightskip { \l_galley_line_right_skip }
+ }
+% \end{macrocode}
+% \end{variable}
+% To deal with a single paragraph, the approach used is to save the current
+% settings to the paragraph-reset code, then to assign the template in the
+% same way as for the |std| template.
+% \begin{macrocode}
+\DeclareTemplateCode { justification } { single } { 0 }
+ {
+ end-skip = \l_galley_par_end_skip ,
+ fixed-word-spacing = \l_galley_fixed_spacing_bool ,
+ indent-width = \l_galley_par_indent_dim ,
+ left-skip = \l_galley_line_left_skip ,
+ right-skip = \l_galley_line_right_skip ,
+ start-skip = \l_galley_par_begin_skip ,
+ stretch-last-line = \l_galley_par_stretch_last_bool
+ }
+ {
+ \tl_put_left:Nx \l_@@_justification_other_tl
+ {
+ \skip_set:Nn \exp_not:N \l_galley_par_end_skip
+ { \skip_use:N \l_galley_par_end_skip }
+ \bool_if:NTF \l_galley_fixed_spacing_bool
+ { \bool_set_true:N \exp_not:N \l_galley_fixed_spacing_bool }
+ { \bool_set_false:N \exp_not:N \l_galley_fixed_spacing_bool }
+ \galley_interword_spacing_set:N
+ \exp_not:N \l_galley_fixed_spacing_bool
+ \dim_set:Nn \exp_not:N \l_galley_par_indent_dim
+ { \dim_use:N \l_galley_par_indent_dim }
+ \skip_set:Nn \l_galley_line_left_skip
+ { \skip_use:N \l_galley_line_left_skip }
+ \skip_set:Nn \exp_not:N \l_galley_line_right_skip
+ { \skip_use:N \l_galley_line_right_skip }
+ \skip_set:Nn \exp_not:N \l_galley_par_begin_skip
+ { \skip_use:N \l_galley_par_begin_skip }
+ \int_set:Nn \exp_not:N \l_galley_last_line_fit_int
+ { \int_use:N \l_galley_last_line_fit_int }
+ \skip_set:Nn \exp_not:N \@rightskip
+ { \skip_use:N \l_galley_line_right_skip }
+ }
+ \tl_gput_right:Nn \g_galley_restore_running_tl
+ { \l_@@_justification_other_tl }
+ \AssignTemplateKeys
+ \galley_interword_spacing_set:N \l_galley_fixed_spacing_bool
+ \bool_if:NTF \l_galley_par_stretch_last_bool
+ { \int_set:Nn \l_galley_last_line_fit_int { 1000 } }
+ { \int_zero:N \l_galley_last_line_fit_int }
+ \skip_set:Nn \@rightskip { \l_galley_line_right_skip }
+ }
+% \end{macrocode}
+% The standard instance for justification is very simple to set up as
+% the default values for the template are set up for exactly this case.
+% The advantage of this scheme is that at a design level altering the
+% indent used for justified paragraphs is very easy to do. As this is
+% the standard template for all \LaTeX3 documents, it is applied here.
+% \begin{macrocode}
+\DeclareInstance { justification } { justified } { std }
+ { indent-width = 15pt }
+\UseInstance { justification } { justified }
+% \end{macrocode}
+% The instance for no indentation at all but with justified text is
+% intended for layouts which leave white space between paragraphs. With
+% no indentation, some space has to be included at the end of each
+% paragraph. This is set up to mirror the indent that has been removed.
+% \begin{macrocode}
+\DeclareInstance { justification } { noindent } { std }
+ {
+ end-skip = 15pt plus 1fil ,
+ indent-width = 0pt
+ }
+% \end{macrocode}
+% The other standard justification schemes are for text which ragged.
+% The settings here are taken from the \LaTeXe{}
+% \pkg{ragged2e} package, as they maintain a reasonable appearance by
+% ensuring that \TeX{} will not be too tolerant of very short lines.
+% To keep the design clear here, no default values are relied on even
+% though this would make the instance declarations shorter.
+% \begin{macrocode}
+\DeclareInstance { justification } { ragged-left } { std }
+ {
+ end-skip = 0pt ,
+ fixed-word-spacing = true ,
+ indent-width = 0pt ,
+ left-skip = 0pt plus 2em ,
+ right-skip = 0pt
+ }
+\DeclareInstance { justification } { ragged-right } { std }
+ {
+ end-skip = 0pt plus 1fil ,
+ fixed-word-spacing = true ,
+ indent-width = 0pt ,
+ left-skip = 0pt ,
+ right-skip = 0pt plus 2em
+ }
+% \end{macrocode}
+% The \texttt{center} instance is used to center material with minimal
+% hyphenation.
+% \begin{macrocode}
+\DeclareInstance { justification } { center } { std }
+ {
+ end-skip = 0pt ,
+ fixed-word-spacing = true ,
+ indent-width = 0pt ,
+ left-skip = 0pt plus 1fil ,
+ right-skip = 0pt plus 1fil
+ }
+% \end{macrocode}
+%
+% \begin{macro}{\@@_justification_first:, \@@_justification_other:}
+% A second form of justification template is the case where the first
+% paragraph is different from all of the others. This is set up by
+% getting the justification to reset itself after the first paragraph.
+% The code built into the \texttt{std} version will ensure that any
+% subsequent template use will over-ride the setting here correctly.
+% \begin{macrocode}
+\DeclareTemplateInterface { justification } { compound } { 0 }
+ {
+ first-paragraph : instance { justification } ,
+ other-paragraphs : instance { justification }
+ }
+\DeclareTemplateCode { justification } { compound } { 0 }
+ {
+ first-paragraph = \@@_justification_first: ,
+ other-paragraphs = \@@_justification_other:
+ }
+ {
+ \AssignTemplateKeys
+ \@@_justification_first:
+ \tl_set:Nn \l_@@_justification_other_tl
+ { \@@_justification_other: }
+ \tl_gput_right:Nn \g_galley_restore_running_tl
+ { \l_@@_justification_other_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% How \TeX{} breaks text into lines is influences by a number of
+% parameters, most of which are not actually likely to change. These
+% work with the \texttt{hyphenation} but are independent of whether
+% any hyphenation is actually active. The math values here could be
+% set up as a separate template, but in practice this seems likely to
+% be overkill.
+% \begin{macrocode}
+\DeclareObjectType { line-breaking } { 0 }
+% \end{macrocode}
+% The only template provided for line breaking is a simple interface to
+% \TeX{}'s parameters. There is not really much that can be added to this:
+% after all, the way that penalties work is more or less arbitrary but
+% works well! The default values given here are intended to be sensible
+% for a lot of cases.
+% \begin{macrocode}
+\DeclareTemplateInterface { line-breaking } { std } { 0 }
+ {
+ badness : integer = 1000 ,
+ binop-penalty : integer = 700 ,
+ double-hyphen-demerits : integer = 10000 ,
+ emergency-stretch : skip = 0pt ,
+ final-hyphen-demerits : integer = 5000 ,
+ fuzz : length = 0.1pt ,
+ line-penalty : integer = 10 ,
+ mismatch-demerits : integer = 10000 ,
+ pretolerance : integer = 100 ,
+ relation-penalty : integer = 500 ,
+ tolerance : integer = 200
+ }
+\DeclareTemplateCode{ line-breaking } { std } { 0 }
+ {
+ badness = \l_galley_linebreak_badness_int ,
+ binop-penalty = \l_@@_binop_penalty_int ,
+ double-hyphen-demerits = \l_galley_double_hyphen_demerits_int ,
+ emergency-stretch = \l_galley_emergency_stretch_skip ,
+ final-hyphen-demerits = \l_galley_final_hyphen_demerits_int ,
+ fuzz = \l_galley_linebreak_fuzz_dim ,
+ line-penalty = \l_@@_linebreak_penalty_int ,
+ mismatch-demerits = \l_galley_mismatch_demerits_int ,
+ pretolerance = \l_galley_linebreak_pretolerance_int ,
+ relation-penalty = \l_@@_relation_penalty_int ,
+ tolerance = \l_galley_linebreak_tolerance_int
+ }
+ { \AssignTemplateKeys }
+% \end{macrocode}
+% The default values are set such that they are suitable for good
+% quality typesetting. So the standard template changes nothing at
+% all from the template. This instance should also be applied now,
+% as it will then apply to the entire document unless changed
+% deliberately.
+% \begin{macrocode}
+\DeclareInstance { line-breaking } { std } { std } { }
+\UseInstance { line-breaking } { std }
+% \end{macrocode}
+%
+% \subsection{Between paragraphs}
+%
+% \begin{variable}
+% {
+% \l_@@_club_penalty_int ,
+% \l_@@_display_club_penalty_int ,
+% \l_@@_display_widow_penalty_int ,
+% \l_@@_interline_penalty_int ,
+% \l_@@_widow_penalty_int
+% }
+% The second object here sets up how \TeX{} acts to break paragraphs at
+% page boundaries. As with the \texttt{line-breaking} object, there is not
+% much to do except provide an interface to the \TeX{} internals. The
+% \texttt{std} template does \emph{not} make the \eTeX{} array nature of
+% various penalties available.
+% \begin{macrocode}
+\DeclareObjectType { paragraph-breaking } { 0 }
+\DeclareTemplateInterface { paragraph-breaking } { std } { 0 }
+ {
+ badness : integer = 1000 ,
+ broken-penalty : integer = 100 ,
+ club-penalty : integer = 150 ,
+ display-club-penalty : integer = 150 ,
+ display-widow-penalty : integer = 150 ,
+ fuzz : length = 0.1pt ,
+ interline-penalty : integer = 0 ,
+ post-display-penalty : integer = 0 ,
+ pre-display-penalty : integer = 10000 ,
+ widow-penalty : integer = 150
+ }
+\DeclareTemplateCode { paragraph-breaking } { std } { 0 }
+ {
+ badness = \l_galley_parbreak_badness_int ,
+ broken-penalty = \l_@@_broken_penalty_int ,
+ club-penalty = \l_@@_club_penalty_int ,
+ display-club-penalty = \l_@@_display_club_penalty_int ,
+ display-widow-penalty = \l_@@_display_widow_penalty_int ,
+ fuzz = \l_galley_parbreak_fuzz_dim ,
+ interline-penalty = \l_@@_interline_penalty_int ,
+ post-display-penalty = \l_@@_post_display_penalty_int ,
+ pre-display-penalty = \l_@@_pre_display_penalty_int ,
+ widow-penalty = \l_@@_widow_penalty_int
+ }
+ {
+ \AssignTemplateKeys
+ \galley_club_penalties_set:V \l_@@_club_penalty_int
+ \galley_display_club_penalties_set:V \l_@@_display_club_penalty_int
+ \galley_display_widow_penalties_set:V \l_@@_display_widow_penalty_int
+ \galley_interline_penalty_set:n \l_@@_interline_penalty_int
+ \galley_widow_penalties_set:V \l_@@_widow_penalty_int
+ }
+% \end{macrocode}
+% \end{variable}
+% The standard instance of the \texttt{paragraph-breaking} object simply
+% applies the defaults: this is used.
+% \begin{macrocode}
+\DeclareInstance { paragraph-breaking } { std } { std } { }
+\UseInstance { paragraph-breaking } { std }
+% \end{macrocode}
+% Two additional instances are provided: one to prevent any breaks
+% at all, and a second to prevent any widow or club lines.
+% \begin{macrocode}
+\DeclareInstance { paragraph-breaking } { nobreak } { std }
+ {
+ interline-penalty = 10 000 ,
+ post-display-penalty = 10 000
+ }
+\DeclareInstance { paragraph-breaking } { nolone } { std }
+ {
+ club-penalty = 10 000 ,
+ display-club-penalty = 10 000 ,
+ display-widow-penalty = 10 000 ,
+ widow-penalty = 10 000
+ }
+% \end{macrocode}
+% \begin{variable}
+% {
+% \l_@@_parbreak_badness_tl ,
+% \l_@@_broken_penalty_tl ,
+% \l_@@_club_penalties_tl ,
+% \l_@@_display_club_penalties_tl ,
+% \l_@@_display_widow_penalties_tl ,
+% \l_@@_parbreak_fuzz_tl ,
+% \l_@@_interline_penalty_tl ,
+% \l_@@_post_display_penalty_tl ,
+% \l_@@_pre_display_penalty_tl ,
+% \l_@@_widow_penalties_tl ,
+% \c_@@_parbreak_multi_seq ,
+% \c_@@_parbreak_single_seq
+% }
+% There is also a version of this code which applies only to one
+% paragraph. This is done by storing the input in token list variables
+% with no default: only explicit settings will be picked up.
+% \begin{macrocode}
+\DeclareTemplateInterface { paragraph-breaking } { single } { 0 }
+ {
+ badness : tokenlist ,
+ broken-penalty : tokenlist ,
+ club-penalty : tokenlist ,
+ display-club-penalty : tokenlist ,
+ display-widow-penalty : tokenlist ,
+ fuzz : tokenlist ,
+ interline-penalty : tokenlist ,
+ post-display-penalty : tokenlist ,
+ pre-display-penalty : tokenlist ,
+ widow-penalty : tokenlist
+ }
+\DeclareTemplateCode { paragraph-breaking } { single } { 0 }
+ {
+ badness = \l_@@_parbreak_badness_tl ,
+ broken-penalty = \l_@@_broken_penalty_tl ,
+ club-penalty = \l_@@_club_penalties_tl ,
+ display-club-penalty = \l_@@_display_club_penalties_tl ,
+ display-widow-penalty = \l_@@_display_widow_penalties_tl ,
+ fuzz = \l_@@_parbreak_fuzz_tl ,
+ interline-penalty = \l_@@_interline_penalty_tl ,
+ post-display-penalty = \l_@@_post_display_penalty_tl ,
+ pre-display-penalty = \l_@@_pre_display_penalty_tl ,
+ widow-penalty = \l_@@_widow_penalties_tl
+ }
+ {
+ \AssignTemplateKeys
+% \end{macrocode}
+% The fuzz and interline penalties are handled explicitly as they have
+% particular requirements.
+% \begin{macrocode}
+ \tl_if_empty:NF \l_@@_interline_penalty_tl
+ {
+ \tl_gput_right:Nx \g_galley_par_reset_hook_tl
+ {
+ \int_set:Nn \exp_not:N \l_@@_interline_penalty_int
+ { \galley_interline_penalty: }
+ }
+ \int_set:Nn \l_@@_interline_penalty_int
+ { \l_@@_interline_penalty_tl }
+ }
+ \tl_if_empty:NF \l_@@_parbreak_fuzz_tl
+ {
+ \tl_gput_right:Nx \g_galley_par_reset_hook_tl
+ {
+ \dim_set:Nn \exp_not:N \l_galley_parbreak_fuzz_dim
+ { \dim_use:N \l_galley_parbreak_fuzz_dim }
+ }
+ \dim_set:Nn \l_galley_parbreak_fuzz_dim { \l_@@_parbreak_fuzz_tl }
+ }
+% \end{macrocode}
+% For the single integer penalties, a simple check is needed to save the
+% value.
+% \begin{macrocode}
+ \seq_map_inline:Nn \c_@@_parbreak_single_seq
+ {
+ \tl_if_empty:cF { l_galley_ ##1 _tl }
+ {
+ \tl_gput_right:Nx \g_galley_par_reset_hook_tl
+ {
+ \int_set:Nn \exp_not:c { l_galley_ ##1 _int }
+ { \int_use:c { l_galley_ ##1 _int } }
+ }
+ \int_set:cn { l_galley_ ##1 _int }
+ { \tl_use:c { l_galley_ ##1 _tl } }
+ }
+ }
+% \end{macrocode}
+% A bit more complex for the array penalties. Although the interface here
+% does not expose the arrays, it is necessary to correctly save them.
+% We suspend debugging to allow an assignment to a constant.
+% \begin{macrocode}
+ \seq_map_inline:Nn \c_@@_parbreak_multi_seq
+ {
+ \tl_if_empty:cF { l_galley_ ##1 _tl }
+ {
+ \use:c { galley_save_ ##1 :N } \l_@@_tmpa_clist
+ \tl_gput_right:Nx \g_galley_par_reset_hook_tl
+ {
+ \exp_not:c { galley_set_ ##1 :n }
+ { \exp_not:o \l_@@_tmpa_clist }
+ }
+ \use:c { galley_set_ ##1 :v } { l_galley_ ##1 _tl }
+ }
+ }
+ }
+\seq_const_from_clist:Nn \c_@@_parbreak_multi_seq
+ {
+ club_penalties ,
+ display_club_penalties ,
+ display_widow_penalties ,
+ widow_penalties ,
+ }
+\seq_const_from_clist:Nn \c_@@_parbreak_single_seq
+ {
+ parbreak_badness ,
+ broken_penalty ,
+ post_display_penalty ,
+ pre_display_penalty ,
+ }
+% \end{macrocode}
+% \end{variable}
+% \begin{macrocode}
+\DeclareInstance { paragraph-breaking } { single-std } { single } { }
+\DeclareInstance { paragraph-breaking } { single-nobreak } { single }
+ {
+ interline-penalty = 10 000 ,
+ post-display-penalty = 10 000
+ }
+\DeclareInstance { paragraph-breaking } { single-noclub } { single }
+ {
+ club-penalty = 10 000 ,
+ display-club-penalty = 10 000
+ }
+\DeclareInstance { paragraph-breaking } { single-nolone } { single }
+ {
+ club-penalty = 10 000 ,
+ display-club-penalty = 10 000 ,
+ display-widow-penalty = 10 000 ,
+ widow-penalty = 10 000
+ }
+\DeclareInstance { paragraph-breaking } { single-nowidow } { single }
+ {
+ display-widow-penalty = 10 000 ,
+ widow-penalty = 10 000
+ }
+% \end{macrocode}
+%
+% \subsection{Templates for display material}
+%
+% To allow special handling of display-like material, templates are
+% needed at the beginning and end of the block which set up any special
+% space or breaks. These need to be optional, and so are stored as token
+% lists: rather than \enquote{magic} values, empty lists indicate that
+% standard settings are to be used. To ensure that the error checking
+% needed takes place early, each token list is re-set with the
+% appropriate evaluation.
+% \begin{macrocode}
+\DeclareObjectType { display-begin } { 0 }
+\DeclareObjectType { display-end } { 0 }
+\DeclareTemplateInterface { display-begin } { std } { 0 }
+ {
+ par-penalty : tokenlist ,
+ par-space : tokenlist ,
+ penalty : tokenlist ,
+ space : tokenlist
+ }
+\DeclareTemplateInterface { display-end } { std } { 0 }
+ {
+ par-penalty : tokenlist ,
+ par-space : tokenlist ,
+ penalty : tokenlist ,
+ space : tokenlist
+ }
+\DeclareTemplateCode { display-begin } { std } { 0 }
+ {
+ par-penalty = \l_galley_display_begin_par_vpenalty_tl ,
+ par-space = \l_galley_display_begin_par_vspace_tl ,
+ penalty = \l_galley_display_begin_vpenalty_tl ,
+ space = \l_galley_display_begin_vspace_tl
+ }
+ {
+ \AssignTemplateKeys
+ \tl_if_empty:NF \l_galley_display_begin_par_vpenalty_tl
+ {
+ \tl_set:Nx \l_galley_display_begin_par_vpenalty_tl
+ { \int_eval:n { \l_galley_display_begin_par_vpenalty_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_begin_par_vspace_tl
+ {
+ \tl_set:Nx \l_galley_display_begin_par_vspace_tl
+ { \skip_eval:n { \l_galley_display_begin_par_vspace_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_begin_vpenalty_tl
+ {
+ \tl_set:Nx \l_galley_display_begin_vpenalty_tl
+ { \int_eval:n { \l_galley_display_begin_vpenalty_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_begin_vspace_tl
+ {
+ \tl_set:Nx \l_galley_display_begin_vspace_tl
+ { \skip_eval:n { \l_galley_display_begin_vspace_tl } }
+ }
+ }
+\DeclareTemplateCode { display-end } { std } { 0 }
+ {
+ par-penalty = \l_galley_display_end_par_vpenalty_tl ,
+ par-space = \l_galley_display_end_par_vspace_tl ,
+ penalty = \l_galley_display_end_vpenalty_tl ,
+ space = \l_galley_display_end_vspace_tl
+ }
+ {
+ \AssignTemplateKeys
+ \tl_if_empty:NF \l_galley_display_end_par_vpenalty_tl
+ {
+ \tl_set:Nx \l_galley_display_end_par_vpenalty_tl
+ { \int_eval:n { \l_galley_display_end_par_vpenalty_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_end_par_vspace_tl
+ {
+ \tl_set:Nx \l_galley_display_end_par_vspace_tl
+ { \skip_eval:n { \l_galley_display_end_par_vspace_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_end_vpenalty_tl
+ {
+ \tl_set:Nx \l_galley_display_end_vpenalty_tl
+ { \int_eval:n { \l_galley_display_end_vpenalty_tl } }
+ }
+ \tl_if_empty:NF \l_galley_display_end_vspace_tl
+ {
+ \tl_set:Nx \l_galley_display_end_vspace_tl
+ { \skip_eval:n { \l_galley_display_end_vspace_tl } }
+ }
+ }
+% \end{macrocode}
+%
+% \begin{macrocode}
+%</package>
+% \end{macrocode}
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/l3experimental/xgalley.ins b/macros/latex/contrib/l3experimental/xgalley.ins
new file mode 100644
index 0000000000..13388c46d8
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xgalley.ins
@@ -0,0 +1,59 @@
+\iffalse meta-comment
+
+File: xgalley.ins
+
+Copyright (C) 2010-2012,2016,2017,2019 The LaTeX3 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 "l3experimental 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.
+
+-----------------------------------------------------------------------
+
+Any modification of this file should ensure that the copyright and
+license information is placed in the derived files.
+
+\fi
+
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Copyright (C) 2010-2019 The LaTeX3 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 "l3experimental bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+\generate{\file{l3galley.sty}{\from{l3galley.dtx}{package}}}
+\generate{\file{xgalley.sty} {\from{xgalley.dtx} {package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/l3experimental/xgalley.pdf b/macros/latex/contrib/l3experimental/xgalley.pdf
new file mode 100644
index 0000000000..9ca057e508
--- /dev/null
+++ b/macros/latex/contrib/l3experimental/xgalley.pdf
Binary files differ