% \iffalse meta-comment % %% File: l3candidates.dtx Copyright (C) 2012-2017 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 "l3kernel bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3candidates} package\\ Experimental additions to % \pkg{l3kernel}^^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 2017/04/01} % % \maketitle % % \begin{documentation} % % \section{Important notice} % % This module provides a space in which functions can be added to % \pkg{l3kernel} (\pkg{expl3}) while still being experimental. % \begin{quote} % \bfseries % As such, the % functions here may not remain in their current form, or indeed at all, % in \pkg{l3kernel} in the future. % \end{quote} % In contrast to the material in % \pkg{l3experimental}, the functions here are all \emph{small} additions to % the kernel. We encourage programmers to test them out and report back on % the \texttt{LaTeX-L} mailing list. % % \medskip % % Thus, if you intend to use any of these functions from the candidate module in a public package % offered to others for productive use (e.g., being placed on CTAN) please consider the following points carefully: % \begin{itemize} % \item Be prepared that your public packages might require updating when such functions % are being finalized. % \item Consider informing us that you use a particular function in your public package, e.g., by % discussing this on the \texttt{LaTeX-L} % mailing list. This way it becomes easier to coordinate any updates necessary without issues % for the users of your package. % \item Discussing and understanding use cases for a particular addition or concept also helps to % ensure that we provide the right interfaces in the final version so please give us feedback % if you consider a certain candidate function useful (or not). % \end{itemize} % We only add functions in this space if we consider them being serious candidates for a final inclusion % into the kernel. However, real use sometimes leads to better ideas, so functions from this module are % \textbf{not necessarily stable} and we may have to adjust them! % % \section{Additions to \pkg{l3box}} % % \subsection{Affine transformations} % % Affine transformations are changes which (informally) preserve straight % lines. Simple translations are affine transformations, but are better handled % in \TeX{} by doing the translation first, then inserting an unmodified box. % On the other hand, rotation and resizing of boxed material can best be % handled by modifying boxes. These transformations are described here. % % \begin{function}{\box_resize:Nnn, \box_resize:cnn} % \begin{syntax} % \cs{box_resize:Nnn} \meta{box} \Arg{x-size} \Arg{y-size} % \end{syntax} % Resize the \meta{box} to \meta{x-size} horizontally and \meta{y-size} % vertically (both of the sizes are dimension expressions). % The \meta{y-size} is the vertical size (height plus depth) of % the box. The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the resizing is applied. Negative sizes will % cause the material in the \meta{box} to be reversed in direction, but the % reference point of the \meta{box} will be unchanged. % Thus negative $y$-sizes will result in a box a depth dependent on the % height of the original box a height dependent on the depth. % The resizing applies within the current \TeX{} group level. % \end{function} % % \begin{function} % {\box_resize_to_ht_plus_dp:Nn, \box_resize_to_ht_plus_dp:cn} % \begin{syntax} % \cs{box_resize_to_ht_plus_dp:Nn} \meta{box} \Arg{y-size} % \end{syntax} % Resize the \meta{box} to \meta{y-size} vertically, scaling the horizontal % size by the same amount (\meta{y-size} is a dimension expression). % The \meta{y-size} is the vertical size (height plus depth) of % the box. % The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the resizing is applied. A negative size will % cause the material in the \meta{box} to be reversed in direction, but the % reference point of the \meta{box} will be unchanged. % Thus negative $y$-sizes will result in a box with depth dependent on the % height of the original box and height dependent on the depth of the original. % The resizing applies within the current \TeX{} group level. % \end{function} % % \begin{function} % {\box_resize_to_ht:Nn, \box_resize_to_ht:cn} % \begin{syntax} % \cs{box_resize_to_ht:Nn} \meta{box} \Arg{y-size} % \end{syntax} % Resize the \meta{box} to \meta{y-size} vertically, scaling the horizontal % size by the same amount (\meta{y-size} is a dimension expression). % The \meta{y-size} is the height only, not including depth, of % the box. % The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the resizing is applied. % A negative size will % cause the material in the \meta{box} to be reversed in direction, but the % reference point of the \meta{box} will be unchanged. % Thus negative $y$-sizes will result in a box with depth dependent on the % height of the original box and height dependent on the depth of the original. % The resizing applies within the current \TeX{} group level. % \end{function} % % \begin{function}{\box_resize_to_wd:Nn, \box_resize_to_wd:cn} % \begin{syntax} % \cs{box_resize_to_wd:Nn} \meta{box} \Arg{x-size} % \end{syntax} % Resize the \meta{box} to \meta{x-size} horizontally, scaling the vertical % size by the same amount (\meta{x-size} is a dimension expression). % The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the resizing is applied. A negative size will % cause the material in the \meta{box} to be reversed in direction, but the % reference point of the \meta{box} will be unchanged. % Thus negative $y$-sizes will result in a box a depth dependent on the % height of the original box a height dependent on the depth. % The resizing applies within the current \TeX{} group level. % \end{function} % % \begin{function}[added = 2014-07-03] % {\box_resize_to_wd_and_ht:Nnn, \box_resize_to_wd_and_ht:cnn} % \begin{syntax} % \cs{box_resize_to_wd_and_ht:Nnn} \meta{box} \Arg{x-size} \Arg{y-size} % \end{syntax} % Resize the \meta{box} to a \emph{height} of % \meta{x-size} horizontally and \meta{y-size} % vertically (both of the sizes are dimension expressions). % The \meta{y-size} is the \emph{height} of the box, ignoring any depth. % The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the resizing is applied. Negative sizes will % cause the material in the \meta{box} to be reversed in direction, but the % reference point of the \meta{box} will be unchanged. % \end{function} % % \begin{function}{\box_rotate:Nn, \box_rotate:cn} % \begin{syntax} % \cs{box_rotate:Nn} \meta{box} \Arg{angle} % \end{syntax} % Rotates the \meta{box} by \meta{angle} (in degrees) anti-clockwise about % its reference point. The reference point of the updated box will be moved % horizontally such that it is at the left side of the smallest rectangle % enclosing the rotated material. % The updated \meta{box} will be an hbox, irrespective of the nature % of the \meta{box} before the rotation is applied. The rotation applies % within the current \TeX{} group level. % \end{function} % % \begin{function}{\box_scale:Nnn, \box_scale:cnn} % \begin{syntax} % \cs{box_scale:Nnn} \meta{box} \Arg{x-scale} \Arg{y-scale} % \end{syntax} % Scales the \meta{box} by factors \meta{x-scale} and \meta{y-scale} in % the horizontal and vertical directions, respectively (both scales are % integer expressions). The updated \meta{box} will be an hbox, irrespective % of the nature of the \meta{box} before the scaling is applied. Negative % scalings will cause the material in the \meta{box} to be reversed in % direction, but the reference point of the \meta{box} will be unchanged. % Thus negative $y$-scales will result in a box a depth dependent on the % height of the original box a height dependent on the depth. % The resizing applies within the current \TeX{} group level. % \end{function} % % \subsection{Viewing part of a box} % % \begin{function}{\box_clip:N, \box_clip:c} % \begin{syntax} % \cs{box_clip:N} \meta{box} % \end{syntax} % Clips the \meta{box} in the output so that only material inside the % bounding box is displayed in the output. The updated \meta{box} will be an % hbox, irrespective of the nature of the \meta{box} before the clipping is % applied. The clipping applies within the current \TeX{} group level. % % \textbf{These functions require the \LaTeX3 native drivers: they will % not work with the \LaTeXe{} \pkg{graphics} drivers!} % % \begin{texnote} % Clipping is implemented by the driver, and as such the full content of % the box is placed in the output file. Thus clipping does not remove % any information from the raw output, and hidden material can therefore % be viewed by direct examination of the file. % \end{texnote} % \end{function} % % \begin{function}{\box_trim:Nnnnn, \box_trim:cnnnn} % \begin{syntax} % \cs{box_trim:Nnnnn} \meta{box} \Arg{left} \Arg{bottom} \Arg{right} \Arg{top} % \end{syntax} % Adjusts the bounding box of the \meta{box} \meta{left} is removed from % the left-hand edge of the bounding box, \meta{right} from the right-hand % edge and so fourth. All adjustments are \meta{dimension expressions}. % Material output of the bounding box will still be displayed in the output % unless \cs{box_clip:N} is subsequently applied. % The updated \meta{box} will be an % hbox, irrespective of the nature of the \meta{box} before the trim % operation is applied. The adjustment applies within the current \TeX{} % group level. The behavior of the operation where the trims requested is % greater than the size of the box is undefined. % \end{function} % % \begin{function}{\box_viewport:Nnnnn, \box_viewport:cnnnn} % \begin{syntax} % \cs{box_viewport:Nnnnn} \meta{box} \Arg{llx} \Arg{lly} \Arg{urx} \Arg{ury} % \end{syntax} % Adjusts the bounding box of the \meta{box} such that it has lower-left % co-ordinates (\meta{llx}, \meta{lly}) and upper-right co-ordinates % (\meta{urx}, \meta{ury}). All four co-ordinate positions are % \meta{dimension expressions}. Material output of the bounding box will % still be displayed in the output unless \cs{box_clip:N} is % subsequently applied. % The updated \meta{box} will be an % hbox, irrespective of the nature of the \meta{box} before the viewport % operation is applied. The adjustment applies within the current \TeX{} % group level. % \end{function} % % \section{Additions to \pkg{l3clist}} % % \begin{function}[EXP, added = 2016-12-06] % {\clist_rand_item:N, \clist_rand_item:n, \clist_rand_item:c} % \begin{syntax} % \cs{clist_rand_item:N} \meta{clist~var} % \cs{clist_rand_item:n} \Arg{comma list} % \end{syntax} % Selects a pseudo-random item of the \meta{comma list}. If the % \meta{comma list} has no item, the result is empty. This is only % available in \pdfTeX{} and \LuaTeX{}. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{item} % will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \section{Additions to \pkg{l3coffins}} % % \begin{function}{\coffin_resize:Nnn, \coffin_resize:cnn} % \begin{syntax} % \cs{coffin_resize:Nnn} \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}{\coffin_rotate:Nn, \coffin_rotate:cn} % \begin{syntax} % \cs{coffin_rotate:Nn} \meta{coffin} \Arg{angle} % \end{syntax} % Rotates the \meta{coffin} by the given \meta{angle} (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. % \end{function} % % \begin{function}{\coffin_scale:Nnn, \coffin_scale:cnn} % \begin{syntax} % \cs{coffin_scale:Nnn} \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} % % \section{Additions to \pkg{l3file}} % % \begin{function}[TF, added = 2014-07-02]{\file_if_exist_input:n} % \begin{syntax} % \cs{file_if_exist_input:n} \Arg{file name} % \cs{file_if_exist_input:nTF} \Arg{file name} \Arg{true code} \Arg{false code} % \end{syntax} % Searches for \meta{file name} using the current \TeX{} search % path and the additional paths controlled by % \cs{file_path_include:n}). If found, inserts the \meta{true code} then % reads in the file as additional \LaTeX{} source as described for % \cs{file_input:n}. Note that \cs{file_if_exist_input:n} does not raise % an error if the file is not found, in contrast to \cs{file_input:n}. % \end{function} % % \begin{function}[added = 2012-02-11]{\ior_map_inline:Nn} % \begin{syntax} % \cs{ior_map_inline:Nn} \meta{stream} \Arg{inline function} % \end{syntax} % Applies the \meta{inline function} to \meta{lines} obtained by % reading one or more lines (until an equal number of left and right % braces are found) from the \meta{stream}. The \meta{inline function} % should consist of code which will receive the \meta{line} as |#1|. % Note that \TeX{} removes trailing space and tab characters % (character codes 32 and 9) from every line upon input. \TeX{} also % ignores any trailing new-line marker from the file it reads. % \end{function} % % \begin{function}[added = 2012-02-11]{\ior_str_map_inline:Nn} % \begin{syntax} % \cs{ior_str_map_inline:Nn} \Arg{stream} \Arg{inline function} % \end{syntax} % Applies the \meta{inline function} to every \meta{line} % in the \meta{stream}. The material is read from the \meta{stream} % as a series of tokens with category code $12$ (other), with the % exception of space characters which are given category code $10$ % (space). The \meta{inline function} should consist of code which % will receive the \meta{line} as |#1|. % Note that \TeX{} removes trailing space and tab characters % (character codes 32 and 9) from every line upon input. \TeX{} also % ignores any trailing new-line marker from the file it reads. % \end{function} % % \begin{function}[added = 2012-06-29]{\ior_map_break:} % \begin{syntax} % \cs{ior_map_break:} % \end{syntax} % Used to terminate a \cs[no-index]{ior_map_\ldots} function before all % lines from the \meta{stream} have been processed. This will % normally take place within a conditional statement, for example % \begin{verbatim} % \ior_map_inline:Nn \l_my_ior % { % \str_if_eq:nnTF { #1 } { bingo } % { \ior_map_break: } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{ior_map_\ldots} scenario will lead to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted by the % internal macro \cs{__prg_break_point:Nn} before further items are taken % from the input stream. This will depend on the design of the mapping % function. % \end{texnote} % \end{function} % % \begin{function}[added = 2012-06-29]{\ior_map_break:n} % \begin{syntax} % \cs{ior_map_break:n} \Arg{tokens} % \end{syntax} % Used to terminate a \cs[no-index]{ior_map_\ldots} function before all % lines in the \meta{stream} have been processed, inserting % the \meta{tokens} after the mapping has ended. This will % normally take place within a conditional statement, for example % \begin{verbatim} % \ior_map_inline:Nn \l_my_ior % { % \str_if_eq:nnTF { #1 } { bingo } % { \ior_map_break:n { } } % { % % Do something useful % } % } % \end{verbatim} % Use outside of a \cs[no-index]{ior_map_\ldots} scenario will lead to low % level \TeX{} errors. % \begin{texnote} % When the mapping is broken, additional tokens may be inserted by the % internal macro \cs{__prg_break_point:Nn} before the \meta{tokens} are % inserted into the input stream. % This will depend on the design of the mapping function. % \end{texnote} % \end{function} % % \begin{function}[added = 2014-08-22] % {\ior_log_streams:, \iow_log_streams:} % \begin{syntax} % \cs{ior_log_streams:} % \cs{iow_log_streams:} % \end{syntax} % Writes in the log file a list of the file names associated with each % open stream: intended for tracking down problems. % \end{function} % % \section{Additions to \pkg{l3int}} % % \begin{function}[EXP, added = 2016-12-06]{\int_rand:nn} % \begin{syntax} % \cs{int_rand:nn} \Arg{intexpr_1} \Arg{intexpr_2} % \end{syntax} % Evaluates the two \meta{integer expressions} and produces a % pseudo-random number between the two (with bounds included). This % is only available in \pdfTeX{} and \LuaTeX{}. % \end{function} % % \section{Additions to \pkg{l3msg}} % % In very rare cases it may be necessary to produce errors in an % expansion-only context. The functions in this section should only be % used if there is no alternative approach using \cs{msg_error:nnnnnn} % or other non-expandable commands from the previous section. Despite % having a similar interface as non-expandable messages, expandable % errors must be handled internally very differently from normal error % messages, as none of the tools to print to the terminal or the log % file are expandable. As a result, the message text and arguments are % not expanded, and messages must be very short (with default settings, % they are truncated after approximately 50 characters). It is % advisable to ensure that the message is understandable even when % truncated. Another particularity of expandable messages is that they % cannot be redirected or turned off by the user. % % \begin{function}[EXP, added = 2015-08-06] % { % \msg_expandable_error:nnnnnn , % \msg_expandable_error:nnnnn , % \msg_expandable_error:nnnn , % \msg_expandable_error:nnn , % \msg_expandable_error:nn , % \msg_expandable_error:nnffff , % \msg_expandable_error:nnfff , % \msg_expandable_error:nnff , % \msg_expandable_error:nnf , % } % \begin{syntax} % \cs{msg_expandable_error:nnnnnn} \Arg{module} \Arg{message} \Arg{arg one} \Arg{arg two} \Arg{arg three} \Arg{arg four} % \end{syntax} % Issues an \enquote{Undefined error} message from \TeX{} itself % using the undefined control sequence \cs{::error} then prints % \enquote{! \meta{module}: }\meta{error message}, which should be % short. With default settings, anything beyond approximately $60$ % characters long (or bytes in some engines) is cropped. A leading % space might be removed as well. % \end{function} % % \section{Additions to \pkg{l3prop}} % % \begin{function}[EXP]{\prop_count:N, \prop_count:c} % \begin{syntax} % \cs{prop_count:N} \meta{property list} % \end{syntax} % Leaves the number of key--value pairs in the \meta{property list} in % the input stream as an \meta{integer denotation}. % \end{function} % % \begin{function}[rEXP] % {\prop_map_tokens:Nn, \prop_map_tokens:cn} % \begin{syntax} % \cs{prop_map_tokens:Nn} \meta{property list} \Arg{code} % \end{syntax} % Analogue of \cs{prop_map_function:NN} which maps several tokens % instead of a single function. The \meta{code} receives each % key--value pair in the \meta{property list} as two trailing brace % groups. For instance, % \begin{verbatim} % \prop_map_tokens:Nn \l_my_prop { \str_if_eq:nnT { mykey } } % \end{verbatim} % will expand to the value corresponding to \texttt{mykey}: for each % pair in |\l_my_prop| the function \cs{str_if_eq:nnT} receives % \texttt{mykey}, the \meta{key} and the \meta{value} as its three % arguments. For that specific task, \cs{prop_item:Nn} is faster. % \end{function} % % \begin{function}[EXP, added = 2016-12-06] % {\prop_rand_key_value:N, \prop_rand_key_value:c} % \begin{syntax} % \cs{prop_rand_key_value:N} \meta{prop~var} % \end{syntax} % Selects a pseudo-random key--value pair in the \meta{property list} % and returns \Arg{key}\Arg{value}. If the \meta{property list} is % empty the result is empty. This is only available in \pdfTeX{} and % \LuaTeX{}. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{value} % will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \section{Additions to \pkg{l3seq}} % % \begin{function}[rEXP] % { % \seq_mapthread_function:NNN, \seq_mapthread_function:NcN, % \seq_mapthread_function:cNN, \seq_mapthread_function:ccN % } % \begin{syntax} % \cs{seq_mapthread_function:NNN} \meta{seq_1} \meta{seq_2} \meta{function} % \end{syntax} % Applies \meta{function} to every pair of items % \meta{seq_1-item}--\meta{seq_2-item} from the two sequences, returning % items from both sequences from left to right. The \meta{function} will % receive two \texttt{n}-type arguments for each iteration. The mapping % will terminate when % the end of either sequence is reached (\emph{i.e.}~whichever sequence has % fewer items determines how many iterations % occur). % \end{function} % % \begin{function}{\seq_set_filter:NNn, \seq_gset_filter:NNn} % \begin{syntax} % \cs{seq_set_filter:NNn} \meta{sequence_1} \meta{sequence_2} \Arg{inline boolexpr} % \end{syntax} % Evaluates the \meta{inline boolexpr} for every \meta{item} stored % within the \meta{sequence_2}. The \meta{inline boolexpr} will % receive the \meta{item} as |#1|. The sequence of all \meta{items} % for which the \meta{inline boolexpr} evaluated to \texttt{true} % is assigned to \meta{sequence_1}. % \begin{texnote} % Contrarily to other mapping functions, \cs{seq_map_break:} cannot % be used in this function, and will lead to low-level \TeX{} errors. % \end{texnote} % \end{function} % % \begin{function}[added = 2011-12-22] % {\seq_set_map:NNn, \seq_gset_map:NNn} % \begin{syntax} % \cs{seq_set_map:NNn} \meta{sequence_1} \meta{sequence_2} \Arg{inline function} % \end{syntax} % Applies \meta{inline function} to every \meta{item} stored % within the \meta{sequence_2}. The \meta{inline function} should % consist of code which will receive the \meta{item} as |#1|. % The sequence resulting from \texttt{x}-expanding % \meta{inline function} applied to each \meta{item} % is assigned to \meta{sequence_1}. As such, the code % in \meta{inline function} should be expandable. % \begin{texnote} % Contrarily to other mapping functions, \cs{seq_map_break:} cannot % be used in this function, and will lead to low-level \TeX{} errors. % \end{texnote} % \end{function} % % \begin{function}[EXP, added = 2016-12-06]{\seq_rand_item:N, \seq_rand_item:c} % \begin{syntax} % \cs{seq_rand_item:N} \meta{seq~var} % \end{syntax} % Selects a pseudo-random item of the \meta{sequence}. If the % \meta{sequence} is empty the result is empty. This is only % available in \pdfTeX{} and \LuaTeX{}. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{item} % will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \section{Additions to \pkg{l3skip}} % % \begin{function}{\skip_split_finite_else_action:nnNN} % \begin{syntax} % \cs{skip_split_finite_else_action:nnNN} \Arg{skipexpr} \Arg{action} % ~~\meta{dimen_1} \meta{dimen_2} % \end{syntax} % Checks if the \meta{skipexpr} contains finite glue. If it does then it % assigns % \meta{dimen_1} the stretch component and \meta{dimen_2} the shrink % component. If % it contains infinite glue set \meta{dimen_1} and \meta{dimen_2} to $0$\,pt % and place |#2| into the input stream: this is usually an error or % warning message of some sort. % \end{function} % % \section{Additions to \pkg{l3tl}} % % \begin{function}[EXP,pTF]{\tl_if_single_token:n} % \begin{syntax} % \cs{tl_if_single_token_p:n} \Arg{token list} % \cs{tl_if_single_token:nTF} \Arg{token list} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the token list consists of exactly one token, \emph{i.e.}~is % either a single space character or a single \enquote{normal} token. % Token groups (|{|\ldots|}|) are not single tokens. % \end{function} % % \begin{function}[EXP]{\tl_reverse_tokens:n} % \begin{syntax} % \cs{tl_reverse_tokens:n} \Arg{tokens} % \end{syntax} % This function, which works directly on \TeX{} tokens, reverses % the order of the \meta{tokens}: the first will be the last and % the last will become first. Spaces are preserved. The reversal % also operates within brace groups, but the braces themselves % are not exchanged, as this would lead to an unbalanced token % list. For instance, \cs{tl_reverse_tokens:n} |{a~{b()}}| % leaves |{)(b}~a| in the input stream. This function requires % two steps of expansion. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the token % list will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \begin{function}[EXP]{\tl_count_tokens:n} % \begin{syntax} % \cs{tl_count_tokens:n} \Arg{tokens} % \end{syntax} % Counts the number of \TeX{} tokens in the \meta{tokens} and leaves % this information in the input stream. Every token, including spaces and % braces, contributes one to the total; thus for instance, the token count of % |a~{bc}| is $6$. % This function requires three expansions, % giving an \meta{integer denotation}. % \end{function} % % \begin{function}[EXP, added = 2014-06-30, updated = 2016-01-12] % { % \tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n, % \tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn % } % \begin{syntax} % \cs{tl_upper_case:n} \Arg{tokens} % \cs{tl_upper_case:nn} \Arg{language} \Arg{tokens} % \end{syntax} % These functions are intended to be applied to input which may be % regarded broadly as \enquote{text}. They traverse the \meta{tokens} and % change the case of characters as discussed below. The character code of % the characters replaced may be arbitrary: the replacement characters will % have standard document-level category codes ($11$ for letters, $12$ for % letter-like characters which can also be case-changed). Begin-group and % end-group characters in the \meta{tokens} are normalized and become |{| % and |}|, respectively. % % Importantly, notice that these functions are intended for working with % user text for typesetting. For case changing programmatic data see the % \pkg{l3str} module and discussion there of \cs{str_lower_case:n}, % \cs{str_upper_case:n} and \cs{str_fold_case:n}. % \end{function} % % The functions perform expansion on the input in most cases. In particular, % input in the form of token lists or expandable functions will be expanded % \emph{unless} it falls within one of the special handling classes described % below. This expansion approach means that in general the result of case % changing will match the \enquote{natural} outcome expected from a % \enquote{functional} approach to case modification. For example % \begin{verbatim} % \tl_set:Nn \l_tmpa_tl { hello } % \tl_upper_case:n { \l_tmpa_tl \c_space_tl world } % \end{verbatim} % will produce % \begin{verbatim} % HELLO WORLD % \end{verbatim} % The expansion approach taken means that in package mode any \LaTeXe{} % \enquote{robust} commands which may appear in the input should be converted % to engine-protected versions using for example the \tn{robustify} command % from the \pkg{etoolbox} package. % % \begin{variable}{\l_tl_case_change_math_tl} % Case changing will not take place within math mode material so for example % \begin{verbatim} % \tl_upper_case:n { Some~text~$y = mx + c$~with~{Braces} } % \end{verbatim} % will become % \begin{verbatim} % SOME TEXT $y = mx + c$ WITH {BRACES} % \end{verbatim} % Material inside math mode is left entirely unchanged: in particular, no % expansion is undertaken. % % Detection of math mode is controlled by the list of tokens in % \cs{l_tl_case_change_math_tl}, which should be in open--close pairs. In % package mode the standard settings is % \begin{verbatim} % $ $ \( \) % \end{verbatim} % % Note that while expansion occurs when searching the text it does not % apply to math mode material (which should be unaffected by case changing). % As such, whilst the opening token for math mode may be \enquote{hidden} % inside a command/macro, the closing one cannot be as this is being % searched for in math mode. Typically, in the types of \enquote{text} % the case changing functions are intended to apply to this should not be % an issue. % \end{variable} % % \begin{variable}{\l_tl_case_change_exclude_tl} % Case changing can be prevented by using any command on the list % \cs{l_tl_case_change_exclude_tl}. Each entry should be a function % to be followed by one argument: the latter will be preserved as-is % with no expansion. Thus for example following % \begin{verbatim} % \tl_put_right:Nn \l_tl_case_change_exclude_tl { \NoChangeCase } % \end{verbatim} % the input % \begin{verbatim} % \tl_upper_case:n % { Some~text~$y = mx + c$~with~\NoChangeCase {Protection} } % \end{verbatim} % will result in % \begin{verbatim} % SOME TEXT $y = mx + c$ WITH \NoChangeCase {Protection} % \end{verbatim} % Notice that the case changing mapping preserves the inclusion of % the escape functions: it is left to other code to provide suitable % definitions (typically equivalent to \cs{use:n}). In particular, the % result of case changing is returned protected by \cs{exp_not:n}. % % When used with \LaTeXe{} the commands |\cite|, |\ensuremath|, |\label| % and |\ref| are automatically included in the list for exclusion from % case changing. % \end{variable} % % \begin{variable}{\l_tl_case_change_accents_tl} % This list specifies accent commands which should be left unexpanded % in the output. This allows for example % \begin{verbatim} % \tl_upper_case:n { \" { a } } % \end{verbatim} % to yield % \begin{verbatim} % \" { A } % \end{verbatim} % irrespective of the expandability of |\"|. % % The standard contents of this variable is |\"|, |\'|, |\.|, |\^|, |\`|, % |\~|, |\c|, |\H|, |\k|, |\r|, |\t|, |\u| and |\v|. % \end{variable} % % \enquote{Mixed} case conversion may be regarded informally as converting the % first character of the \meta{tokens} to upper case and the rest to lower % case. However, the process is more complex than this as there are some % situations where a single lower case character maps to a special form, for % example \texttt{ij} in Dutch which becomes \texttt{IJ}. As such, % \cs[index=tl_mixed_case:n]{tl_mixed_case:n(n)} % implement a more sophisticated mapping which accounts % for this and for modifying accents on the first letter. Spaces at the start % of the \meta{tokens} are ignored when finding the first \enquote{letter} for % conversion. % \begin{verbatim} % \tl_mixed_case:n { hello~WORLD } % => "Hello world" % \tl_mixed_case:n { ~hello~WORLD } % => " Hello world" % \tl_mixed_case:n { {hello}~WORLD } % => "{Hello} world" % \end{verbatim} % When finding the first \enquote{letter} for this process, any content in % math mode or covered by \cs{l_tl_case_change_exclude_tl} is ignored. % % (Note that the Unicode Consortium describe this as \enquote{title case}, but % that in English title case applies on a word-by-word basis. The % \enquote{mixed} case implemented here is a lower level concept needed for % both \enquote{title} and \enquote{sentence} casing of text.) % % \begin{variable}{\l_tl_mixed_case_ignore_tl} % The list of characters to ignore when searching for the first % \enquote{letter} in mixed-casing is determined by % \cs{l_tl_mixed_change_ignore_tl}. This has the standard setting % \begin{verbatim} % ( [ { ` - % \end{verbatim} % where comparisons are made on a character basis. % \end{variable} % % As is generally true for \pkg{expl3}, these functions are designed to % work with Unicode input only. As such, UTF-8 input is assumed for % \emph{all} engines. When used with \XeTeX{} or \LuaTeX{} a full range of % Unicode transformations are enabled. Specifically, the standard mappings % here follow those defined by the \href{http://www.unicode.org}^^A % {Unicode Consortium} in \texttt{UnicodeData.txt} and % \texttt{SpecialCasing.txt}. In the case of $8$-bit engines, mappings % are provided for characters which can be represented in output typeset % using the |T1| font encoding. Thus for example |รค| can be case-changed % using \pdfTeX{}. For \pTeX{} only the ASCII range is covered as the % engine treats input outside of this range as east Asian. % % Context-sensitive mappings are enabled: language-dependent cases are % discussed below. Context detection will expand input but treats any % unexpandable control sequences as \enquote{failures} to match a context. % % Language-sensitive conversions are enabled using the \meta{language} % argument, and follow Unicode Consortium guidelines. Currently, the % languages recognised for special handling are as follows. % \begin{itemize} % \item Azeri and Turkish (\texttt{az} and \texttt{tr}). % The case pairs I/i-dotless and I-dot/i are activated for these % languages. The combining dot mark is removed when lower % casing I-dot and introduced when upper casing i-dotless. % \item German (\texttt{de-alt}). % An alternative mapping for German in which the lower case % \emph{Eszett} maps to a \emph{gro\ss{}es Eszett}. % \item Lithuanian (\texttt{lt}). % The lower case letters i and j should retain a dot above when the % accents grave, acute or tilde are present. This is implemented for % lower casing of the relevant upper case letters both when input as % single Unicode codepoints and when using combining accents. The % combining dot is removed when upper casing in these cases. Note that % \emph{only} the accents used in Lithuanian are covered: the behaviour % of other accents are not modified. % \item Dutch (\texttt{nl}). % Capitalisation of \texttt{ij} at the beginning of mixed cased % input produces \texttt{IJ} rather than \texttt{Ij}. The output % retains two separate letters, thus this transformation \emph{is} % available using \pdfTeX{}. % \end{itemize} % % Creating additional context-sensitive mappings requires knowledge % of the underlying mapping implementation used here. The team are happy % to add these to the kernel where they are well-documented % (\emph{e.g.}~in Unicode Consortium or relevant government publications). % % \begin{function}[added = 2014-06-25] % { % \tl_set_from_file:Nnn, \tl_set_from_file:cnn, % \tl_gset_from_file:Nnn, \tl_gset_from_file:cnn % } % \begin{syntax} % \cs{tl_set_from_file:Nnn} \meta{tl} \Arg{setup} \Arg{filename} % \end{syntax} % Defines \meta{tl} to the contents of \meta{filename}. % Category codes may need to be set appropriately via the \meta{setup} % argument. % \end{function} % % \begin{function}[added = 2014-06-25] % { % \tl_set_from_file_x:Nnn, \tl_set_from_file_x:cnn, % \tl_gset_from_file_x:Nnn, \tl_gset_from_file_x:cnn % } % \begin{syntax} % \cs{tl_set_from_file_x:Nnn} \meta{tl} \Arg{setup} \Arg{filename} % \end{syntax} % Defines \meta{tl} to the contents of \meta{filename}, expanding % the contents of the file as it is read. Category codes and other % definitions may need to be set appropriately via the \meta{setup} % argument. % \end{function} % % \begin{function}[EXP, added = 2016-12-06] % {\tl_rand_item:N, \tl_rand_item:c, \tl_rand_item:n} % \begin{syntax} % \cs{tl_rand_item:N} \meta{tl~var} % \cs{tl_rand_item:n} \Arg{token list} % \end{syntax} % Selects a pseudo-random item of the \meta{token list}. If the % \meta{token list} is blank, the result is empty. This is only % available in \pdfTeX{} and \LuaTeX{}. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{item} % will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \begin{function}[EXP, added = 2017-02-17] % {\tl_range:Nnn, \tl_range:cnn, \tl_range:nnn} % \begin{syntax} % \cs{tl_range:Nnn} \meta{tl~var} \Arg{start index} \Arg{end index} % \cs{tl_range:nnn} \Arg{token list} \Arg{start index} \Arg{end index} % \end{syntax} % Leaves in the input stream the items from the \meta{start index} to the % \meta{end index} inclusive. Positive \meta{indices} are counted % from the start of the \meta{token list}, $1$~being the first item, and % negative \meta{indices} are counted from the end of the token list, % $-1$~being the last item. If either of \meta{start index} or % \meta{end index} is~$0$, the result is empty. For instance, % \begin{verbatim} % \iow_term:x { \tl_range:nnn { abcdef } { 2 } { 5 } } % \iow_term:x { \tl_range:nnn { abcdef } { -4 } { -1 } } % \iow_term:x { \tl_range:nnn { abcdef } { -2 } { -1 } } % \iow_term:x { \tl_range:nnn { abcdef } { 0 } { -1 } } % \end{verbatim} % will print \texttt{{b}{c}{d}{e}}, \texttt{{c}{d}{e}{f}}, \texttt{{e}{f}}, and an empty % line to the terminal. The \meta{start index} must always be smaller than % or equal to the \meta{end index}: if this is not the case then no output % is generated. Thus % \begin{verbatim} % \iow_term:x { \tl_range:nnn { abcdef } { 5 } { 2 } } % \iow_term:x { \tl_range:nnn { abcdef } { -1 } { -4 } } % \end{verbatim} % both yield empty token lists. % \begin{texnote} % The result is returned within the \tn{unexpanded} % primitive (\cs{exp_not:n}), which means that the \meta{item} % will not expand further when appearing in an \texttt{x}-type % argument expansion. % \end{texnote} % \end{function} % % \section{Additions to \pkg{l3tokens}} % % \begin{function}[TF, updated = 2012-12-20]{\peek_N_type:} % \begin{syntax} % \cs{peek_N_type:TF} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the next \meta{token} in the input stream can be safely % grabbed as an \texttt{N}-type argument. The test will be \meta{false} % if the next \meta{token} is either an explicit or implicit % begin-group or end-group token (with any character code), or % an explicit or implicit space character (with character code $32$ % and category code $10$), or an outer token (never used in \LaTeX3) % and \meta{true} in all other cases. % Note that a \meta{true} result ensures that the next \meta{token} is % a valid \texttt{N}-type argument. However, if the next \meta{token} % is for instance \cs{c_space_token}, the test will take the % \meta{false} branch, even though the next \meta{token} is in fact % a valid \texttt{N}-type argument. The \meta{token} will be left % in the input stream after the \meta{true code} or \meta{false code} % (as appropriate to the result of the test). % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3candidates} Implementation} % % \begin{macrocode} %<*initex|package> % \end{macrocode} % % \subsection{Additions to \pkg{l3box}} % % \begin{macrocode} %<@@=box> % \end{macrocode} % % \subsection{Affine transformations} % % \begin{variable}{\l_@@_angle_fp} % When rotating boxes, the angle itself may be needed by the % engine-dependent code. This is done using the \pkg{fp} module so % that the value is tidied up properly. % \begin{macrocode} \fp_new:N \l_@@_angle_fp % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_cos_fp, \l_@@_sin_fp} % These are used to hold the calculated sine and cosine values while % carrying out a rotation. % \begin{macrocode} \fp_new:N \l_@@_cos_fp \fp_new:N \l_@@_sin_fp % \end{macrocode} % \end{variable} % % \begin{variable} % {\l_@@_top_dim, \l_@@_bottom_dim, \l_@@_left_dim, \l_@@_right_dim} % These are the positions of the four edges of a box before % manipulation. % \begin{macrocode} \dim_new:N \l_@@_top_dim \dim_new:N \l_@@_bottom_dim \dim_new:N \l_@@_left_dim \dim_new:N \l_@@_right_dim % \end{macrocode} % \end{variable} % % \begin{variable} % { % \l_@@_top_new_dim, \l_@@_bottom_new_dim , % \l_@@_left_new_dim, \l_@@_right_new_dim % } % These are the positions of the four edges of a box after % manipulation. % \begin{macrocode} \dim_new:N \l_@@_top_new_dim \dim_new:N \l_@@_bottom_new_dim \dim_new:N \l_@@_left_new_dim \dim_new:N \l_@@_right_new_dim % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_internal_box} % Scratch space, but also needed by some parts of the driver. % \begin{macrocode} \box_new:N \l_@@_internal_box % \end{macrocode} % \end{variable} % % \begin{macro}{\box_rotate:Nn} % \begin{macro}[aux]{\@@_rotate:N} % \begin{macro}[aux]{\@@_rotate_x:nnN, \@@_rotate_y:nnN} % \begin{macro}[aux] % { % \@@_rotate_quadrant_one:, \@@_rotate_quadrant_two:, % \@@_rotate_quadrant_three:, \@@_rotate_quadrant_four: % } % Rotation of a box starts with working out the relevant sine and % cosine. The actual rotation is in an auxiliary to keep the flow slightly % clearer % \begin{macrocode} \cs_new_protected:Npn \box_rotate:Nn #1#2 { \hbox_set:Nn #1 { \group_begin: \fp_set:Nn \l_@@_angle_fp {#2} \fp_set:Nn \l_@@_sin_fp { sind ( \l_@@_angle_fp ) } \fp_set:Nn \l_@@_cos_fp { cosd ( \l_@@_angle_fp ) } \@@_rotate:N #1 \group_end: } } % \end{macrocode} % The edges of the box are then recorded: the left edge will % always be at zero. Rotation of the four edges then takes place: this is % most efficiently done on a quadrant by quadrant basis. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate:N #1 { \dim_set:Nn \l_@@_top_dim { \box_ht:N #1 } \dim_set:Nn \l_@@_bottom_dim { -\box_dp:N #1 } \dim_set:Nn \l_@@_right_dim { \box_wd:N #1 } \dim_zero:N \l_@@_left_dim % \end{macrocode} % The next step is to work out the $x$ and $y$ coordinates of vertices of % the rotated box in relation to its original coordinates. The box can be % visualized with vertices $B$, $C$, $D$ and $E$ is illustrated % (Figure~\ref{fig:l3candidates:rotation}). The vertex $O$ is the reference point % on the baseline, and in this implementation is also the centre of rotation. % \begin{figure} % \centering % \setlength{\unitlength}{3pt}^^A % \begin{picture}(34,36)(12,44) % \thicklines % \put(20,52){\dashbox{1}(20,21){}} % \put(20,80){\line(0,-1){36}} % \put(12,58){\line(1, 0){34}} % \put(41,59){A} % \put(40,74){B} % \put(21,74){C} % \put(21,49){D} % \put(40,49){E} % \put(21,59){O} % \end{picture} % \caption{Co-ordinates of a box prior to rotation.} % \label{fig:l3candidates:rotation} % \end{figure} % The formulae are, for a point $P$ and angle $\alpha$: % \[ % \begin{array}{l} % P'_x = P_x - O_x \\ % P'_y = P_y - O_y \\ % P''_x = ( P'_x \cos(\alpha)) - ( P'_y \sin(\alpha) ) \\ % P''_y = ( P'_x \sin(\alpha)) + ( P'_y \cos(\alpha) ) \\ % P'''_x = P''_x + O_x + L_x \\ % P'''_y = P''_y + O_y % \end{array} % \] % The \enquote{extra} horizontal translation $L_x$ at the end is calculated % so that the leftmost point of the resulting box has $x$-coordinate $0$. % This is desirable as \TeX{} boxes must have the reference point at % the left edge of the box. (As $O$ is always $(0,0)$, this part of the % calculation is omitted here.) % \begin{macrocode} \fp_compare:nNnTF \l_@@_sin_fp > \c_zero_fp { \fp_compare:nNnTF \l_@@_cos_fp > \c_zero_fp { \@@_rotate_quadrant_one: } { \@@_rotate_quadrant_two: } } { \fp_compare:nNnTF \l_@@_cos_fp < \c_zero_fp { \@@_rotate_quadrant_three: } { \@@_rotate_quadrant_four: } } % \end{macrocode} % The position of the box edges are now known, but the box at this % stage be misplaced relative to the current \TeX{} reference point. So the % content of the box is moved such that the reference point of the % rotated box will be in the same place as the original. % \begin{macrocode} \hbox_set:Nn \l_@@_internal_box { \box_use:N #1 } \hbox_set:Nn \l_@@_internal_box { \tex_kern:D -\l_@@_left_new_dim \hbox:n { \__driver_box_use_rotate:Nn \l_@@_internal_box \l_@@_angle_fp } } % \end{macrocode} % Tidy up the size of the box so that the material is actually inside % the bounding box. The result can then be used to reset the original % box. % \begin{macrocode} \box_set_ht:Nn \l_@@_internal_box { \l_@@_top_new_dim } \box_set_dp:Nn \l_@@_internal_box { -\l_@@_bottom_new_dim } \box_set_wd:Nn \l_@@_internal_box { \l_@@_right_new_dim - \l_@@_left_new_dim } \box_use:N \l_@@_internal_box } % \end{macrocode} % \end{macro} % \end{macro} % These functions take a general point $(|#1|, |#2|)$ and rotate its % location about the origin, using the previously-set sine and cosine % values. Each function gives only one component of the location of the % updated point. This is because for rotation of a box each step needs % only one value, and so performance is gained by avoiding working % out both $x'$ and $y'$ at the same time. Contrast this with % the equivalent function in the \pkg{l3coffins} module, where both parts % are needed. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate_x:nnN #1#2#3 { \dim_set:Nn #3 { \fp_to_dim:n { \l_@@_cos_fp * \dim_to_fp:n {#1} - \l_@@_sin_fp * \dim_to_fp:n {#2} } } } \cs_new_protected:Npn \@@_rotate_y:nnN #1#2#3 { \dim_set:Nn #3 { \fp_to_dim:n { \l_@@_sin_fp * \dim_to_fp:n {#1} + \l_@@_cos_fp * \dim_to_fp:n {#2} } } } % \end{macrocode} % Rotation of the edges is done using a different formula for each % quadrant. In every case, the top and bottom edges only need the % resulting $y$-values, whereas the left and right edges need the % $x$-values. Each case is a question of picking out which corner % ends up at with the maximum top, bottom, left and right value. Doing % this by hand means a lot less calculating and avoids lots of % comparisons. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate_quadrant_one: { \@@_rotate_y:nnN \l_@@_right_dim \l_@@_top_dim \l_@@_top_new_dim \@@_rotate_y:nnN \l_@@_left_dim \l_@@_bottom_dim \l_@@_bottom_new_dim \@@_rotate_x:nnN \l_@@_left_dim \l_@@_top_dim \l_@@_left_new_dim \@@_rotate_x:nnN \l_@@_right_dim \l_@@_bottom_dim \l_@@_right_new_dim } \cs_new_protected:Npn \@@_rotate_quadrant_two: { \@@_rotate_y:nnN \l_@@_right_dim \l_@@_bottom_dim \l_@@_top_new_dim \@@_rotate_y:nnN \l_@@_left_dim \l_@@_top_dim \l_@@_bottom_new_dim \@@_rotate_x:nnN \l_@@_right_dim \l_@@_top_dim \l_@@_left_new_dim \@@_rotate_x:nnN \l_@@_left_dim \l_@@_bottom_dim \l_@@_right_new_dim } \cs_new_protected:Npn \@@_rotate_quadrant_three: { \@@_rotate_y:nnN \l_@@_left_dim \l_@@_bottom_dim \l_@@_top_new_dim \@@_rotate_y:nnN \l_@@_right_dim \l_@@_top_dim \l_@@_bottom_new_dim \@@_rotate_x:nnN \l_@@_right_dim \l_@@_bottom_dim \l_@@_left_new_dim \@@_rotate_x:nnN \l_@@_left_dim \l_@@_top_dim \l_@@_right_new_dim } \cs_new_protected:Npn \@@_rotate_quadrant_four: { \@@_rotate_y:nnN \l_@@_left_dim \l_@@_top_dim \l_@@_top_new_dim \@@_rotate_y:nnN \l_@@_right_dim \l_@@_bottom_dim \l_@@_bottom_new_dim \@@_rotate_x:nnN \l_@@_left_dim \l_@@_bottom_dim \l_@@_left_new_dim \@@_rotate_x:nnN \l_@@_right_dim \l_@@_top_dim \l_@@_right_new_dim } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{variable}{\l_@@_scale_x_fp, \l_@@_scale_y_fp} % Scaling is potentially-different in the two axes. % \begin{macrocode} \fp_new:N \l_@@_scale_x_fp \fp_new:N \l_@@_scale_y_fp % \end{macrocode} % \end{variable} % % \begin{macro}{\box_resize:Nnn, \box_resize:cnn} % \begin{macro}[aux]{\@@_resize_set_corners:N} % \begin{macro}[aux]{\@@_resize:N} % \begin{macro}[aux]{\@@_resize:NNN} % Resizing a box starts by working out the various dimensions of the % existing box. % \begin{macrocode} \cs_new_protected:Npn \box_resize:Nnn #1#2#3 { \hbox_set:Nn #1 { \group_begin: \@@_resize_set_corners:N #1 % \end{macrocode} % The $x$-scaling and resulting box size is easy enough to work % out: the dimension is that given as |#2|, and the scale is simply the % new width divided by the old one. % \begin{macrocode} \fp_set:Nn \l_@@_scale_x_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \l_@@_right_dim } } % \end{macrocode} % The $y$-scaling needs both the height and the depth of the current box. % \begin{macrocode} \fp_set:Nn \l_@@_scale_y_fp { \dim_to_fp:n {#3} / \dim_to_fp:n { \l_@@_top_dim - \l_@@_bottom_dim } } % \end{macrocode} % Hand off to the auxiliary which does the rest of the work. % \begin{macrocode} \@@_resize:N #1 \group_end: } } \cs_generate_variant:Nn \box_resize:Nnn { c } \cs_new_protected:Npn \@@_resize_set_corners:N #1 { \dim_set:Nn \l_@@_top_dim { \box_ht:N #1 } \dim_set:Nn \l_@@_bottom_dim { -\box_dp:N #1 } \dim_set:Nn \l_@@_right_dim { \box_wd:N #1 } \dim_zero:N \l_@@_left_dim } % \end{macrocode} % With at least one real scaling to do, the next phase is to find the new % edge co-ordinates. In the $x$~direction this is relatively easy: just % scale the right edge. In the $y$~direction, both dimensions have to be % scaled, and this again needs the absolute scale value. % Once that is all done, the common resize/rescale code can be employed. % \begin{macrocode} \cs_new_protected:Npn \@@_resize:N #1 { \@@_resize:NNN \l_@@_right_new_dim \l_@@_scale_x_fp \l_@@_right_dim \@@_resize:NNN \l_@@_bottom_new_dim \l_@@_scale_y_fp \l_@@_bottom_dim \@@_resize:NNN \l_@@_top_new_dim \l_@@_scale_y_fp \l_@@_top_dim \@@_resize_common:N #1 } \cs_new_protected:Npn \@@_resize:NNN #1#2#3 { \dim_set:Nn #1 { \fp_to_dim:n { \fp_abs:n { #2 } * \dim_to_fp:n { #3 } } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\box_resize_to_ht:Nn, \box_resize_to_ht:cn} % \begin{macro}{\box_resize_to_ht_plus_dp:Nn, \box_resize_to_ht_plus_dp:cn} % \begin{macro}{\box_resize_to_wd:Nn, \box_resize_to_wd:cn} % \begin{macro}{\box_resize_to_wd_and_ht:Nnn, \box_resize_to_wd_and_ht:cnn} % Scaling to a (total) height or to a width is a simplified version of the main % resizing operation, with the scale simply copied between the two parts. The % internal auxiliary is called using the scaling value twice, as the sign for % both parts is needed (as this allows the same internal code to be used as % for the general case). % \begin{macrocode} \cs_new_protected:Npn \box_resize_to_ht:Nn #1#2 { \hbox_set:Nn #1 { \group_begin: \@@_resize_set_corners:N #1 \fp_set:Nn \l_@@_scale_y_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \l_@@_top_dim } } \fp_set_eq:NN \l_@@_scale_x_fp \l_@@_scale_y_fp \@@_resize:N #1 \group_end: } } \cs_generate_variant:Nn \box_resize_to_ht:Nn { c } \cs_new_protected:Npn \box_resize_to_ht_plus_dp:Nn #1#2 { \hbox_set:Nn #1 { \group_begin: \@@_resize_set_corners:N #1 \fp_set:Nn \l_@@_scale_y_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \l_@@_top_dim - \l_@@_bottom_dim } } \fp_set_eq:NN \l_@@_scale_x_fp \l_@@_scale_y_fp \@@_resize:N #1 \group_end: } } \cs_generate_variant:Nn \box_resize_to_ht_plus_dp:Nn { c } \cs_new_protected:Npn \box_resize_to_wd:Nn #1#2 { \hbox_set:Nn #1 { \group_begin: \@@_resize_set_corners:N #1 \fp_set:Nn \l_@@_scale_x_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \l_@@_right_dim } } \fp_set_eq:NN \l_@@_scale_y_fp \l_@@_scale_x_fp \@@_resize:N #1 \group_end: } } \cs_generate_variant:Nn \box_resize_to_wd:Nn { c } \cs_new_protected:Npn \box_resize_to_wd_and_ht:Nnn #1#2#3 { \hbox_set:Nn #1 { \group_begin: \@@_resize_set_corners:N #1 \fp_set:Nn \l_@@_scale_x_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \l_@@_right_dim } } \fp_set:Nn \l_@@_scale_y_fp { \dim_to_fp:n {#3} / \dim_to_fp:n { \l_@@_top_dim } } \@@_resize:N #1 \group_end: } } \cs_generate_variant:Nn \box_resize_to_wd_and_ht:Nnn { c } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\box_scale:Nnn, \box_scale:cnn} % When scaling a box, setting the scaling itself is easy enough. The % new dimensions are also relatively easy to find, allowing only for % the need to keep them positive in all cases. Once that is done then % after a check for the trivial scaling a hand-off can be made to the % common code. The dimension scaling operations are carried out using % the \TeX{} mechanism as it avoids needing to use too many \texttt{fp} % operations. % \begin{macrocode} \cs_new_protected:Npn \box_scale:Nnn #1#2#3 { \hbox_set:Nn #1 { \group_begin: \fp_set:Nn \l_@@_scale_x_fp {#2} \fp_set:Nn \l_@@_scale_y_fp {#3} \dim_set:Nn \l_@@_top_dim { \box_ht:N #1 } \dim_set:Nn \l_@@_bottom_dim { -\box_dp:N #1 } \dim_set:Nn \l_@@_right_dim { \box_wd:N #1 } \dim_zero:N \l_@@_left_dim \dim_set:Nn \l_@@_top_new_dim { \fp_abs:n { \l_@@_scale_y_fp } \l_@@_top_dim } \dim_set:Nn \l_@@_bottom_new_dim { \fp_abs:n { \l_@@_scale_y_fp } \l_@@_bottom_dim } \dim_set:Nn \l_@@_right_new_dim { \fp_abs:n { \l_@@_scale_x_fp } \l_@@_right_dim } \@@_resize_common:N #1 \group_end: } } \cs_generate_variant:Nn \box_scale:Nnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_resize_common:N} % The main resize function places in input into a box which will start % of with zero width, and includes the handles for engine rescaling. % \begin{macrocode} \cs_new_protected:Npn \@@_resize_common:N #1 { \hbox_set:Nn \l_@@_internal_box { \__driver_box_use_scale:Nnn #1 \l_@@_scale_x_fp \l_@@_scale_y_fp } % \end{macrocode} % The new height and depth can be applied directly. % \begin{macrocode} \fp_compare:nNnTF \l_@@_scale_y_fp > \c_zero_fp { \box_set_ht:Nn \l_@@_internal_box { \l_@@_top_new_dim } \box_set_dp:Nn \l_@@_internal_box { -\l_@@_bottom_new_dim } } { \box_set_dp:Nn \l_@@_internal_box { \l_@@_top_new_dim } \box_set_ht:Nn \l_@@_internal_box { -\l_@@_bottom_new_dim } } % \end{macrocode} % Things are not quite as obvious for the width, as the reference point % needs to remain unchanged. For positive scaling factors resizing the % box is all that is needed. However, for case of a negative scaling % the material must be shifted such that the reference point ends up in % the right place. % \begin{macrocode} \fp_compare:nNnTF \l_@@_scale_x_fp < \c_zero_fp { \hbox_to_wd:nn { \l_@@_right_new_dim } { \tex_kern:D \l_@@_right_new_dim \box_use:N \l_@@_internal_box \tex_hss:D } } { \box_set_wd:Nn \l_@@_internal_box { \l_@@_right_new_dim } \hbox:n { \tex_kern:D \c_zero_dim \box_use:N \l_@@_internal_box \tex_hss:D } } } % \end{macrocode} % \end{macro} % % \subsection{Viewing part of a box} % % \begin{macro}{\box_clip:N, \box_clip:c} % A wrapper around the driver-dependent code. % \begin{macrocode} \cs_new_protected:Npn \box_clip:N #1 { \hbox_set:Nn #1 { \__driver_box_use_clip:N #1 } } \cs_generate_variant:Nn \box_clip:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\box_trim:Nnnnn, \box_trim:cnnnn} % Trimming from the left- and right-hand edges of the box is easy: kern the % appropriate parts off each side. % \begin{macrocode} \cs_new_protected:Npn \box_trim:Nnnnn #1#2#3#4#5 { \hbox_set:Nn \l_@@_internal_box { \tex_kern:D -\__dim_eval:w #2 \__dim_eval_end: \box_use:N #1 \tex_kern:D -\__dim_eval:w #4 \__dim_eval_end: } % \end{macrocode} % For the height and depth, there is a need to watch the baseline is % respected. Material always has to stay on the correct side, so trimming % has to check that there is enough material to trim. First, the bottom % edge. If there is enough depth, simply set the depth, or if not move % down so the result is zero depth. \cs{box_move_down:nn} is used in both % cases so the resulting box always contains a \tn{lower} primitive. % The internal box is used here as it allows safe use of \cs{box_set_dp:Nn}. % \begin{macrocode} \dim_compare:nNnTF { \box_dp:N #1 } > {#3} { \hbox_set:Nn \l_@@_internal_box { \box_move_down:nn \c_zero_dim { \box_use:N \l_@@_internal_box } } \box_set_dp:Nn \l_@@_internal_box { \box_dp:N #1 - (#3) } } { \hbox_set:Nn \l_@@_internal_box { \box_move_down:nn { #3 - \box_dp:N #1 } { \box_use:N \l_@@_internal_box } } \box_set_dp:Nn \l_@@_internal_box \c_zero_dim } % \end{macrocode} % Same thing, this time from the top of the box. % \begin{macrocode} \dim_compare:nNnTF { \box_ht:N \l_@@_internal_box } > {#5} { \hbox_set:Nn \l_@@_internal_box { \box_move_up:nn \c_zero_dim { \box_use:N \l_@@_internal_box } } \box_set_ht:Nn \l_@@_internal_box { \box_ht:N \l_@@_internal_box - (#5) } } { \hbox_set:Nn \l_@@_internal_box { \box_move_up:nn { #5 - \box_ht:N \l_@@_internal_box } { \box_use:N \l_@@_internal_box } } \box_set_ht:Nn \l_@@_internal_box \c_zero_dim } \box_set_eq:NN #1 \l_@@_internal_box } \cs_generate_variant:Nn \box_trim:Nnnnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\box_viewport:Nnnnn, \box_viewport:cnnnn} % The same general logic as for the trim operation, but with absolute % dimensions. As a result, there are some things to watch out for in the % vertical direction. % \begin{macrocode} \cs_new_protected:Npn \box_viewport:Nnnnn #1#2#3#4#5 { \hbox_set:Nn \l_@@_internal_box { \tex_kern:D -\__dim_eval:w #2 \__dim_eval_end: \box_use:N #1 \tex_kern:D \__dim_eval:w #4 - \box_wd:N #1 \__dim_eval_end: } \dim_compare:nNnTF {#3} < \c_zero_dim { \hbox_set:Nn \l_@@_internal_box { \box_move_down:nn \c_zero_dim { \box_use:N \l_@@_internal_box } } \box_set_dp:Nn \l_@@_internal_box { -\dim_eval:n {#3} } } { \hbox_set:Nn \l_@@_internal_box { \box_move_down:nn {#3} { \box_use:N \l_@@_internal_box } } \box_set_dp:Nn \l_@@_internal_box \c_zero_dim } \dim_compare:nNnTF {#5} > \c_zero_dim { \hbox_set:Nn \l_@@_internal_box { \box_move_up:nn \c_zero_dim { \box_use:N \l_@@_internal_box } } \box_set_ht:Nn \l_@@_internal_box { #5 \dim_compare:nNnT {#3} > \c_zero_dim { - (#3) } } } { \hbox_set:Nn \l_@@_internal_box { \box_move_up:nn { -\dim_eval:n {#5} } { \box_use:N \l_@@_internal_box } } \box_set_ht:Nn \l_@@_internal_box \c_zero_dim } \box_set_eq:NN #1 \l_@@_internal_box } \cs_generate_variant:Nn \box_viewport:Nnnnn { c } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3clist}} % % \begin{macrocode} %<@@=clist> % \end{macrocode} % % \begin{macro}{\clist_rand_item:n, \clist_rand_item:N, \clist_rand_item:c} % \begin{macro}[aux]{\@@_rand_item:nn} % The |N|-type function is not implemented through the |n|-type % function for efficiency: for instance comma-list variables do not % require space-trimming of their items. Even testing for emptyness % of an |n|-type comma-list is slow, so we count items first and use % that both for the emptyness test and the pseudo-random integer. % Importantly, \cs{clist_item:Nn} and \cs{clist_item:nn} only evaluate % their argument once. % \begin{macrocode} \cs_new:Npn \clist_rand_item:n #1 { \exp_args:Nf \@@_rand_item:nn { \clist_count:n {#1} } {#1} } \cs_new:Npn \@@_rand_item:nn #1#2 { \int_compare:nNnF {#1} = 0 { \clist_item:nn {#2} { \int_rand:nn { 1 } {#1} } } } \cs_new:Npn \clist_rand_item:N #1 { \clist_if_empty:NF #1 { \clist_item:Nn #1 { \int_rand:nn { 1 } { \clist_count:N #1 } } } } \cs_generate_variant:Nn \clist_rand_item:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3coffins}} % % \begin{macrocode} %<@@=coffin> % \end{macrocode} % % \subsection{Rotating coffins} % % \begin{variable}{\l_@@_sin_fp} % \begin{variable}{\l_@@_cos_fp} % Used for rotations to get the sine and cosine values. % \begin{macrocode} \fp_new:N \l_@@_sin_fp \fp_new:N \l_@@_cos_fp % \end{macrocode} % \end{variable} % \end{variable} % % \begin{variable}{\l_@@_bounding_prop} % A property list for the bounding box of a coffin. This is only needed % during the rotation, so there is just the one. % \begin{macrocode} \prop_new:N \l_@@_bounding_prop % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_bounding_shift_dim} % The shift of the bounding box of a coffin from the real content. % \begin{macrocode} \dim_new:N \l_@@_bounding_shift_dim % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_left_corner_dim} % \begin{variable}{\l_@@_right_corner_dim} % \begin{variable}{\l_@@_bottom_corner_dim} % \begin{variable}{\l_@@_top_corner_dim} % These are used to hold maxima for the various corner values: these % thus define the minimum size of the bounding box after rotation. % \begin{macrocode} \dim_new:N \l_@@_left_corner_dim \dim_new:N \l_@@_right_corner_dim \dim_new:N \l_@@_bottom_corner_dim \dim_new:N \l_@@_top_corner_dim % \end{macrocode} % \end{variable} % \end{variable} % \end{variable} % \end{variable} % % \begin{macro}{\coffin_rotate:Nn, \coffin_rotate:cn} % Rotating a coffin requires several steps which can be conveniently % run together. The sine and cosine of the angle in degrees are % computed. This is then used to set \cs{l_@@_sin_fp} and % \cs{l_@@_cos_fp}, which are carried through unchanged for the rest % of the procedure. % \begin{macrocode} \cs_new_protected:Npn \coffin_rotate:Nn #1#2 { \fp_set:Nn \l_@@_sin_fp { sind ( #2 ) } \fp_set:Nn \l_@@_cos_fp { cosd ( #2 ) } % \end{macrocode} % The corners and poles of the coffin can now be rotated around the % origin. This is best achieved using mapping functions. % \begin{macrocode} \prop_map_inline:cn { l_@@_corners_ \__int_value:w #1 _prop } { \@@_rotate_corner:Nnnn #1 {##1} ##2 } \prop_map_inline:cn { l_@@_poles_ \__int_value:w #1 _prop } { \@@_rotate_pole:Nnnnnn #1 {##1} ##2 } % \end{macrocode} % The bounding box of the coffin needs to be rotated, and to do this % the corners have to be found first. They are then rotated in the same % way as the corners of the coffin material itself. % \begin{macrocode} \@@_set_bounding:N #1 \prop_map_inline:Nn \l_@@_bounding_prop { \@@_rotate_bounding:nnn {##1} ##2 } % \end{macrocode} % At this stage, there needs to be a calculation to find where the % corners of the content and the box itself will end up. % \begin{macrocode} \@@_find_corner_maxima:N #1 \@@_find_bounding_shift: \box_rotate:Nn #1 {#2} % \end{macrocode} % The correction of the box position itself takes place here. The idea % is that the bounding box for a coffin is tight up to the content, and % has the reference point at the bottom-left. The $x$-direction is % handled by moving the content by the difference in the positions of % the bounding box and the content left edge. The $y$-direction is % dealt with by moving the box down by any depth it has acquired. The % internal box is used here to allow for the next step. % \begin{macrocode} \hbox_set:Nn \l_@@_internal_box { \tex_kern:D \__dim_eval:w \l_@@_bounding_shift_dim - \l_@@_left_corner_dim \__dim_eval_end: \box_move_down:nn { \l_@@_bottom_corner_dim } { \box_use:N #1 } } % \end{macrocode} % If there have been any previous rotations then the size of the % bounding box will be bigger than the contents. This can be corrected % easily by setting the size of the box to the height and width of the % content. As this operation requires setting box dimensions and these % transcend grouping, the safe way to do this is to use the internal box % and to reset the result into the target box. % \begin{macrocode} \box_set_ht:Nn \l_@@_internal_box { \l_@@_top_corner_dim - \l_@@_bottom_corner_dim } \box_set_dp:Nn \l_@@_internal_box { 0 pt } \box_set_wd:Nn \l_@@_internal_box { \l_@@_right_corner_dim - \l_@@_left_corner_dim } \hbox_set:Nn #1 { \box_use:N \l_@@_internal_box } % \end{macrocode} % The final task is to move the poles and corners such that they are % back in alignment with the box reference point. % \begin{macrocode} \prop_map_inline:cn { l_@@_corners_ \__int_value:w #1 _prop } { \@@_shift_corner:Nnnn #1 {##1} ##2 } \prop_map_inline:cn { l_@@_poles_ \__int_value:w #1 _prop } { \@@_shift_pole:Nnnnnn #1 {##1} ##2 } } \cs_generate_variant:Nn \coffin_rotate:Nn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_set_bounding:N} % The bounding box corners for a coffin are easy enough to find: this % is the same code as for the corners of the material itself, but % using a dedicated property list. % \begin{macrocode} \cs_new_protected:Npn \@@_set_bounding:N #1 { \prop_put:Nnx \l_@@_bounding_prop { tl } { { 0 pt } { \dim_eval:n { \box_ht:N #1 } } } \prop_put:Nnx \l_@@_bounding_prop { tr } { { \dim_eval:n { \box_wd:N #1 } } { \dim_eval:n { \box_ht:N #1 } } } \dim_set:Nn \l_@@_internal_dim { -\box_dp:N #1 } \prop_put:Nnx \l_@@_bounding_prop { bl } { { 0 pt } { \dim_use:N \l_@@_internal_dim } } \prop_put:Nnx \l_@@_bounding_prop { br } { { \dim_eval:n { \box_wd:N #1 } } { \dim_use:N \l_@@_internal_dim } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_rotate_bounding:nnn} % \begin{macro}{\@@_rotate_corner:Nnnn} % Rotating the position of the corner of the coffin is just a case % of treating this as a vector from the reference point. The same % treatment is used for the corners of the material itself and the % bounding box. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate_bounding:nnn #1#2#3 { \@@_rotate_vector:nnNN {#2} {#3} \l_@@_x_dim \l_@@_y_dim \prop_put:Nnx \l_@@_bounding_prop {#1} { { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim } } } \cs_new_protected:Npn \@@_rotate_corner:Nnnn #1#2#3#4 { \@@_rotate_vector:nnNN {#3} {#4} \l_@@_x_dim \l_@@_y_dim \prop_put:cnx { l_@@_corners_ \__int_value:w #1 _prop } {#2} { { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_rotate_pole:Nnnnnn} % Rotating a single pole simply means shifting the co-ordinate of % the pole and its direction. The rotation here is about the bottom-left % corner of the coffin. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate_pole:Nnnnnn #1#2#3#4#5#6 { \@@_rotate_vector:nnNN {#3} {#4} \l_@@_x_dim \l_@@_y_dim \@@_rotate_vector:nnNN {#5} {#6} \l_@@_x_prime_dim \l_@@_y_prime_dim \@@_set_pole:Nnx #1 {#2} { { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim } { \dim_use:N \l_@@_x_prime_dim } { \dim_use:N \l_@@_y_prime_dim } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_rotate_vector:nnNN} % A rotation function, which needs only an input vector (as dimensions) % and an output space. The values \cs{l_@@_cos_fp} and % \cs{l_@@_sin_fp} should previously have been set up correctly. % Working this way means that the floating point work is kept to a % minimum: for any given rotation the sin and cosine values do no % change, after all. % \begin{macrocode} \cs_new_protected:Npn \@@_rotate_vector:nnNN #1#2#3#4 { \dim_set:Nn #3 { \fp_to_dim:n { \dim_to_fp:n {#1} * \l_@@_cos_fp - \dim_to_fp:n {#2} * \l_@@_sin_fp } } \dim_set:Nn #4 { \fp_to_dim:n { \dim_to_fp:n {#1} * \l_@@_sin_fp + \dim_to_fp:n {#2} * \l_@@_cos_fp } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_find_corner_maxima:N} % \begin{macro}[aux]{\@@_find_corner_maxima_aux:nn} % The idea here is to find the extremities of the content of the % coffin. This is done by looking for the smallest values for the bottom % and left corners, and the largest values for the top and right % corners. The values start at the maximum dimensions so that the % case where all are positive or all are negative works out correctly. % \begin{macrocode} \cs_new_protected:Npn \@@_find_corner_maxima:N #1 { \dim_set:Nn \l_@@_top_corner_dim { -\c_max_dim } \dim_set:Nn \l_@@_right_corner_dim { -\c_max_dim } \dim_set:Nn \l_@@_bottom_corner_dim { \c_max_dim } \dim_set:Nn \l_@@_left_corner_dim { \c_max_dim } \prop_map_inline:cn { l_@@_corners_ \__int_value:w #1 _prop } { \@@_find_corner_maxima_aux:nn ##2 } } \cs_new_protected:Npn \@@_find_corner_maxima_aux:nn #1#2 { \dim_set:Nn \l_@@_left_corner_dim { \dim_min:nn { \l_@@_left_corner_dim } {#1} } \dim_set:Nn \l_@@_right_corner_dim { \dim_max:nn { \l_@@_right_corner_dim } {#1} } \dim_set:Nn \l_@@_bottom_corner_dim { \dim_min:nn { \l_@@_bottom_corner_dim } {#2} } \dim_set:Nn \l_@@_top_corner_dim { \dim_max:nn { \l_@@_top_corner_dim } {#2} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_find_bounding_shift:} % \begin{macro}[aux]{\@@_find_bounding_shift_aux:nn} % The approach to finding the shift for the bounding box is similar to % that for the corners. However, there is only one value needed here and % a fixed input property list, so things are a bit clearer. % \begin{macrocode} \cs_new_protected:Npn \@@_find_bounding_shift: { \dim_set:Nn \l_@@_bounding_shift_dim { \c_max_dim } \prop_map_inline:Nn \l_@@_bounding_prop { \@@_find_bounding_shift_aux:nn ##2 } } \cs_new_protected:Npn \@@_find_bounding_shift_aux:nn #1#2 { \dim_set:Nn \l_@@_bounding_shift_dim { \dim_min:nn { \l_@@_bounding_shift_dim } {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_shift_corner:Nnnn} % \begin{macro}{\@@_shift_pole:Nnnnnn} % Shifting the corners and poles of a coffin means subtracting the % appropriate values from the $x$- and $y$-components. For % the poles, this means that the direction vector is unchanged. % \begin{macrocode} \cs_new_protected:Npn \@@_shift_corner:Nnnn #1#2#3#4 { \prop_put:cnx { l_@@_corners_ \__int_value:w #1 _ prop } {#2} { { \dim_eval:n { #3 - \l_@@_left_corner_dim } } { \dim_eval:n { #4 - \l_@@_bottom_corner_dim } } } } \cs_new_protected:Npn \@@_shift_pole:Nnnnnn #1#2#3#4#5#6 { \prop_put:cnx { l_@@_poles_ \__int_value:w #1 _ prop } {#2} { { \dim_eval:n { #3 - \l_@@_left_corner_dim } } { \dim_eval:n { #4 - \l_@@_bottom_corner_dim } } {#5} {#6} } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Resizing coffins} % % \begin{variable}{\l_@@_scale_x_fp} % \begin{variable}{\l_@@_scale_y_fp} % Storage for the scaling factors in $x$ and $y$, respectively. % \begin{macrocode} \fp_new:N \l_@@_scale_x_fp \fp_new:N \l_@@_scale_y_fp % \end{macrocode} % \end{variable} % \end{variable} % % \begin{variable}{\l_@@_scaled_total_height_dim} % \begin{variable}{\l_@@_scaled_width_dim} % When scaling, the values given have to be turned into absolute values. % \begin{macrocode} \dim_new:N \l_@@_scaled_total_height_dim \dim_new:N \l_@@_scaled_width_dim % \end{macrocode} % \end{variable} % \end{variable} % % \begin{macro}{\coffin_resize:Nnn, \coffin_resize:cnn} % Resizing a coffin begins by setting up the user-friendly names for % the dimensions of the coffin box. The new sizes are then turned into % scale factor. This is the same operation as takes place for the % underlying box, but that operation is grouped and so the same % calculation is done here. % \begin{macrocode} \cs_new_protected:Npn \coffin_resize:Nnn #1#2#3 { \fp_set:Nn \l_@@_scale_x_fp { \dim_to_fp:n {#2} / \dim_to_fp:n { \coffin_wd:N #1 } } \fp_set:Nn \l_@@_scale_y_fp { \dim_to_fp:n {#3} / \dim_to_fp:n { \coffin_ht:N #1 + \coffin_dp:N #1 } } \box_resize:Nnn #1 {#2} {#3} \@@_resize_common:Nnn #1 {#2} {#3} } \cs_generate_variant:Nn \coffin_resize:Nnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_resize_common:Nnn} % The poles and corners of the coffin are scaled to the appropriate % places before actually resizing the underlying box. % \begin{macrocode} \cs_new_protected:Npn \@@_resize_common:Nnn #1#2#3 { \prop_map_inline:cn { l_@@_corners_ \__int_value:w #1 _prop } { \@@_scale_corner:Nnnn #1 {##1} ##2 } \prop_map_inline:cn { l_@@_poles_ \__int_value:w #1 _prop } { \@@_scale_pole:Nnnnnn #1 {##1} ##2 } % \end{macrocode} % Negative $x$-scaling values will place the poles in the wrong % location: this is corrected here. % \begin{macrocode} \fp_compare:nNnT \l_@@_scale_x_fp < \c_zero_fp { \prop_map_inline:cn { l_@@_corners_ \__int_value:w #1 _prop } { \@@_x_shift_corner:Nnnn #1 {##1} ##2 } \prop_map_inline:cn { l_@@_poles_ \__int_value:w #1 _prop } { \@@_x_shift_pole:Nnnnnn #1 {##1} ##2 } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\coffin_scale:Nnn, \coffin_scale:cnn} % For scaling, the opposite calculation is done to find the new % dimensions for the coffin. Only the total height is needed, as this % is the shift required for corners and poles. The scaling is done % the \TeX{} way as this works properly with floating point values % without needing to use the \texttt{fp} module. % \begin{macrocode} \cs_new_protected:Npn \coffin_scale:Nnn #1#2#3 { \fp_set:Nn \l_@@_scale_x_fp {#2} \fp_set:Nn \l_@@_scale_y_fp {#3} \box_scale:Nnn #1 { \l_@@_scale_x_fp } { \l_@@_scale_y_fp } \dim_set:Nn \l_@@_internal_dim { \coffin_ht:N #1 + \coffin_dp:N #1 } \dim_set:Nn \l_@@_scaled_total_height_dim { \fp_abs:n { \l_@@_scale_y_fp } \l_@@_internal_dim } \dim_set:Nn \l_@@_scaled_width_dim { -\fp_abs:n { \l_@@_scale_x_fp } \coffin_wd:N #1 } \@@_resize_common:Nnn #1 { \l_@@_scaled_width_dim } { \l_@@_scaled_total_height_dim } } \cs_generate_variant:Nn \coffin_scale:Nnn { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_scale_vector:nnNN} % This functions scales a vector from the origin using the pre-set scale % factors in $x$ and $y$. This is a much less complex operation % than rotation, and as a result the code is a lot clearer. % \begin{macrocode} \cs_new_protected:Npn \@@_scale_vector:nnNN #1#2#3#4 { \dim_set:Nn #3 { \fp_to_dim:n { \dim_to_fp:n {#1} * \l_@@_scale_x_fp } } \dim_set:Nn #4 { \fp_to_dim:n { \dim_to_fp:n {#2} * \l_@@_scale_y_fp } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_scale_corner:Nnnn} % \begin{macro}{\@@_scale_pole:Nnnnnn} % Scaling both corners and poles is a simple calculation using the % preceding vector scaling. % \begin{macrocode} \cs_new_protected:Npn \@@_scale_corner:Nnnn #1#2#3#4 { \@@_scale_vector:nnNN {#3} {#4} \l_@@_x_dim \l_@@_y_dim \prop_put:cnx { l_@@_corners_ \__int_value:w #1 _prop } {#2} { { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim } } } \cs_new_protected:Npn \@@_scale_pole:Nnnnnn #1#2#3#4#5#6 { \@@_scale_vector:nnNN {#3} {#4} \l_@@_x_dim \l_@@_y_dim \@@_set_pole:Nnx #1 {#2} { { \dim_use:N \l_@@_x_dim } { \dim_use:N \l_@@_y_dim } {#5} {#6} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_x_shift_corner:Nnnn} % \begin{macro}{\@@_x_shift_pole:Nnnnnn} % These functions correct for the $x$ displacement that takes % place with a negative horizontal scaling. % \begin{macrocode} \cs_new_protected:Npn \@@_x_shift_corner:Nnnn #1#2#3#4 { \prop_put:cnx { l_@@_corners_ \__int_value:w #1 _prop } {#2} { { \dim_eval:n { #3 + \box_wd:N #1 } } {#4} } } \cs_new_protected:Npn \@@_x_shift_pole:Nnnnnn #1#2#3#4#5#6 { \prop_put:cnx { l_@@_poles_ \__int_value:w #1 _prop } {#2} { { \dim_eval:n #3 + \box_wd:N #1 } {#4} {#5} {#6} } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3file}} % % \begin{macrocode} %<@@=file> % \end{macrocode} % % \begin{macro}[TF]{\file_if_exist_input:n} % Input of a file with a test for existence cannot be done the usual % way as the tokens to insert are in an odd place. % \begin{macrocode} \cs_new_protected:Npn \file_if_exist_input:n #1 { \file_if_exist:nT {#1} { \@@_input:V \l_@@_internal_name_tl } } \cs_new_protected:Npn \file_if_exist_input:nT #1#2 { \file_if_exist:nT {#1} { #2 \@@_input:V \l_@@_internal_name_tl } } \cs_new_protected:Npn \file_if_exist_input:nF #1 { \file_if_exist:nTF {#1} { \@@_input:V \l_@@_internal_name_tl } } \cs_new_protected:Npn \file_if_exist_input:nTF #1#2 { \file_if_exist:nTF {#1} { #2 \@@_input:V \l_@@_internal_name_tl } } % \end{macrocode} % \end{macro} % % \begin{macrocode} %<@@=ior> % \end{macrocode} % % \begin{macro}[EXP]{\ior_map_break:, \ior_map_break:n} % Usual map breaking functions. Those are not yet in \pkg{l3kernel} % proper since the mapping below is the first of its kind. % \begin{macrocode} \cs_new:Npn \ior_map_break: { \__prg_map_break:Nn \ior_map_break: { } } \cs_new:Npn \ior_map_break:n { \__prg_map_break:Nn \ior_map_break: } % \end{macrocode} % \end{macro} % % \begin{macro}{\ior_map_inline:Nn, \ior_str_map_inline:Nn} % \begin{macro}[aux]{\@@_map_inline:NNn} % \begin{macro}[aux]{\@@_map_inline:NNNn} % \begin{macro}[aux]{\@@_map_inline_loop:NNN} % \begin{variable}{\l_@@_internal_tl} % Mapping to an input stream can be done on either a token or a string % basis, hence the set up. Within that, there is a check to avoid reading % past the end of a file, hence the two applications of \cs{ior_if_eof:N}. % This mapping cannot be nested as the stream has only one \enquote{current % line}. % \begin{macrocode} \cs_new_protected:Npn \ior_map_inline:Nn { \@@_map_inline:NNn \ior_get:NN } \cs_new_protected:Npn \ior_str_map_inline:Nn { \@@_map_inline:NNn \ior_str_get:NN } \cs_new_protected:Npn \@@_map_inline:NNn { \int_gincr:N \g__prg_map_int \exp_args:Nc \@@_map_inline:NNNn { __prg_map_ \int_use:N \g__prg_map_int :n } } \cs_new_protected:Npn \@@_map_inline:NNNn #1#2#3#4 { \cs_set:Npn #1 ##1 {#4} \ior_if_eof:NF #3 { \@@_map_inline_loop:NNN #1#2#3 } \__prg_break_point:Nn \ior_map_break: { \int_gdecr:N \g__prg_map_int } } \cs_new_protected:Npn \@@_map_inline_loop:NNN #1#2#3 { #2 #3 \l_@@_internal_tl \ior_if_eof:NF #3 { \exp_args:No #1 \l_@@_internal_tl \@@_map_inline_loop:NNN #1#2#3 } } \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\ior_log_streams:} % Redirect output of \cs{ior_list_streams:} to the log. % \begin{macrocode} \cs_new_protected:Npn \ior_log_streams: { \__msg_log_next: \ior_list_streams: } % \end{macrocode} % \end{macro} % % \begin{macrocode} %<@@=iow> % \end{macrocode} % % \begin{macro}{\iow_log_streams:} % Redirect output of \cs{iow_list_streams:} to the log. % \begin{macrocode} \cs_new_protected:Npn \iow_log_streams: { \__msg_log_next: \iow_list_streams: } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3int}} % % \begin{macrocode} %<@@=int> % \end{macrocode} % % \begin{macro}[EXP]{\int_rand:nn} % \begin{macro}[aux, EXP] % { % \@@_rand:ww, \@@_rand_narrow:n, % \@@_rand_narrow:nnn, \@@_rand_narrow:nnnn % } % Evaluate the argument and filter out the case where the lower % bound~|#1| is more than the upper bound~|#2|. Then determine % whether the range is narrower than \cs{c__fp_rand_size_int}; |#2-#1| % may overflow for very large positive~|#2| and negative~|#1|. If the % range is wide, use slower code from \pkg{l3fp}. If the range is % narrow, call \cs{@@_rand_narrow:nn} \Arg{choices} |{#1}| where % \meta{choices} is the number of possible outcomes. Then % \cs{@@_rand_narrow:nnnn} receives a random number reduced modulo % \meta{choices}, the random number itself, \meta{choices} and |#1|. % To avoid bias, throw away the random number if it lies in the last, % incomplete, interval of size \meta{choices} in % $[0,\cs{c__fp_rand_size_int}-1]$, and try again. % \begin{macrocode} \cs_if_exist:NTF \pdftex_uniformdeviate:D { \cs_new:Npn \int_rand:nn #1#2 { \exp_after:wN \@@_rand:ww \__int_value:w \__int_eval:w #1 \exp_after:wN ; \__int_value:w \__int_eval:w #2 ; } \cs_new:Npn \@@_rand:ww #1; #2; { \int_compare:nNnTF {#1} > {#2} { \__msg_kernel_expandable_error:nnnn { kernel } { backward-range } {#1} {#2} \@@_rand:ww #2; #1; } { \int_compare:nNnTF {#1} > 0 { \int_compare:nNnTF { #2 - #1 } < \c__fp_rand_size_int } { \int_compare:nNnTF {#2} < { #1 + \c__fp_rand_size_int } } { \exp_args:Nf \@@_rand_narrow:nn { \int_eval:n { #2 - #1 + 1 } } {#1} } { \fp_to_int:n { randint(#1,#2) } } } } \cs_new:Npn \@@_rand_narrow:nn { \exp_args:No \@@_rand_narrow:nnn { \pdftex_uniformdeviate:D \c__fp_rand_size_int } } \cs_new:Npn \@@_rand_narrow:nnn #1#2 { \exp_args:Nf \@@_rand_narrow:nnnn { \int_mod:nn {#1} {#2} } {#1} {#2} } \cs_new:Npn \@@_rand_narrow:nnnn #1#2#3#4 { \int_compare:nNnTF { #2 - #1 + #3 } > \c__fp_rand_size_int { \@@_rand_narrow:nn {#3} {#4} } { \int_eval:n { #4 + #1 } } } } { \cs_new:Npn \int_rand:nn #1#2 { \__msg_kernel_expandable_error:nn { kernel } { fp-no-random } \int_eval:n {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % % The following must be added to \pkg{l3msg}. % \begin{macrocode} \cs_if_exist:NT \pdftex_uniformdeviate:D { \__msg_kernel_new:nnn { kernel } { backward-range } { Bounds~ordered~backwards~in~\int_rand:nn {#1}~{#2}. } } % \end{macrocode} % % \subsection{Additions to \pkg{l3msg}} % % \begin{macrocode} %<@@=msg> % \end{macrocode} % % \begin{macro}[EXP] % { % \msg_expandable_error:nnnnnn , % \msg_expandable_error:nnnnn , % \msg_expandable_error:nnnn , % \msg_expandable_error:nnn , % \msg_expandable_error:nn , % \msg_expandable_error:nnffff , % \msg_expandable_error:nnfff , % \msg_expandable_error:nnff , % \msg_expandable_error:nnf % } % \begin{macro}[aux]{\__msg_expandable_error_module:nn} % Pass to an auxiliary the message to display and the module name % \begin{macrocode} \cs_new:Npn \msg_expandable_error:nnnnnn #1#2#3#4#5#6 { \exp_args:Nf \@@_expandable_error_module:nn { \exp_args:Nf \tl_to_str:n { \use:c { \c_@@_text_prefix_tl #1 / #2 } {#3} {#4} {#5} {#6} } } {#1} } \cs_new:Npn \msg_expandable_error:nnnnn #1#2#3#4#5 { \msg_expandable_error:nnnnnn {#1} {#2} {#3} {#4} {#5} { } } \cs_new:Npn \msg_expandable_error:nnnn #1#2#3#4 { \msg_expandable_error:nnnnnn {#1} {#2} {#3} {#4} { } { } } \cs_new:Npn \msg_expandable_error:nnn #1#2#3 { \msg_expandable_error:nnnnnn {#1} {#2} {#3} { } { } { } } \cs_new:Npn \msg_expandable_error:nn #1#2 { \msg_expandable_error:nnnnnn {#1} {#2} { } { } { } { } } \cs_generate_variant:Nn \msg_expandable_error:nnnnnn { nnffff } \cs_generate_variant:Nn \msg_expandable_error:nnnnn { nnfff } \cs_generate_variant:Nn \msg_expandable_error:nnnn { nnff } \cs_generate_variant:Nn \msg_expandable_error:nnn { nnf } \cs_new:Npn \@@_expandable_error_module:nn #1#2 { \exp_after:wN \exp_after:wN \exp_after:wN \use_none_delimit_by_q_stop:w \use:n { \::error ! ~ #2 : ~ #1 } \q_stop } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3prop}} % % \begin{macrocode} %<@@=prop> % \end{macrocode} % % \begin{macro}[EXP]{\prop_count:N, \prop_count:c} % \begin{macro}[aux,EXP]{\@@_count:nn} % Counting the key--value pairs in a property list is done using the % same approach as for other count functions: turn each entry into a % \texttt{+1} then use integer evaluation to actually do the % mathematics. % \begin{macrocode} \cs_new:Npn \prop_count:N #1 { \int_eval:n { 0 \prop_map_function:NN #1 \@@_count:nn } } \cs_new:Npn \@@_count:nn #1#2 { + 1 } \cs_generate_variant:Nn \prop_count:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[rEXP]{\prop_map_tokens:Nn, \prop_map_tokens:cn} % \begin{macro}[aux]{\@@_map_tokens:nwwn} % The mapping is very similar to \cs{prop_map_function:NN}. It grabs % one key--value pair at a time, and stops when reaching the marker % key \cs{q_recursion_tail}, which cannot appear in normal keys since % those are strings. The odd construction |\use:n {#1}| allows |#1| % to contain any token without interfering with \cs{prop_map_break:}. % Argument |#2| of \cs{@@_map_tokens:nwwn} is \cs{s_@@} the first % time, and is otherwise empty. % \begin{macrocode} \cs_new:Npn \prop_map_tokens:Nn #1#2 { \exp_last_unbraced:Nno \@@_map_tokens:nwwn {#2} #1 \@@_pair:wn \q_recursion_tail \s_@@ { } \__prg_break_point:Nn \prop_map_break: { } } \cs_new:Npn \@@_map_tokens:nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 { \if_meaning:w \q_recursion_tail #3 \exp_after:wN \prop_map_break: \fi: \use:n {#1} {#3} {#4} \@@_map_tokens:nwwn {#1} } \cs_generate_variant:Nn \prop_map_tokens:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP] % {\prop_rand_key_value:N, \prop_rand_key_value:c} % \begin{macro}[aux, EXP]{\@@_rand:NN, \@@_rand_item:Nw} % Contrarily to |clist|, |seq| and |tl|, there is no function to get % an item of a |prop| given an integer between $1$ and the number of % items, so we write the appropriate code. There is no bounds % checking because \cs{int_rand:nn} is always within bounds. At the % end, leave either the key |#3| or the value |#4| in the input % stream. % \begin{macrocode} \cs_new:Npn \prop_rand_key_value:N { \@@_rand:NN \@@_rand:nNn } \cs_new:Npn \@@_rand:nNn #1#2#3 { \exp_not:n { {#1} {#3} } } \cs_new:Npn \@@_rand:NN #1#2 { \prop_if_empty:NTF #2 { } { \exp_after:wN \@@_rand_item:Nw \exp_after:wN #1 \__int_value:w \int_rand:nn { 1 } { \prop_count:N #2 } #2 \q_stop } } \cs_new:Npn \@@_rand_item:Nw #1#2 \s_@@ \@@_pair:wn #3 \s_@@ #4 { \int_compare:nNnF {#2} > 1 { \use_i_delimit_by_q_stop:nw { #1 {#3} \exp_not:n {#4} } } \exp_after:wN \@@_rand_item:Nw \exp_after:wN #1 \__int_value:w \int_eval:n { #2 - 1 } \s_@@ } \cs_generate_variant:Nn \prop_rand_key_value:N { c } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3seq}} % % \begin{macrocode} %<@@=seq> % \end{macrocode} % % \begin{macro} % { % \seq_mapthread_function:NNN, \seq_mapthread_function:NcN, % \seq_mapthread_function:cNN, \seq_mapthread_function:ccN % } % \begin{macro}[aux] % { % \@@_mapthread_function:wNN, \@@_mapthread_function:wNw, % \@@_mapthread_function:Nnnwnn % } % The idea is to first expand both sequences, adding the % usual |{ ? \__prg_break: } { }| to the end of each one. This is % most conveniently done in two steps using an auxiliary function. % The mapping then throws away the first tokens of |#2| and |#5|, % which for items in the sequences will both be \cs{s_@@} % \cs{@@_item:n}. The function to be mapped will then be applied to % the two entries. When the code hits the end of one of the % sequences, the break material will stop the entire loop and tidy up. % This avoids needing to find the count of the two sequences, or % worrying about which is longer. % \begin{macrocode} \cs_new:Npn \seq_mapthread_function:NNN #1#2#3 { \exp_after:wN \@@_mapthread_function:wNN #2 \q_stop #1 #3 } \cs_new:Npn \@@_mapthread_function:wNN \s_@@ #1 \q_stop #2#3 { \exp_after:wN \@@_mapthread_function:wNw #2 \q_stop #3 #1 { ? \__prg_break: } { } \__prg_break_point: } \cs_new:Npn \@@_mapthread_function:wNw \s_@@ #1 \q_stop #2 { \@@_mapthread_function:Nnnwnn #2 #1 { ? \__prg_break: } { } \q_stop } \cs_new:Npn \@@_mapthread_function:Nnnwnn #1#2#3#4 \q_stop #5#6 { \use_none:n #2 \use_none:n #5 #1 {#3} {#6} \@@_mapthread_function:Nnnwnn #1 #4 \q_stop } \cs_generate_variant:Nn \seq_mapthread_function:NNN { Nc } \cs_generate_variant:Nn \seq_mapthread_function:NNN { c , cc } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\seq_set_filter:NNn, \seq_gset_filter:NNn} % \begin{macro}[aux]{\@@_set_filter:NNNn} % Similar to \cs{seq_map_inline:Nn}, without a % \cs{__prg_break_point:} because the user's code % is performed within the evaluation of a boolean expression, % and skipping out of that would break horribly. % The \cs{@@_wrap_item:n} function inserts the relevant % \cs{@@_item:n} without expansion in the input stream, % hence in the \texttt{x}-expanding assignment. % \begin{macrocode} \cs_new_protected:Npn \seq_set_filter:NNn { \@@_set_filter:NNNn \tl_set:Nx } \cs_new_protected:Npn \seq_gset_filter:NNn { \@@_set_filter:NNNn \tl_gset:Nx } \cs_new_protected:Npn \@@_set_filter:NNNn #1#2#3#4 { \@@_push_item_def:n { \bool_if:nT {#4} { \@@_wrap_item:n {##1} } } #1 #2 { #3 } \@@_pop_item_def: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\seq_set_map:NNn, \seq_gset_map:NNn} % \begin{macro}[aux]{\@@_set_map:NNNn} % Very similar to \cs{seq_set_filter:NNn}. We could actually % merge the two within a single function, but it would have weird % semantics. % \begin{macrocode} \cs_new_protected:Npn \seq_set_map:NNn { \@@_set_map:NNNn \tl_set:Nx } \cs_new_protected:Npn \seq_gset_map:NNn { \@@_set_map:NNNn \tl_gset:Nx } \cs_new_protected:Npn \@@_set_map:NNNn #1#2#3#4 { \@@_push_item_def:n { \exp_not:N \@@_item:n {#4} } #1 #2 { #3 } \@@_pop_item_def: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\seq_rand_item:N, \seq_rand_item:c} % Importantly, \cs{seq_item:Nn} only evaluates its argument once. % \begin{macrocode} \cs_new:Npn \seq_rand_item:N #1 { \seq_if_empty:NF #1 { \seq_item:Nn #1 { \int_rand:nn { 1 } { \seq_count:N #1 } } } } \cs_generate_variant:Nn \seq_rand_item:N { c } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3skip}} % % \begin{macrocode} %<@@=skip> % \end{macrocode} % % \begin{macro}{\skip_split_finite_else_action:nnNN} % This macro is useful when performing error checking in certain % circumstances. If the \meta{skip} register holds finite glue it sets % |#3| and |#4| to the stretch and shrink component, resp. If it holds % infinite glue set |#3| and |#4| to zero and issue the special action % |#2| which is probably an error message. % Assignments are local. % \begin{macrocode} \cs_new:Npn \skip_split_finite_else_action:nnNN #1#2#3#4 { \skip_if_finite:nTF {#1} { #3 = \etex_gluestretch:D #1 \scan_stop: #4 = \etex_glueshrink:D #1 \scan_stop: } { #3 = \c_zero_skip #4 = \c_zero_skip #2 } } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3tl}} % % \begin{macrocode} %<@@=tl> % \end{macrocode} % % \begin{macro}[EXP,pTF]{\tl_if_single_token:n} % There are four cases: empty token list, token list starting with a % normal token, with a brace group, or with a space token. If the % token list starts with a normal token, remove it and check for % emptiness. For the next case, an empty token list is not a single % token. Finally, we have a non-empty token list starting with a % space or a brace group. Applying \texttt{f}-expansion yields an % empty result if and only if the token list is a single space. % \begin{macrocode} \prg_new_conditional:Npnn \tl_if_single_token:n #1 { p , T , F , TF } { \tl_if_head_is_N_type:nTF {#1} { \@@_if_empty_return:o { \use_none:n #1 } } { \tl_if_empty:nTF {#1} { \prg_return_false: } { \@@_if_empty_return:o { \exp:w \exp_end_continue_f:w #1 } } } } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\tl_reverse_tokens:n} % \begin{macro}[EXP,aux]{\@@_reverse_group:nn} % The same as \cs{tl_reverse:n} but with recursion within brace groups. % \begin{macrocode} \cs_new:Npn \tl_reverse_tokens:n #1 { \etex_unexpanded:D \exp_after:wN { \exp:w \@@_act:NNNnn \@@_reverse_normal:nN \@@_reverse_group:nn \@@_reverse_space:n { } {#1} } } \cs_new:Npn \@@_reverse_group:nn #1 { \@@_act_group_recurse:Nnn \@@_act_reverse_output:n { \tl_reverse_tokens:n } } % \end{macrocode} % \end{macro} % \begin{macro}[EXP,aux]{\@@_act_group_recurse:Nnn} % In many applications of \cs{@@_act:NNNnn}, we need to recursively % apply some transformation within brace groups, then output. In this % code, |#1| is the output function, |#2| is the transformation, % which should expand in two steps, and |#3| is the group. % \begin{macrocode} \cs_new:Npn \@@_act_group_recurse:Nnn #1#2#3 { \exp_args:Nf #1 { \exp_after:wN \exp_after:wN \exp_after:wN { #2 {#3} } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[EXP]{\tl_count_tokens:n} % \begin{macro}[EXP,aux]{\@@_act_count_normal:nN, % \@@_act_count_group:nn, \@@_act_count_space:n} % The token count is computed through an \cs{int_eval:n} construction. % Each \texttt{1+} is output to the \emph{left}, into the integer % expression, and the sum is ended by the \cs{exp_end:} inserted by % \cs{@@_act_end:wn} (which is technically implemented as \cs{c_zero}). % Somewhat a hack! % \begin{macrocode} \cs_new:Npn \tl_count_tokens:n #1 { \int_eval:n { \@@_act:NNNnn \@@_act_count_normal:nN \@@_act_count_group:nn \@@_act_count_space:n { } {#1} } } \cs_new:Npn \@@_act_count_normal:nN #1 #2 { 1 + } \cs_new:Npn \@@_act_count_space:n #1 { 1 + } \cs_new:Npn \@@_act_count_group:nn #1 #2 { 2 + \tl_count_tokens:n {#2} + } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % { % \tl_set_from_file:Nnn, \tl_set_from_file:cnn, % \tl_gset_from_file:Nnn, \tl_gset_from_file:cnn % } % \begin{macro}[aux]{\@@_set_from_file:NNnn} % \begin{macro}[aux]{\@@_from_file_do:w} % The approach here is similar to that for doing a rescan, and so the same % internals can be reused. Thus the plan is to insert a pair of tokens of % the same charcode but different catcodes after the file has been read. % This plus \cs{exp_not:N} allows the primitive to be used to carry out % a set operation. % \begin{macrocode} \cs_new_protected:Npn \tl_set_from_file:Nnn { \@@_set_from_file:NNnn \tl_set:Nn } \cs_new_protected:Npn \tl_gset_from_file:Nnn { \@@_set_from_file:NNnn \tl_gset:Nn } \cs_generate_variant:Nn \tl_set_from_file:Nnn { c } \cs_generate_variant:Nn \tl_gset_from_file:Nnn { c } \cs_new_protected:Npn \@@_set_from_file:NNnn #1#2#3#4 { \__file_if_exist:nT {#4} { \group_begin: \exp_args:No \etex_everyeof:D { \c_@@_rescan_marker_tl \exp_not:N } #3 \scan_stop: \exp_after:wN \@@_from_file_do:w \exp_after:wN \prg_do_nothing: \tex_input:D \l__file_internal_name_tl \scan_stop: \exp_args:NNNo \group_end: #1 #2 \l_@@_internal_a_tl } } \exp_args:Nno \use:nn { \cs_new_protected:Npn \@@_from_file_do:w #1 } { \c_@@_rescan_marker_tl } { \tl_set:No \l_@@_internal_a_tl {#1} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % { % \tl_set_from_file_x:Nnn, \tl_set_from_file_x:cnn, % \tl_gset_from_file_x:Nnn, \tl_gset_from_file_x:cnn % } % \begin{macro}[aux]{\@@_set_from_file_x:NNnn} % When reading a file and allowing expansion of the content, the set up % only needs to prevent \TeX{} complaining about the end of the file. That % is done simply, with a group then used to trap the definition needed. % Once the business is done using some scratch space, the tokens can be % transferred to the real target. % \begin{macrocode} \cs_new_protected:Npn \tl_set_from_file_x:Nnn { \@@_set_from_file_x:NNnn \tl_set:Nn } \cs_new_protected:Npn \tl_gset_from_file_x:Nnn { \@@_set_from_file_x:NNnn \tl_gset:Nn } \cs_generate_variant:Nn \tl_set_from_file_x:Nnn { c } \cs_generate_variant:Nn \tl_gset_from_file_x:Nnn { c } \cs_new_protected:Npn \@@_set_from_file_x:NNnn #1#2#3#4 { \__file_if_exist:nT {#4} { \group_begin: \etex_everyeof:D { \exp_not:N } #3 \scan_stop: \tl_set:Nx \l_@@_internal_a_tl { \tex_input:D \l__file_internal_name_tl \c_space_token } \exp_args:NNNo \group_end: #1 #2 \l_@@_internal_a_tl } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Unicode case changing} % % The mechanisms needed for case changing are somewhat involved, particularly % to allow for all of the special cases. These functions also require the % appropriate data extracted from the Unicode documentation (either manually % or automatically). % % \begin{macro}[EXP, documented-as=\tl_if_head_eq_catcode:nNTF] % {\tl_if_head_eq_catcode:oNTF} % Extra variants. % \begin{macrocode} \cs_generate_variant:Nn \tl_if_head_eq_catcode:nNTF { o } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\tl_lower_case:n, \tl_upper_case:n, \tl_mixed_case:n} % \begin{macro}[EXP]{\tl_lower_case:nn, \tl_upper_case:nn, \tl_mixed_case:nn} % The user level functions here are all wrappers around the internal % functions for case changing. Note that \cs{tl_mixed_case:nn} could be % done without an internal, but this way the logic is slightly clearer as % everything essentially follows the same path. % \begin{macrocode} \cs_new:Npn \tl_lower_case:n { \@@_change_case:nnn { lower } { } } \cs_new:Npn \tl_upper_case:n { \@@_change_case:nnn { upper } { } } \cs_new:Npn \tl_mixed_case:n { \@@_mixed_case:nn { } } \cs_new:Npn \tl_lower_case:nn { \@@_change_case:nnn { lower } } \cs_new:Npn \tl_upper_case:nn { \@@_change_case:nnn { upper } } \cs_new:Npn \tl_mixed_case:nn { \@@_mixed_case:nn } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_change_case:nnn} % \begin{macro}[aux, EXP]{\@@_change_case_aux:nnn} % \begin{macro}[aux, EXP]{\@@_change_case_loop:wnn} % \begin{macro}[aux, EXP] % { % \@@_change_case_output:nwn , % \@@_change_case_output:Vwn , % \@@_change_case_output:own , % \@@_change_case_output:vwn , % \@@_change_case_output:fwn , % } % \begin{macro}[aux, EXP]{\@@_change_case_end:wn} % \begin{macro}[aux, EXP]{\@@_change_case_group:nwnn} % \begin{macro}[aux, EXP]{\@@_change_case_space:wnn} % \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nwnn} % \begin{macro}[aux, EXP]{\@@_change_case_N_type:NNNnnn} % \begin{macro}[aux, EXP]{\@@_change_case_math:NNNnnn} % \begin{macro}[aux, EXP]{\@@_change_case_math_loop:wNNnn} % \begin{macro}[aux, EXP]{\@@_change_case_math:NwNNnn} % \begin{macro}[aux, EXP]{\@@_change_case_math_group:nwNNnn} % \begin{macro}[aux, EXP]{\@@_change_case_math_space:wNNnn} % \begin{macro}[aux, EXP]{\@@_change_case_N_type:Nnnn} % \begin{macro}[aux, EXP]{\@@_change_case_char:Nnn} % \begin{macro}[aux, EXP]{\@@_change_case_char:nN} % \begin{macro}[aux, EXP] % {\@@_change_case_char_auxi:nN, \@@_change_case_char_auxii:nN} % \begin{macro}[aux] % {\@@_lookup_lower:N, \@@_lookup_upper:N, \@@_lookup_title:N} % \begin{macro}[aux, EXP]{\@@_change_case_char_UTFviii:nNN} % \begin{macro}[aux, EXP]{\@@_change_case_char_UTFviii:nNNN} % \begin{macro}[aux, EXP]{\@@_change_case_char_UTFviii:nNNNN} % \begin{macro}[aux, EXP]{\@@_change_case_char_UTFviii:nn} % \begin{macro}[aux, EXP]{\@@_change_case_cs_letterlike:Nnn} % \begin{macro}[aux, EXP]{\@@_change_case_cs_accents:NN} % \begin{macro}[aux, EXP]{\@@_change_case_cs:N} % \begin{macro}[aux, EXP]{\@@_change_case_cs:NN} % \begin{macro}[aux, EXP]{\@@_change_case_cs:NNn} % \begin{macro}[aux, EXP]{\@@_change_case_protect:wNN} % \begin{macro}[aux, EXP]{\@@_change_case_if_expandable:NTF} % \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_cs_expand:NN} % The mechanism for the core conversion of case is based on the idea that % we can use a loop to grab the entire token list plus a quark: the latter is % used as an end marker and to avoid any brace stripping. Depending on the % nature of the first item in the grabbed argument, it can either processed % as a single token, treated as a group or treated as a space. These % different cases all work by re-reading |#1| in the appropriate way, hence % the repetition of |#1 \q_recursion_stop|. % \begin{macrocode} \cs_new:Npn \@@_change_case:nnn #1#2#3 { \etex_unexpanded:D \exp_after:wN { \exp:w \@@_change_case_aux:nnn {#1} {#2} {#3} } } \cs_new:Npn \@@_change_case_aux:nnn #1#2#3 { \group_align_safe_begin: \@@_change_case_loop:wnn #3 \q_recursion_tail \q_recursion_stop {#1} {#2} \@@_change_case_result:n { } } \cs_new:Npn \@@_change_case_loop:wnn #1 \q_recursion_stop { \tl_if_head_is_N_type:nTF {#1} { \@@_change_case_N_type:Nwnn } { \tl_if_head_is_group:nTF {#1} { \@@_change_case_group:nwnn } { \@@_change_case_space:wnn } } #1 \q_recursion_stop } % \end{macrocode} % Earlier versions of the code where only \texttt{x}-type expandable rather % than \texttt{f}-type: this causes issues with nesting and so the slight % performance hit is taken for a better outcome in usability terms. Setting % up for \texttt{f}-type expandability has two requirements: a marker % token after the main loop (see above) and a mechanism to \enquote{load} % and finalise the result. That is handled in the code below, which includes % the necessary material to end the \cs{exp:w} expansion. % \begin{macrocode} \cs_new:Npn \@@_change_case_output:nwn #1#2 \@@_change_case_result:n #3 { #2 \@@_change_case_result:n { #3 #1 } } \cs_generate_variant:Nn \@@_change_case_output:nwn { V , o , v , f } \cs_new:Npn \@@_change_case_end:wn #1 \@@_change_case_result:n #2 { \group_align_safe_end: \exp_end: #2 } % \end{macrocode} % Handling for the cases where the current argument is a brace group or % a space is relatively easy. For the brace case, the routine works % recursively, using the expandability of the mechanism to ensure that the % result is finalised before storage. For the space case it is simply a % question of removing the space in the input and storing it in the output. % In both cases, and indeed for the \texttt{N}-type grabber, after removing % the current item from the input \cs{@@_change_case_loop:wnn} is inserted % in front of the remaining tokens. % \begin{macrocode} \cs_new:Npn \@@_change_case_group:nwnn #1#2 \q_recursion_stop #3#4 { \@@_change_case_output:own { \exp_after:wN { \exp:w \@@_change_case_aux:nnn {#3} {#4} {#1} } } \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4} } \exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_space:wnn \c_space_tl { \@@_change_case_output:nwn { ~ } \@@_change_case_loop:wnn } % \end{macrocode} % For \texttt{N}-type arguments there are several stages to the approach. % First, a simply check for the end-of-input marker, which if found triggers % the final clean up and output step. Assuming that is not the case, the % first check is for math-mode escaping: this test can encompass control % sequences or other \texttt{N}-type tokens so is handled up front. % \begin{macrocode} \cs_new:Npn \@@_change_case_N_type:Nwnn #1#2 \q_recursion_stop { \quark_if_recursion_tail_stop_do:Nn #1 { \@@_change_case_end:wn } \exp_after:wN \@@_change_case_N_type:NNNnnn \exp_after:wN #1 \l_tl_case_change_math_tl \q_recursion_tail ? \q_recursion_stop {#2} } % \end{macrocode} % Looking for math mode escape first requires a loop over the possible % token pairs to see if the current input (|#1|) matches an open-math case % (|#2|). If if does then this test loop is ended and a new input-gathering % one is begun. The latter simply transfers material from the input to the % output without any expansion, testing each \texttt{N}-type token to see % if it matches the close-math case required. If that is the situation then % the \enquote{math loop} stops and resumes the main loop: as that might % be either the standard case-changing one or the mixed-case alternative, % it is not hard-coded into the math loop but is rather passed as argument % |#3| to \cs{@@_change_case_math:NNNnnn}. If no close-math token is found % then the final clean-up will be forced (\emph{i.e.}~there is no assumption % of \enquote{well-behaved} code in terms of math mode). % \begin{macrocode} \cs_new:Npn \@@_change_case_N_type:NNNnnn #1#2#3 { \quark_if_recursion_tail_stop_do:Nn #2 { \@@_change_case_N_type:Nnnn #1 } \token_if_eq_meaning:NNTF #1 #2 { \use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_math:NNNnnn #1 #3 \@@_change_case_loop:wnn } } { \@@_change_case_N_type:NNNnnn #1 } } \cs_new:Npn \@@_change_case_math:NNNnnn #1#2#3#4 { \@@_change_case_output:nwn {#1} \@@_change_case_math_loop:wNNnn #4 \q_recursion_stop #2 #3 } \cs_new:Npn \@@_change_case_math_loop:wNNnn #1 \q_recursion_stop { \tl_if_head_is_N_type:nTF {#1} { \@@_change_case_math:NwNNnn } { \tl_if_head_is_group:nTF {#1} { \@@_change_case_math_group:nwNNnn } { \@@_change_case_math_space:wNNnn } } #1 \q_recursion_stop } \cs_new:Npn \@@_change_case_math:NwNNnn #1#2 \q_recursion_stop #3#4 { \token_if_eq_meaning:NNTF \q_recursion_tail #1 { \@@_change_case_end:wn } { \@@_change_case_output:nwn {#1} \token_if_eq_meaning:NNTF #1 #3 { #4 #2 \q_recursion_stop } { \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop #3#4 } } } \cs_new:Npn \@@_change_case_math_group:nwNNnn #1#2 \q_recursion_stop { \@@_change_case_output:nwn { {#1} } \@@_change_case_math_loop:wNNnn #2 \q_recursion_stop } \exp_last_unbraced:NNo \cs_new:Npn \@@_change_case_math_space:wNNnn \c_space_tl { \@@_change_case_output:nwn { ~ } \@@_change_case_math_loop:wNNnn } % \end{macrocode} % Once potential math-mode cases are filtered out the next stage is to % test if the token grabbed is a control sequence: they cannot be used in % the lookup table and also may require expansion. At this stage the loop % code starting \cs{@@_change_case_loop:wnn} is inserted: all subsequent % steps in the code which need a look-ahead are coded to rely on this and % thus have \texttt{w}-type arguments if they may do a look-ahead. % \begin{macrocode} \cs_new:Npn \@@_change_case_N_type:Nnnn #1#2#3#4 { \token_if_cs:NTF #1 { \@@_change_case_cs_letterlike:Nnn #1 {#3} { } } { \@@_change_case_char:Nnn #1 {#3} {#4} } \@@_change_case_loop:wnn #2 \q_recursion_stop {#3} {#4} } % \end{macrocode} % For character tokens there are some special cases to deal with then % the majority of changes are covered by using the \TeX{} data as a lookup % along with expandable character generation. This avoids needing a very % large number of macros or (as seen in earlier versions) a somewhat tricky % split of the characters into various blocks. Notice that the special case % code may do a look-ahead so requires a final \texttt{w}-type argument % whereas the core lookup table does not and also guarantees an output so % \texttt{f}-type expansion may be used to obtain the case-changed result. % \begin{macrocode} \cs_new:Npn \@@_change_case_char:Nnn #1#2#3 { \cs_if_exist_use:cF { @@_change_case_ #2 _ #3 :Nnw } { \use_ii:nn } #1 { \use:c { @@_change_case_ #2 _ sigma:Nnw } #1 { \@@_change_case_char:nN {#2} #1 } } } % \end{macrocode} % For Unicode engines we can handle all characters directly. However, for % the $8$-bit engines the aim is to deal with (a subset of) Unicode (UTF-8) % input. They deal with that by making the upper half of the range active, % so we look for that and if found work out how many UTF-8 octets there % are to deal with. Those can then be grabbed to reconstruct the full % Unicode character, which is then used in a lookup. (As will become % obvious below, there is no intention here of covering all of Unicode.) % \begin{macrocode} \cs_if_exist:NTF \utex_char:D { \cs_new:Npn \@@_change_case_char:nN #1#2 { \@@_change_case_char_auxi:nN {#1} #2 } } { \cs_new:Npn \@@_change_case_char:nN #1#2 { \int_compare:nNnTF { `#2 } > { "80 } { \int_compare:nNnTF { `#2 } < { "E0 } { \@@_change_case_char_UTFviii:nNNN {#1} #2 } { \int_compare:nNnTF { `#2 } < { "F0 } { \@@_change_case_char_UTFviii:nNNNN {#1} #2 } { \@@_change_case_char_UTFviii:nNNNNN {#1} #2 } } } { \@@_change_case_char_auxi:nN {#1} #2 } } } \cs_new:Npn \@@_change_case_char_auxi:nN #1#2 { \@@_change_case_output:fwn { \cs_if_exist_use:cF { c__unicode_ #1 _ \token_to_str:N #2 _tl } { \@@_change_case_char_auxii:nN {#1} #2 } } } \cs_if_exist:NTF \utex_char:D { \cs_new:Npn \@@_change_case_char_auxii:nN #1#2 { \int_compare:nNnTF { \use:c { @@_lookup_ #1 :N } #2 } = { 0 } { \exp_stop_f: #2 } { \char_generate:nn { \use:c { @@_lookup_ #1 :N } #2 } { \char_value_catcode:n { \use:c { @@_lookup_ #1 :N } #2 } } } } \cs_new_protected:Npn \@@_lookup_lower:N #1 { \tex_lccode:D `#1 } \cs_new_protected:Npn \@@_lookup_upper:N #1 { \tex_uccode:D `#1 } \cs_new_eq:NN \@@_lookup_title:N \@@_lookup_upper:N } { \cs_new:Npn \@@_change_case_char_auxii:nN #1#2 { \exp_stop_f: #2 } \cs_new:Npn \@@_change_case_char_UTFviii:nNNN #1#2#3#4 { \@@_change_case_char_UTFviii:nnN {#1} {#2#4} #3 } \cs_new:Npn \@@_change_case_char_UTFviii:nNNNN #1#2#3#4#5 { \@@_change_case_char_UTFviii:nnN {#1} {#2#4#5} #3 } \cs_new:Npn \@@_change_case_char_UTFviii:nNNNNN #1#2#3#4#5#6 { \@@_change_case_char_UTFviii:nnN {#1} {#2#4#5#6} #3 } \cs_new:Npn \@@_change_case_char_UTFviii:nnN #1#2#3 { \cs_if_exist:cTF { c__unicode_ #1 _ \tl_to_str:n {#2} _tl } { \@@_change_case_output:vwn { c__unicode_ #1 _ \tl_to_str:n {#2} _tl } } { \@@_change_case_output:nwn {#2} } #3 } } % \end{macrocode} % Before dealing with general control sequences there are the special % ones to deal with. Letter-like control sequences are a simple look-up, % while for accents the loop is much as done elsewhere. Notice that % we have a no-op test to make sure there is no unexpected expansion of % letter-like input. The third argument here is needed for mixed casing, % where it if there is a hit there has to be a change-of-path. % \begin{macrocode} \cs_new:Npn \@@_change_case_cs_letterlike:Nnn #1#2#3 { \cs_if_exist:cTF { c_@@_change_case_ #2 _ \token_to_str:N #1 _tl } { \@@_change_case_output:vwn { c_@@_change_case_ #2 _ \token_to_str:N #1 _tl } #3 } { \cs_if_exist:cTF { c_@@_change_case_ \str_if_eq:nnTF {#2} { lower } { upper } { lower } _ \token_to_str:N #1 _tl } { \@@_change_case_output:nwn {#1} #3 } { \exp_after:wN \@@_change_case_cs_accents:NN \exp_after:wN #1 \l_tl_case_change_accents_tl \q_recursion_tail \q_recursion_stop } } } \cs_new:Npn \@@_change_case_cs_accents:NN #1#2 { \quark_if_recursion_tail_stop_do:Nn #2 { \@@_change_case_cs:N #1 } \str_if_eq:nnTF {#1} {#2} { \use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_output:nwn {#1} } } { \@@_change_case_cs_accents:NN #1 } } % \end{macrocode} % To deal with a control sequence there is first a need to test if it is % on the list which indicate that case changing should be skipped. That's % done using a loop as for the other special cases. If a hit is found then % the argument is grabbed: that comes \emph{after} the loop function which % is therefore rearranged. In a \LaTeXe{} context, \tn{protect} needs % to be treated specially, to prevent expansion of the next token but % output it without braces. % \begin{macrocode} \cs_new:Npn \@@_change_case_cs:N #1 { %<*package> \str_if_eq:nnTF {#1} { \protect } { \@@_change_case_protect:wNN } % \exp_after:wN \@@_change_case_cs:NN \exp_after:wN #1 \l_tl_case_change_exclude_tl \q_recursion_tail \q_recursion_stop } \cs_new:Npn \@@_change_case_cs:NN #1#2 { \quark_if_recursion_tail_stop_do:Nn #2 { \@@_change_case_cs_expand:Nnw #1 { \@@_change_case_output:nwn {#1} } } \str_if_eq:nnTF {#1} {#2} { \use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_cs:NNn #1 } } { \@@_change_case_cs:NN #1 } } \cs_new:Npn \@@_change_case_cs:NNn #1#2#3 { \@@_change_case_output:nwn { #1 {#3} } #2 } %<*package> \cs_new:Npn \@@_change_case_protect:wNN #1 \q_recursion_stop #2 #3 { \@@_change_case_output:nwn { \protect #3 } #2 } % % \end{macrocode} % When a control sequence is not on the exclude list the other test if % to see if it is expandable. Once again, if there is a hit then the loop % function is grabbed as part of the clean-up and reinserted before the % now expanded material. The test for expandablity has to check for % end-of-recursion as it is needed by the look-ahead code which might hit % the end of the input. The test is done in two parts as \cs{bool_if:nTF} % will choke if |#1| is |(|! % \begin{macrocode} \cs_new:Npn \@@_change_case_if_expandable:NTF #1 { \token_if_expandable:NTF #1 { \bool_lazy_any:nTF { { \token_if_eq_meaning_p:NN \q_recursion_tail #1 } { \token_if_protected_macro_p:N #1 } { \token_if_protected_long_macro_p:N #1 } } { \use_ii:nn } { \use_i:nn } } { \use_ii:nn } } \cs_new:Npn \@@_change_case_cs_expand:Nnw #1#2 { \@@_change_case_if_expandable:NTF #1 { \@@_change_case_cs_expand:NN #1 } { #2 } } \cs_new:Npn \@@_change_case_cs_expand:NN #1#2 { \exp_after:wN #2 #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} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:w} % \begin{macro}[aux, EXP]{\@@_change_case_lower_sigma:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_sigma:Nnw} % If the current char is an upper case sigma, the a check is made on the next % item in the input. If it is \texttt{N}-type and not a control sequence % then there is a look-ahead phase. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_sigma:Nnw #1#2#3#4 \q_recursion_stop { \int_compare:nNnTF { `#1 } = { "03A3 } { \@@_change_case_output:fwn { \@@_change_case_lower_sigma:w #4 \q_recursion_stop } } {#2} #3 #4 \q_recursion_stop } \cs_new:Npn \@@_change_case_lower_sigma:w #1 \q_recursion_stop { \tl_if_head_is_N_type:nTF {#1} { \@@_change_case_lower_sigma:Nw #1 \q_recursion_stop } { \c__unicode_final_sigma_tl } } \cs_new:Npn \@@_change_case_lower_sigma:Nw #1#2 \q_recursion_stop { \@@_change_case_if_expandable:NTF #1 { \exp_after:wN \@@_change_case_lower_sigma:w #1 #2 \q_recursion_stop } { \token_if_letter:NTF #1 { \c__unicode_std_sigma_tl } { \c__unicode_final_sigma_tl } } } % \end{macrocode} % Simply skip to the final step for upper casing. % \begin{macrocode} \cs_new_eq:NN \@@_change_case_upper_sigma:Nnw \use_ii:nn % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_change_case_lower_tr:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_tr_auxi:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_tr_auxii:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_tr:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_az:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_az:Nnw} % The Turkic languages need special treatment for dotted-i and dotless-i. % The lower casing rule can be expressed in terms of searching first for % either a dotless-I or a dotted-I. In the latter case the mapping is % easy, but in the former there is a second stage search. % \begin{macrocode} \cs_if_exist:NTF \utex_char:D { \cs_new:Npn \@@_change_case_lower_tr:Nnw #1#2 { \int_compare:nNnTF { `#1 } = { "0049 } { \@@_change_case_lower_tr_auxi:Nw } { \int_compare:nNnTF { `#1 } = { "0130 } { \@@_change_case_output:nwn { i } } {#2} } } % \end{macrocode} % After a dotless-I there may be a dot-above character. If there is then % a dotted-i should be produced, otherwise output a dotless-i. When the % combination is found both the dotless-I and the dot-above char have to % be removed from the input, which is done by the \cs{use_i:nn} % (it grabs \cs{@@_change_case_loop:wn} and the dot-above char and % discards the latter). % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_tr_auxi:Nw #1#2 \q_recursion_stop { \tl_if_head_is_N_type:nTF {#2} { \@@_change_case_lower_tr_auxii:Nw #2 \q_recursion_stop } { \@@_change_case_output:Vwn \c__unicode_dotless_i_tl } #1 #2 \q_recursion_stop } \cs_new:Npn \@@_change_case_lower_tr_auxii:Nw #1#2 \q_recursion_stop { \@@_change_case_if_expandable:NTF #1 { \exp_after:wN \@@_change_case_lower_tr_auxi:Nw #1 #2 \q_recursion_stop } { \bool_lazy_or:nnTF { \token_if_cs_p:N #1 } { ! \int_compare_p:nNn { `#1 } = { "0307 } } { \@@_change_case_output:Vwn \c__unicode_dotless_i_tl } { \@@_change_case_output:nwn { i } \use_i:nn } } } } % \end{macrocode} % For $8$-bit engines, dot-above is not available so there is a simple % test for an upper-case I. Then we can look for the UTF-8 representation of % an upper case dotted-I without the combining char. If it's not there, % preserve the UTF-8 sequence as-is. % \begin{macrocode} { \cs_new:Npn \@@_change_case_lower_tr:Nnw #1#2 { \int_compare:nNnTF { `#1 } = { "0049 } { \@@_change_case_output:Vwn \c__unicode_dotless_i_tl } { \int_compare:nNnTF { `#1 } = { 196 } { \@@_change_case_lower_tr_auxi:Nw #1 {#2} } {#2} } } \cs_new:Npn \@@_change_case_lower_tr_auxi:Nw #1#2#3#4 { \int_compare:nNnTF { `#4 } = { 176 } { \@@_change_case_output:nwn { i } #3 } { #2 #3 #4 } } } % \end{macrocode} % Upper casing is easier: just one exception with no context. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_tr:Nnw #1#2 { \int_compare:nNnTF { `#1 } = { "0069 } { \@@_change_case_output:Vwn \c__unicode_dotted_I_tl } {#2} } % \end{macrocode} % Straight copies. % \begin{macrocode} \cs_new_eq:NN \@@_change_case_lower_az:Nnw \@@_change_case_lower_tr:Nnw \cs_new_eq:NN \@@_change_case_upper_az:Nnw \@@_change_case_upper_tr:Nnw % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:nNnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:nnw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_lower_lt:NNw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:nnw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_upper_lt:NNw} % For Lithuanian, the issue to be dealt with is dots over lower case % letters: these should be present if there is another accent. That means % that there is some work to do when lower casing I and J. The first step % is a simple match attempt: \cs{c_@@_accents_lt_tl} contains % accented upper case letters which should gain a dot-above char in their % lower case form. This is done using \texttt{f}-type expansion so only one % pass is needed to find if it works or not. If there was no hit, the second % stage is to check for I, J and I-ogonek, and if the current char is a % match to look for a following accent. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_lt:Nnw #1 { \exp_args:Nf \@@_change_case_lower_lt:nNnw { \str_case:nVF #1 \c__unicode_accents_lt_tl \exp_stop_f: } #1 } \cs_new:Npn \@@_change_case_lower_lt:nNnw #1#2 { \tl_if_blank:nTF {#1} { \exp_args:Nf \@@_change_case_lower_lt:nnw { \int_case:nnF {`#2} { { "0049 } i { "004A } j { "012E } \c__unicode_i_ogonek_tl } \exp_stop_f: } } { \@@_change_case_output:nwn {#1} \use_none:n } } \cs_new:Npn \@@_change_case_lower_lt:nnw #1#2 { \tl_if_blank:nTF {#1} {#2} { \@@_change_case_output:nwn {#1} \@@_change_case_lower_lt:Nw } } % \end{macrocode} % Grab the next char and see if it is one of the accents used in Lithuanian: % if it is, add the dot-above char into the output. % \begin{macrocode} \cs_new:Npn \@@_change_case_lower_lt:Nw #1#2 \q_recursion_stop { \tl_if_head_is_N_type:nT {#2} { \@@_change_case_lower_lt:NNw } #1 #2 \q_recursion_stop } \cs_new:Npn \@@_change_case_lower_lt:NNw #1#2#3 \q_recursion_stop { \@@_change_case_if_expandable:NTF #2 { \exp_after:wN \@@_change_case_lower_lt:Nw \exp_after:wN #1 #2 #3 \q_recursion_stop } { \bool_lazy_and:nnT { ! \token_if_cs_p:N #2 } { \bool_lazy_any_p:n { { \int_compare_p:nNn { `#2 } = { "0300 } } { \int_compare_p:nNn { `#2 } = { "0301 } } { \int_compare_p:nNn { `#2 } = { "0303 } } } } { \@@_change_case_output:Vwn \c__unicode_dot_above_tl } #1 #2#3 \q_recursion_stop } } % \end{macrocode} % For upper casing, the test required is for a dot-above char after an I, % J or I-ogonek. First a test for the appropriate letter, and if found a % look-ahead and potentially one token dropped. % \begin{macrocode} \cs_new:Npn \@@_change_case_upper_lt:Nnw #1 { \exp_args:Nf \@@_change_case_upper_lt:nnw { \int_case:nnF {`#1} { { "0069 } I { "006A } J { "012F } \c__unicode_I_ogonek_tl } \exp_stop_f: } } \cs_new:Npn \@@_change_case_upper_lt:nnw #1#2 { \tl_if_blank:nTF {#1} {#2} { \@@_change_case_output:nwn {#1} \@@_change_case_upper_lt:Nw } } \cs_new:Npn \@@_change_case_upper_lt:Nw #1#2 \q_recursion_stop { \tl_if_head_is_N_type:nT {#2} { \@@_change_case_upper_lt:NNw } #1 #2 \q_recursion_stop } \cs_new:Npn \@@_change_case_upper_lt:NNw #1#2#3 \q_recursion_stop { \@@_change_case_if_expandable:NTF #2 { \exp_after:wN \@@_change_case_upper_lt:Nw \exp_after:wN #1 #2 #3 \q_recursion_stop } { \bool_lazy_and:nnTF { ! \token_if_cs_p:N #2 } { \int_compare_p:nNn { `#2 } = { "0307 } } { #1 } { #1 #2 } #3 \q_recursion_stop } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_change_case_upper_de-alt:Nnw} % A simple alternative version for German. % \begin{macrocode} \cs_new:cpn { @@_change_case_upper_de-alt:Nnw } #1#2 { \int_compare:nNnTF { `#1 } = { 223 } { \@@_change_case_output:Vwn \c__unicode_upper_Eszett_tl } {#2} } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP, int]{\__unicode_codepoint_to_UTFviii:n} % \begin{macro}[EXP, aux]{\__unicode_codepoint_to_UTFviii_auxi:n} % \begin{macro}[EXP, aux]{\__unicode_codepoint_to_UTFviii_auxii:Nnn} % \begin{macro}[EXP, aux]{\__unicode_codepoint_to_UTFviii_auxiii:n} % This code will convert a codepoint into the correct UTF-8 representation. % As there are a variable number of octets, the result starts with the % numeral |1|--|4| to indicate the nature of the returned value. Note that % this code will cover the full range even though at this stage it is not % required here. Also note that longer-term this is likely to need a public % interface and/or moving to \pkg{l3str} (see experimental string % conversions). In terms of the algorithm itself, see % \url{https://en.wikipedia.org/wiki/UTF-8} for the octet pattern. % \begin{macrocode} \cs_new:Npn \__unicode_codepoint_to_UTFviii:n #1 { \exp_args:Nf \__unicode_codepoint_to_UTFviii_auxi:n { \int_eval:n {#1} } } \cs_new:Npn \__unicode_codepoint_to_UTFviii_auxi:n #1 { \if_int_compare:w #1 > "80 ~ \if_int_compare:w #1 < "800 ~ 2 \__unicode_codepoint_to_UTFviii_auxii:Nnn C {#1} { 64 } \__unicode_codepoint_to_UTFviii_auxiii:n {#1} \else: \if_int_compare:w #1 < "10000 ~ 3 \__unicode_codepoint_to_UTFviii_auxii:Nnn E {#1} { 64 * 64 } \__unicode_codepoint_to_UTFviii_auxiii:n {#1} \__unicode_codepoint_to_UTFviii_auxiii:n { \int_div_truncate:nn {#1} { 64 } } \else: 4 \__unicode_codepoint_to_UTFviii_auxii:Nnn F {#1} { 64 * 64 * 64 } \__unicode_codepoint_to_UTFviii_auxiii:n { \int_div_truncate:nn {#1} { 64 * 64 } } \__unicode_codepoint_to_UTFviii_auxiii:n { \int_div_truncate:nn {#1} { 64 } } \__unicode_codepoint_to_UTFviii_auxiii:n {#1} \fi: \fi: \else: 1 {#1} \fi: } \cs_new:Npn \__unicode_codepoint_to_UTFviii_auxii:Nnn #1#2#3 { { \int_eval:n { "#10 + \int_div_truncate:nn {#2} {#3} } } } \cs_new:Npn \__unicode_codepoint_to_UTFviii_auxiii:n #1 { { \int_eval:n { \int_mod:nn {#1} { 64 } + 128 } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{variable} % { % \c__unicode_std_sigma_tl , % \c__unicode_final_sigma_tl , % \c__unicode_accents_lt_tl , % \c__unicode_dot_above_tl , % \c__unicode_upper_Eszett_tl % } % The above needs various special token lists containg pre-formed characters. % This set are only available in Unicode engines, with no-op definitions % for $8$-bit use. % \begin{macrocode} \cs_if_exist:NTF \utex_char:D { \tl_const:Nx \c__unicode_std_sigma_tl { \utex_char:D "03C3 ~ } \tl_const:Nx \c__unicode_final_sigma_tl { \utex_char:D "03C2 ~ } \tl_const:Nx \c__unicode_accents_lt_tl { \utex_char:D "00CC ~ { \utex_char:D "0069 ~ \utex_char:D "0307 ~ \utex_char:D "0300 ~ } \utex_char:D "00CD ~ { \utex_char:D "0069 ~ \utex_char:D "0307 ~ \utex_char:D "0301 ~ } \utex_char:D "0128 ~ { \utex_char:D "0069 ~ \utex_char:D "0307 ~ \utex_char:D "0303 ~ } } \tl_const:Nx \c__unicode_dot_above_tl { \utex_char:D "0307 ~ } \tl_const:Nx \c__unicode_upper_Eszett_tl { \utex_char:D "1E9E ~ } } { \tl_const:Nn \c__unicode_std_sigma_tl { } \tl_const:Nn \c__unicode_final_sigma_tl { } \tl_const:Nn \c__unicode_accents_lt_tl { } \tl_const:Nn \c__unicode_dot_above_tl { } \tl_const:Nn \c__unicode_upper_Eszett_tl { } } % \end{macrocode} % \end{variable} % \begin{variable} % { % \c__unicode_dotless_i_tl , % \c__unicode_dotted_I_tl , % \c__unicode_i_ogonek_tl , % \c__unicode_I_ogonek_tl , % } % For cases where there is an $8$-bit option in the |T1| font set up, % a variant is provided in both cases. % \begin{macrocode} \group_begin: \cs_if_exist:NTF \utex_char:D { \cs_set_protected:Npn \@@_tmp:w #1#2 { \tl_const:Nx #1 { \utex_char:D "#2 ~ } } } { \cs_set_protected:Npn \@@_tmp:w #1#2 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3 { \tl_const:Nx #1 { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {##2} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {##3} { 13 } } } \tl_set:Nx \l_@@_internal_a_tl { \__unicode_codepoint_to_UTFviii:n {"#2} } \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl \group_end: } } \@@_tmp:w \c__unicode_dotless_i_tl { 0131 } \@@_tmp:w \c__unicode_dotted_I_tl { 0130 } \@@_tmp:w \c__unicode_i_ogonek_tl { 012F } \@@_tmp:w \c__unicode_I_ogonek_tl { 012E } \group_end: % \end{macrocode} % \end{variable} % % For $8$-bit engines we now need to define the case-change data for % the multi-octet mappings. These need a list of what code points are % doable in |T1| so the list is hard coded (there's no saving in loading % the mappings dynamically). All of the straight-forward ones have two % octets, so that is taken as read. % \begin{macrocode} \group_begin: \bool_if:nT { \sys_if_engine_pdftex_p: || \sys_if_engine_uptex_p: } { \cs_set_protected:Npn \@@_loop:nn #1#2 { \quark_if_recursion_tail_stop:n {#1} \tl_set:Nx \l_@@_internal_a_tl { \__unicode_codepoint_to_UTFviii:n {"#1} \__unicode_codepoint_to_UTFviii:n {"#2} } \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl \@@_loop:nn } \cs_set_protected:Npn \@@_tmp:w #1#2#3#4#5#6 { \tl_const:cx { c__unicode_lower_ \char_generate:nn {#2} { 12 } \char_generate:nn {#3} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {#5} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {#6} { 13 } } \tl_const:cx { c__unicode_upper_ \char_generate:nn {#5} { 12 } \char_generate:nn {#6} { 12 } _tl } { \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {#2} { 13 } \exp_after:wN \exp_after:wN \exp_after:wN \exp_not:N \__char_generate:nn {#3} { 13 } } } \@@_loop:nn { 00C0 } { 00E0 } { 00C2 } { 00E2 } { 00C3 } { 00E3 } { 00C4 } { 00E4 } { 00C5 } { 00E5 } { 00C6 } { 00E6 } { 00C7 } { 00E7 } { 00C8 } { 00E8 } { 00C9 } { 00E9 } { 00CA } { 00EA } { 00CB } { 00EB } { 00CC } { 00EC } { 00CD } { 00ED } { 00CE } { 00EE } { 00CF } { 00EF } { 00D0 } { 00F0 } { 00D1 } { 00F1 } { 00D2 } { 00F2 } { 00D3 } { 00F3 } { 00D4 } { 00F4 } { 00D5 } { 00F5 } { 00D6 } { 00F6 } { 00D8 } { 00F8 } { 00D9 } { 00F9 } { 00DA } { 00FA } { 00DB } { 00FB } { 00DC } { 00FC } { 00DD } { 00FD } { 00DE } { 00FE } { 0100 } { 0101 } { 0102 } { 0103 } { 0104 } { 0105 } { 0106 } { 0107 } { 0108 } { 0109 } { 010A } { 010B } { 010C } { 010D } { 010E } { 010F } { 0110 } { 0111 } { 0112 } { 0113 } { 0114 } { 0115 } { 0116 } { 0117 } { 0118 } { 0119 } { 011A } { 011B } { 011C } { 011D } { 011E } { 011F } { 0120 } { 0121 } { 0122 } { 0123 } { 0124 } { 0125 } { 0128 } { 0129 } { 012A } { 012B } { 012C } { 012D } { 012E } { 012F } { 0132 } { 0133 } { 0134 } { 0135 } { 0136 } { 0137 } { 0139 } { 013A } { 013B } { 013C } { 013E } { 013F } { 0141 } { 0142 } { 0143 } { 0144 } { 0145 } { 0146 } { 0147 } { 0148 } { 014A } { 014B } { 014C } { 014D } { 014E } { 014F } { 0150 } { 0151 } { 0152 } { 0153 } { 0154 } { 0155 } { 0156 } { 0157 } { 0158 } { 0159 } { 015A } { 015B } { 015C } { 015D } { 015E } { 015F } { 0160 } { 0161 } { 0162 } { 0163 } { 0164 } { 0165 } { 0168 } { 0169 } { 016A } { 016B } { 016C } { 016D } { 016E } { 016F } { 0170 } { 0171 } { 0172 } { 0173 } { 0174 } { 0175 } { 0176 } { 0177 } { 0178 } { 00FF } { 0179 } { 017A } { 017B } { 017C } { 017D } { 017E } { 01CD } { 01CE } { 01CF } { 01D0 } { 01D1 } { 01D2 } { 01D3 } { 01D4 } { 01E2 } { 01E3 } { 01E6 } { 01E7 } { 01E8 } { 01E9 } { 01EA } { 01EB } { 01F4 } { 01F5 } { 0218 } { 0219 } { 021A } { 021B } \q_recursion_tail ? \q_recursion_stop \cs_set_protected:Npn \@@_tmp:w #1#2#3 { \group_begin: \cs_set_protected:Npn \@@_tmp:w ##1##2##3 { \tl_const:cx { c__unicode_ #3 _ \char_generate:nn {##2} { 12 } \char_generate:nn {##3} { 12 } _tl } {#2} } \tl_set:Nx \l_@@_internal_a_tl { \__unicode_codepoint_to_UTFviii:n { "#1 } } \exp_after:wN \@@_tmp:w \l_@@_internal_a_tl \group_end: } \@@_tmp:w { 00DF } { SS } { upper } \@@_tmp:w { 00DF } { Ss } { title } \@@_tmp:w { 0131 } { I } { upper } } \group_end: % \end{macrocode} % % The (fixed) look-up mappings for letter-like control sequences. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_change_case_setup:NN #1#2 { \quark_if_recursion_tail_stop:N #1 \tl_const:cn { c_@@_change_case_lower_ \token_to_str:N #1 _tl } { #2 } \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N #2 _tl } { #1 } \@@_change_case_setup:NN } \@@_change_case_setup:NN \AA \aa \AE \ae \DH \dh \DJ \dj \IJ \ij \L \l \NG \ng \O \o \OE \oe \SS \ss \TH \th \q_recursion_tail ? \q_recursion_stop \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N \i _tl } { I } \tl_const:cn { c_@@_change_case_upper_ \token_to_str:N \j _tl } { J } \group_end: % \end{macrocode} % % \begin{variable}{\l_tl_case_change_accents_tl} % A list of accents to leave alone. % \begin{macrocode} \tl_new:N \l_tl_case_change_accents_tl \tl_set:Nn \l_tl_case_change_accents_tl { \" \' \. \^ \` \~ \c \H \k \r \t \u \v } % \end{macrocode} % \end{variable} % % \begin{macro}[aux, EXP]{\@@_mixed_case:nn} % \begin{macro}[aux, EXP]{\@@_mixed_case_aux:nn} % \begin{macro}[aux, EXP]{\@@_mixed_case_loop:wn} % \begin{macro}[aux, EXP]{\@@_mixed_case_group:nwn} % \begin{macro}[aux, EXP]{\@@_mixed_case_space:wn} % \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nwn} % \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:NNNnn} % \begin{macro}[aux, EXP]{\@@_mixed_case_N_type:Nnn} % \begin{macro}[aux, EXP]{\@@_mixed_case_letterlike:Nw} % \begin{macro}[aux, EXP]{\@@_mixed_case_char:N} % \begin{macro}[aux, EXP]{\@@_mixed_case_skip:N} % \begin{macro}[aux, EXP]{\@@_mixed_case_skip:NN} % \begin{macro}[aux, EXP]{\@@_mixed_case_skip_tidy:Nwn} % \begin{macro}[aux, EXP]{\@@_mixed_case_char:nN} % Mixed (title) casing requires some custom handling of the case changing % of the first letter in the input followed by a switch to the normal % lower casing routine. That could be covered by passing a set of functions % to generic routines, but at the cost of making the process rather opaque. % Instead, the approach taken here is to use a dedicated set of functions % which keep the different loop requirements clearly separate. % % The main loop looks for the first \enquote{real} char in the input % (skipping any pre-letter chars). Once one is found, it is case changed to % upper case but first checking that there is not an entry in the exceptions % list. Note that simply grabbing the first token in the input is no good % here: it can't handle pre-letter tokens or any special treatment of the % first letter found (\emph{e.g.}~words starting with \texttt{i} in % Turkish). Spaces at the start of the input are passed through without % counting as being the \enquote{start} of the first word, while a brace % group is assumed to be contain the first char with everything after the % brace therefore lower cased. % \begin{macrocode} \cs_new:Npn \@@_mixed_case:nn #1#2 { \etex_unexpanded:D \exp_after:wN { \exp:w \@@_mixed_case_aux:nn {#1} {#2} } } \cs_new:Npn \@@_mixed_case_aux:nn #1#2 { \group_align_safe_begin: \@@_mixed_case_loop:wn #2 \q_recursion_tail \q_recursion_stop {#1} \@@_change_case_result:n { } } \cs_new:Npn \@@_mixed_case_loop:wn #1 \q_recursion_stop { \tl_if_head_is_N_type:nTF {#1} { \@@_mixed_case_N_type:Nwn } { \tl_if_head_is_group:nTF {#1} { \@@_mixed_case_group:nwn } { \@@_mixed_case_space:wn } } #1 \q_recursion_stop } \cs_new:Npn \@@_mixed_case_group:nwn #1#2 \q_recursion_stop #3 { \@@_change_case_output:own { \exp_after:wN { \exp:w \@@_mixed_case_aux:nn {#3} {#1} } } \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#3} } \exp_last_unbraced:NNo \cs_new:Npn \@@_mixed_case_space:wn \c_space_tl { \@@_change_case_output:nwn { ~ } \@@_mixed_case_loop:wn } \cs_new:Npn \@@_mixed_case_N_type:Nwn #1#2 \q_recursion_stop { \quark_if_recursion_tail_stop_do:Nn #1 { \@@_change_case_end:wn } \exp_after:wN \@@_mixed_case_N_type:NNNnn \exp_after:wN #1 \l_tl_case_change_math_tl \q_recursion_tail ? \q_recursion_stop {#2} } \cs_new:Npn \@@_mixed_case_N_type:NNNnn #1#2#3 { \quark_if_recursion_tail_stop_do:Nn #2 { \@@_mixed_case_N_type:Nnn #1 } \token_if_eq_meaning:NNTF #1 #2 { \use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_math:NNNnnn #1 #3 \@@_mixed_case_loop:wn } } { \@@_mixed_case_N_type:NNNnn #1 } } % \end{macrocode} % The business end of the loop is here: there is first a need to deal % with any control sequence cases before looking for characters to skip. % If there is a hit for a letter-like control sequence, switch to lower % casing. % \begin{macrocode} \cs_new:Npn \@@_mixed_case_N_type:Nnn #1#2#3 { \token_if_cs:NTF #1 { \@@_change_case_cs_letterlike:Nnn #1 { upper } { \@@_mixed_case_letterlike:Nw } \@@_mixed_case_loop:wn #2 \q_recursion_stop {#3} } { \@@_mixed_case_char:Nn #1 {#3} \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } {#3} } } \cs_new:Npn \@@_mixed_case_letterlike:Nw #1#2 \q_recursion_stop { \@@_change_case_loop:wnn #2 \q_recursion_stop { lower } } % \end{macrocode} % As detailed above, handling a mixed case char means first looking for % exceptions then treating as an upper cased letter, but with a list of % tokens to skip over too. % \begin{macrocode} \cs_new:Npn \@@_mixed_case_char:Nn #1#2 { \cs_if_exist_use:cF { @@_change_case_mixed_ #2 :Nnw } { \cs_if_exist_use:cF { @@_change_case_upper_ #2 :Nnw } { \use_ii:nn } } #1 { \@@_mixed_case_skip:N #1 } } \cs_new:Npn \@@_mixed_case_skip:N #1 { \exp_after:wN \@@_mixed_case_skip:NN \exp_after:wN #1 \l_tl_mixed_case_ignore_tl \q_recursion_tail \q_recursion_stop } \cs_new:Npn \@@_mixed_case_skip:NN #1#2 { \quark_if_recursion_tail_stop_do:nn {#2} { \@@_mixed_case_char:N #1 } \int_compare:nNnT { `#1 } = { `#2 } { \use_i_delimit_by_q_recursion_stop:nw { \@@_change_case_output:nwn {#1} \@@_mixed_case_skip_tidy:Nwn } } \@@_mixed_case_skip:NN #1 } \cs_new:Npn \@@_mixed_case_skip_tidy:Nwn #1#2 \q_recursion_stop #3 { \@@_mixed_case_loop:wn #2 \q_recursion_stop } \cs_new:Npn \@@_mixed_case_char:N #1 { \cs_if_exist:cTF { c__unicode_title_ #1 _tl } { \@@_change_case_output:fwn { \tl_use:c { c__unicode_title_ #1 _tl } } } { \@@_change_case_char:nN { upper } #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} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:Nnw} % \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:Nw} % \begin{macro}[aux, EXP]{\@@_change_case_mixed_nl:NNw} % For Dutch, there is a single look-ahead test for \texttt{ij} when % title casing. If the appropriate letters are found, produce \texttt{IJ} % and gobble the \texttt{j}/\texttt{J}. % \begin{macrocode} \cs_new:Npn \@@_change_case_mixed_nl:Nnw #1 { \bool_lazy_or:nnTF { \int_compare_p:nNn { `#1 } = { `i } } { \int_compare_p:nNn { `#1 } = { `I } } { \@@_change_case_output:nwn { I } \@@_change_case_mixed_nl:Nw } } \cs_new:Npn \@@_change_case_mixed_nl:Nw #1#2 \q_recursion_stop { \tl_if_head_is_N_type:nT {#2} { \@@_change_case_mixed_nl:NNw } #1 #2 \q_recursion_stop } \cs_new:Npn \@@_change_case_mixed_nl:NNw #1#2#3 \q_recursion_stop { \@@_change_case_if_expandable:NTF #2 { \exp_after:wN \@@_change_case_mixed_nl:Nw \exp_after:wN #1 #2 #3 \q_recursion_stop } { \bool_lazy_and:nnTF { ! ( \token_if_cs_p:N #2 ) } { \bool_lazy_or_p:nn { \int_compare_p:nNn { `#2 } = { `j } } { \int_compare_p:nNn { `#2 } = { `J } } } { \@@_change_case_output:nwn { J } #1 } { #1 #2 } #3 \q_recursion_stop } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{variable}{\l_tl_case_change_math_tl} % The list of token pairs which are treated as math mode and so % not case changed. % \begin{macrocode} \tl_new:N \l_tl_case_change_math_tl %<*package> \tl_set:Nn \l_tl_case_change_math_tl { $ $ \( \) } % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_tl_case_change_exclude_tl} % The list of commands for which an argument is not case changed. % \begin{macrocode} \tl_new:N \l_tl_case_change_exclude_tl %<*package> \tl_set:Nn \l_tl_case_change_exclude_tl { \cite \ensuremath \label \ref } % % \end{macrocode} % \end{variable} % % \begin{variable}{\l_tl_mixed_case_ignore_tl} % Characters to skip over when finding the first letter in a word to be % mixed cased. % \begin{macrocode} \tl_new:N \l_tl_mixed_case_ignore_tl \tl_set:Nx \l_tl_mixed_case_ignore_tl { ( % ) [ % ] \cs_to_str:N \{ % \} ` - } % \end{macrocode} % \end{variable} % % \subsubsection{Other additions to \pkg{l3tl}} % % \begin{macro}{\tl_rand_item:n, \tl_rand_item:N, \tl_rand_item:c} % Importantly \cs{tl_item:nn} only evaluates its argument once. % \begin{macrocode} \cs_new:Npn \tl_rand_item:n #1 { \tl_if_blank:nF {#1} { \tl_item:nn {#1} { \int_rand:nn { 1 } { \tl_count:n {#1} } } } } \cs_new:Npn \tl_rand_item:N { \exp_args:No \tl_rand_item:n } \cs_generate_variant:Nn \tl_rand_item:N { c } % \end{macrocode} % \end{macro} % % \begin{macro}{\tl_range:Nnn, \tl_range:cnn, \tl_range:nnn} % \begin{macro}[aux] % {\@@_range:nnnw, \@@_range:nnw, \@@_range_normalize:nn, \@@_range_collect:w, \@@_range_skip:w} % To avoid checking for the end of the token list at every step, start % by counting the number $l$ of items and \enquote{normalizing} the % bounds, namely clamping them to the interval $[0,l]$ and dealing % with negative indices. More precisely, \cs{@@_range:nnw} receives % the number of items to skip at the beginning of the token list, and % the index of the last item to keep. If nothing should be kept, % leave |{}|: this stops the \texttt{f}-expansion of \cs{tl_head:f} % and that function produces an empty result. Otherwise, repeatedly % call \cs{@@_range_skip:w} to delete |#1| items from the input stream % (the extra brace group avoids an off-by-one mistake), then % repeatedly call \cs{@@_range_collect:w} to store in its second % argument the items to be kept. Eventually, the result is a brace % group followed by the rest of the token list, and \cs{tl_head:f} % cleans up and gives the result in \cs{exp_not:n}. % \begin{macrocode} \cs_new:Npn \tl_range:Nnn { \exp_args:No \tl_range:nnn } \cs_generate_variant:Nn \tl_range:Nnn { c } \cs_new:Npn \tl_range:nnn #1#2#3 { \tl_head:f { \exp_args:Nf \@@_range:nnnw { \tl_count:n {#1} } {#2} {#3} #1 } } \cs_new:Npn \@@_range:nnnw #1#2#3 { \exp_args:Nff \@@_range:nnw { \exp_args:Nf \@@_range_normalize:nn { \int_eval:n { #2 - 1 } } {#1} } { \exp_args:Nf \@@_range_normalize:nn { \int_eval:n {#3} } {#1} } } \cs_new:Npn \@@_range:nnw #1#2 { \if_int_compare:w #2 > #1 \exp_stop_f: \else: \exp_after:wN { \exp_after:wN } \fi: \exp_after:wN \@@_range_collect:w \__int_value:w \__int_eval:w #2 - #1 \exp_after:wN ; \exp_after:wN { \exp_after:wN } \exp:w \@@_range_skip:w #1 ; { } } \cs_new:Npn \@@_range_skip:w #1 ; #2 { \if_int_compare:w #1 > 0 \exp_stop_f: \exp_after:wN \@@_range_skip:w \__int_value:w \__int_eval:w #1 - 1 \exp_after:wN ; \else: \exp_after:wN \exp_end: \fi: } \cs_new:Npn \@@_range_collect:w #1 ; #2#3 { \if_int_compare:w #1 > 1 \exp_stop_f: \exp_after:wN \@@_range_collect:w \__int_value:w \__int_eval:w #1 - 1 \exp_after:wN ; \fi: { #2 {#3} } } % \end{macrocode} % \end{macro} % \end{macro} % \begin{macro}[EXP, aux]{\@@_range_normalize:nn} % This function converts an \meta{index} argument into an explicit % position in the token list (a result of $0$ denoting \enquote{out of % bounds}). Expects two explicit integer arguments: the \meta{index} % |#1| and the string count~|#2|. If |#1| is negative, replace it by % $|#1| + |#2| + 1$, then limit to the range $[0, |#2|]$. % \begin{macrocode} \cs_new:Npn \@@_range_normalize:nn #1#2 { \int_eval:n { \if_int_compare:w #1 < 0 \exp_stop_f: \if_int_compare:w #1 < -#2 \exp_stop_f: 0 \else: #1 + #2 + 1 \fi: \else: \if_int_compare:w #1 < #2 \exp_stop_f: #1 \else: #2 \fi: \fi: } } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3tokens}} % % \begin{macrocode} %<@@=peek> % \end{macrocode} % % \begin{macro}[TF]{\peek_N_type:} % \begin{macro}[aux] % {\@@_execute_branches_N_type:, \@@_N_type:w, \@@_N_type_aux:nnw} % All tokens are \texttt{N}-type tokens, except in four cases: % begin-group tokens, end-group tokens, space tokens with character % code~$32$, and outer tokens. Since \cs{l_peek_token} might be % outer, we cannot use the convenient \cs{bool_if:nTF} function, and % must resort to the old trick of using \tn{ifodd} to expand a set of % tests. The \texttt{false} branch of this test is taken if the token % is one of the first three kinds of non-\texttt{N}-type tokens % (explicit or implicit), thus we call \cs{@@_false:w}. In the % \texttt{true} branch, we must detect outer tokens, without impacting % performance too much for non-outer tokens. The first filter is to % search for \texttt{outer} in the \tn{meaning} of \cs{l_peek_token}. % If that is absent, \cs{use_none_delimit_by_q_stop:w} cleans up, and % we call \cs{@@_true:w}. Otherwise, the token can be a non-outer % macro or a primitive mark whose parameter or replacement text % contains \texttt{outer}, it can be the primitive \tn{outer}, or it % can be an outer token. Macros and marks would have \texttt{ma} in % the part before the first occurrence of \texttt{outer}; the meaning % of \tn{outer} has nothing after \texttt{outer}, contrarily to outer % macros; and that covers all cases, calling \cs{@@_true:w} or % \cs{@@_false:w} as appropriate. Here, there is no \meta{search % token}, so we feed a dummy \cs{scan_stop:} to the % \cs{@@_token_generic:NNTF} function. % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_tmp:w #1 \q_stop { \cs_new_protected:Npn \@@_execute_branches_N_type: { \if_int_odd:w \if_catcode:w \exp_not:N \l_peek_token { 0 \exp_stop_f: \fi: \if_catcode:w \exp_not:N \l_peek_token } 0 \exp_stop_f: \fi: \if_meaning:w \l_peek_token \c_space_token 0 \exp_stop_f: \fi: 1 \exp_stop_f: \exp_after:wN \@@_N_type:w \token_to_meaning:N \l_peek_token \q_mark \@@_N_type_aux:nnw #1 \q_mark \use_none_delimit_by_q_stop:w \q_stop \exp_after:wN \@@_true:w \else: \exp_after:wN \@@_false:w \fi: } \cs_new_protected:Npn \@@_N_type:w ##1 #1 ##2 \q_mark ##3 { ##3 {##1} {##2} } } \exp_after:wN \@@_tmp:w \tl_to_str:n { outer } \q_stop \group_end: \cs_new_protected:Npn \@@_N_type_aux:nnw #1 #2 #3 \fi: { \fi: \tl_if_in:noTF {#1} { \tl_to_str:n {ma} } { \@@_true:w } { \tl_if_empty:nTF {#2} { \@@_true:w } { \@@_false:w } } } \cs_new_protected:Npn \peek_N_type:TF { \@@_token_generic:NNTF \@@_execute_branches_N_type: \scan_stop: } \cs_new_protected:Npn \peek_N_type:T { \@@_token_generic:NNT \@@_execute_branches_N_type: \scan_stop: } \cs_new_protected:Npn \peek_N_type:F { \@@_token_generic:NNF \@@_execute_branches_N_type: \scan_stop: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex