summaryrefslogtreecommitdiff
path: root/macros/latex/required/l3backend/l3backend-box.dtx
diff options
context:
space:
mode:
Diffstat (limited to 'macros/latex/required/l3backend/l3backend-box.dtx')
-rw-r--r--macros/latex/required/l3backend/l3backend-box.dtx470
1 files changed, 470 insertions, 0 deletions
diff --git a/macros/latex/required/l3backend/l3backend-box.dtx b/macros/latex/required/l3backend/l3backend-box.dtx
new file mode 100644
index 0000000000..93d83293ef
--- /dev/null
+++ b/macros/latex/required/l3backend/l3backend-box.dtx
@@ -0,0 +1,470 @@
+% \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-04-11}
+%
+% \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