From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- macros/latex/contrib/l3experimental/CHANGELOG.md | 130 + macros/latex/contrib/l3experimental/README.md | 147 ++ .../latex/contrib/l3experimental/l3benchmark.dtx | 584 +++++ .../latex/contrib/l3experimental/l3benchmark.ins | 58 + .../latex/contrib/l3experimental/l3benchmark.pdf | Bin 0 -> 314549 bytes macros/latex/contrib/l3experimental/l3cctab.dtx | 422 ++++ macros/latex/contrib/l3experimental/l3cctab.ins | 62 + macros/latex/contrib/l3experimental/l3cctab.pdf | Bin 0 -> 330803 bytes macros/latex/contrib/l3experimental/l3color.dtx | 902 +++++++ macros/latex/contrib/l3experimental/l3color.ins | 58 + macros/latex/contrib/l3experimental/l3color.pdf | Bin 0 -> 350093 bytes .../latex/contrib/l3experimental/l3draw-boxes.dtx | 155 ++ .../latex/contrib/l3experimental/l3draw-code.pdf | Bin 0 -> 613079 bytes .../latex/contrib/l3experimental/l3draw-code.tex | 64 + .../latex/contrib/l3experimental/l3draw-layers.dtx | 216 ++ .../latex/contrib/l3experimental/l3draw-paths.dtx | 1037 ++++++++ .../latex/contrib/l3experimental/l3draw-points.dtx | 971 +++++++ .../latex/contrib/l3experimental/l3draw-scopes.dtx | 270 ++ .../contrib/l3experimental/l3draw-softpath.dtx | 615 +++++ .../latex/contrib/l3experimental/l3draw-state.dtx | 205 ++ .../contrib/l3experimental/l3draw-transforms.dtx | 339 +++ macros/latex/contrib/l3experimental/l3draw.dtx | 1166 +++++++++ macros/latex/contrib/l3experimental/l3draw.ins | 70 + macros/latex/contrib/l3experimental/l3draw.pdf | Bin 0 -> 491444 bytes macros/latex/contrib/l3experimental/l3galley.dtx | 2662 ++++++++++++++++++++ macros/latex/contrib/l3experimental/l3galley.pdf | Bin 0 -> 386124 bytes macros/latex/contrib/l3experimental/l3graphics.dtx | 716 ++++++ macros/latex/contrib/l3experimental/l3graphics.ins | 56 + macros/latex/contrib/l3experimental/l3graphics.pdf | Bin 0 -> 363470 bytes macros/latex/contrib/l3experimental/l3pdf-code.pdf | Bin 0 -> 276751 bytes macros/latex/contrib/l3experimental/l3pdf-code.tex | 56 + macros/latex/contrib/l3experimental/l3pdf.dtx | 337 +++ macros/latex/contrib/l3experimental/l3pdf.ins | 60 + macros/latex/contrib/l3experimental/l3pdf.pdf | Bin 0 -> 350290 bytes .../latex/contrib/l3experimental/l3str-format.dtx | 982 ++++++++ .../latex/contrib/l3experimental/l3str-format.pdf | Bin 0 -> 345219 bytes macros/latex/contrib/l3experimental/l3str.ins | 58 + .../latex/contrib/l3experimental/l3sys-shell.dtx | 372 +++ .../latex/contrib/l3experimental/l3sys-shell.ins | 62 + .../latex/contrib/l3experimental/l3sys-shell.pdf | Bin 0 -> 312113 bytes macros/latex/contrib/l3experimental/xcoffins.dtx | 938 +++++++ macros/latex/contrib/l3experimental/xcoffins.ins | 58 + macros/latex/contrib/l3experimental/xcoffins.pdf | Bin 0 -> 373079 bytes macros/latex/contrib/l3experimental/xgalley.dtx | 1559 ++++++++++++ macros/latex/contrib/l3experimental/xgalley.ins | 59 + macros/latex/contrib/l3experimental/xgalley.pdf | Bin 0 -> 286254 bytes 46 files changed, 15446 insertions(+) create mode 100644 macros/latex/contrib/l3experimental/CHANGELOG.md create mode 100644 macros/latex/contrib/l3experimental/README.md create mode 100644 macros/latex/contrib/l3experimental/l3benchmark.dtx create mode 100644 macros/latex/contrib/l3experimental/l3benchmark.ins create mode 100644 macros/latex/contrib/l3experimental/l3benchmark.pdf create mode 100644 macros/latex/contrib/l3experimental/l3cctab.dtx create mode 100644 macros/latex/contrib/l3experimental/l3cctab.ins create mode 100644 macros/latex/contrib/l3experimental/l3cctab.pdf create mode 100644 macros/latex/contrib/l3experimental/l3color.dtx create mode 100644 macros/latex/contrib/l3experimental/l3color.ins create mode 100644 macros/latex/contrib/l3experimental/l3color.pdf create mode 100644 macros/latex/contrib/l3experimental/l3draw-boxes.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-code.pdf create mode 100644 macros/latex/contrib/l3experimental/l3draw-code.tex create mode 100644 macros/latex/contrib/l3experimental/l3draw-layers.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-paths.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-points.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-scopes.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-softpath.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-state.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw-transforms.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw.dtx create mode 100644 macros/latex/contrib/l3experimental/l3draw.ins create mode 100644 macros/latex/contrib/l3experimental/l3draw.pdf create mode 100644 macros/latex/contrib/l3experimental/l3galley.dtx create mode 100644 macros/latex/contrib/l3experimental/l3galley.pdf create mode 100644 macros/latex/contrib/l3experimental/l3graphics.dtx create mode 100644 macros/latex/contrib/l3experimental/l3graphics.ins create mode 100644 macros/latex/contrib/l3experimental/l3graphics.pdf create mode 100644 macros/latex/contrib/l3experimental/l3pdf-code.pdf create mode 100644 macros/latex/contrib/l3experimental/l3pdf-code.tex create mode 100644 macros/latex/contrib/l3experimental/l3pdf.dtx create mode 100644 macros/latex/contrib/l3experimental/l3pdf.ins create mode 100644 macros/latex/contrib/l3experimental/l3pdf.pdf create mode 100644 macros/latex/contrib/l3experimental/l3str-format.dtx create mode 100644 macros/latex/contrib/l3experimental/l3str-format.pdf create mode 100644 macros/latex/contrib/l3experimental/l3str.ins create mode 100644 macros/latex/contrib/l3experimental/l3sys-shell.dtx create mode 100644 macros/latex/contrib/l3experimental/l3sys-shell.ins create mode 100644 macros/latex/contrib/l3experimental/l3sys-shell.pdf create mode 100644 macros/latex/contrib/l3experimental/xcoffins.dtx create mode 100644 macros/latex/contrib/l3experimental/xcoffins.ins create mode 100644 macros/latex/contrib/l3experimental/xcoffins.pdf create mode 100644 macros/latex/contrib/l3experimental/xgalley.dtx create mode 100644 macros/latex/contrib/l3experimental/xgalley.ins create mode 100644 macros/latex/contrib/l3experimental/xgalley.pdf (limited to 'macros/latex/contrib/l3experimental') 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 + +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: ; for general LaTeX3 discussion +the [LaTeX-L list](#Discussion) should be used. + +----- + +

Copyright (C) 1998-2011,2015-2019 The LaTeX3 Project
+http://latex-project.org/
+All rights reserved.

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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3benchmark.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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 } + } +% +%<*package> + \newcatcodetable #1 +% + } +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3cctab.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% !10!yellow +% \end{verbatim} +% where || 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} +% +% \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 +% + \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3color.pdf 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} +% +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3draw-code.pdf 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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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} +% +% \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> +\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} +% +% \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. +% } +% \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} +% +% \end{macrocode} +% +% \begin{macrocode} +\RequirePackage { l3color } +% \end{macrocode} +% +% Everything else is in the sub-files! +% +% \begin{macrocode} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3draw.pdf 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] +%\@ifpackagelater{expl3}{2018/02/21} +% {} +% {% +% \PackageError{l3galley}{Support package l3kernel too old} +% {% +% Please install an up to date version of l3kernel\MessageBreak +% using your TeX package manager or from CTAN.\MessageBreak +% \MessageBreak +% Loading l3galley will abort!% +% }% +% \endinput +% } +% +%<*driver> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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: +% +% \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 +% +%<*package> +\cs_new_eq:NN \l_galley_text_width_dim \linewidth +% +% \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 +% +%<*package> +\cs_new_eq:NN \l_galley_total_left_margin_dim \@totalleftmargin +% +\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: } +% +% \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: } + } +% +%<*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 + } + } +% +% \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 +% +% \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 } +% +% \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} +% +% \end{macrocode} +% +% \begin{macrocode} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3galley.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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 { } +% { +% % Code to read the bb +% \graphics_bb_save:n { } +% } +% \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} +% +% \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 } +% + \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 +% + \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3graphics.pdf 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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3pdf-code.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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 } + } +% +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3pdf.pdf 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> +\documentclass[full]{l3doc} +\usepackage{amsmath} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3str-format.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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} +% +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/l3sys-shell.pdf 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> +\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} +% +% \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}. +% } +% \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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/xcoffins.pdf 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> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \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> +\cs_new_eq:NN \l_@@_right_margin_dim \rightmargin +% +\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} +% +% \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 Binary files /dev/null and b/macros/latex/contrib/l3experimental/xgalley.pdf differ -- cgit v1.2.3