% \iffalse meta-comment % %% File: l3candidates.dtx Copyright(C) 2012-2014 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 released version of this bundle is available from CTAN. %% %% ----------------------------------------------------------------------- %% %% The development version of the bundle can be found at %% %% http://www.latex-project.org/svnroot/experimental/trunk/ %% %% for those people who are interested. %% %%%%%%%%%%% %% NOTE: %% %%%%%%%%%%% %% %% Snapshots taken from the repository represent work in progress and may %% not work or may contain conflicting material! We therefore ask %% people _not_ to put them into distributions, archives, etc. without %% prior consultation with the LaTeX Project Team. %% %% ----------------------------------------------------------------------- %% % %<*driver> \documentclass[full]{l3doc} % %<*driver|package> \GetIdInfo$Id: l3candidates.dtx 4734 2014-05-04 21:48:49Z joseph $ {L3 Experimental additions to l3kernel} % %<*driver> \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3candidates} package\\ Experimental additions to % \pkg{l3kernel}^^A % \thanks{This file describes v\ExplFileVersion, % last revised \ExplFileDate.}^^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 \ExplFileDate} % % \maketitle % % \begin{documentation} % % This module provides a space in which functions can be added to % \pkg{l3kernel} (\pkg{expl3}) while still being experimental. As such, the % functions here may not remain in their current form, or indeed at all, % in \pkg{l3kernel} in the future. 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. % % \section{Additions to \pkg{l3basics}} % % \begin{function}[EXP,TF]{\cs_if_exist_use:N, \cs_if_exist_use:c} % \begin{syntax} % \cs{cs_if_exist_use:NTF} \meta{control sequence} \Arg{true code} \Arg{false code} % \end{syntax} % If the \meta{control sequence} exists, leave it in the input stream, % followed by the \meta{true code} (unbraced). Otherwise, leave the % \meta{false} code in the input stream. For example, % \begin{verbatim} % \cs_set:Npn \mypkg_use_character:N #1 % { \cs_if_exist_use:cF { mypkg_#1:n } { \mypkg_default:N #1 } } % \end{verbatim} % calls the function |\mypkg_#1:n| if it exists, and falls back to % a default action otherwise. This could also be done (more slowly) % using \cs{str_case_x:nnn}. % \begin{texnote} % The \texttt{c} variants do not introduce the \meta{control sequence} % in the hash table if it is not there. % \end{texnote} % \end{function} % % \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. 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. 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. The resizing applies % within the current \TeX{} group level. % \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. % The scaling 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} % % \subsection{Internal variables} % % \begin{variable}{\l__box_angle_fp} % The angle through which a box is rotated by \cs{box_rotate:Nn}, given in % degrees counter-clockwise. This value is required by the underlying % driver code in \pkg{l3driver} to carry out the driver-dependent part % of box rotation. % \end{variable} % % \begin{variable}{\l__box_cos_fp, \l__box_sin_fp} % The sine and cosine of the angle through which a box is rotated by % \cs{box_rotate:Nn}: the values refer to the angle counter-clockwise. These % values are required by the underlying driver code in \pkg{l3driver} to % carry out the driver-dependent part of box rotation. % \end{variable} % % \begin{variable}{\l__box_scale_x_fp, \l__box_scale_y_fp} % The scaling factors by which a box is scaled by \cs{box_scale:Nnn} % or \cs{box_resize:Nnn}. These values are required by the underlying % driver code in \pkg{l3driver} to carry out the driver-dependent part % of box rotation. % \end{variable} % % \begin{variable}{\l__box_internal_box} % Box used for affine transformations, which is used to contain rotated % material when applying \cs{box_rotate:Nn}. This box must be correctly % constructed for the driver-dependent code in \pkg{l3driver} to function % correctly. % \end{variable} % % \section{Additions to \pkg{l3clist}} % % \begin{function}[EXP]{\clist_item:Nn, \clist_item:cn, \clist_item:nn} % \begin{syntax} % \cs{clist_item:Nn} \meta{comma list} \Arg{integer expression} % \end{syntax} % Indexing items in the \meta{comma list} from~$1$ at the top (left), this % function will evaluate the \meta{integer expression} and leave the % appropriate item from the comma list in the input stream. If the % \meta{integer expression} is negative, indexing occurs from the % bottom (right) of the comma list. When the \meta{integer expression} % is larger than the number of items in the \meta{comma list} (as % calculated by \cs{clist_count:N}) then the function will expand to % nothing. % \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} % { % \clist_set_from_seq:NN, \clist_set_from_seq:cN, % \clist_set_from_seq:Nc, \clist_set_from_seq:cc, % \clist_gset_from_seq:NN, \clist_gset_from_seq:cN, % \clist_gset_from_seq:Nc, \clist_gset_from_seq:cc % } % \begin{syntax} % \cs{clist_set_from_seq:NN} \meta{comma list} \meta{sequence} % \end{syntax} % Sets the \meta{comma list} to be equal to the content of the % \meta{sequence}. % Items which contain either spaces or commas are surrounded by braces. % \end{function} % % \begin{function} % { % \clist_const:Nn, \clist_const:Nx, % \clist_const:cn, \clist_const:cx % } % \begin{syntax} % \cs{clist_const:Nn} \meta{clist~var} \Arg{comma list} % \end{syntax} % Creates a new constant \meta{clist~var} or raises an error % if the name is already taken. The value of the % \meta{clist~var} will be set globally to the % \meta{comma list}. % \end{function} % % \begin{function}[EXP, pTF]{\clist_if_empty:n} % \begin{syntax} % \cs{clist_if_empty_p:n} \Arg{comma list} % \cs{clist_if_empty:nTF} \Arg{comma list} \Arg{true code} \Arg{false code} % \end{syntax} % Tests if the \meta{comma list} is empty (containing no items). % The rules for space trimming are as for other \texttt{n}-type % comma-list functions, hence the comma list |{~,~,,~}| (without % outer braces) is empty, while |{~,{},}| (without outer braces) % contains one element, which happens to be empty: the comma-list % is not empty. % \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}[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{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{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{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{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} % % \section{Additions to \pkg{l3fp}} % % \begin{function} % { % \fp_set_from_dim:Nn, \fp_set_from_dim:cn, % \fp_gset_from_dim:Nn, \fp_gset_from_dim:cn % } % \begin{syntax} % \cs{fp_set_from_dim:Nn} \meta{floating point variable} \Arg{dimexpr} % \end{syntax} % Sets the \meta{floating point variable} to the distance represented % by the \meta{dimension expression} in the units points. This means % that distances given in other units are first converted to points % before being assigned to the \meta{floating point variable}. % \end{function} % % \begin{function}[EXP]{\fp_function:Nw} % \begin{syntax} % \cs{fp_eval:n} % ~~|{| % ~~~~\cs{fp_function:Nw} \meta{function} % ~~~~~~|(| \meta{fpexpr_1} |,| \ldots{} |,| \meta{fpexpr_n} |)| % ~~|}| % \end{syntax} % Finds one or more arguments \meta{fpexpr_1} \ldots{} \meta{fpexpr_n} % following the \meta{function}, and evaluate them. Then calls the % \meta{function} followed by a single brace group containing % \Arg{result_1} \ldots{} \Arg{result_n}. For instance, % \begin{verbatim} % \cs_new_nopar:Npn \mypkg_log:w % { \fp_function:Nw \__mypkg_log:n } % \cs_new:Npn \__mypkg_log:n #1 % { % \int_case:nnF { \tl_count:n {#1} } % { % { 1 } { \__mypkg_log_aux:nn #1 { 10 } } % { 2 } { \__mypkg_log_aux:nn #1 } % } % { \ERROR \c_nan_fp } % } % \cs_new:Npn \__mypkg_log_aux:nn #1#2 { ln(#1) / ln(#2) } % \fp_show:n { \mypkg_log:w (8, 2) + \mypkg_log:w (1e17) } % \end{verbatim} % shows $20 = \log_2(8) + \log(10^{17})$. The function |\mypkg_log:w| % behaves like other built-in functions such as |ln|, but allows $1$ % or~$2$ arguments, and computes either the base~$10$ logarithm or the % logarithm of the first argument in a base given by the second % argument. Checking the number of arguments is acheived by % |\__mypkg_log:n|, which provides the default base~$10$ when there is % only one argument. The computation itself is done by % |\__mypkg_log_aux:nn|. % \end{function} % % \begin{function}{\fp_new_function:Npn} % \begin{syntax} % \cs{fp_new_function:Npn} \meta{function} \meta{parameters} \Arg{code} % \cs{fp_eval:n} |{| \meta{function} |(| \meta{fpexpr_1} |,| \ldots{} |,| \meta{fpexpr_n} |)| |}| % \end{syntax} % Defines the \meta{function} for use within floating point % expressions, expecting some \meta{parameters}, and evaluating the % \meta{code}, which must be expandable. When the \meta{function} % appears in a floating point expression, arguments \meta{fpexpr_1}, % \ldots{}, \meta{fpexpr_n} are found and evaluated in the same way as % for built-in functions such as~|max|. If the number of arguments % matches the number of \meta{parameters}, the arguments replace |#1|, % \ldots{}, |#|$n$ in the \meta{code}, which is then evaluated to % produce a floating point result. Otherwise, the result is % \texttt{nan} after an error. The \meta{parameter text} must not % contain delimited arguments, that is, it must be empty or one of % |#1|, |#1#2|, |#1#2#3|, \ldots{} |#1#2#3#4#5#6#7#8#9|. The % arguments replacing parameters in the \meta{code} are internal % floating point numbers; operations such as |#1^2| thus correctly % take into account the sign of~|#1|. For instance, % \begin{verbatim} % \fp_new_function:Npn \mypkg_sqrt:w #1 { #1^.5 } % \fp_new_function:Npn \mypkg_veclen:w #1#2 % { \mypkg_sqrt:w ( #1^2 + #2^2 ) } % \fp_show:n { \mypkg_veclen:w ( 42 / 7 , 2 * 4 - 0 ) } % \end{verbatim} % shows~$10$. In the example, |\mypkg_veclen:w| receives the % arguments $6=42/7$ and~$8=2\times 4-0$, thus expands to % |\mypkg_sqrt:w ( 6^2 + 8^2 )|, then |\mypkg_sqrt:w| receives % $100=6^2+8^2$ as an argument, and evaluates the square root $10 = % 100^{0.5}$. % \end{function} % % \section{Additions to \pkg{l3prop}} % % \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 \cs{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_get:Nn} is faster. % \end{function} % % \begin{function}[EXP]{\prop_get:Nn, \prop_get:cn} % \begin{syntax} % \cs{prop_get:Nn} \meta{property list} \Arg{key} % \end{syntax} % Expands to the \meta{value} corresponding to the \meta{key} in % the \meta{property list}. If the \meta{key} is missing, this has % an empty expansion. % \begin{texnote} % This function is slower than the non-expandable analogue % \cs{prop_get:NnN}. % 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}[EXP]{\seq_item:Nn, \seq_item:cn} % \begin{syntax} % \cs{seq_item:Nn} \meta{sequence} \Arg{integer expression} % \end{syntax} % Indexing items in the \meta{sequence} from~$1$ at the top (left), this % function will evaluate the \meta{integer expression} and leave the % appropriate item from the sequence in the input stream. If the % \meta{integer expression} is negative, indexing occurs from the % bottom (right) of the sequence. When the \meta{integer expression} % is larger than the number of items in the \meta{sequence} (as % calculated by \cs{seq_count:N}) then the function will expand to % nothing. % \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}[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_from_clist:NN, \seq_set_from_clist:cN, % \seq_set_from_clist:Nc, \seq_set_from_clist:cc, % \seq_set_from_clist:Nn, \seq_set_from_clist:cn, % \seq_gset_from_clist:NN, \seq_gset_from_clist:cN, % \seq_gset_from_clist:Nc, \seq_gset_from_clist:cc, % \seq_gset_from_clist:Nn, \seq_gset_from_clist:cn % } % \begin{syntax} % \cs{seq_set_from_clist:NN} \meta{sequence} \meta{comma-list} % \end{syntax} % Sets the \meta{sequence} within the current \TeX{} group to be equal % to the content of the \meta{comma-list}. % \end{function} % % \begin{function}{\seq_reverse:N, \seq_greverse:N} % \begin{syntax} % \cs{seq_reverse:N} \meta{sequence} % \end{syntax} % Reverses the order of items in the \meta{sequence}, and % assigns the result to \meta{sequence}, locally or globally % according to the variant chosen. % \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} % % \section{Additions to \pkg{l3skip}} % % \begin{function}[added = 2013-05-06, EXP]{\dim_to_pt:n} % \begin{syntax} % \cs{dim_to_pt:n} \Arg{dimexpr} % \end{syntax} % Evaluates the \meta{dimension expression}, and leaves the result, % expressed in points (\texttt{pt}) in the input stream, with \emph{no % units}. The result is rounded by \TeX{} to four or five decimal % places. If the decimal part of the result is zero, it is omitted, % together with the decimal marker. % % If the \meta{dimension expression} contains additional tokens such % as redundant units, these will be ignored, so for example % \begin{verbatim} % \dim_to_pt:n { 1 bp pt } % \end{verbatim} % leaves |1.00374| in the input stream, \emph{i.e.}~the magnitude of % one \enquote{big point} when converted to points. % \end{function} % % \begin{function}[added = 2013-05-06, EXP]{\dim_to_unit:nn} % \begin{syntax} % \cs{dim_to_unit:nn} \Arg{dimexpr_1} \Arg{dimexpr_2} % \end{syntax} % Evaluates the \meta{dimension expressions}, and leaves the value of % \meta{dimexpr_1}, expressed in a unit given by \meta{dimexpr_2}, in % the input stream. The result is a decimal number, rounded by \TeX{} % to four or five decimal places. If the decimal part of the result % is zero, it is omitted, together with the decimal marker. % % If the \meta{dimension expressions} contain additional tokens such % as redundant units, these will be ignored, so for example % \begin{verbatim} % \dim_to_unit:nn { 1 bp pt } { 1 mm } % \end{verbatim} % leaves |0.35277| in the input stream, \emph{i.e.}~the magnitude of % one \enquote{big point} when converted to millimeters. % \end{function} % % \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]{\tl_expandable_uppercase:n,\tl_expandable_lowercase:n} % \begin{syntax} % \cs{tl_expandable_uppercase:n} \Arg{tokens} % \cs{tl_expandable_lowercase:n} \Arg{tokens} % \end{syntax} % The \cs{tl_expandable_uppercase:n} function works through all of % the \meta{tokens}, replacing characters in the range |a|--|z| % (with arbitrary category code) by the corresponding letter % in the range |A|--|Z|, with category code $11$ (letter). Similarly, % \cs{tl_expandable_lowercase:n} replaces characters in the range % |A|--|Z| by letters in the range |a|--|z|, and leaves other tokens % unchanged. This function requires two steps of expansion. % \begin{texnote} % Begin-group and end-group characters are normalized and become % |{| and |}|, respectively. % 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_item:nn, \tl_item:Nn, \tl_item:cn} % \begin{syntax} % \cs{tl_item:nn} \Arg{token list} \Arg{integer expression} % \end{syntax} % Indexing items in the \meta{token list} from~$1$ on the left, this % function will evaluate the \meta{integer expression} and leave the % appropriate item from the \meta{token list} in the input stream. % If the \meta{integer expression} is negative, indexing occurs from % the right of the token list, starting at $-1$ for the right-most item. % If the index is out of bounds, then thr function expands to nothing. % \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}{\char_set_active:Npn, \char_set_active:Npx} % \begin{syntax} % \cs{char_set_active:Npn} \meta{char} \meta{parameters} \Arg{code} % \end{syntax} % Makes \meta{char} an active character to expand to \meta{code} as % replacement text. % Within the \meta{code}, the \meta{parameters} (|#1|, |#2|, % \emph{etc.}) will be replaced by those absorbed. The \meta{char} is % made active within the current \TeX{} group level, and the definition % is also local. % \end{function} % % \begin{function}{\char_gset_active:Npn, \char_gset_active:Npx} % \begin{syntax} % \cs{char_gset_active:Npn} \meta{char} \meta{parameters} \Arg{code} % \end{syntax} % Makes \meta{char} an active character to expand to \meta{code} as % replacement text. % Within the \meta{code}, the \meta{parameters} (|#1|, |#2|, % \emph{etc.}) will be replaced by those absorbed. The \meta{char} is % made active within the current \TeX{} group level, but the definition % is global. This function is therefore suited to cases where an active % character definition should be applied only in some context (where the % \meta{char} is again made active). % \end{function} % % \begin{function}{\char_set_active_eq:NN} % \begin{syntax} % \cs{char_set_active_eq:NN} \meta{char} \meta{function} % \end{syntax} % Makes \meta{char} an active character equivalent in meaning to the % \meta{function} (which may itself be an active character). The \meta{char} % is made active within the current \TeX{} group level, and the definition % is also local. % \end{function} % % \begin{function}{\char_gset_active_eq:NN} % \begin{syntax} % \cs{char_gset_active_eq:NN} \meta{char} \meta{function} % \end{syntax} % Makes \meta{char} an active character equivalent in meaning to the % \meta{function} (which may itself be an active character). The \meta{char} % is made active within the current \TeX{} group level, but the definition % is global. This function is therefore suited to cases where an active % character definition should be applied only in some context (where the % \meta{char} is again made active). % \end{function} % % \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_rotate_begin: \box_use:N \l_@@_internal_box \__driver_box_rotate_end: } } % \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: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: \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 $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 work. % \begin{macrocode} \@@_resize:Nnn #1 {#2} {#3} \group_end: } } \cs_generate_variant:Nn \box_resize:Nnn { c } % \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. This is done using the absolute value of the % scale so that the new edge is in the correct place. 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:Nnn #1#2#3 { \dim_set:Nn \l_@@_right_new_dim { \dim_abs:n {#2} } \dim_set:Nn \l_@@_bottom_new_dim { \fp_abs:n { \l_@@_scale_y_fp } \l_@@_bottom_dim } \dim_set:Nn \l_@@_top_new_dim { \fp_abs:n { \l_@@_scale_y_fp } \l_@@_top_dim } \@@_resize_common:N #1 } % \end{macrocode} % \end{macro} % \end{macro} % % \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} % 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_plus_dp:Nn #1#2 { \hbox_set:Nn #1 { \group_begin: \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 \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:Nnn #1 {#2} {#2} \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: \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 \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:Nnn #1 {#2} {#2} \group_end: } } \cs_generate_variant:Nn \box_resize_to_wd:Nn { c } % \end{macrocode} % \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_scale_begin: \hbox_overlap_right:n { \box_use:N #1 } \__driver_box_scale_end: } % \end{macrocode} % The new height and depth can be applied directly. % \begin{macrocode} \box_set_ht:Nn \l_@@_internal_box { \l_@@_top_new_dim } \box_set_dp: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_item:Nn, \clist_item:cn} % \begin{macro}[aux]{\@@_item:nnNn} % \begin{macro}[aux]{\@@_item_N_loop:nw} % To avoid needing to test the end of the list at each step, % we first compute the \meta{length} of the list. If the item number % is~$0$, less than $-\meta{length}$, or more than $\meta{length}$, % the result is empty. If it is negative, but not less than $-\meta{length}$, % add $\meta{length}+1$ to the item number before performing the loop. % The loop itself is very simple, return the item if the counter % reached~$1$, otherwise, decrease the counter and repeat. % \begin{macrocode} \cs_new:Npn \clist_item:Nn #1#2 { \exp_args:Nfo \@@_item:nnNn { \clist_count:N #1 } #1 \@@_item_N_loop:nw {#2} } \cs_new:Npn \@@_item:nnNn #1#2#3#4 { \int_compare:nNnTF {#4} < \c_zero { \int_compare:nNnTF {#4} < { - #1 } { \use_none_delimit_by_q_stop:w } { \exp_args:Nf #3 { \int_eval:n { #4 + \c_one + #1 } } } } { \int_compare:nNnTF {#4} > {#1} { \use_none_delimit_by_q_stop:w } { #3 {#4} } } { } , #2 , \q_stop } \cs_new:Npn \@@_item_N_loop:nw #1 #2, { \int_compare:nNnTF {#1} = \c_zero { \use_i_delimit_by_q_stop:nw { \exp_not:n {#2} } } { \exp_args:Nf \@@_item_N_loop:nw { \int_eval:n { #1 - 1 } } } } \cs_generate_variant:Nn \clist_item:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\clist_item:nn} % \begin{macro}[aux]{ % \@@_item_n:nw, % \@@_item_n_loop:nw, % \@@_item_n_end:n, % \@@_item_n_strip:w} % This starts in the same way as \cs{clist_item:Nn} by counting the items % of the comma list. The final item should be space-trimmed before being % brace-stripped, hence we insert a couple of odd-looking % \cs{prg_do_nothing:} to avoid losing braces. Blank items are ignored. % \begin{macrocode} \cs_new:Npn \clist_item:nn #1#2 { \exp_args:Nf \@@_item:nnNn { \clist_count:n {#1} } {#1} \@@_item_n:nw {#2} } \cs_new:Npn \@@_item_n:nw #1 { \@@_item_n_loop:nw {#1} \prg_do_nothing: } \cs_new:Npn \@@_item_n_loop:nw #1 #2, { \exp_args:No \tl_if_blank:nTF {#2} { \@@_item_n_loop:nw {#1} \prg_do_nothing: } { \int_compare:nNnTF {#1} = \c_zero { \exp_args:No \@@_item_n_end:n {#2} } { \exp_args:Nf \@@_item_n_loop:nw { \int_eval:n { #1 - 1 } } \prg_do_nothing: } } } \cs_new:Npn \@@_item_n_end:n #1 #2 \q_stop { \__tl_trim_spaces:nn { \q_mark #1 } { \exp_last_unbraced:No \@@_item_n_strip:w } , } \cs_new:Npn \@@_item_n_strip:w #1 , { \exp_not:n {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % { % \clist_set_from_seq:NN, \clist_set_from_seq:cN, % \clist_set_from_seq:Nc, \clist_set_from_seq:cc % } % \UnitTested % \begin{macro} % { % \clist_gset_from_seq:NN, \clist_gset_from_seq:cN, % \clist_gset_from_seq:Nc, \clist_gset_from_seq:cc % } % \UnitTested % \begin{macro}[aux]{\@@_set_from_seq:NNNN} % \begin{macro}[aux]{\@@_wrap_item:n} % \begin{macro}[aux]{\@@_set_from_seq:w} % Setting a comma list from a comma-separated list is done using a simple % mapping. We wrap most items with \cs{exp_not:n}, and a comma. Items which % contain a comma or a space are surrounded by an extra set of braces. The % first comma must be removed, except in the case of an empty comma-list. % \begin{macrocode} \cs_new_protected:Npn \clist_set_from_seq:NN { \@@_set_from_seq:NNNN \clist_clear:N \tl_set:Nx } \cs_new_protected:Npn \clist_gset_from_seq:NN { \@@_set_from_seq:NNNN \clist_gclear:N \tl_gset:Nx } \cs_new_protected:Npn \@@_set_from_seq:NNNN #1#2#3#4 { \seq_if_empty:NTF #4 { #1 #3 } { #2 #3 { \exp_last_unbraced:Nf \use_none:n { \seq_map_function:NN #4 \@@_wrap_item:n } } } } \cs_new:Npn \@@_wrap_item:n #1 { , \tl_if_empty:oTF { \@@_set_from_seq:w #1 ~ , #1 ~ } { \exp_not:n {#1} } { \exp_not:n { {#1} } } } \cs_new:Npn \@@_set_from_seq:w #1 , #2 ~ { } \cs_generate_variant:Nn \clist_set_from_seq:NN { Nc } \cs_generate_variant:Nn \clist_set_from_seq:NN { c , cc } \cs_generate_variant:Nn \clist_gset_from_seq:NN { Nc } \cs_generate_variant:Nn \clist_gset_from_seq:NN { c , cc } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % { % \clist_const:Nn, \clist_const:cn, % \clist_const:Nx, \clist_const:cx % } % Creating and initializing a constant comma list is done in a way % similar to \cs{clist_set:Nn} and \cs{clist_gset:Nn}, being careful % to strip spaces. % \begin{macrocode} \cs_new_protected:Npn \clist_const:Nn #1#2 { \tl_const:Nx #1 { \@@_trim_spaces:n {#2} } } \cs_generate_variant:Nn \clist_const:Nn { c , Nx , cx } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP, pTF]{\clist_if_empty:n} % \begin{macro}[aux, EXP]{\@@_if_empty_n:w} % \begin{macro}[aux, EXP]{\@@_if_empty_n:wNw} % As usual, we insert a token (here |?|) before grabbing % any argument: this avoids losing braces. The argument % of \cs{tl_if_empty:oTF} is empty if |#1| is |?| followed % by blank spaces (besides, this particular variant of % the emptiness test is optimized). If the item of the % comma list is blank, grab the next one. As soon as one % item is non-blank, exit: the second auxiliary will grab % \cs{prg_return_false:} as |#2|, unless every item in % the comma list was blank and the loop actually got broken % by the trailing |\q_mark \prg_return_false:| item. % \begin{macrocode} \prg_new_conditional:Npnn \clist_if_empty:n #1 { p , T , F , TF } { \@@_if_empty_n:w ? #1 , \q_mark \prg_return_false: , \q_mark \prg_return_true: \q_stop } \cs_new:Npn \@@_if_empty_n:w #1 , { \tl_if_empty:oTF { \use_none:nn #1 ? } { \@@_if_empty_n:w ? } { \@@_if_empty_n:wNw } } \cs_new:Npn \@@_if_empty_n:wNw #1 \q_mark #2#3 \q_stop {#2} % \end{macrocode} % \end{macro} % \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_use:N \box_ht:N #1 } } \prop_put:Nnx \l_@@_bounding_prop { tr } { { \dim_use:N \box_wd:N #1 } { \dim_use: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_use: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_nopar: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} %<@@=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_nopar:Npn \ior_map_break: { \__prg_map_break:Nn \ior_map_break: { } } \cs_new_nopar: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_nopar:Npn \ior_map_inline:Nn { \@@_map_inline:NNn \ior_get:NN } \cs_new_protected_nopar:Npn \ior_str_map_inline:Nn { \@@_map_inline:NNn \ior_get_str:NN } \cs_new_protected_nopar: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} % % \subsection{Additions to \pkg{l3fp}} % % \begin{macrocode} %<@@=fp> % \end{macrocode} % % \begin{macro} % { % \fp_set_from_dim:Nn, \fp_set_from_dim:cn, % \fp_gset_from_dim:Nn, \fp_gset_from_dim:cn % } % Use the appropriate function from \pkg{l3fp-convert}. % \begin{macrocode} \cs_new_protected:Npn \fp_set_from_dim:Nn #1#2 { \tl_set:Nx #1 { \dim_to_fp:n {#2} } } \cs_new_protected:Npn \fp_gset_from_dim:Nn #1#2 { \tl_gset:Nx #1 { \dim_to_fp:n {#2} } } \cs_generate_variant:Nn \fp_set_from_dim:Nn { c } \cs_generate_variant:Nn \fp_gset_from_dim:Nn { c } % \end{macrocode} % \end{macro} % % \subsection{Additions to \pkg{l3prop}} % % \begin{macrocode} %<@@=prop> % \end{macrocode} % % \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_get:Nn, \prop_get:cn} % \begin{macro}[aux, EXP]{\@@_get_Nn:nwwn} % Getting the value corresponding to a key in a property list in an % expandable fashion is similar to mapping some tokens. Go through % the property list one \meta{key}--\meta{value} pair at a time: the % arguments of \cs{@@_get_Nn:nwn} are the \meta{key} we are looking % for, a \meta{key} of the property list, and its associated value. % The \meta{keys} are compared (as strings). If they match, the % \meta{value} is returned, within \cs{exp_not:n}. The loop % terminates even if the \meta{key} is missing, and yields an empty % value, because we have appended the appropriate % \meta{key}--\meta{empty value} pair to the property list. % \begin{macrocode} \cs_new:Npn \prop_get:Nn #1#2 { \exp_last_unbraced:Noo \@@_get_Nn:nwwn { \tl_to_str:n {#2} } #1 \@@_pair:wn \tl_to_str:n {#2} \s_@@ { } \__prg_break_point: } \cs_new:Npn \@@_get_Nn:nwwn #1#2 \@@_pair:wn #3 \s_@@ #4 { \str_if_eq_x:nnTF {#1} {#3} { \__prg_break:n { \exp_not:n {#4} } } { \@@_get_Nn:nwwn {#1} } } \cs_generate_variant:Nn \prop_get:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3seq}} % % \begin{macrocode} %<@@=seq> % \end{macrocode} % % \begin{macro}{\seq_item:Nn, \seq_item:cn} % \begin{macro}[aux]{\@@_item:wNn, \@@_item:nnn} % The idea here is to find the offset of the item from the left, then use % a loop to grab the correct item. If the resulting offset is too large, % then the stop code |{ ? \__prg_break: } { }| will be used by the auxiliary, % terminating the loop and returning nothing at all. % \begin{macrocode} \cs_new:Npn \seq_item:Nn #1 { \exp_after:wN \@@_item:wNn #1 \q_stop #1 } \cs_new:Npn \@@_item:wNn \s_@@ #1 \q_stop #2#3 { \exp_args:Nf \@@_item:nnn { \int_eval:n { \int_compare:nNnT {#3} < \c_zero { \seq_count:N #2 + \c_one + } #3 } } #1 { ? \__prg_break: } { } \__prg_break_point: } \cs_new:Npn \@@_item:nnn #1#2#3 { \use_none:n #2 \int_compare:nNnTF {#1} = \c_one { \__prg_break:n { \exp_not:n {#3} } } { \exp_args:Nf \@@_item:nnn { \int_eval:n { #1 - 1 } } } } \cs_generate_variant:Nn \seq_item:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \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 here 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_from_clist:NN, \seq_set_from_clist:cN, % \seq_set_from_clist:Nc, \seq_set_from_clist:cc, % \seq_set_from_clist:Nn, \seq_set_from_clist:cn % } % \begin{macro} % { % \seq_gset_from_clist:NN, \seq_gset_from_clist:cN, % \seq_gset_from_clist:Nc, \seq_gset_from_clist:cc, % \seq_gset_from_clist:Nn, \seq_gset_from_clist:cn % } % Setting a sequence from a comma-separated list is done using a simple % mapping. % \begin{macrocode} \cs_new_protected:Npn \seq_set_from_clist:NN #1#2 { \tl_set:Nx #1 { \s_@@ \clist_map_function:NN #2 \@@_wrap_item:n } } \cs_new_protected:Npn \seq_set_from_clist:Nn #1#2 { \tl_set:Nx #1 { \s_@@ \clist_map_function:nN {#2} \@@_wrap_item:n } } \cs_new_protected:Npn \seq_gset_from_clist:NN #1#2 { \tl_gset:Nx #1 { \s_@@ \clist_map_function:NN #2 \@@_wrap_item:n } } \cs_new_protected:Npn \seq_gset_from_clist:Nn #1#2 { \tl_gset:Nx #1 { \s_@@ \clist_map_function:nN {#2} \@@_wrap_item:n } } \cs_generate_variant:Nn \seq_set_from_clist:NN { Nc } \cs_generate_variant:Nn \seq_set_from_clist:NN { c , cc } \cs_generate_variant:Nn \seq_set_from_clist:Nn { c } \cs_generate_variant:Nn \seq_gset_from_clist:NN { Nc } \cs_generate_variant:Nn \seq_gset_from_clist:NN { c , cc } \cs_generate_variant:Nn \seq_gset_from_clist:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro} % {\seq_reverse:N, \seq_reverse:c, \seq_greverse:N, \seq_greverse:c} % \begin{macro}[aux]{\@@_reverse:NN} % \begin{macro}[aux, EXP]{\@@_reverse_item:nwn} % Previously, \cs{seq_reverse:N} was coded by collecting the items % in reverse order after an \cs{exp_stop_f:} marker. % \begin{verbatim} % \cs_new_protected:Npn \seq_reverse:N #1 % { % \cs_set_eq:NN \@@_item:n \@@_reverse_item:nw % \tl_set:Nf #2 { #2 \exp_stop_f: } % } % \cs_new:Npn \@@_reverse_item:nw #1 #2 \exp_stop_f: % { % #2 \exp_stop_f: % \@@_item:n {#1} % } % \end{verbatim} % At first, this seems optimal, since we can forget about each item % as soon as it is placed after \cs{exp_stop_f:}. Unfortunately, % \TeX{}'s usual tail recursion does not take place in this case: % since the following \cs{@@_reverse_item:nw} only reads % tokens until \cs{exp_stop_f:}, and never reads the % |\@@_item:n {#1}| left by the previous call, \TeX{} cannot % remove that previous call from the stack, and in particular % must retain the various macro parameters in memory, until the % end of the replacement text is reached. The stack is thus % only flushed after all the \cs{@@_reverse_item:nw} are % expanded. Keeping track of the arguments of all those calls % uses up a memory quadratic in the length of the sequence. % \TeX{} can then not cope with more than a few thousand items. % % Instead, we collect the items in the argument % of \cs{exp_not:n}. The previous calls are cleanly removed % from the stack, and the memory consumption becomes linear. % \begin{macrocode} \cs_new_protected_nopar:Npn \seq_reverse:N { \@@_reverse:NN \tl_set:Nx } \cs_new_protected_nopar:Npn \seq_greverse:N { \@@_reverse:NN \tl_gset:Nx } \cs_new_protected:Npn \@@_reverse:NN #1 #2 { \cs_set_eq:NN \@@_tmp:w \@@_item:n \cs_set_eq:NN \@@_item:n \@@_reverse_item:nwn #1 #2 { #2 \exp_not:n { } } \cs_set_eq:NN \@@_item:n \@@_tmp:w } \cs_new:Npn \@@_reverse_item:nwn #1 #2 \exp_not:n #3 { #2 \exp_not:n { \@@_item:n {#1} #3 } } \cs_generate_variant:Nn \seq_reverse:N { c } \cs_generate_variant:Nn \seq_greverse:N { c } % \end{macrocode} % \end{macro} % \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_nopar:Npn \seq_set_filter:NNn { \@@_set_filter:NNNn \tl_set:Nx } \cs_new_protected_nopar: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_nopar:Npn \seq_set_map:NNn { \@@_set_map:NNNn \tl_set:Nx } \cs_new_protected_nopar: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} % % \subsection{Additions to \pkg{l3skip}} % % \begin{macrocode} %<@@=dim> % \end{macrocode} % % \begin{macro}[EXP]{\dim_to_pt:n} % A copy of the internal function \cs{@@_strip_pt:n}, which should % perhaps be eliminated in favor of \cs{dim_to_pt:n}. % \begin{macrocode} \cs_new_eq:NN \dim_to_pt:n \@@_strip_pt:n % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\dim_to_unit:nn} % \begin{macro}[aux, EXP]{\@@_to_unit:n} % An analog of \cs{dim_ratio:nn} that produces a decimal number as its % result, rather than a rational fraction for use within dimension % expressions. The naive implementation as % \begin{verbatim} % \cs_new:Npn \dim_to_unit:nn #1#2 % { \dim_to_pt:n { 1pt * \dim_ratio:nn {#1} {#2} } } % \end{verbatim} % would not ignore trailing tokens (see documentation), so we need a % bit more work. % \begin{macrocode} \cs_new:Npn \dim_to_unit:nn #1#2 { \dim_to_pt:n { 1pt * \@@_to_unit:n { \dim_to_pt:n {#1} pt } / \@@_to_unit:n { \dim_to_pt:n {#2} pt } } } \cs_new:Npn \@@_to_unit:n #1 { \__int_value:w \@@_eval:w #1 \@@_eval_end: } % \end{macrocode} % \end{macro} % \end{macro} % % \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. Otherwise, compare with a single % space, only case where we have a single token. % \begin{macrocode} \prg_new_conditional:Npnn \tl_if_single_token:n #1 { p , T , F , TF } { \tl_if_head_is_N_type:nTF {#1} { \__str_if_eq_x_return:nn { \exp_not:o { \use_none:n #1 } } { } } { \__str_if_eq_x_return:nn { \exp_not:n {#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 { \tex_romannumeral:D \@@_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{c_zero} inserted by % \cs{@@_act_end:wn}. 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{variable}{\c_@@_act_uppercase_tl, \c_@@_act_lowercase_tl} % These constants contain the correspondence between lowercase % and uppercase letters, in the form |aAbBcC...| and |AaBbCc...| % respectively. % \begin{macrocode} \tl_const:Nn \c_@@_act_uppercase_tl { aA bB cC dD eE fF gG hH iI jJ kK lL mM nN oO pP qQ rR sS tT uU vV wW xX yY zZ } \tl_const:Nn \c_@@_act_lowercase_tl { Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz } % \end{macrocode} % \end{variable} % % \begin{macro}[EXP]{\tl_expandable_uppercase:n,\tl_expandable_lowercase:n} % \begin{macro}[EXP,aux]{\@@_act_case_normal:nN, % \@@_act_case_group:nn,\@@_act_case_space:n} % The only difference between uppercasing and lowercasing is % the table of correspondence that is used. As for other % token list actions, we feed \cs{@@_act:NNNnn} three % functions, and this time, we use the \meta{parameters} % argument to carry which case-changing we are applying. % A space is simply output. A normal token is compared % to each letter in the alphabet using \cs{str_if_eq:nn} % tests, and converted if necessary to upper/lowercase, % before being output. For a group, we must perform the % conversion within the group (the \cs{exp_after:wN} trigger % \tn{romannumeral}, which expands fully to give the % converted group), then output. % \begin{macrocode} \cs_new:Npn \tl_expandable_uppercase:n #1 { \etex_unexpanded:D \exp_after:wN { \tex_romannumeral:D \@@_act_case_aux:nn { \c_@@_act_uppercase_tl } {#1} } } \cs_new:Npn \tl_expandable_lowercase:n #1 { \etex_unexpanded:D \exp_after:wN { \tex_romannumeral:D \@@_act_case_aux:nn { \c_@@_act_lowercase_tl } {#1} } } \cs_new:Npn \@@_act_case_aux:nn { \@@_act:NNNnn \@@_act_case_normal:nN \@@_act_case_group:nn \@@_act_case_space:n } \cs_new:Npn \@@_act_case_space:n #1 { \@@_act_output:n {~} } \cs_new:Npn \@@_act_case_normal:nN #1 #2 { \exp_args:Nf \@@_act_output:n { \exp_args:NNo \str_case:nnF #2 {#1} { \exp_stop_f: #2 } } } \cs_new:Npn \@@_act_case_group:nn #1 #2 { \exp_after:wN \@@_act_output:n \exp_after:wN { \exp_after:wN { \tex_romannumeral:D \@@_act_case_aux:nn {#1} {#2} } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\tl_item:nn, \tl_item:Nn, \tl_item:cn} % \begin{macro}[aux]{\@@_item:nn} % The idea here is to find the offset of the item from the left, then use % a loop to grab the correct item. If the resulting offset is too large, % then \cs{quark_if_recursion_tail_stop:n} terminates the loop, and returns % nothing at all. % \begin{macrocode} \cs_new:Npn \tl_item:nn #1#2 { \exp_args:Nf \@@_item:nn { \int_eval:n { \int_compare:nNnT {#2} < \c_zero { \tl_count:n {#1} + \c_one + } #2 } } #1 \q_recursion_tail \__prg_break_point: } \cs_new:Npn \@@_item:nn #1#2 { \__quark_if_recursion_tail_break:nN {#2} \__prg_break: \int_compare:nNnTF {#1} = \c_one { \__prg_break:n { \exp_not:n {#2} } } { \exp_args:Nf \@@_item:nn { \int_eval:n { #1 - 1 } } } } \cs_new_nopar:Npn \tl_item:Nn { \exp_args:No \tl_item:nn } \cs_generate_variant:Nn \tl_item:Nn { c } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additions to \pkg{l3tokens}} % % \begin{macrocode} %<@@=char> % \end{macrocode} % % \begin{macro}{\char_set_active:Npn,\char_set_active:Npx} % \begin{macro}{\char_gset_active:Npn,\char_gset_active:Npx} % \begin{macro}{\char_set_active_eq:NN,\char_gset_active_eq:NN} % \begin{macrocode} \group_begin: \char_set_catcode_active:N \^^@ \cs_set:Npn \char_tmp:NN #1#2 { \cs_new:Npn #1 ##1 { \char_set_catcode_active:n { `##1 } \group_begin: \char_set_lccode:nn { `\^^@ } { `##1 } \tl_to_lowercase:n { \group_end: #2 ^^@ } } } \char_tmp:NN \char_set_active:Npn \cs_set:Npn \char_tmp:NN \char_set_active:Npx \cs_set:Npx \char_tmp:NN \char_gset_active:Npn \cs_gset:Npn \char_tmp:NN \char_gset_active:Npx \cs_gset:Npx \char_tmp:NN \char_set_active_eq:NN \cs_set_eq:NN \char_tmp:NN \char_gset_active_eq:NN \cs_gset_eq:NN \group_end: % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \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: \char_set_catcode_other:N \O \char_set_catcode_other:N \U \char_set_catcode_other:N \T \char_set_catcode_other:N \E \char_set_catcode_other:N \R \tl_to_lowercase:n { \cs_new_protected_nopar:Npn \@@_execute_branches_N_type: { \if_int_odd:w \if_catcode:w \exp_not:N \l_peek_token { \c_two \fi: \if_catcode:w \exp_not:N \l_peek_token } \c_two \fi: \if_meaning:w \l_peek_token \c_space_token \c_two \fi: \c_one \exp_after:wN \@@_N_type:w \token_to_meaning:N \l_peek_token \q_mark \@@_N_type_aux:nnw OUTER \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 OUTER #2 \q_mark #3 { #3 {#1} {#2} } } \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_nopar:Npn \peek_N_type:TF { \@@_token_generic:NNTF \@@_execute_branches_N_type: \scan_stop: } \cs_new_protected_nopar:Npn \peek_N_type:T { \@@_token_generic:NNT \@@_execute_branches_N_type: \scan_stop: } \cs_new_protected_nopar: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