% \iffalse meta-comment % %% File: l3backend-box.dtx % % Copyright (C) 2019-2024 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3backend bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} %</driver> % \fi % % \title{^^A % The \pkg{l3backend-box} package\\ Backend box support^^A % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2024-05-08} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3backend-box} implementation} % % \begin{macrocode} %<*package> %<@@=box> % \end{macrocode} % % \subsection{\texttt{dvips} backend} % % \begin{macrocode} %<*dvips> % \end{macrocode} % % \begin{macro}{\@@_backend_clip:N} % The \texttt{dvips} backend scales all absolute dimensions based on the % output resolution selected and any \TeX{} magnification. Thus for any % operation involving absolute lengths there is a correction to make. See % \texttt{normalscale} from \texttt{special.pro} for the variables, noting % that here everything is saved on the stack rather than as a separate % variable. Once all of that is done, the actual clipping is trivial. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_clip:N #1 { \__kernel_backend_scope_begin: \__kernel_backend_align_begin: \__kernel_backend_literal_postscript:n { matrix~currentmatrix } \__kernel_backend_literal_postscript:n { Resolution~72~div~VResolution~72~div~scale } \__kernel_backend_literal_postscript:n { DVImag~dup~scale } \__kernel_backend_literal_postscript:e { 0 ~ \dim_to_decimal_in_bp:n { \box_dp:N #1 } ~ \dim_to_decimal_in_bp:n { \box_wd:N #1 } ~ \dim_to_decimal_in_bp:n { -\box_ht:N #1 - \box_dp:N #1 } ~ rectclip } \__kernel_backend_literal_postscript:n { setmatrix } \__kernel_backend_align_end: \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: \skip_horizontal:n { \box_wd:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_rotate:Nn} % \begin{macro}{\@@_backend_rotate_aux:Nn} % Rotating using \texttt{dvips} does not require that the box dimensions % are altered and has a very convenient built-in operation. Zero rotation % must be written as |0| not |-0| so there is a quick test. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_rotate:Nn #1#2 { \exp_args:NNf \@@_backend_rotate_aux:Nn #1 { \fp_eval:n {#2} } } \cs_new_protected:Npn \@@_backend_rotate_aux:Nn #1#2 { \__kernel_backend_scope_begin: \__kernel_backend_align_begin: \__kernel_backend_literal_postscript:e { \fp_compare:nNnTF {#2} = \c_zero_fp { 0 } { \fp_eval:n { round ( -(#2) , 5 ) } } ~ rotate } \__kernel_backend_align_end: \box_use:N #1 \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_backend_scale:Nnn} % The \texttt{dvips} backend once again has a dedicated operation we can % use here. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_scale:Nnn #1#2#3 { \__kernel_backend_scope_begin: \__kernel_backend_align_begin: \__kernel_backend_literal_postscript:e { \fp_eval:n { round ( #2 , 5 ) } ~ \fp_eval:n { round ( #3 , 5 ) } ~ scale } \__kernel_backend_align_end: \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % % \begin{macrocode} %</dvips> % \end{macrocode} % % \subsection{\LuaTeX{} and \pdfTeX{} backends} % % \begin{macrocode} %<*luatex|pdftex> % \end{macrocode} % % \begin{macro}{\@@_backend_clip:N} % The general method is to save the current location, define a clipping path % equivalent to the bounding box, then insert the content at the current % position and in a zero width box. The \enquote{real} width is then made up % using a horizontal skip before tidying up. There are other approaches that % can be taken (for example using XForm objects), but the logic here shares % as much code as possible and uses the same conversions (and so same % rounding errors) in all cases. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_clip:N #1 { \__kernel_backend_scope_begin: \__kernel_backend_literal_pdf:e { 0~ \dim_to_decimal_in_bp:n { -\box_dp:N #1 } ~ \dim_to_decimal_in_bp:n { \box_wd:N #1 } ~ \dim_to_decimal_in_bp:n { \box_ht:N #1 + \box_dp:N #1 } ~ re~W~n } \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: \skip_horizontal:n { \box_wd:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_rotate:Nn} % \begin{macro}{\@@_backend_rotate_aux:Nn} % \begin{variable}{\l_@@_backend_cos_fp, \l_@@_backend_sin_fp} % Rotations are set using an affine transformation matrix which therefore % requires sine/cosine values not the angle itself. We store the rounded % values to avoid rounding twice. There are also a couple of comparisons to % ensure that |-0| is not written to the output, as this avoids any issues % with problematic display programs. Note that numbers are compared to~$0$ % after rounding. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_rotate:Nn #1#2 { \exp_args:NNf \@@_backend_rotate_aux:Nn #1 { \fp_eval:n {#2} } } \cs_new_protected:Npn \@@_backend_rotate_aux:Nn #1#2 { \__kernel_backend_scope_begin: \box_set_wd:Nn #1 { 0pt } \fp_set:Nn \l_@@_backend_cos_fp { round ( cosd ( #2 ) , 5 ) } \fp_compare:nNnT \l_@@_backend_cos_fp = \c_zero_fp { \fp_zero:N \l_@@_backend_cos_fp } \fp_set:Nn \l_@@_backend_sin_fp { round ( sind ( #2 ) , 5 ) } \__kernel_backend_matrix:e { \fp_use:N \l_@@_backend_cos_fp \c_space_tl \fp_compare:nNnTF \l_@@_backend_sin_fp = \c_zero_fp { 0~0 } { \fp_use:N \l_@@_backend_sin_fp \c_space_tl \fp_eval:n { -\l_@@_backend_sin_fp } } \c_space_tl \fp_use:N \l_@@_backend_cos_fp } \box_use:N #1 \__kernel_backend_scope_end: } \fp_new:N \l_@@_backend_cos_fp \fp_new:N \l_@@_backend_sin_fp % \end{macrocode} % \end{variable} % \end{macro} % \end{macro} % % \begin{macro}{\@@_backend_scale:Nnn} % The same idea as for rotation but without the complexity of signs and % cosines. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_scale:Nnn #1#2#3 { \__kernel_backend_scope_begin: \__kernel_backend_matrix:e { \fp_eval:n { round ( #2 , 5 ) } ~ 0~0~ \fp_eval:n { round ( #3 , 5 ) } } \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % % \begin{macrocode} %</luatex|pdftex> % \end{macrocode} % % \subsection{\texttt{dvipdfmx}/\XeTeX{} backend} % % \begin{macrocode} %<*dvipdfmx|xetex> % \end{macrocode} % % \begin{macro}{\@@_backend_clip:N} % The code here is identical to that for \LuaTeX{}/\pdfTeX{}: unlike rotation and % scaling, there is no higher-level support in the backend for clipping. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_clip:N #1 { \__kernel_backend_scope_begin: \__kernel_backend_literal_pdf:e { 0~ \dim_to_decimal_in_bp:n { -\box_dp:N #1 } ~ \dim_to_decimal_in_bp:n { \box_wd:N #1 } ~ \dim_to_decimal_in_bp:n { \box_ht:N #1 + \box_dp:N #1 } ~ re~W~n } \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: \skip_horizontal:n { \box_wd:N #1 } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_rotate:Nn} % \begin{macro}{\@@_backend_rotate_aux:Nn} % Rotating in \texttt{dvipdmfx}/\XeTeX{} can be implemented using either PDF or % backend-specific code. The former approach however is not \enquote{aware} % of the content of boxes: this means that any embedded links would not be % adjusted by the rotation. As such, the backend-native approach is preferred: % the code therefore is similar (though not identical) to the \texttt{dvips} % version (notice the rotation angle here is positive). As for % \texttt{dvips}, zero rotation is written as |0| not |-0|. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_rotate:Nn #1#2 { \exp_args:NNf \@@_backend_rotate_aux:Nn #1 { \fp_eval:n {#2} } } \cs_new_protected:Npn \@@_backend_rotate_aux:Nn #1#2 { \__kernel_backend_scope_begin: \__kernel_backend_literal:e { x:rotate~ \fp_compare:nNnTF {#2} = \c_zero_fp { 0 } { \fp_eval:n { round ( #2 , 5 ) } } } \box_use:N #1 \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_backend_scale:Nnn} % Much the same idea for scaling: use the higher-level backend operation to allow % for box content. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_scale:Nnn #1#2#3 { \__kernel_backend_scope_begin: \__kernel_backend_literal:e { x:scale~ \fp_eval:n { round ( #2 , 5 ) } ~ \fp_eval:n { round ( #3 , 5 ) } } \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % % \begin{macrocode} %</dvipdfmx|xetex> % \end{macrocode} % % \subsection{\texttt{dvisvgm} backend} % % \begin{macrocode} %<*dvisvgm> % \end{macrocode} % % \begin{macro}{\@@_backend_clip:N} % \begin{variable}{\g__kernel_clip_path_int} % Clipping in SVG is more involved than with other backends. The first issue % is that the clipping path must be defined separately from where it is used, % so we need to track how many paths have applied. The naming here uses % \texttt{l3cp} as the namespace with a number following. Rather than use % a rectangular operation, we define the path manually as this allows it to % have a depth: easier than the alternative approach of shifting content % up and down using scopes to allow for the depth of the \TeX{} box and % keep the reference point the same! % \begin{macrocode} \cs_new_protected:Npn \@@_backend_clip:N #1 { \int_gincr:N \g__kernel_clip_path_int \__kernel_backend_literal_svg:e { < clipPath~id = " l3cp \int_use:N \g__kernel_clip_path_int " > } \__kernel_backend_literal_svg:e { < path ~ d = " M ~ 0 ~ \dim_to_decimal:n { -\box_dp:N #1 } ~ L ~ \dim_to_decimal:n { \box_wd:N #1 } ~ \dim_to_decimal:n { -\box_dp:N #1 } ~ L ~ \dim_to_decimal:n { \box_wd:N #1 } ~ \dim_to_decimal:n { \box_ht:N #1 + \box_dp:N #1 } ~ L ~ 0 ~ \dim_to_decimal:n { \box_ht:N #1 + \box_dp:N #1 } ~ Z " /> } \__kernel_backend_literal_svg:n { < /clipPath > } % \end{macrocode} % In general the SVG set up does not try to transform coordinates to the % current point. For clipping we need to do that, so have a transformation % here to get us to the right place, and a matching one just before the % \TeX{} box is inserted to get things back on track. The clip path needs to % come between those two such that if lines up with the current point, as % does the \TeX{} box. % \begin{macrocode} \__kernel_backend_scope_begin:n { transform = " translate ( { ?x } , { ?y } ) ~ scale ( 1 , -1 ) " } \__kernel_backend_scope:e { clip-path = "url ( \c_hash_str l3cp \int_use:N \g__kernel_clip_path_int ) " } \__kernel_backend_scope:n { transform = " scale ( -1 , 1 ) ~ translate ( { ?x } , { ?y } ) ~ scale ( -1 , -1 ) " } \box_use:N #1 \__kernel_backend_scope_end: } \int_new:N \g__kernel_clip_path_int % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\@@_backend_rotate:Nn} % Rotation has a dedicated operation which includes a centre-of-rotation % optional pair. That can be picked up from the backend syntax, so there is % no need to worry about the transformation matrix. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_rotate:Nn #1#2 { \__kernel_backend_scope_begin:e { transform = " rotate ( \fp_eval:n { round ( -(#2) , 5 ) } , ~ { ?x } , ~ { ?y } ) " } \box_use:N #1 \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_scale:Nnn} % In contrast to rotation, we have to account for the current position in this % case. That is done using a couple of translations in addition to the scaling % (which is therefore done backward with a flip). % \begin{macrocode} \cs_new_protected:Npn \@@_backend_scale:Nnn #1#2#3 { \__kernel_backend_scope_begin:e { transform = " translate ( { ?x } , { ?y } ) ~ scale ( \fp_eval:n { round ( -#2 , 5 ) } , \fp_eval:n { round ( -#3 , 5 ) } ) ~ translate ( { ?x } , { ?y } ) ~ scale ( -1 ) " } \hbox_overlap_right:n { \box_use:N #1 } \__kernel_backend_scope_end: } % \end{macrocode} % \end{macro} % % \begin{macrocode} %</dvisvgm> % \end{macrocode} % % \begin{macrocode} %</package> % \end{macrocode} % % \end{implementation} % % \PrintIndex