% \iffalse meta-comment
%
%% File: l3backend-draw.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}
%
% \fi
%
% \title{^^A
% The \pkg{l3backend-draw} package\\ Backend drawing 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-draw} implementation}
%
% \begin{macrocode}
%<*package>
%<@@=draw>
% \end{macrocode}
%
% \subsection{\texttt{dvips} backend}
%
% \begin{macrocode}
%<*dvips>
% \end{macrocode}
%
% \begin{macro}{\@@_backend_literal:n, \@@_backend_literal:e}
% The same as literal PostScript: same arguments about positioning apply
% here.
% \begin{macrocode}
\cs_new_eq:NN \@@_backend_literal:n \__kernel_backend_literal_postscript:n
\cs_generate_variant:Nn \@@_backend_literal:n { e }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_begin:, \@@_backend_end:}
% The |ps::[begin]| special here deals with positioning but allows us to
% continue on to a matching |ps::[end]|: contrast with |ps:|, which positions
% but where we can't split material between separate calls. The
% |@beginspecial|/|@endspecial| pair are from |special.pro| and correct the
% scale and $y$-axis direction.
% As for \pkg{pgf}, we need to save the current point as this is
% required for box placement. (Note that
% |@beginspecial|/|@endspecial| forms a backend scope.)
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_begin:
{
\@@_backend_literal:n { [begin] }
\@@_backend_literal:n { /draw.x~currentpoint~/draw.y~exch~def~def }
\@@_backend_literal:n { @beginspecial }
}
\cs_new_protected:Npn \@@_backend_end:
{
\@@_backend_literal:n { @endspecial }
\@@_backend_literal:n { [end] }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_scope_begin:, \@@_backend_scope_end:}
% Scope here may need to contain saved definitions, so the entire memory
% rather than just the graphic state has to be sent to the stack.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_scope_begin:
{ \@@_backend_literal:n { save } }
\cs_new_protected:Npn \@@_backend_scope_end:
{ \@@_backend_literal:n { restore } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_moveto:nn, \@@_backend_lineto:nn}
% \begin{macro}{\@@_backend_rectangle:nnnn}
% \begin{macro}{\@@_backend_curveto:nnnnnn}
% Path creation operations mainly resolve directly to PostScript primitive
% steps, with only the need to convert to \texttt{bp}. Notice that
% \texttt{x}-type expansion is included here to ensure that any variable
% values are forced to literals before any possible caching. There is
% no native rectangular path command (without also clipping, filling or
% stroking), so that task is done using a small amount of PostScript.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_moveto:nn #1#2
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#1} ~
\dim_to_decimal_in_bp:n {#2} ~ moveto
}
}
\cs_new_protected:Npn \@@_backend_lineto:nn #1#2
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#1} ~
\dim_to_decimal_in_bp:n {#2} ~ lineto
}
}
\cs_new_protected:Npn \@@_backend_rectangle:nnnn #1#2#3#4
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#4} ~ \dim_to_decimal_in_bp:n {#3} ~
\dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~
moveto~dup~0~rlineto~exch~0~exch~rlineto~neg~0~rlineto~closepath
}
}
\cs_new_protected:Npn \@@_backend_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~
\dim_to_decimal_in_bp:n {#3} ~ \dim_to_decimal_in_bp:n {#4} ~
\dim_to_decimal_in_bp:n {#5} ~ \dim_to_decimal_in_bp:n {#6} ~
curveto
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_evenodd_rule:, \@@_backend_nonzero_rule:}
% \begin{variable}{\g_@@_draw_eor_bool}
% The even-odd rule here can be implemented as a simply switch.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_evenodd_rule:
{ \bool_gset_true:N \g_@@_draw_eor_bool }
\cs_new_protected:Npn \@@_backend_nonzero_rule:
{ \bool_gset_false:N \g_@@_draw_eor_bool }
\bool_new:N \g_@@_draw_eor_bool
% \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}
% {
% \@@_backend_closepath: ,
% \@@_backend_stroke: ,
% \@@_backend_closestroke: ,
% \@@_backend_fill: ,
% \@@_backend_fillstroke: ,
% \@@_backend_clip: ,
% \@@_backend_discardpath:
% }
% \begin{variable}{\g_@@_draw_clip_bool}
% Unlike PDF, PostScript doesn't track separate colors for strokes and other
% elements. It is also desirable to have the |clip| keyword after a stroke or
% fill. To achieve those outcomes, there is some work to do. For color, the
% stoke color is simple but the fill one has to be inserted by hand. For
% clipping, the required ordering is achieved using a \TeX{} switch. All of
% the operations end with a new path instruction as they do not terminate
% (again in contrast to PDF).
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_closepath:
{ \@@_backend_literal:n { closepath } }
\cs_new_protected:Npn \@@_backend_stroke:
{
\@@_backend_literal:n { gsave }
\@@_backend_literal:n { color.sc }
\@@_backend_literal:n { stroke }
\@@_backend_literal:n { grestore }
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_backend_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \@@_backend_closestroke:
{
\@@_backend_closepath:
\@@_backend_stroke:
}
\cs_new_protected:Npn \@@_backend_fill:
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
fill
}
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_backend_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \@@_backend_fillstroke:
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
fill
}
\@@_backend_literal:n { gsave }
\@@_backend_literal:n { color.sc }
\@@_backend_literal:n { stroke }
\@@_backend_literal:n { grestore }
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_backend_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \@@_backend_clip:
{ \bool_gset_true:N \g_@@_draw_clip_bool }
\bool_new:N \g_@@_draw_clip_bool
\cs_new_protected:Npn \@@_backend_discardpath:
{
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_backend_literal:e
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_backend_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
% \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\@@_backend_dash_pattern:nn}
% \begin{macro}{\@@_backend_dash:n}
% \begin{macro}{\@@_backend_linewidth:n}
% \begin{macro}{\@@_backend_miterlimit:n}
% \begin{macro}
% {
% \@@_backend_cap_butt:, \@@_backend_cap_round:, \@@_backend_cap_rectangle:,
% \@@_backend_join_miter:, \@@_backend_join_round:, \@@_backend_join_bevel:
% }
% Converting paths to output is again a case of mapping directly to
% PostScript operations.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_dash_pattern:nn #1#2
{
\@@_backend_literal:e
{
[
\exp_args:Nf \use:n
{ \clist_map_function:nN {#1} \@@_backend_dash:n }
] ~
\dim_to_decimal_in_bp:n {#2} ~ setdash
}
}
\cs_new:Npn \@@_backend_dash:n #1
{ ~ \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \@@_backend_linewidth:n #1
{
\@@_backend_literal:e
{ \dim_to_decimal_in_bp:n {#1} ~ setlinewidth }
}
\cs_new_protected:Npn \@@_backend_miterlimit:n #1
{ \@@_backend_literal:n { #1 ~ setmiterlimit } }
\cs_new_protected:Npn \@@_backend_cap_butt:
{ \@@_backend_literal:n { 0 ~ setlinecap } }
\cs_new_protected:Npn \@@_backend_cap_round:
{ \@@_backend_literal:n { 1 ~ setlinecap } }
\cs_new_protected:Npn \@@_backend_cap_rectangle:
{ \@@_backend_literal:n { 2 ~ setlinecap } }
\cs_new_protected:Npn \@@_backend_join_miter:
{ \@@_backend_literal:n { 0 ~ setlinejoin } }
\cs_new_protected:Npn \@@_backend_join_round:
{ \@@_backend_literal:n { 1 ~ setlinejoin } }
\cs_new_protected:Npn \@@_backend_join_bevel:
{ \@@_backend_literal:n { 2 ~ setlinejoin } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
%
% \begin{macro}{\@@_backend_cm:nnnn}
% In \texttt{dvips}, keeping the transformations in line with the engine
% is unfortunately not possible for scaling and rotations: even if we
% decompose the matrix into those operations, there is still no backend
% tracking (\emph{cf.}~\texttt{dvipdfmx}/\XeTeX{}). Thus we take the shortest
% path available and simply dump the matrix as given.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_cm:nnnn #1#2#3#4
{
\@@_backend_literal:n
{ [ #1 ~ #2 ~ #3 ~ #4 ~ 0 ~ 0 ] ~ concat }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_box_use:Nnnnn}
% Inside a picture |@beginspecial|/|@endspecial| are active, which is
% normally a good thing but means that the position and scaling would be off
% if the box was inserted directly. To deal with that, there are a number of
% possible approaches. A previous implementation suggested by Tom Rokici
% used |@endspecial|/|@beginspecial|. This avoids needing internals of
% \texttt{dvips}, but fails if there the box is used inside a scope
% (see \url{https://github.com/latex3/latex3/issues/1504}). Instead,
% we use the same method as \pkg{pgf}, which means tracking the position
% at the PostScript level. Also note that using |@endspecial| would
% close the scope it creates, meaning that after a box insertion, any
% local changes would be lost. Keeping \texttt{dvips} on track is
% non-trivial, hence the |[begin]|/|[end]| pair before the
% |save| and around the |restore|.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_box_use:Nnnnn #1#2#3#4#5
{
\@@_backend_literal:n { save }
\@@_backend_literal:n { 72~Resolution~div~72~VResolution~div~neg~scale }
\@@_backend_literal:n { magscale { 1~DVImag~div~dup~scale } if }
\@@_backend_literal:n { draw.x~neg~draw.y~neg~translate }
\@@_backend_literal:n { [end] }
\@@_backend_literal:n { [begin] }
\@@_backend_literal:n { save }
\@@_backend_literal:n { currentpoint }
\@@_backend_literal:n { currentpoint~translate }
\@@_backend_cm:nnnn { 1 } { 0 } { 0 } { -1 }
\@@_backend_cm:nnnn {#2} {#3} {#4} {#5}
\@@_backend_cm:nnnn { 1 } { 0 } { 0 } { -1 }
\@@_backend_literal:n { neg~exch~neg~exch~translate }
\@@_backend_literal:n { [end] }
\hbox_overlap_right:n { \box_use:N #1 }
\@@_backend_literal:n { [begin] }
\@@_backend_literal:n { restore }
\@@_backend_literal:n { [end] }
\@@_backend_literal:n { [begin] }
\@@_backend_literal:n { restore }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\LuaTeX{}, \pdfTeX{}, \texttt{dvipdfmx} and \XeTeX{}}
%
% \LuaTeX{}, \pdfTeX{}, \texttt{dvipdfmx} and \XeTeX{} directly produce PDF output
% and understand a shared set of specials for drawing commands.
%
% \begin{macrocode}
%<*dvipdfmx|luatex|pdftex|xetex>
% \end{macrocode}
%
% \subsubsection{Drawing}
%
% \begin{macro}{\@@_backend_literal:n, \@@_backend_literal:e}
% Pass data through using a dedicated interface.
% \begin{macrocode}
\cs_new_eq:NN \@@_backend_literal:n \__kernel_backend_literal_pdf:n
\cs_generate_variant:Nn \@@_backend_literal:n { e }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_begin:, \@@_backend_end:}
% No special requirements here, so simply set up a drawing scope.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_begin:
{ \@@_backend_scope_begin: }
\cs_new_protected:Npn \@@_backend_end:
{ \@@_backend_scope_end: }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_scope_begin:, \@@_backend_scope_end:}
% Use the backend-level scope mechanisms.
% \begin{macrocode}
\cs_new_eq:NN \@@_backend_scope_begin: \__kernel_backend_scope_begin:
\cs_new_eq:NN \@@_backend_scope_end: \__kernel_backend_scope_end:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_moveto:nn, \@@_backend_lineto:nn}
% \begin{macro}{\@@_backend_curveto:nnnnnn}
% \begin{macro}{\@@_backend_rectangle:nnnn}
% Path creation operations all resolve directly to PDF primitive steps, with
% only the need to convert to \texttt{bp}.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_moveto:nn #1#2
{
\@@_backend_literal:e
{ \dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~ m }
}
\cs_new_protected:Npn \@@_backend_lineto:nn #1#2
{
\@@_backend_literal:e
{ \dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~ l }
}
\cs_new_protected:Npn \@@_backend_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~
\dim_to_decimal_in_bp:n {#3} ~ \dim_to_decimal_in_bp:n {#4} ~
\dim_to_decimal_in_bp:n {#5} ~ \dim_to_decimal_in_bp:n {#6} ~
c
}
}
\cs_new_protected:Npn \@@_backend_rectangle:nnnn #1#2#3#4
{
\@@_backend_literal:e
{
\dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~
\dim_to_decimal_in_bp:n {#3} ~ \dim_to_decimal_in_bp:n {#4} ~
re
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_evenodd_rule:, \@@_backend_nonzero_rule:}
% \begin{variable}{\g_@@_draw_eor_bool}
% The even-odd rule here can be implemented as a simply switch.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_evenodd_rule:
{ \bool_gset_true:N \g_@@_draw_eor_bool }
\cs_new_protected:Npn \@@_backend_nonzero_rule:
{ \bool_gset_false:N \g_@@_draw_eor_bool }
\bool_new:N \g_@@_draw_eor_bool
% \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}
% {
% \@@_backend_closepath: ,
% \@@_backend_stroke: ,
% \@@_backend_closestroke: ,
% \@@_backend_fill: ,
% \@@_backend_fillstroke: ,
% \@@_backend_clip: ,
% \@@_backend_discardpath:
% }
% Converting paths to output is again a case of mapping directly to
% PDF operations.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_closepath:
{ \@@_backend_literal:n { h } }
\cs_new_protected:Npn \@@_backend_stroke:
{ \@@_backend_literal:n { S } }
\cs_new_protected:Npn \@@_backend_closestroke:
{ \@@_backend_literal:n { s } }
\cs_new_protected:Npn \@@_backend_fill:
{
\@@_backend_literal:e
{ f \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \@@_backend_fillstroke:
{
\@@_backend_literal:e
{ B \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \@@_backend_clip:
{
\@@_backend_literal:e
{ W \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \@@_backend_discardpath:
{ \@@_backend_literal:n { n } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_dash_pattern:nn}
% \begin{macro}{\@@_backend_dash:n}
% \begin{macro}{\@@_backend_linewidth:n}
% \begin{macro}{\@@_backend_miterlimit:n}
% \begin{macro}
% {
% \@@_backend_cap_butt:, \@@_backend_cap_round:, \@@_backend_cap_rectangle:,
% \@@_backend_join_miter:, \@@_backend_join_round:, \@@_backend_join_bevel:
% }
% Converting paths to output is again a case of mapping directly to
% PDF operations.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_dash_pattern:nn #1#2
{
\@@_backend_literal:e
{
[
\exp_args:Nf \use:n
{ \clist_map_function:nN {#1} \@@_backend_dash:n }
] ~
\dim_to_decimal_in_bp:n {#2} ~ d
}
}
\cs_new:Npn \@@_backend_dash:n #1
{ ~ \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \@@_backend_linewidth:n #1
{
\@@_backend_literal:e
{ \dim_to_decimal_in_bp:n {#1} ~ w }
}
\cs_new_protected:Npn \@@_backend_miterlimit:n #1
{ \@@_backend_literal:e { #1 ~ M } }
\cs_new_protected:Npn \@@_backend_cap_butt:
{ \@@_backend_literal:n { 0 ~ J } }
\cs_new_protected:Npn \@@_backend_cap_round:
{ \@@_backend_literal:n { 1 ~ J } }
\cs_new_protected:Npn \@@_backend_cap_rectangle:
{ \@@_backend_literal:n { 2 ~ J } }
\cs_new_protected:Npn \@@_backend_join_miter:
{ \@@_backend_literal:n { 0 ~ j } }
\cs_new_protected:Npn \@@_backend_join_round:
{ \@@_backend_literal:n { 1 ~ j } }
\cs_new_protected:Npn \@@_backend_join_bevel:
{ \@@_backend_literal:n { 2 ~ j } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_cm:nnnn}
% \begin{macro}{\@@_backend_cm_aux:nnnn}
% Another split here between \LuaTeX{}/pdfTeX{} and \texttt{dvipdfmx}/\XeTeX{}.
% In the former, we have a direct method to maintain alignment: the backend
% can use a matrix itself. For \texttt{dvipdfmx}/\XeTeX{}, we can to decompose the
% matrix into rotations and a scaling, then use those operations as they
% are handled by the backend. (There is backend support for matrix operations in
% \texttt{dvipdfmx}/\XeTeX{}, but as a matched pair so not suitable for the
% \enquote{stand alone} transformation set up here.) The specials used here
% are from \texttt{xdvipdfmx} originally: they are well-tested, but probably
% equivalent to the \texttt{pdf:} versions!
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_cm:nnnn #1#2#3#4
{
%<*luatex|pdftex>
\__kernel_backend_matrix:n { #1 ~ #2 ~ #3 ~ #4 }
%
%<*dvipdfmx|xetex>
\@@_backend_cm_decompose:nnnnN {#1} {#2} {#3} {#4}
\@@_backend_cm_aux:nnnn
%
}
%<*dvipdfmx|xetex>
\cs_new_protected:Npn \@@_backend_cm_aux:nnnn #1#2#3#4
{
\__kernel_backend_literal:e
{
x:rotate~
\fp_compare:nNnTF {#1} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( -#1 , 5 ) } }
}
\__kernel_backend_literal:e
{
x:scale~
\fp_eval:n { round ( #2 , 5 ) } ~
\fp_eval:n { round ( #3 , 5 ) }
}
\__kernel_backend_literal:e
{
x:rotate~
\fp_compare:nNnTF {#4} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( -#4 , 5 ) } }
}
}
%
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_cm_decompose:nnnnN}
% \begin{macro}
% {
% \@@_backend_cm_decompose_auxi:nnnnN,
% \@@_backend_cm_decompose_auxii:nnnnN,
% \@@_backend_cm_decompose_auxiii:nnnnN,
% }
% Internally, transformations for drawing are tracked as a matrix. Not all
% engines provide a way of dealing with this: if we use a raw matrix, the
% engine looses track of positions (for example for hyperlinks), and this is
% not desirable. They do, however, allow us to track rotations and scalings.
% Luckily, we can decompose any (two-dimensional) matrix into two rotations
% and a single scaling:
% \[
% \begin{bmatrix}
% A & B \\ C & D
% \end{bmatrix}
% =
% \begin{bmatrix}
% \cos\beta & \sin\beta \\ -\sin\beta & \cos\beta
% \end{bmatrix}
% \begin{bmatrix}
% w_{1} & 0 \\ 0 & w_{2}
% \end{bmatrix}
% \begin{bmatrix}
% \cos\gamma & \sin\gamma \\ -\sin\gamma & \cos\gamma
% \end{bmatrix}
% \]
% The parent matrix can be converted to
% \[
% \begin{bmatrix}
% A & B \\ C & D
% \end{bmatrix}
% =
% \begin{bmatrix}
% E & H \\-H & E
% \end{bmatrix}
% +
% \begin{bmatrix}
% F & G \\ G & -F
% \end{bmatrix}
% \]
% From these, we can find that
% \begin{align*}
% \frac{w_{1} + w_{2}}{2} &= \sqrt{E^{2} + H^{2}} \\
% \frac{w_{1} - w_{2}}{2} &= \sqrt{F^{2} + G^{2}} \\
% \gamma - \beta &= \tan^{-1}(G/F) \\
% \gamma + \beta &= \tan^{-1}(H/E)
% \end{align*}
% at which point we just have to do various pieces of re-arrangement to
% get all of the values. (See J.~Blinn, \emph{IEEE Comput.\ Graph.\ Appl.},
% 1996, \textbf{16}, 82--88.) There is one wrinkle: the PostScript (and PDF)
% way of specifying a transformation matrix exchanges where one would
% normally expect $B$ and $C$ to be.
% \begin{macrocode}
%<*dvipdfmx|xetex>
\cs_new_protected:Npn \@@_backend_cm_decompose:nnnnN #1#2#3#4#5
{
\use:e
{
\@@_backend_cm_decompose_auxi:nnnnN
{ \fp_eval:n { (#1 + #4) / 2 } }
{ \fp_eval:n { (#1 - #4) / 2 } }
{ \fp_eval:n { (#3 + #2) / 2 } }
{ \fp_eval:n { (#3 - #2) / 2 } }
}
#5
}
\cs_new_protected:Npn \@@_backend_cm_decompose_auxi:nnnnN #1#2#3#4#5
{
\use:e
{
\@@_backend_cm_decompose_auxii:nnnnN
{ \fp_eval:n { 2 * sqrt ( #1 * #1 + #4 * #4 ) } }
{ \fp_eval:n { 2 * sqrt ( #2 * #2 + #3 * #3 ) } }
{ \fp_eval:n { atand ( #3 , #2 ) } }
{ \fp_eval:n { atand ( #4 , #1 ) } }
}
#5
}
\cs_new_protected:Npn \@@_backend_cm_decompose_auxii:nnnnN #1#2#3#4#5
{
\use:e
{
\@@_backend_cm_decompose_auxiii:nnnnN
{ \fp_eval:n { ( #4 - #3 ) / 2 } }
{ \fp_eval:n { ( #1 + #2 ) / 2 } }
{ \fp_eval:n { ( #1 - #2 ) / 2 } }
{ \fp_eval:n { ( #4 + #3 ) / 2 } }
}
#5
}
\cs_new_protected:Npn \@@_backend_cm_decompose_auxiii:nnnnN #1#2#3#4#5
{
\fp_compare:nNnTF { abs( #2 ) } > { abs ( #3 ) }
{ #5 {#1} {#2} {#3} {#4} }
{ #5 {#1} {#3} {#2} {#4} }
}
%
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_box_use:Nnnnn}
% Inserting a \TeX{} box transformed to the requested position and using
% the current matrix is done using a mixture of \TeX{} and low-level
% manipulation. The offset can be handled by \TeX{}, so only any rotation/^^A
% skew/scaling component needs to be done using the matrix operation. As this
% operation can never be cached, the scope is set directly not using the
% \texttt{draw} version.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_box_use:Nnnnn #1#2#3#4#5
{
\__kernel_backend_scope_begin:
%<*luatex|pdftex>
\@@_backend_cm:nnnn {#2} {#3} {#4} {#5}
%
%<*dvipdfmx|xetex>
\__kernel_backend_literal:n
{ pdf:btrans~matrix~ #2 ~ #3 ~ #4 ~ #5 ~ 0 ~ 0 }
%
\hbox_overlap_right:n { \box_use:N #1 }
%<*dvipdfmx|xetex>
\__kernel_backend_literal:n { pdf:etrans }
%
\__kernel_backend_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{dvisvgm} backend}
%
% \begin{macrocode}
%<*dvisvgm>
% \end{macrocode}
%
% \begin{macro}{\@@_backend_literal:n, \@@_backend_literal:e}
% The same as the more general literal call.
% \begin{macrocode}
\cs_new_eq:NN \@@_backend_literal:n \__kernel_backend_literal_svg:n
\cs_generate_variant:Nn \@@_backend_literal:n { e }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_scope_begin:, \@@_backend_scope_end:}
% Use the backend-level scope mechanisms.
% \begin{macrocode}
\cs_new_eq:NN \@@_backend_scope_begin: \__kernel_backend_scope_begin:
\cs_new_eq:NN \@@_backend_scope_end: \__kernel_backend_scope_end:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_begin:, \@@_backend_end:}
% A drawing needs to be set up such that the coordinate system is
% translated. That is done inside a scope, which as described below
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_begin:
{
\__kernel_backend_scope_begin:
\__kernel_backend_scope:n { transform="translate({?x},{?y})~scale(1,-1)" }
}
\cs_new_eq:NN \@@_backend_end: \__kernel_backend_scope_end:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_moveto:nn, \@@_backend_lineto:nn}
% \begin{macro}{\@@_backend_rectangle:nnnn}
% \begin{macro}{\@@_backend_curveto:nnnnnn}
% \begin{macro}{\@@_backend_add_to_path:n}
% \begin{variable}{\g_@@_backend_path_tl}
% Once again, some work is needed to get path constructs correct. Rather
% then write the values as they are given, the entire path needs to be
% collected up before being output in one go. For that we use a dedicated
% storage routine, which adds spaces as required. Since paths should
% be fully expanded there is no need to worry about the internal
% \texttt{x}-type expansion.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_moveto:nn #1#2
{
\@@_backend_add_to_path:n
{ M ~ \dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2} }
}
\cs_new_protected:Npn \@@_backend_lineto:nn #1#2
{
\@@_backend_add_to_path:n
{ L ~ \dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2} }
}
\cs_new_protected:Npn \@@_backend_rectangle:nnnn #1#2#3#4
{
\@@_backend_add_to_path:n
{
M ~ \dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2}
h ~ \dim_to_decimal:n {#3} ~
v ~ \dim_to_decimal:n {#4} ~
h ~ \dim_to_decimal:n { -#3 } ~
Z
}
}
\cs_new_protected:Npn \@@_backend_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_backend_add_to_path:n
{
C ~
\dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2} ~
\dim_to_decimal:n {#3} ~ \dim_to_decimal:n {#4} ~
\dim_to_decimal:n {#5} ~ \dim_to_decimal:n {#6}
}
}
\cs_new_protected:Npn \@@_backend_add_to_path:n #1
{
\tl_gset:Ne \g_@@_backend_path_tl
{
\g_@@_backend_path_tl
\tl_if_empty:NF \g_@@_backend_path_tl { \c_space_tl }
#1
}
}
\tl_new:N \g_@@_backend_path_tl
% \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_evenodd_rule:, \@@_backend_nonzero_rule:}
% The fill rules here have to be handled as scopes.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_evenodd_rule:
{ \__kernel_backend_scope:n { fill-rule="evenodd" } }
\cs_new_protected:Npn \@@_backend_nonzero_rule:
{ \__kernel_backend_scope:n { fill-rule="nonzero" } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_path:n}
% \begin{macro}
% {
% \@@_backend_closepath: ,
% \@@_backend_stroke: ,
% \@@_backend_closestroke: ,
% \@@_backend_fill: ,
% \@@_backend_fillstroke: ,
% \@@_backend_clip: ,
% \@@_backend_discardpath:
% }
% \begin{variable}{\g_@@_draw_clip_bool}
% \begin{variable}{\g_@@_draw_path_int}
% Setting fill and stroke effects and doing clipping all has to be done using
% scopes. This means setting up the various requirements in a shared
% auxiliary which deals with the bits and pieces. Clipping paths are reused
% for path drawing: not essential but avoids constructing them twice.
% Discarding a path needs a separate function as it's not quite the same.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_closepath:
{ \@@_backend_add_to_path:n { Z } }
\cs_new_protected:Npn \@@_backend_path:n #1
{
\bool_if:NTF \g_@@_draw_clip_bool
{
\int_gincr:N \g__kernel_clip_path_int
\@@_backend_literal:e
{
< clipPath~id = " l3cp \int_use:N \g__kernel_clip_path_int " >
{ ?nl }
{ ?nl }
< /clipPath > { ? nl }
<
use~xlink:href =
"\c_hash_str l3path \int_use:N \g_@@_backend_path_int " ~
#1
/>
}
\__kernel_backend_scope:e
{
clip-path =
"url( \c_hash_str l3cp \int_use:N \g__kernel_clip_path_int)"
}
}
{
\@@_backend_literal:e
{ }
}
\tl_gclear:N \g_@@_backend_path_tl
\bool_gset_false:N \g_@@_draw_clip_bool
}
\int_new:N \g_@@_backend_path_int
\cs_new_protected:Npn \@@_backend_stroke:
{ \@@_backend_path:n { style="fill:none" } }
\cs_new_protected:Npn \@@_backend_closestroke:
{
\@@_backend_closepath:
\@@_backend_stroke:
}
\cs_new_protected:Npn \@@_backend_fill:
{ \@@_backend_path:n { style="stroke:none" } }
\cs_new_protected:Npn \@@_backend_fillstroke:
{ \@@_backend_path:n { } }
\cs_new_protected:Npn \@@_backend_clip:
{ \bool_gset_true:N \g_@@_draw_clip_bool }
\bool_new:N \g_@@_draw_clip_bool
\cs_new_protected:Npn \@@_backend_discardpath:
{
\bool_if:NT \g_@@_draw_clip_bool
{
\int_gincr:N \g__kernel_clip_path_int
\@@_backend_literal:e
{
< clipPath~id = " l3cp \int_use:N \g__kernel_clip_path_int " >
{ ?nl }
{ ?nl }
< /clipPath >
}
\__kernel_backend_scope:e
{
clip-path =
"url( \c_hash_str l3cp \int_use:N \g__kernel_clip_path_int)"
}
}
\tl_gclear:N \g_@@_backend_path_tl
\bool_gset_false:N \g_@@_draw_clip_bool
}
% \end{macrocode}
% \end{variable}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_dash_pattern:nn}
% \begin{macro}{\@@_backend_dash:n}
% \begin{macro}{\@@_backend_dash_aux:nn}
% \begin{macro}{\@@_backend_linewidth:n}
% \begin{macro}{\@@_backend_miterlimit:n}
% \begin{macro}
% {
% \@@_backend_cap_butt:, \@@_backend_cap_round:, \@@_backend_cap_rectangle:,
% \@@_backend_join_miter:, \@@_backend_join_round:, \@@_backend_join_bevel:
% }
% All of these ideas are properties of scopes in SVG. The only slight
% complexity is converting the dash array properly (doing any required
% maths).
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_dash_pattern:nn #1#2
{
\use:e
{
\@@_backend_dash_aux:nn
{ \clist_map_function:nN {#1} \@@_backend_dash:n }
{ \dim_to_decimal:n {#2} }
}
}
\cs_new:Npn \@@_backend_dash:n #1
{ , \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \@@_backend_dash_aux:nn #1#2
{
\__kernel_backend_scope:e
{
stroke-dasharray =
"
\tl_if_empty:nTF {#1}
{ none }
{ \use_none:n #1 }
" ~
stroke-offset=" #2 "
}
}
\cs_new_protected:Npn \@@_backend_linewidth:n #1
{ \__kernel_backend_scope:e { stroke-width=" \dim_to_decimal:n {#1} " } }
\cs_new_protected:Npn \@@_backend_miterlimit:n #1
{ \__kernel_backend_scope:e { stroke-miterlimit=" #1 " } }
\cs_new_protected:Npn \@@_backend_cap_butt:
{ \__kernel_backend_scope:n { stroke-linecap="butt" } }
\cs_new_protected:Npn \@@_backend_cap_round:
{ \__kernel_backend_scope:n { stroke-linecap="round" } }
\cs_new_protected:Npn \@@_backend_cap_rectangle:
{ \__kernel_backend_scope:n { stroke-linecap="square" } }
\cs_new_protected:Npn \@@_backend_join_miter:
{ \__kernel_backend_scope:n { stroke-linejoin="miter" } }
\cs_new_protected:Npn \@@_backend_join_round:
{ \__kernel_backend_scope:n { stroke-linejoin="round" } }
\cs_new_protected:Npn \@@_backend_join_bevel:
{ \__kernel_backend_scope:n { stroke-linejoin="bevel" } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_backend_cm:nnnn}
% The four arguments here are floats (the affine matrix), the last
% two are a displacement vector.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_cm:nnnn #1#2#3#4
{
\__kernel_backend_scope:n
{
transform =
" matrix ( #1 , #2 , #3 , #4 , 0pt , 0pt ) "
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_backend_box_use:Nnnnn}
% No special savings can be made here: simply displace the box inside
% a scope. As there is nothing to re-box, just make the box passed of
% zero size.
% \begin{macrocode}
\cs_new_protected:Npn \@@_backend_box_use:Nnnnn #1#2#3#4#5
{
\__kernel_backend_scope_begin:
\@@_backend_cm:nnnn {#2} {#3} {#4} {#5}
\__kernel_backend_literal_svg:n
{
< g~
stroke="none"~
transform="scale(-1,1)~translate({?x},{?y})~scale(-1,-1)"
>
}
\box_set_wd:Nn #1 { 0pt }
\box_set_ht:Nn #1 { 0pt }
\box_set_dp:Nn #1 { 0pt }
\box_use:N #1
\__kernel_backend_literal_svg:n { }
\__kernel_backend_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex