% \iffalse meta-comment
%
%% File: l3drivers.dtx Copyright(C) 2011-2018 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
%
% https://www.latex-project.org/lppl.txt
%
% This file is part of the "l3kernel bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
% https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
\DocInput{\jobname.dtx}
\end{document}
%
% \fi
%
% \title{^^A
% The \textsf{l3drivers} package\\ Drivers^^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 2018-10-17}
%
% \maketitle
%
% \begin{documentation}
%
% \TeX{} relies on drivers in order to carry out a number of tasks, such
% as using color, including graphics and setting up hyper-links. The nature
% of the code required depends on the exact driver in use. Currently,
% \LaTeX3 is aware of the following drivers:
% \begin{itemize}
% \item \texttt{pdfmode}: The \enquote{driver} for direct PDF output by
% \emph{both} \pdfTeX{} and \LuaTeX{} (no separate driver is used in this
% case: the engine deals with PDF creation itself).
% \item \texttt{dvips}: The \texttt{dvips} program, which works in
% conjugation with \pdfTeX{} or \LuaTeX{} in DVI mode.
% \item \texttt{dvipdfmx}: The \texttt{dvipdfmx} program, which works in
% conjugation with \pdfTeX{} or \LuaTeX{} in DVI mode.
% \item \texttt{dvisvgm}: The \texttt{dvisvgm} program, which works in
% conjugation with \pdfTeX{} or \LuaTeX{} when run in DVI mode as well
% as with (u)\pTeX{} and \XeTeX{}.
% \item \texttt{xdvipdfmx}: The driver used by \XeTeX{}.
% \end{itemize}
%
% This module provides code closely tied to the exact driver in use: broadly,
% the functions here are implemented entirely independently for each case.
% As such, they often rely on higher-level code to provide necessary but
% shared operations. For example, in box rotation and scaling the functions
% here do no correct the final size of the box: this will always be required
% and thus is handled in the \pkg{box} module.
%
% Several of the operations here are low-level, and so may be used only in
% restricted contexts. Some also require understanding of PostScript/PDF
% concepts to be used corrected as they take \enquote{raw} arguments, similar
% in format to those used by the underlying driver.
%
% The functions in this module should be regarded as experimental with
% the following exceptions:
% \begin{itemize}
% \item \dots
% \end{itemize}
%
% \section{Box clipping}
%
% \begin{function}[added = 2017-12-13]{\driver_box_use_clip:N}
% \begin{syntax}
% \cs{driver_box_use_clip:N} \meta{box}
% \end{syntax}
% Inserts the content of the \meta{box} at the current insertion point
% such that any material outside of the bounding box is not displayed
% by the driver. The material in the \meta{box} is still placed in the
% output stream: the clipping takes place at a driver level.
% \end{function}
%
% \section{Box rotation and scaling}
%
% \begin{function}[added = 2017-12-13, updated = 2018-04-26]
% {\driver_box_use_rotate:Nn}
% \begin{syntax}
% \cs{driver_box_use_rotate:Nn} \meta{box} \Arg{angle}
% \end{syntax}
% Inserts the content of the \meta{box} at the current insertion point
% rotated by the \meta{angle} (an \meta{fp expression} expressed in degrees).
% The material is
% rotated such the the \TeX{} reference point of the box is the center of
% rotation and remains the reference point after rotation. It is the
% responsibility of the code using this function to adjust the apparent
% size of the inserted material.
% \end{function}
%
% \begin{function}[added = 2017-12-13, updated = 2018-04-26]
% {\driver_box_use_scale:Nnn}
% \begin{syntax}
% \cs{driver_box_use_scale:Nnn} \meta{box} \Arg{x-scale} \Arg{y-scale}
% \end{syntax}
% Inserts the content of the \meta{box} at the current insertion point
% scale by the \meta{x-scale} and \meta{y-scale} (both \meta{fp expressions}).
% The reference point
% of the material will be unchanged. It is the responsibility of the
% code using this function to adjust the apparent size of the inserted
% material.
% \end{function}
%
% \section{Color support}
%
% \begin{function}[added = 2018-02-20]{\driver_color_cmyk:nnnn}
% \begin{syntax}
% \cs{driver_color_cmyk:nnnn} \Arg{cyan} \Arg{magenta} \Arg{yellow}
% \Arg{black}
% \end{syntax}
% Sets the color to the CMYK values specified, all of which are
% fp denotations in the range $0$ and $1$. For drawing colors, see
% \cs{driver_draw_stroke_cmyk:nnnn}, \emph{etc.}
% \end{function}
%
% \begin{function}[added = 2018-02-20]{\driver_color_gray:n}
% \begin{syntax}
% \cs{driver_color_gray:n} \Arg{gray}
% \end{syntax}
% Sets the color to the grayscale value specified, which is
% fp denotations in the range $0$ and $1$. For drawing colors, see
% \cs{driver_draw_stroke_gray:n}, \emph{etc.}
% \end{function}
%
% \begin{function}[added = 2018-02-20]{\driver_color_rgb:nnn}
% \begin{syntax}
% \cs{driver_color_rgb:nnn} \Arg{red} \Arg{green} \Arg{blue}
% \end{syntax}
% Sets the color to the RGB values specified, all of which are
% fp denotations in the range $0$ and $1$. For drawing colors, see
% \cs{driver_draw_stroke_rgb:nnn}, \emph{etc.}
% \end{function}
%
% \begin{function}[added = 2018-02-20]{\driver_color_pickup:N}
% \begin{syntax}
% \cs{driver_color_pickup:N} \meta{tl}
% \end{syntax}
% In \LaTeXe{} package mode, collects data on the current color from
% \tn{current@color} and stores it in the low-level format used by \pkg{expl3}
% in the \meta{tl}.
% \end{function}
%
% \section{Drawing}
%
% The drawing functions provided here are \emph{highly} experimental. They
% are inspired heavily by the system layer of \pkg{pgf} (most have the
% same interface as the same functions in the latter's \cs{pgfsys@\ldots}
% namespace). They are intended to form the basis for higher level drawing
% interfaces, which themselves are likely to be further abstracted for user
% access. Again, this model is heavily inspired by \pkg{pgf} and
% Ti\textit{k}z.
%
% These low level drawing interfaces abstract from the driver raw requirements
% but still require an appreciation of the concepts of PostScript/PDF/SVG
% graphic creation.
%
% \begin{function}
% {\driver_draw_begin:, \driver_draw_end:}
% \begin{syntax}
% \cs{driver_draw_begin:}
% \meta{content}
% \cs{driver_draw_end:}
% \end{syntax}
% Defines a drawing environment. This is a scope for the purposes of
% the graphics state. Depending on the driver, other set up may or may not
% take place here. The natural size of the \meta{content} should be zero
% from the \TeX{} perspective: allowance for the size of the content must
% be made at a higher level (or indeed this can be skipped if the content is
% to overlap other material).
% \end{function}
%
% \begin{function}
% {\driver_draw_scope_begin:, \driver_draw_scope_end:}
% \begin{syntax}
% \cs{driver_draw_scope_begin:}
% \meta{content}
% \cs{driver_draw_scope_end:}
% \end{syntax}
% Defines a scope for drawing settings and so on. Changes to the graphic
% state and concepts such as color or linewidth are localised to a scope.
% This function pair must never be used if an partial path is under
% construction: such paths must be entirely contained at one unbroken
% scope level. Note that scopes do not form \TeX{} groups and may not
% be aligned with them.
% \end{function}
%
% \subsection{Path construction}
%
% \begin{function}{\driver_draw_moveto:nn}
% \begin{syntax}
% \cs{driver_draw_move:nn} \Arg{x} \Arg{y}
% \end{syntax}
% Moves the current drawing reference point to (\meta{x}, \meta{y});
% any active transformation matrix applies.
% \end{function}
%
% \begin{function}{\driver_draw_lineto:nn}
% \begin{syntax}
% \cs{driver_draw_lineto:nn} \Arg{x} \Arg{y}
% \end{syntax}
% Adds a path from the current drawing reference point to
% (\meta{x}, \meta{y}); any active transformation matrix applies. Note
% that nothing is drawn until a fill or stroke operation is applied, and that
% the path may be discarded or used as a clip without appearing itself.
% \end{function}
%
% \begin{function}{\driver_draw_curveto:nnnnnn}
% \begin{syntax}
% \cs{driver_draw_curveto:nnnnnn} \Arg{x_1} \Arg{y_1}
% \Arg{x_2} \Arg{y_2} \Arg{x_3} \Arg{y_3}
% \end{syntax}
% Adds a Bezier curve path from the current drawing reference point to
% (\meta{x_3}, \meta{y_3}), using (\meta{x_1}, \meta{y_1}) and
% (\meta{x_2}, \meta{y_2}) as control points; any active transformation
% matrix applies. Note that nothing is drawn until a fill or stroke
% operation is applied, and that the path may be discarded or used as a clip
% without appearing itself.
% \end{function}
%
% \begin{function}{\driver_draw_rectangle:nnnn}
% \begin{syntax}
% \cs{driver_draw_rectangle:nnnn} \Arg{x} \Arg{y} \Arg{width} \Arg{height}
% \end{syntax}
% Adds rectangular path from (\meta{x_1}, \meta{y_1}) of \meta{height}
% and \meta{width}; any active transformation matrix applies. Note that
% nothing is drawn until a fill or stroke operation is applied, and that the
% path may be discarded or used as a clip without appearing itself.
% \end{function}
%
% \begin{function}{\driver_draw_closepath:}
% \begin{syntax}
% \cs{driver_draw_closepath:}
% \end{syntax}
% Closes an existing path, adding a line from the current point to the
% start of path. Note that nothing is drawn until a fill or stroke
% operation is applied, and that the path may be discarded or used as a clip
% without appearing itself.
% \end{function}
%
% \subsection{Stroking and filling}
%
% \begin{function}{\driver_draw_stroke:, \driver_draw_closestroke:}
% \begin{syntax}
% \meta{path construction}
% \cs{driver_draw_stroke:}
% \end{syntax}
% Draws a line along the current path, which is also closed in the case of
% \cs{driver_draw_closestroke:}. The nature of the line drawn
% is influenced by settings for
% \begin{itemize}
% \item Line thickness
% \item Stroke color (or the current color if no specific stroke color
% is set)
% \item Line capping (how non-closed line ends should look)
% \item Join style (how a bend in the path should be rendered)
% \item Dash pattern
% \end{itemize}
% The path may also be used for clipping.
% \end{function}
%
% \begin{function}{\driver_draw_fill:, \driver_draw_fillstroke:}
% \begin{syntax}
% \meta{path construction}
% \cs{driver_draw_fill:}
% \end{syntax}
% Fills the area surrounded by the current path: this will be closed prior
% to filling if it is not already. The \texttt{fillstroke} version also
% strokes the path as described for \cs{driver_draw_stroke:}. The fill is
% influenced by the setting for fill color (or the current color if no
% specific stroke color is set). The path may also be used for clipping.
% For paths which are self-intersecting or comprising multiple parts, the
% determination of which areas are inside the path is made using the non-zero
% winding number rule unless the even-odd rule is active.
% \end{function}
%
% \begin{function}{\driver_draw_nonzero_rule:, \driver_draw_evenodd_rule:}
% \begin{syntax}
% \cs{driver_draw_nonzero_rule:}
% \end{syntax}
% Active either the non-zero winding number or the even-odd rule,
% respectively, for determining what is inside a fill or clip area.
% For technical reasons, these command are not influenced by scoping
% and apply on an ongoing basis.
% \end{function}
%
% \begin{function}{\driver_draw_clip:}
% \begin{syntax}
% \meta{path construction}
% \cs{driver_draw_clip:}
% \end{syntax}
% Indicates that the current path should be used for clipping, such that
% any subsequent material outside of the path (but within the current
% scope) will not be shown. This command should be given once a path is
% complete but before it is stroked or filled (if appropriate). This
% command is \emph{not} affected by scoping: it applies to exactly one
% path as shown.
% \end{function}
%
% \begin{function}{\driver_draw_discardpath:}
% \begin{syntax}
% \meta{path construction}
% \cs{driver_draw_discardpath:}
% \end{syntax}
% Discards the current path without stroking or filling. This is primarily
% useful for paths constructed purely for clipping, as this alone does not
% end the paths existence.
% \end{function}
%
% \subsection{Stroke options}
%
% \begin{function}{\driver_draw_linewidth:n}
% \begin{syntax}
% \cs{driver_draw_linewidth:n} \Arg{dimexpr}
% \end{syntax}
% Sets the width to be used for stroking to \meta{dimexpr}.
% \end{function}
%
% \begin{function}{\driver_draw_dash_pattern:nn}
% \begin{syntax}
% \cs{driver_draw_dash:nn} \Arg{dash pattern} \Arg{phase}
% \end{syntax}
% Sets the pattern of dashing to be used when stroking a line. The
% \meta{dash pattern} should be a comma-separated list of dimension
% expressions. This is then interpreted as a series of pairs of line-on
% and line-off lengths. For example \texttt{3pt, 4pt} means that $3$\,pt on,
% $4$\,pt off, $3$\,pt on, and so on. A more complex pattern will also
% repeat: \texttt{3pt, 4pt, 1pt, 2pt} results in $3$\,pt on, $4$\,pt off,
% $1$\,pt on, $2$\,pt off, $3$\,pt on, and so on. An odd number of entries
% means that the last is repeated, for example \texttt{3pt} is equal to
% \texttt{3pt, 3pt}. An empty pattern yields a solid line.
%
% The \meta{phase} specifies an offset at the start of the cycle. For
% example, with a pattern \texttt{3pt} a phase of \texttt{1pt} means
% that the output is $2$\,pt on, $3$\,pt off, $3$\,pt on, $3$\,pt on,
% \emph{etc.}
% \end{function}
%
% \begin{function}
% {
% \driver_draw_cap_butt: ,
% \driver_draw_cap_rectangle: ,
% \driver_draw_cap_round:
% }
% \begin{syntax}
% \cs{driver_draw_cap_butt:}
% \end{syntax}
% Sets the style of terminal stroke position to one of butt, rectangle or
% round.
% \end{function}
%
% \begin{function}
% {
% \driver_draw_join_bevel: ,
% \driver_draw_join_miter: ,
% \driver_draw_join_round:
% }
% \begin{syntax}
% \cs{driver_draw_cap_butt:}
% \end{syntax}
% Sets the style of stroke joins to one of bevel, miter or round.
% \end{function}
%
% \begin{function}{\driver_draw_miterlimit:n}
% \begin{syntax}
% \cs{driver_draw_miterlimit:n} \Arg{factor}
% \end{syntax}
% Sets the miter limit of lines joined as a miter, as described in the
% PDF and PostScript manuals. The \meta{factor} here is an
% \meta{fp expression}.
% \end{function}
%
% \subsection{Color}
%
% \begin{function}
% {
% \driver_draw_color_fill_cmyk:nnnn ,
% \driver_draw_color_stroke_cmyk:nnnn
% }
% \begin{syntax}
% \cs{driver_draw_color_fill_cmyk:nnnn} \Arg{cyan} \Arg{magenta} \Arg{yellow}
% \Arg{black}
% \end{syntax}
% Sets the color for drawing to the CMYK values specified, all of which are
% fp denotations in the range $0$ and $1$.
% \end{function}
%
% \begin{function}
% {
% \driver_draw_color_fill_gray:n ,
% \driver_draw_color_stroke_gray:n
% }
% \begin{syntax}
% \cs{driver_draw_color_fill_gray:n} \Arg{gray}
% \end{syntax}
% Sets the color for drawing to the grayscale value specified, which is
% fp denotations in the range $0$ and $1$.
% \end{function}
%
% \begin{function}
% {
% \driver_draw_color_fill_rgb:nnn ,
% \driver_draw_color_stroke_rgb:nnn
% }
% \begin{syntax}
% \cs{driver_draw_color_fill_rgb:nnn} \Arg{red} \Arg{green} \Arg{blue}
% \end{syntax}
% Sets the color for drawing to the RGB values specified, all of which are
% fp denotations in the range $0$ and $1$.
% \end{function}
%
% \subsection{Inserting \TeX{} material}
%
% \begin{function}{\driver_draw_box_use:Nnnnn}
% \begin{syntax}
% \cs{driver_draw_box:Nnnnnnn} \meta{box}
% \Arg{a} \Arg{b} \Arg{c} \Arg{d} \Arg{x} \Arg{y}
% \end{syntax}
% Inserts the \meta{box} as an hbox with the box reference point placed
% at ($x$, $y$). The transformation matrix $[a b c d]$ is applied
% to the box, allowing it to be in synchronisation with any scaling, rotation
% or skewing applying more generally. Note that \TeX{} material should not
% be inserted directly into a drawing as it would not be in the correct
% location. Also note that as for other drawing elements the box here
% has no size from a \TeX{} perspective.
% \end{function}
%
% \subsection{Coordinate system transformations}
%
% \begin{function}{\driver_draw_cm:nnnn}
% \begin{syntax}
% \cs{driver_draw_cm:nnnn} \Arg{a} \Arg{b} \Arg{c} \Arg{d}
% \end{syntax}
% Applies the transformation matrix $[a b c d]$ to the current graphic state.
% This affects any subsequent items in the same scope but not those already
% given.
% \end{function}
%
% \section{PDF Features}
%
% A range of PDF features are exposed by \pdfTeX{} and \LuaTeX{} in direct PDF
% output mode, and the vast majority of these are also controllable using
% the \texttt{(x)dvipdfmx} driver (as DVI instructions are converted directly
% to PDF). Some of these functions are also available for cases where PDFs
% are generated by \texttt{dvips}: this depends on being able to pass
% information through correctly.
%
% \subsection{PDF Objects}
%
% Objects are used to provide a range of data structures in a PDF. At the
% driver level, different PDF object types are declared separately. Objects
% are only \emph{written} to the PDF when referenced.
%
% \begin{function}{\driver_pdf_object_new:nn}
% \begin{syntax}
% \cs{driver_pdf_object_new:n} \Arg{name} \Arg{type}
% \end{syntax}
% Declares \meta{name} as a PDF object. The \texttt{type} should be one of
% |array| or |dict|, |fstream| or |stream|.
% \end{function}
%
% \begin{function}[EXP]{\driver_pdf_object_ref:n}
% \begin{syntax}
% \cs{driver_pdf_object_ref:n} \Arg{object}
% \end{syntax}
% Inserts the appropriate information to reference the \meta{object}
% in for example page resource allocation.
% \end{function}
%
% \begin{function}{\driver_pdf_object_write:nn}
% \begin{syntax}
% \cs{driver_pdf_object_write:nn} \Arg{name} \Arg{data}
% \end{syntax}
% Writes the \meta{data} as content of the \meta{object}. Depending on the
% \meta{type} declared for the object, the format required for the
% \meta{data} will vary
% \begin{itemize}
% \item[\texttt{array}] A space-separated list of values
% \item[\texttt{dict}] Key--value pairs in the form
% \texttt{/\meta{key} \meta{value}}
% \item[\texttt{fstream}] Two brace groups: \meta{content} and
% \meta{file name}
% \item[\texttt{stream}] Two brace groups: \meta{content} and
% \meta{additional attributes}
% \end{itemize}
% \end{function}
%
% \subsection{PDF structure}
%
% \begin{function}{\driver_pdf_compresslevel:n}
% \begin{syntax}
% \cs{driver_pdf_compresslevel:n} \Arg{level}
% \end{syntax}
% Sets the degree of compression used for PDF files: the \meta{level} should
% be in the range $0$ to $9$ (higher is more compression). Typically, either
% compression is disables ($0$) or maximised ($9$). When used with
% \texttt{(x)dvipdfmx}, this setting may only be applied globally: it should
% be set only once.
% \end{function}
%
% \begin{function}{\driver_pdf_objects_enable:, \driver_pdf_objects_disable:}
% \begin{syntax}
% \cs{driver_pdf_objects_disable:}
% \end{syntax}
% Enables or disables the creation of PDF objects. These objects are used to
% reduce the size of PDFs, and typically are enabled as standard. When used
% with \texttt{(x)dvipdfmx}, object creation can be disabled but not
% re-enabled, and this setting may only be applied globally: it should
% be set only once.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3drivers} Implementation}
%
% \begin{macrocode}
%<*initex|package>
%<@@=driver>
% \end{macrocode}
%
% Whilst there is a reasonable amount of code overlap between drivers,
% it is much clearer to have the blocks more-or-less separated than run
% in together and DocStripped out in parts. As such, most of the following
% is set up on a per-driver basis, though there is some common code (again
% given in blocks not interspersed with other material).
%
% All the file identifiers are up-front so that they come out in the right
% place in the files.
% \begin{macrocode}
%<*package>
\ProvidesExplFile
%<*dvipdfmx>
{l3dvidpfmx.def}{2018-10-17}{}
{L3 Experimental driver: dvipdfmx}
%
%<*dvips>
{l3dvips.def}{2018-10-17}{}
{L3 Experimental driver: dvips}
%
%<*dvisvgm>
{l3dvisvgm.def}{2018-10-17}{}
{L3 Experimental driver: dvisvgm}
%
%<*pdfmode>
{l3pdfmode.def}{2018-10-17}{}
{L3 Experimental driver: PDF mode}
%
%<*xdvipdfmx>
{l3xdvidpfmx.def}{2018-10-17}{}
{L3 Experimental driver: xdvipdfmx}
%
%
% \end{macrocode}
%
% The order of the driver code here is such that we get somewhat logical
% outcomes in terms of code sharing whilst keeping things readable. (Trying to
% mix all of the code by concept is almost unmanageable.) The key parts which
% are shared are
% \begin{itemize}
% \item Color support is either \texttt{dvips}-like or \texttt{pdfmode}-like.
% \item \texttt{pdfmode} and \texttt{(x)dvipdfmx} share drawing routines.
% \item \texttt{xdvipdfmx} is largely the same as \texttt{dvipdfmx} so
% takes most of the same code.
% \end{itemize}
%
% \begin{macro}{\@@_literal:e, \@@_literal:n, \@@_literal:x}
% The one shared function for all drivers is access to the basic
% \tn{special} primitive: it has slightly odd expansion behaviour
% so a wrapper is provided.
% \begin{macrocode}
\cs_new_eq:NN \@@_literal:e \tex_special:D
\cs_new_protected:Npn \@@_literal:n #1
{ \@@_literal:e { \exp_not:n {#1} } }
\cs_generate_variant:Nn \@@_literal:n { x }
% \end{macrocode}
% \end{macro}
%
% \subsection{Color support}
%
% Color support is split into two parts: a \enquote{general} concept and
% one directly linked to drawings (or rather the split between filling
% and stroking). General color is relatively easy to handle: we have a color
% stack available with all modern drivers, and can use that.%
% Whilst \texttt{(x)dvipdfmx} does have its own approach to color specials,
% it is easier to use \texttt{dvips}-like ones for all cases except direct
% PDF output.
%
% \subsubsection{\texttt{dvips}-style}
%
% \begin{macrocode}
%<*dvisvgm|dvipdfmx|dvips|xdvipdfmx>
% \end{macrocode}
%
% \begin{macro}{\driver_color_pickup:N}
% \begin{macro}{\@@_color_pickup:w}
% Allow for \LaTeXe{} color. Here, the possible input values are limited:
% \texttt{dvips}-style colors can mainly be taken as-is with the exception
% spot ones (here we need a model and a tint).
% \begin{macrocode}
%<*package>
\cs_new_protected:Npn \driver_color_pickup:N #1 { }
\AtBeginDocument
{
\@ifpackageloaded { color }
{
\cs_set_protected:Npn \driver_color_pickup:N #1
{
\exp_args:NV \tl_if_head_is_space:nTF \current@color
{
\tl_set:Nx #1
{
spot ~
\exp_after:wN \use:n \current@color \c_space_tl 1
}
}
{
\exp_after:wN \@@_color_pickup:w
\current@color \q_stop #1
}
}
\cs_new_protected:Npn \@@_color_pickup:w #1 ~ #2 \q_stop #3
{ \tl_set:Nn #3 { #1 ~ #2 } }
}
{ }
}
%
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_color_cmyk:nnnn}
% \begin{macro}{\driver_color_gray:n}
% \begin{macro}{\driver_color_rgb:nnn}
% \begin{macro}{\driver_color_spot:nn}
% \begin{macro}{\@@_color_select:n, \@@_color_select:x}
% \begin{macro}{\@@_color_reset:}
% Push the data to the stack. In the case of \texttt{dvips} also reset the
% drawing fill color in raw PostScript.
% \begin{macrocode}
\cs_new_protected:Npn \driver_color_cmyk:nnnn #1#2#3#4
{
\@@_color_select:x
{
cmyk~
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4}
}
}
\cs_new_protected:Npn \driver_color_gray:n #1
{ \@@_color_select:x { gray~ \fp_eval:n {#1} } }
\cs_new_protected:Npn \driver_color_rgb:nnn #1#2#3
{
\@@_color_select:x
{ rgb~ \fp_eval:n {#1} ~ \fp_eval:n {#2} ~ \fp_eval:n {#3} }
}
\cs_new_protected:Npn \driver_color_spot:nn #1#2
{ \@@_color_select:n { #1 } }
\cs_new_protected:Npn \@@_color_select:n #1
{
\@@_literal:n { color~push~ #1 }
%<*dvips>
\@@_literal_postscript:n { /l3fc~{ }~def }
%
\group_insert_after:N \@@_color_reset:
}
\cs_generate_variant:Nn \@@_color_select:n { x }
\cs_new_protected:Npn \@@_color_reset:
{ \@@_literal:n { color~pop } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsubsection{\texttt{pdfmode}}
%
% \begin{macrocode}
%<*pdfmode>
% \end{macrocode}
%
% \begin{macro}{\driver_color_pickup:N}
% \begin{macro}{\@@_color_pickup:w}
% The current color in driver-dependent format: pick up the package-mode
% data if available. We end up converting back and forward in this route as
% we store our color data in \texttt{dvips} format.
% The \tn{current@color} needs to be \texttt{x}-expanded before
% \cs{@@_color_pickup:w} breaks it apart, because for instance
% \pkg{xcolor} sets it to be instructions to generate a colour
% \begin{macrocode}
%<*package>
\cs_new_protected:Npn \driver_color_pickup:N #1 { }
\AtBeginDocument
{
\@ifpackageloaded { color }
{
\cs_set_protected:Npn \driver_color_pickup:N #1
{
\exp_last_unbraced:Nx \@@_color_pickup:w
{ \current@color } ~ 0 ~ 0 ~ 0 \q_stop #1
}
\cs_new_protected:Npn \@@_color_pickup:w
#1 ~ #2 ~ #3 ~ #4 ~ #5 ~ #6 \q_stop #7
{
\str_if_eq:nnTF {#2} { g }
{ \tl_set:Nn #7 { gray ~ #1 } }
{
\str_if_eq:nnTF {#4} { rg }
{ \tl_set:Nn #7 { rgb ~ #1 ~ #2 ~ #3 } }
{
\str_if_eq:nnTF {#5} { k }
{ \tl_set:Nn #7 { cmyk ~ #1 ~ #2 ~ #3 ~ #4 } }
{
\str_if_eq:nnTF {#2} { cs }
{
\tl_set:Nx #7 { spot ~ \use_none:n #1 ~ #5 }
}
{
\tl_set:Nn #7 { gray ~ 0 }
}
}
}
}
}
}
{ }
}
%
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\l_@@_color_stack_int}
% \pdfTeX{} and \LuaTeX{} have multiple stacks available, and to track
% which one is in use a variable is required.
% \begin{macrocode}
\int_new:N \l_@@_color_stack_int
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\driver_color_cmyk:nnnn, \@@_color_cmyk:nnnn}
% \begin{macro}{\driver_color_gray:n, \@@_color_gray:n}
% \begin{macro}{\driver_color_rgb:nnn, \@@_color_rgb:nnn}
% \begin{macro}{\driver_color_spot:nn}
% \begin{macro}{\@@_color_select:n, \@@_color_select:x}
% \begin{macro}{\@@_color_reset:}
% Simply dump the data, but allowing for \LuaTeX{}.
% \begin{macrocode}
\cs_new_protected:Npn \driver_color_cmyk:nnnn #1#2#3#4
{
\use:x
{
\@@_color_cmyk:nnnn
{ \fp_eval:n {#1} }
{ \fp_eval:n {#2} }
{ \fp_eval:n {#3} }
{ \fp_eval:n {#4} }
}
}
\cs_new_protected:Npn \@@_color_cmyk:nnnn #1#2#3#4
{
\@@_color_select:n
{ #1 ~ #2 ~ #3 ~ #4 ~ k ~ #1 ~ #2 ~ #3 ~ #4 ~ K }
}
\cs_new_protected:Npn \driver_color_gray:n #1
{ \exp_args:Nx \@@_color_gray:n { \fp_eval:n {#1} } }
\cs_new_protected:Npn \@@_color_gray:n #1
{ \@@_color_select:n { #1 ~ g ~ #1 ~ G } }
\cs_new_protected:Npn \driver_color_rgb:nnn #1#2#3
{
\use:x
{
\@@_color_rgb:nnn
{ \fp_eval:n {#1} }
{ \fp_eval:n {#2} }
{ \fp_eval:n {#3} }
}
}
\cs_new_protected:Npn \@@_color_rgb:nnn #1#2#3
{ \@@_color_select:n { #1 ~ #2 ~ #3 ~ rg ~ #1 ~ #2 ~ #3 ~ RG } }
\cs_new_protected:Npn \driver_color_spot:nn #1#2
{ \@@_color_select:n { /#1 ~ cs ~ /#1 ~ CS ~ #2 ~ sc ~ #2 ~ SC } }
\cs_new_protected:Npx \@@_color_select:n #1
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D colorstack }
{ \tex_pdfcolorstack:D }
\exp_not:N \l_@@_color_stack_int push {#1}
\group_insert_after:N \exp_not:N \@@_color_reset:
}
\cs_generate_variant:Nn \@@_color_select:n { x }
\cs_new_protected:Npx \@@_color_reset:
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D colorstack }
{ \tex_pdfcolorstack:D }
\exp_not:N \l_@@_color_stack_int pop \scan_stop:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{dvips} driver}
%
% \begin{macrocode}
%<*dvips>
% \end{macrocode}
%
% \subsubsection{Basics}
%
% \begin{macro}{\@@_literal_postscript:n, \@@_literal_postscript:x}
% Literal PostScript can be included using a few low-level formats. Here,
% we use the form with no positioning: this is overall more convenient as
% a wrapper. Note that this does require that where position is important,
% an appropriate wrapper is included.
% \begin{macrocode}
\cs_new_protected:Npn \@@_literal_postscript:n #1
{ \@@_literal:n { ps:: #1 } }
\cs_generate_variant:Nn \@@_literal_postscript:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_align_currentpoint_begin:, \@@_align_currentpoint_end:}
% In \texttt{dvips} there is no build-in saving of the current
% position, and so some additional PostScript is required to set up the
% transformation matrix and also to restore it afterwards. Notice the use
% of the stack to save the current position \enquote{up front} and to
% move back to it at the end of the process. Notice that the |[begin]|/^^A
% |[end]| pair here mean that we can use a run of PostScript statements
% in separate lines: not \emph{required} but does make the code and
% output more clear.
% \begin{macrocode}
\cs_new_protected:Npn \@@_align_currentpoint_begin:
{
\@@_literal:n { ps::[begin] }
\@@_literal_postscript:n { currentpoint }
\@@_literal_postscript:n { currentpoint~translate }
}
\cs_new_protected:Npn \@@_align_currentpoint_end:
{
\@@_literal_postscript:n { neg~exch~neg~exch~translate }
\@@_literal:n { ps::[end] }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_scope_begin:, \@@_scope_end:}
% Saving/restoring scope for general operations needs to be done with
% \texttt{dvips} positioning (try without to see this!). Thus we need the
% |ps:| version of the special here. As only the graphics state is ever
% altered within this pairing, we use the lower-cost |g|-versions.
% \begin{macrocode}
\cs_new_protected:Npn \@@_scope_begin:
{ \@@_literal:n { ps:gsave } }
\cs_new_protected:Npn \@@_scope_end:
{ \@@_literal:n { ps:grestore } }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Box operations}
%
% \begin{macro}{\driver_box_use_clip:N}
% The \texttt{dvips} driver 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 \driver_box_use_clip:N #1
{
\@@_scope_begin:
\@@_align_currentpoint_begin:
\@@_literal_postscript:n { matrix~currentmatrix }
\@@_literal_postscript:n
{ Resolution~72~div~VResolution~72~div~scale }
\@@_literal_postscript:n { DVImag~dup~scale }
\@@_literal_postscript:x
{
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
}
\@@_literal_postscript:n { setmatrix }
\@@_align_currentpoint_end:
\hbox_overlap_right:n { \box_use:N #1 }
\@@_scope_end:
\skip_horizontal:n { \box_wd:N #1 }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_box_use_rotate:Nn}
% \begin{macro}{\@@_box_use_rotate: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 \driver_box_use_rotate:Nn #1#2
{ \exp_args:NNf \@@_box_use_rotate:Nn #1 { \fp_eval:n {#2} } }
\cs_new_protected:Npn \@@_box_use_rotate:Nn #1#2
{
\@@_scope_begin:
\@@_align_currentpoint_begin:
\@@_literal_postscript:x
{
\fp_compare:nNnTF {#2} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( -(#2) , 5 ) } } ~
rotate
}
\@@_align_currentpoint_end:
\box_use:N #1
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_box_use_scale:Nnn}
% The \texttt{dvips} driver once again has a dedicated operation we can
% use here.
% \begin{macrocode}
\cs_new_protected:Npn \driver_box_use_scale:Nnn #1#2#3
{
\@@_scope_begin:
\@@_align_currentpoint_begin:
\@@_literal_postscript:x
{
\fp_eval:n { round ( #2 , 5 ) } ~
\fp_eval:n { round ( #3 , 5 ) } ~
scale
}
\@@_align_currentpoint_end:
\hbox_overlap_right:n { \box_use:N #1 }
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Images}
%
% \begin{macro}{\@@_image_getbb_eps:n}
% Simply use the generic function.
% \begin{macrocode}
\cs_new_eq:NN \@@_image_getbb_eps:n \image_read_bb:n
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_image_include_eps:n}
% The special syntax is relatively clear here: remember we need PostScript
% sizes here.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_eps:n #1
{ \@@_literal:n { PSfile = #1 } }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Drawing}
%
% \begin{macro}{\@@_draw_literal:n, \@@_draw_literal:x}
% The same as literal PostScript: same arguments about positioning apply
% her.
% \begin{macrocode}
\cs_new_eq:NN \@@_draw_literal:n \@@_literal_postscript:n
\cs_generate_variant:Nn \@@_draw_literal:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_begin:, \driver_draw_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. The definition of |/l3fc| deals with fill
% color in paths. In contrast to \pkg{pgf}, we don't save the current point:
% discussion with Tom Rokici suggested a better way to handle the necessary
% translations (see \cs{driver_draw_box_use:Nnnnn}). (Note that
% |@beginspecial|/|@endspecial| forms a driver scope.) The |[begin]|/^^A
% |[end]| lines are handled differently from the rest as they are
% conceptually different: not really drawing literals but instructions to
% \texttt{dvips} itself.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_begin:
{
\@@_literal:n { ps::[begin] }
\@@_draw_literal:n { @beginspecial }
\@@_draw_literal:n { /l3fc~{ }~def }
}
\cs_new_protected:Npn \driver_draw_end:
{
\@@_draw_literal:n { @endspecial }
\@@_literal:n { ps::[end] }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_scope_begin:, \driver_draw_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 \driver_draw_scope_begin:
{ \@@_draw_literal:n { save } }
\cs_new_protected:Npn \driver_draw_scope_end:
{ \@@_draw_literal:n { restore } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_moveto:nn, \driver_draw_lineto:nn}
% \begin{macro}{\driver_draw_rectangle:nnnn}
% \begin{macro}{\driver_draw_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 \driver_draw_moveto:nn #1#2
{
\@@_draw_literal:x
{
\dim_to_decimal_in_bp:n {#1} ~
\dim_to_decimal_in_bp:n {#2} ~ moveto
}
}
\cs_new_protected:Npn \driver_draw_lineto:nn #1#2
{
\@@_draw_literal:x
{
\dim_to_decimal_in_bp:n {#1} ~
\dim_to_decimal_in_bp:n {#2} ~ lineto
}
}
\cs_new_protected:Npn \driver_draw_rectangle:nnnn #1#2#3#4
{
\@@_draw_literal:x
{
\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 \driver_draw_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_draw_literal:x
{
\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}{\driver_draw_evenodd_rule:, \driver_draw_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 \driver_draw_evenodd_rule:
{ \bool_gset_true:N \g_@@_draw_eor_bool }
\cs_new_protected:Npn \driver_draw_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}
% {
% \driver_draw_closepath: ,
% \driver_draw_stroke: ,
% \driver_draw_closestroke: ,
% \driver_draw_fill: ,
% \driver_draw_fillstroke: ,
% \driver_draw_clip: ,
% \driver_draw_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 \driver_draw_closepath:
{ \@@_draw_literal:n { closepath } }
\cs_new_protected:Npn \driver_draw_stroke:
{
\@@_draw_literal:n { stroke }
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_draw_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \driver_draw_closestroke:
{
\driver_draw_closepath:
\driver_draw_stroke:
}
\cs_new_protected:Npn \driver_draw_fill:
{
\@@_draw_literal:n { gsave }
\@@_draw_literal:n { l3fc }
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
fill
}
\@@_draw_literal:n { grestore }
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_draw_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \driver_draw_fillstroke:
{
\@@_draw_literal:n { gsave }
\@@_draw_literal:n { l3fc }
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
fill
}
\@@_draw_literal:n { grestore }
\@@_draw_literal:n { stroke }
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_draw_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
\cs_new_protected:Npn \driver_draw_clip:
{ \bool_gset_true:N \g_@@_draw_clip_bool }
\bool_new:N \g_@@_draw_clip_bool
\cs_new_protected:Npn \driver_draw_discardpath:
{
\bool_if:NT \g_@@_draw_clip_bool
{
\@@_draw_literal:x
{
\bool_if:NT \g_@@_draw_eor_bool { eo }
clip
}
}
\@@_draw_literal:n { newpath }
\bool_gset_false:N \g_@@_draw_clip_bool
}
% \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\driver_draw_dash_pattern:nn}
% \begin{macro}{\@@_draw_dash:n}
% \begin{macro}{\driver_draw_linewidth:n}
% \begin{macro}{\driver_draw_miterlimit:n}
% \begin{macro}
% {
% \driver_draw_cap_butt:, \driver_draw_cap_round:, \driver_draw_cap_rectangle:,
% \driver_draw_join_miter:, \driver_draw_join_round:, \driver_draw_join_bevel:
% }
% Converting paths to output is again a case of mapping directly to
% PostScript operations.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_dash_pattern:nn #1#2
{
\@@_draw_literal:x
{
[
\exp_args:Nf \use:n
{ \clist_map_function:nN {#1} \@@_draw_dash:n }
] ~
\dim_to_decimal_in_bp:n {#2} ~ setdash
}
}
\cs_new:Npn \@@_draw_dash:n #1
{ ~ \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \driver_draw_linewidth:n #1
{
\@@_draw_literal:x
{ \dim_to_decimal_in_bp:n {#1} ~ setlinewidth }
}
\cs_new_protected:Npn \driver_draw_miterlimit:n #1
{ \@@_draw_literal:x { \fp_eval:n {#1} ~ setmiterlimit } }
\cs_new_protected:Npn \driver_draw_cap_butt:
{ \@@_draw_literal:n { 0 ~ setlinecap } }
\cs_new_protected:Npn \driver_draw_cap_round:
{ \@@_draw_literal:n { 1 ~ setlinecap } }
\cs_new_protected:Npn \driver_draw_cap_rectangle:
{ \@@_draw_literal:n { 2 ~ setlinecap } }
\cs_new_protected:Npn \driver_draw_join_miter:
{ \@@_draw_literal:n { 0 ~ setlinejoin } }
\cs_new_protected:Npn \driver_draw_join_round:
{ \@@_draw_literal:n { 1 ~ setlinejoin } }
\cs_new_protected:Npn \driver_draw_join_bevel:
{ \@@_draw_literal:n { 2 ~ setlinejoin } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}
% {
% \driver_draw_color_fill_cmyk:nnnn ,
% \driver_draw_color_stroke_cmyk:nnnn
% }
% \begin{macro}
% {
% \driver_draw_color_fill_gray:n ,
% \driver_draw_color_stroke_gray:n
% }
% \begin{macro}
% {
% \driver_draw_color_fill_rgb:nnn ,
% \driver_draw_color_stroke_rgb:nnn
% }
% \begin{macro}
% {
% \@@_draw_color_fill:n, \@@_draw_color_fill:x,
% \@@_draw_color_stroke:n, \@@_draw_color_stroke:x
% }
% For \texttt{dvips}, we can use the standard color stack to deal with
% stroke color, but for fills have to switch to raw PostScript. This is
% thus not handled by the stack, but the context is very restricted. See
% also how fills are implemented.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_color_fill_cmyk:nnnn #1#2#3#4
{
\@@_draw_color_fill:x
{
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4} ~
setcmykcolor
}
}
\cs_new_protected:Npn \driver_draw_color_stroke_cmyk:nnnn #1#2#3#4
{
\@@_draw_color_stroke:x
{
cmyk ~
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4}
}
}
\cs_new_protected:Npn \driver_draw_color_fill_gray:n #1
{ \@@_draw_color_fill:x { \fp_eval:n {#1} ~ setgray } }
\cs_new_protected:Npn \driver_draw_color_stroke_gray:n #1
{ \@@_draw_color_stroke:x { gray ~ \fp_eval:n {#1} } }
\cs_new_protected:Npn \driver_draw_color_fill_rgb:nnn #1#2#3
{
\@@_draw_color_fill:x
{ \fp_eval:n {#1} ~ \fp_eval:n {#2} ~ \fp_eval:n {#3} ~ setrgbcolor }
}
\cs_new_protected:Npn \driver_draw_color_stroke_rgb:nnn #1#2#3
{
\@@_draw_color_stroke:x
{ rgb ~ \fp_eval:n {#1} ~ \fp_eval:n {#2} ~ \fp_eval:n {#3} }
}
\cs_new_protected:Npn \@@_draw_color_fill:n #1
{ \@@_draw_literal:n { /l3fc ~ { #1 } ~ def } }
\cs_generate_variant:Nn \@@_draw_color_fill:n { x }
\cs_new_protected:Npn \@@_draw_color_stroke:n #1
{
\@@_literal:n { color~push~#1 }
\group_insert_after:N \@@_color_reset:
}
\cs_generate_variant:Nn \@@_draw_color_stroke:n { x }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_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 driver
% tracking (\emph{cf.}~\texttt{(x)dvipdfmx}). Thus we take the shortest
% path available and simply dump the matrix as given.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_cm:nnnn #1#2#3#4
{
\@@_draw_literal:n
{
[
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4} ~
0 ~ 0
] ~
concat
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_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. The implementation here was suggested by Tom Rokici
% (author of \texttt{dvips}). We end the current special placement, then
% set the current point with a literal |[begin]|. As for general
% literals, we then use the stack to store the current point and move to
% it. To insert the required transformation, we have to flip the $y$-axis,
% once before and once after it. Then we get back to the \TeX{} reference
% point to insert our content. The clean up has to happen in the right
% places, hence the |[begin]|/|[end]| pair around |restore|. Finally,
% we can return to \enquote{normal} drawing mode. Notice that the set up
% here is very similar to that in \cs{@@_align_currentpoint_\ldots}, but
% the ordering of saving and restoring is different (intermixed).
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_box_use:Nnnnn #1#2#3#4#5
{
\@@_draw_literal:n { @endspecial }
\@@_draw_literal:n { [end] }
\@@_draw_literal:n { [begin] }
\@@_draw_literal:n { save }
\@@_draw_literal:n { currentpoint }
\@@_draw_literal:n { currentpoint~translate }
\driver_draw_cm:nnnn { 1 } { 0 } { 0 } { -1 }
\driver_draw_cm:nnnn {#2} {#3} {#4} {#5}
\driver_draw_cm:nnnn { 1 } { 0 } { 0 } { -1 }
\@@_draw_literal:n { neg~exch~neg~exch~translate }
\@@_draw_literal:n { [end] }
\hbox_overlap_right:n { \box_use:N #1 }
\@@_draw_literal:n { [begin] }
\@@_draw_literal:n { restore }
\@@_draw_literal:n { [end] }
\@@_draw_literal:n { [begin] }
\@@_draw_literal:n { @beginspecial }
}
% \end{macrocode}
% \end{macro}
%
% \subsubsection{PDF Features}
%
% \begin{variable}{\g_@@_pdf_object_int, \g_@@_pdf_object_prop}
% For tracking objects to allow finalisation.
% \begin{macrocode}
\int_new:N \g_@@_pdf_object_int
\prop_new:N \g_@@_pdf_object_prop
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\driver_pdf_object_new:nn}
% \begin{macro}[EXP]{\driver_pdf_object_ref:n}
% Tracking objects is similar to \texttt{dvipdfmx}.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_object_new:nn #1#2
{
\int_gincr:N \g_@@_pdf_object_int
\int_const:cn
{ g_@@_pdf_object_ \tl_to_str:n {#1} _int }
{ \g_@@_pdf_object_int }
\prop_gput:Nnn \g_@@_pdf_object_prop {#1} {#2}
}
\cs_new:Npn \driver_pdf_object_ref:n #1
{ { l3obj \int_use:c { g_@@_pdf_object_ \tl_to_str:n {#1} _int } } }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_pdf_object_write:nn}
% \begin{macro}
% {
% \@@_pdf_object_write_array:nn ,
% \@@_pdf_object_write_dict:nn ,
% \@@_pdf_object_write_stream:nn
% }
% \begin{macro}{\@@_pdf_object_write_stream:nnn}
% This is where we choose the actual type: some work to get things
% right.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_object_write:nn #1#2
{
\@@_literal_postscript:x
{
mark ~ /_objdef ~ \driver_pdf_object_ref:n {#1} ~
/type
\str_case_e:nn
{ \prop_item:Nn \g_@@_pdf_object_prop {#1} }
{
{ array } { /array }
{ dict } { /dict }
{ fstream } { /stream }
{ stream } { /stream }
}
/OBJ ~ pdfmark
}
\use:c
{ @@_pdf_object_write_ \prop_item:Nn \g_@@_pdf_object_prop {#1} :nn }
{#1} {#2}
}
\cs_new_protected:Npn \@@_pdf_object_write_array:nn #1#2
{
\@@_literal_postscript:x
{
mark ~ \driver_pdf_object_ref:n {#1} ~
[ ~ \exp_not:n {#2} ~ ] ~ /PUTINTERVAL ~ pdfmark
}
}
\cs_new_protected:Npn \@@_pdf_object_write_dict:nn #1#2
{
\@@_literal_postscript:x
{
mark ~ \driver_pdf_object_ref:n {#1} ~
<< ~ \exp_not:n {#2} ~ >> ~ /PUT ~ pdfmark
}
}
\cs_new_protected:Npn \@@_pdf_object_write_stream:nn #1#2
{
\exp_args:Nx
\@@_pdf_object_write_stream:nnn
{ \driver_pdf_object_ref:n {#1} }
#2
}
\cs_new_protected:Npn \@@_pdf_object_write_stream:nnn #1#2#3
{
\@@_literal_postscript:n
{
[nobreak] ~
mark ~ #1 ~ ( #3 ) ~ /PUT ~ pdfmark ~
mark ~ #1 ~ << #2 >> ~ /PUT ~ pdfmark
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_pdf_compresslevel:n}
% \begin{macro}{\driver_pdf_objects_enable:, \driver_pdf_objects_disable:}
% These are all no-ops.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_compresslevel:n #1 { }
\cs_new_protected:Npn \driver_pdf_objects_enable: { }
\cs_new_protected:Npn \driver_pdf_objects_disable: { }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{pdfmode} driver}
%
% \begin{macrocode}
%<*pdfmode>
% \end{macrocode}
%
% The direct PDF driver covers both \pdfTeX{} and \LuaTeX{}. The latter
% renames and restructures the driver primitives but this can be handled
% at one level of abstraction. As such, we avoid using two separate drivers
% for this material at the cost of some \texttt{x}-type definitions to get
% everything expanded up-front.
%
% \subsubsection{Basics}
%
% \begin{macro}{\@@_literal_pdf:n, \@@_literal_pdf:x}
% This is equivalent to \verb|\special{pdf:}| but the engine can
% track it. Without the \texttt{direct} keyword everything is kept in
% sync: the transformation matrix is set to the current point automatically.
% Note that this is still inside the text (\texttt{BT} \dots \texttt{ET}
% block).
% \begin{macrocode}
\cs_new_protected:Npx \@@_literal_pdf:n #1
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D literal }
{ \tex_pdfliteral:D }
{ \exp_not:N \exp_not:n {#1} }
}
\cs_generate_variant:Nn \@@_literal_pdf:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_scope_begin:, \@@_scope_end:}
% Higher-level interfaces for saving and restoring the graphic state.
% \begin{macrocode}
\cs_new_protected:Npx \@@_scope_begin:
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D save \scan_stop: }
{ \tex_pdfsave:D }
}
\cs_new_protected:Npx \@@_scope_end:
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D restore \scan_stop: }
{ \tex_pdfrestore:D }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_matrix:n, \@@_matrix:x}
% Here the appropriate function is set up to insert an affine matrix
% into the PDF. With \pdfTeX{} and \LuaTeX{} in direct PDF output mode there
% is a primitive for this, which only needs the rotation/scaling/skew part.
% \begin{macrocode}
\cs_new_protected:Npx \@@_matrix:n #1
{
\cs_if_exist:NTF \tex_pdfextension:D
{ \tex_pdfextension:D setmatrix }
{ \tex_pdfsetmatrix:D }
{ \exp_not:N \exp_not:n {#1} }
}
\cs_generate_variant:Nn \@@_matrix:n { x }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Box operations}
%
% \begin{macro}{\driver_box_use_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 \driver_box_use_clip:N #1
{
\@@_scope_begin:
\@@_literal_pdf:x
{
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 }
\@@_scope_end:
\skip_horizontal:n { \box_wd:N #1 }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_box_use_rotate:Nn}
% \begin{macro}{\@@_box_use_rotate:Nn}
% \begin{variable}{\l_@@_cos_fp, \l_@@_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 \driver_box_use_rotate:Nn #1#2
{ \exp_args:NNf \@@_box_use_rotate:Nn #1 { \fp_eval:n {#2} } }
\cs_new_protected:Npn \@@_box_use_rotate:Nn #1#2
{
\@@_scope_begin:
\box_set_wd:Nn #1 { 0pt }
\fp_set:Nn \l_@@_cos_fp { round ( cosd ( #2 ) , 5 ) }
\fp_compare:nNnT \l_@@_cos_fp = \c_zero_fp
{ \fp_zero:N \l_@@_cos_fp }
\fp_set:Nn \l_@@_sin_fp { round ( sind ( #2 ) , 5 ) }
\@@_matrix:x
{
\fp_use:N \l_@@_cos_fp \c_space_tl
\fp_compare:nNnTF \l_@@_sin_fp = \c_zero_fp
{ 0~0 }
{
\fp_use:N \l_@@_sin_fp
\c_space_tl
\fp_eval:n { -\l_@@_sin_fp }
}
\c_space_tl
\fp_use:N \l_@@_cos_fp
}
\box_use:N #1
\@@_scope_end:
}
\fp_new:N \l_@@_cos_fp
\fp_new:N \l_@@_sin_fp
% \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_box_use_scale:Nnn}
% The same idea as for rotation but without the complexity of signs and
% cosines.
% \begin{macrocode}
\cs_new_protected:Npn \driver_box_use_scale:Nnn #1#2#3
{
\@@_scope_begin:
\@@_matrix:x
{
\fp_eval:n { round ( #2 , 5 ) } ~
0~0~
\fp_eval:n { round ( #3 , 5 ) }
}
\hbox_overlap_right:n { \box_use:N #1 }
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Images}
%
% \begin{variable}{\l_@@_image_attr_tl}
% In PDF mode, additional attributes of an image (such as page number) are
% needed both to obtain the bounding box and when inserting the image: this
% occurs as the image dictionary approach means they are read as part of
% the bounding box operation. As such, it is easier to track additional
% attributes using a dedicated |tl| rather than build up the same data
% twice.
% \begin{macrocode}
\tl_new:N \l_@@_image_attr_tl
% \end{macrocode}
% \end{variable}
%
% \begin{macro}
% {\@@_image_getbb_jpg:n, \@@_image_getbb_pdf:n, \@@_image_getbb_png:n}
% \begin{macro}
% {\@@_image_getbb_auxi:n, \@@_image_getbb_auxii:n}
% Getting the bounding box here requires us to box up the image and
% measure it. To deal with the difference in feature support in bitmap
% and vector images but keeping the common parts, there is a little work
% to do in terms of auxiliaries. The key here is to notice that we need
% two forms of the attributes: a \enquote{short} set to allow us to
% track for caching, and the full form to pass to the primitive.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_getbb_jpg:n #1
{
\int_zero:N \l_image_page_int
\tl_clear:N \l_image_pagebox_tl
\tl_set:Nx \l_@@_image_attr_tl
{
\tl_if_empty:NF \l_image_decode_tl
{ :D \l_image_decodearray_tl }
\bool_if:NT \l_image_interpolate_bool
{ :I }
}
\tl_clear:N \l_@@_image_attr_tl
\@@_image_getbb_auxi:n {#1}
}
\cs_new_eq:NN \@@_image_getbb_png:n \@@_image_getbb_jpg:n
\cs_new_protected:Npn \@@_image_getbb_pdf:n #1
{
\tl_clear:N \l_image_decode_tl
\bool_set_false:N \l_image_interpolate_bool
\tl_set:Nx \l_@@_image_attr_tl
{
: \l_image_pagebox_tl
\int_compare:nNnT \l_image_page_int > 1
{ :P \int_use:N \l_image_page_int }
}
\@@_image_getbb_auxi:n {#1}
}
\cs_new_protected:Npn \@@_image_getbb_auxi:n #1
{
\image_bb_restore:xF { #1 \l_@@_image_attr_tl }
{ \@@_image_getbb_auxii:n {#1} }
}
% \begin{macrocode}
% Measuring the image is done by boxing up: for PDF images we could
% use |\tex_pdfximagebbox:D|, but if doesn't work for other types.
% As the box always starts at $(0,0)$ there is no need to worry about
% the lower-left position.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_getbb_auxii:n #1
{
\tex_immediate:D \tex_pdfximage:D
\bool_lazy_or:nnT
{ \l_image_interpolate_bool }
{ ! \tl_if_empty_p:N \l_image_decodearray_tl }
{
attr ~
{
\tl_if_empty:NF \l_image_decode_tl
{ /Decode~[ \l_image_decodearray_tl ] }
\bool_if:NT \l_image_interpolate_bool
{ /Interpolate~true }
}
}
\int_compare:nNnT \l_image_page_int > 0
{ page ~ \int_use:N \l_image_page_int }
\tl_if_empty:NF \l_image_pagebox_tl
{ \l_image_pagebox_tl }
{#1}
\hbox_set:Nn \l_@@_tmp_box
{ \tex_pdfrefximage:D \tex_pdflastximage:D }
\dim_set:Nn \l_image_urx_dim { \box_wd:N \l_@@_tmp_box }
\dim_set:Nn \l_image_ury_dim { \box_ht:N \l_@@_tmp_box }
\int_const:cn { c_@@_image_ #1 \l_@@_image_attr_tl _int }
{ \tex_the:D \tex_pdflastximage:D }
\image_bb_save:x { #1 \l_@@_image_attr_tl }
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}
% {\@@_image_include_jpg:n, \@@_image_include_pdf:n, \@@_image_include_png:n}
% Images are already loaded for the measurement part of the code, so
% inclusion is straight-forward, with only any attributes to worry about. The
% latter carry through from determination of the bounding box.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_jpg:n #1
{
\tex_pdfrefximage:D
\int_use:c { c_@@_image_ #1 \l_@@_image_attr_tl _int }
}
\cs_new_eq:NN \@@_image_include_pdf:n \@@_image_include_jpg:n
\cs_new_eq:NN \@@_image_include_png:n \@@_image_include_jpg:n
% \end{macrocode}
% \end{macro}
%
% \subsubsection{PDF Objects}
%
% \begin{variable}{\g_@@_pdf_object_prop}
% For tracking objects to allow finalisation.
% \begin{macrocode}
\prop_new:N \g_@@_pdf_object_prop
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\driver_pdf_object_new:nn}
% \begin{macro}[EXP]{\driver_pdf_object_ref:n}
% Declaring objects means reserving at the PDF level plus starting
% tracking.
% \begin{macrocode}
\group_begin:
\cs_set_protected:Npn \@@_tmp:w #1#2
{
\cs_new_protected:Npx \driver_pdf_object_new:nn ##1##2
{
#1 reserveobjnum ~
\int_const:cn
{ g_@@_pdf_object_ \exp_not:N \tl_to_str:n {##1} _int }
{#2}
\prop_gput:Nnn \exp_not:N \g_@@_pdf_object_prop {##1} {##2}
}
}
\cs_if_exist:NTF \tex_pdfextension:D
{
\@@_tmp:w
{ \tex_pdfextension:D obj ~ }
{ \tex_pdffeedback:D lastobj \scan_stop: }
}
{ \@@_tmp:w { \tex_pdfobj:D } { \tex_pdflastobj:D } }
\group_end:
\cs_new:Npn \driver_pdf_object_ref:n #1
{ \int_use:c { g_@@_pdf_object_ \tl_to_str:n {#1} _int } ~ 0 ~ R }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_pdf_object_write:nn}
% \begin{macro}[EXP]{\@@_exp_not_i:nn, \@@_exp_not_ii:nn}
% Writing the data needs a little information about the structure of the
% object.
% \begin{macrocode}
\group_begin:
\cs_set_protected:Npn \@@_tmp:w #1
{
\cs_new_protected:Npn \driver_pdf_object_write:nn ##1##2
{
\tex_immediate:D #1 useobjnum ~
\int_use:c
{ g_@@_pdf_object_ \tl_to_str:n {##1} _int }
\str_case_e:nn
{ \prop_item:Nn \g_@@_pdf_object_prop {##1} }
{
{ array } { { [ ~ \exp_not:n {##2} ~ ] } }
{ dict } { { << ~ \exp_not:n {##2} ~ >> } }
{ fstream }
{
stream ~ attr ~ { \@@_exp_not_i:nn ##2 } ~
file ~ { \@@_exp_not_ii:nn ##2 }
}
{ stream }
{
stream ~ attr ~ { \@@_exp_not_i:nn ##2 } ~
{ \@@_exp_not_ii:nn ##2 }
}
}
}
}
\cs_if_exist:NTF \tex_pdfextension:D
{ \@@_tmp:w { \tex_pdfextension:D obj ~ } }
{ \@@_tmp:w { \tex_pdfobj:D } }
\group_end:
\cs_new:Npn \@@_exp_not_i:nn #1#2 { \exp_not:n {#1} }
\cs_new:Npn \@@_exp_not_ii:nn #1#2 { \exp_not:n {#2} }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{PDF Structure}
%
% \begin{macro}{\driver_pdf_compresslevel:n}
% \begin{macro}{\driver_pdf_objects_enable:, \driver_pdf_objects_disable:}
% \begin{macro}{\@@_pdf_objectlevel:n}
% Simply pass data to the engine.
% \begin{macrocode}
\cs_new_protected:Npx \driver_pdf_compresslevel:n #1
{
\cs_if_exist:NTF \tex_pdfcompresslevel:D
{ \tex_pdfcompresslevel:D }
{ \tex_pdfvariable:D compresslevel }
\exp_not:N \int_value:w \exp_not:N \int_eval:n {#1} \scan_stop:
}
\cs_new_protected:Npn \driver_pdf_objects_enable:
{ \@@_pdf_objectlevel:n { 2 } }
\cs_new_protected:Npn \driver_pdf_objects_disable:
{ \@@_pdf_objectlevel:n { 0 } }
\cs_new_protected:Npx \@@_pdf_objectlevel:n #1
{
\cs_if_exist:NTF \tex_pdfobjcompresslevel:D
{ \tex_pdfobjcompresslevel:D }
{ \tex_pdfvariable:D objcompresslevel }
#1 \scan_stop:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{dvipdfmx} driver}
%
% \begin{macrocode}
%<*dvipdfmx|xdvipdfmx>
% \end{macrocode}
%
% The \texttt{dvipdfmx} shares code with the PDF mode one (using the common
% section to this file) but also with \texttt{xdvipdfmx}. The latter is close
% to identical to \texttt{dvipdfmx} and so all of the code here is extracted
% for both drivers, with some \texttt{clean up} for \texttt{xdvipdfmx} as
% required.
%
% \subsubsection{Basics}
%
% \begin{macro}{\@@_literal_pdf:n, \@@_literal_pdf:x}
% Equivalent to \texttt{pdf:content} but favored as the link to
% the \pdfTeX{} primitive approach is clearer.
% \begin{macrocode}
\cs_new_protected:Npn \@@_literal_pdf:n #1
{ \@@_literal:n { pdf:literal~ #1 } }
\cs_generate_variant:Nn \@@_literal_pdf:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_scope_begin:, \@@_scope_end:}
% Scoping is done using the driver-specific specials.
% \begin{macrocode}
\cs_new_protected:Npn \@@_scope_begin:
{ \@@_literal:n { x:gsave } }
\cs_new_protected:Npn \@@_scope_end:
{ \@@_literal:n { x:grestore } }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Box operations}
%
% \begin{macro}{\driver_box_use_clip:N}
% The code here is identical to that for \texttt{pdfmode}: unlike rotation and
% scaling, there is no higher-level support in the driver for clipping.
% \begin{macrocode}
\cs_new_protected:Npn \driver_box_use_clip:N #1
{
\@@_scope_begin:
\@@_literal_pdf:x
{
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 }
\@@_scope_end:
\skip_horizontal:n { \box_wd:N #1 }
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_box_use_rotate:Nn}
% \begin{macro}{\@@_box_use_rotate:Nn}
% Rotating in \texttt{(x)}dvipdmfx can be implemented using either PDF or
% driver-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 driver-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 \driver_box_use_rotate:Nn #1#2
{ \exp_args:NNf \@@_box_use_rotate:Nn #1 { \fp_eval:n {#2} } }
\cs_new_protected:Npn \@@_box_use_rotate:Nn #1#2
{
\@@_scope_begin:
\@@_literal:x
{
x:rotate~
\fp_compare:nNnTF {#2} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( #2 , 5 ) } }
}
\box_use:N #1
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_box_use_scale:Nnn}
% Much the same idea for scaling: use the higher-level driver operation to allow
% for box content.
% \begin{macrocode}
\cs_new_protected:Npn \driver_box_use_scale:Nnn #1#2#3
{
\@@_scope_begin:
\@@_literal:x
{
x:scale~
\fp_eval:n { round ( #2 , 5 ) } ~
\fp_eval:n { round ( #3 , 5 ) }
}
\hbox_overlap_right:n { \box_use:N #1 }
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Images}
%
% \begin{macro}
% {
% \@@_image_getbb_eps:n, \@@_image_getbb_jpg:n,
% \@@_image_getbb_pdf:n, \@@_image_getbb_png:n
% }
% Simply use the generic functions: only for \texttt{dvipdfmx} in the
% extraction cases.
% \begin{macrocode}
\cs_new_eq:NN \@@_image_getbb_eps:n \image_read_bb:n
%<*dvipdfmx>
\cs_new_protected:Npn \@@_image_getbb_jpg:n #1
{
\int_zero:N \l_image_page_int
\tl_clear:N \l_image_pagebox_tl
\image_extract_bb:n {#1}
}
\cs_new_eq:NN \@@_image_getbb_png:n \@@_image_getbb_jpg:n
\cs_new_protected:Npn \@@_image_getbb_pdf:n #1
{
\tl_clear:N \l_image_decode_tl
\bool_set_false:N \l_image_interpolate_bool
\image_extract_bb:n {#1}
}
%
% \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_image_int}
% Used to track the object number associated with each image.
% \begin{macrocode}
\int_new:N \g_@@_image_int
% \end{macrocode}
% \end{variable}
%
% \begin{macro}
% {
% \@@_image_include_eps:n, \@@_image_include_jpg:n,
% \@@_image_include_pdf:n, \@@_image_include_png:n
% }
% \begin{macro}{\@@_image_include_auxi:nn}
% \begin{macro}{\@@_image_include_auxii:nnn, \@@_image_include_auxii:xnn}
% \begin{macro}{\@@_image_include_auxiii:nn}
% The special syntax depends on the file type. There is a difference in
% how PDF images are best handled between |dvipdfmx| and |xdvipdfmx|: for
% the latter it is better to use the primitive route. The relevant code for
% that is included later in this file.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_eps:n #1
{
\@@_literal:n { PSfile = #1 }
}
\cs_new_protected:Npn \@@_image_include_jpg:n #1
{ \@@_image_include_auxi:nn {#1} { image } }
\cs_new_eq:NN \@@_image_include_png:n \@@_image_include_jpg:n
%<*dvipdfmx>
\cs_new_protected:Npn \@@_image_include_pdf:n #1
{ \@@_image_include_auxi:nn {#1} { epdf } }
%
% \end{macrocode}
% Image inclusion is set up to use the fact that each image is stored in
% the PDF as an XObject. This means that we can include repeated images
% only once and refer to them. To allow that, track the nature of each
% image: much the same as for the direct PDF mode case.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_auxi:nn #1#2
{
\@@_image_include_auxii:xnn
{
\tl_if_empty:NF \l_image_pagebox_tl
{ : \l_image_pagebox_tl }
\int_compare:nNnT \l_image_page_int > 1
{ :P \int_use:N \l_image_page_int }
\tl_if_empty:NF \l_image_decode_tl
{ :D \l_image_decodearray_tl }
\bool_if:NT \l_image_interpolate_bool
{ :I }
}
{#1} {#2}
}
\cs_new_protected:Npn \@@_image_include_auxii:nnn #1#2#3
{
\int_if_exist:cTF { c_@@_image_ #2#1 _int }
{
\@@_literal:x
{ pdf:usexobj~@image \int_use:c { c_@@_image_ #2#1 _int } }
}
{ \@@_image_include_auxiii:nn {#2} {#1} {#3} }
}
\cs_generate_variant:Nn \@@_image_include_auxii:nnn { x }
% \end{macrocode}
% Inclusion using the specials is relatively straight-forward, but there
% is one wrinkle. To get the |pagebox| correct for PDF images in all cases,
% it is necessary to provide both that information and the |bbox| argument:
% odd things happen otherwise!
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_auxiii:nnn #1#2#3
{
\int_gincr:N \g_@@_image_int
\int_const:cn { c_@@_image_ #1#2 _int } { \g_@@_image_int }
\@@_literal:x
{
pdf:#3~
@image \int_use:c { c_@@_image_ #1#2 _int }
\int_compare:nNnT \l_image_page_int > 1
{ page ~ \int_use:N \l_image_page_int \c_space_tl }
\tl_if_empty:NF \l_image_pagebox_tl
{
pagebox ~ \l_image_pagebox_tl \c_space_tl
bbox ~
\dim_to_decimal_in_bp:n \l_image_llx_dim \c_space_tl
\dim_to_decimal_in_bp:n \l_image_lly_dim \c_space_tl
\dim_to_decimal_in_bp:n \l_image_urx_dim \c_space_tl
\dim_to_decimal_in_bp:n \l_image_ury_dim \c_space_tl
}
(#1)
\bool_lazy_or:nnT
{ \l_image_interpolate_bool }
{ ! \tl_if_empty_p:N \l_image_decodearray_tl }
{
<<
\tl_if_empty:NF \l_image_decode_tl
{ /Decode~[ \l_image_decodearray_tl ] }
\bool_if:NT \l_image_interpolate_bool
{ /Interpolate~true> }
>>
}
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsubsection{PDF Objects}
%
% \begin{variable}{\g_@@_pdf_object_int, \g_@@_pdf_object_prop}
% For tracking objects to allow finalisation.
% \begin{macrocode}
\int_new:N \g_@@_pdf_object_int
\prop_new:N \g_@@_pdf_object_prop
% \end{macrocode}
% \end{variable}
%
% \begin{macro}{\driver_pdf_object_new:nn}
% \begin{macro}[EXP]{\driver_pdf_object_ref:n}
% Objects are tracked at the macro level, but we don't have to do anything
% at this stage.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_object_new:nn #1#2
{
\int_gincr:N \g_@@_pdf_object_int
\int_const:cn
{ g_@@_pdf_object_ \tl_to_str:n {#1} _int }
{ \g_@@_pdf_object_int }
\prop_gput:Nnn \g_@@_pdf_object_prop {#1} {#2}
}
\cs_new:Npn \driver_pdf_object_ref:n #1
{ @l3obj \int_use:c { g_@@_pdf_object_ \tl_to_str:n {#1} _int } }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_pdf_object_write:nn}
% \begin{macro}{\@@_pdf_object_write:nnn}
% \begin{macro}
% {
% \@@_pdf_object_write_array:nn ,
% \@@_pdf_object_write_dict:nn ,
% \@@_pdf_object_write_fstream:nn ,
% \@@_pdf_object_write_stream:nn
% }
% \begin{macro}{\@@_pdf_object_write_stream:nnnn}
% This is where we choose the actual type.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_object_write:nn #1#2
{
\exp_args:Nx \@@_pdf_object_write:nnn
{ \prop_item:Nn \g_@@_pdf_object_prop {#1} } {#1} {#2}
}
\cs_new_protected:Npn \@@_pdf_object_write:nnn #1#2#3
{ \use:c { @@_pdf_object_write_ #1 :nn } {#2} {#3} }
\cs_new_protected:Npn \@@_pdf_object_write_array:nn #1#2
{
\@@_literal:x
{
pdf:obj ~ \driver_pdf_object_ref:n {#1} ~
[ ~ \exp_not:n {#2} ~ ]
}
}
\cs_new_protected:Npn \@@_pdf_object_write_dict:nn #1#2
{
\@@_literal:x
{
pdf:obj ~ \driver_pdf_object_ref:n {#1} ~
<< ~ \exp_not:n {#2} ~ >>
}
}
\cs_new_protected:Npn \@@_pdf_object_write_fstream:nn #1#2
{ \@@_pdf_object_write_stream:nnnn { f } {#1} #2 }
\cs_new_protected:Npn \@@_pdf_object_write_stream:nn #1#2
{ \@@_pdf_object_write_stream:nnnn { } {#1} #2 }
\cs_new_protected:Npn \@@_pdf_object_write_stream:nnnn #1#2#3#4
{
\@@_literal:x
{
pdf: #1 stream ~ \driver_pdf_object_ref:n {#2} ~
( \exp_not:n {#4} ) ~ << \exp_not:n {#3} >>
}
}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsubsection{PDF Structure}
%
% \begin{macro}{\driver_pdf_compresslevel:n}
% \begin{macro}{\driver_pdf_objects_enable:, \driver_pdf_objects_disable:}
% Pass data to the driver: these are a one-shot.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_compresslevel:n #1
{ \@@_literal:x { dvipdfmx:config~z~ \int_eval:n {#1} } }
\cs_new_protected:Npn \driver_pdf_objects_enable: { }
\cs_new_protected:Npn \driver_pdf_objects_disable:
{ \@@_literal:n { dvipdfmx:config~C~0x40 } }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{xdvipdfmx} driver}
%
% \begin{macrocode}
%<*xdvipdfmx>
% \end{macrocode}
%
% \subsubsection{Images}
%
% \begin{macro}
% {\@@_image_getbb_jpg:n, \@@_image_getbb_pdf:n, \@@_image_getbb_png:n}
% \begin{macro}{\@@_image_getbb_auxi:nN}
% \begin{macro}{\@@_image_getbb_auxii:nnN, \@@_image_getbb_auxii:VnN}
% \begin{macro}{\@@_image_getbb_auxiii:nNnn}
% \begin{macro}{\@@_image_getbb_auxiv:nnNnn, \@@_image_getbb_auxiv:VnNnn}
% \begin{macro}{\@@_image_getbb_auxv:nNnn, \@@_image_getbb_auxv:nNnn}
% \begin{macro}[EXP]{\@@_image_getbb_pagebox:w}
% For \texttt{xdvipdfmx}, there are two primitives that allow us to obtain
% the bounding box without needing \texttt{extractbb}. The only complexity
% is passing the various minor variations to a common core process. The
% \XeTeX{} primitive omits the text |box| from the page box specification,
% so there is also some \enquote{trimming} to do here.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_getbb_jpg:n #1
{
\int_zero:N \l_image_page_int
\tl_clear:N \l_image_pagebox_tl
\@@_image_getbb_auxi:nN {#1} \tex_XeTeXpicfile:D
}
\cs_new_eq:NN \@@_image_getbb_png:n \@@_image_getbb_jpg:n
\cs_new_protected:Npn \@@_image_getbb_pdf:n #1
{
\tl_clear:N \l_image_decode_tl
\bool_set_false:N \l_image_interpolate_bool
\@@_image_getbb_auxi:nN {#1} \tex_XeTeXpdffile:D
}
\cs_new_protected:Npn \@@_image_getbb_auxi:nN #1#2
{
\int_compare:nNnTF \l_image_page_int > 1
{ \@@_image_getbb_auxii:VnN \l_image_page_int {#1} #2 }
{ \@@_image_getbb_auxiii:nNnn {#1} #2 }
}
\cs_new_protected:Npn \@@_image_getbb_auxii:nnN #1#2#3
{ \@@_image_getbb_aux:nNnn {#2} #3 { :P #1 } { page #1 } }
\cs_generate_variant:Nn \@@_image_getbb_auxii:nnN { V }
\cs_new_protected:Npn \@@_image_getbb_auxiii:nNnn #1#2#3#4
{
\tl_if_empty:NTF \l_image_pagebox_tl
{ \@@_image_getbb_auxiv:VnNnn \l_image_pagebox_tl }
{ \@@_image_getbb_auxv:nNnn }
{#1} #2 {#3} {#4}
}
\cs_new_protected:Npn \@@_image_getbb_auxiv:nnNnn #1#2#3#4#5
{
\use:x
{
\@@_image_getbb_auxv:nNnn {#2} #3 { : #1 #4 }
{ #5 ~ \@@_image_getbb_pagebox:w #1 }
}
}
\cs_generate_variant:Nn \@@_image_getbb_auxiv:nnNnn { V }
\cs_new_protected:Npn \@@_image_getbb_auxv:nNnn #1#2#3#4
{
\image_bb_restore:nF {#1#3}
{ \@@_image_getbb_auxvi:nNnn {#1} #2 {#3} {#4} }
}
\cs_new_protected:Npn \@@_image_getbb_auxvi:nNnn #1#2#3#4
{
\hbox_set:Nn \l_@@_tmp_box { #2 #1 ~ #4 }
\dim_set:Nn \l_image_utx_dim { \box_wd:N \l_@@_tmp_box }
\dim_set:Nn \l_image_ury_dim { \box_ht:N \l_@@_tmp_box }
\image_bb_save:n {#1#3}
}
\cs_new:Npn \@@_image_getbb_pagebox:w #1 box {#1}
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_image_include_pdf:n}
% For PDF images, properly supporting the |pagebox| concept in \XeTeX{}
% is best done using the |\tex_XeTeXpdffile:D| primitive. The syntax here
% is the same as for the image measurement part, although we know at this
% stage that there must be some valid setting for \cs{l_image_pagebox_tl}.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_pdf:n #1
{
\tex_XeTeXpdffile:D "#1" ~
\int_compare:nNnT \l_image_page_int > 0
{ page~ \int_use:N \l_image_page_int }
\@@_image_getbb_auxiv:VnNnn \l_image_pagebox_tl
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{Drawing commands: \texttt{pdfmode} and \texttt{(x)dvipdfmx}}
%
% Both \texttt{pdfmode} and \texttt{(x)dvipdfmx} directly produce PDF output
% and understand a shared set of specials for drawing commands.
%
% \begin{macrocode}
%<*dvipdfmx|pdfmode|xdvipdfmx>
% \end{macrocode}
%
% \subsubsection{Drawing}
%
% \begin{macro}{\@@_draw_literal:n, \@@_draw_literal:x}
% Pass data through using a dedicated interface.
% \begin{macrocode}
\cs_new_eq:NN \@@_draw_literal:n \@@_literal_pdf:n
\cs_generate_variant:Nn \@@_draw_literal:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_begin:, \driver_draw_end:}
% No special requirements here, so simply set up a drawing scope.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_begin:
{ \driver_draw_scope_begin: }
\cs_new_protected:Npn \driver_draw_end:
{ \driver_draw_scope_end: }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_scope_begin:, \driver_draw_scope_end:}
% Use the driver-level scope mechanisms.
% \begin{macrocode}
\cs_new_eq:NN \driver_draw_scope_begin: \@@_scope_begin:
\cs_new_eq:NN \driver_draw_scope_end: \@@_scope_end:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_moveto:nn, \driver_draw_lineto:nn}
% \begin{macro}{\driver_draw_curveto:nnnnnn}
% \begin{macro}{\driver_draw_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 \driver_draw_moveto:nn #1#2
{
\@@_draw_literal:x
{ \dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~ m }
}
\cs_new_protected:Npn \driver_draw_lineto:nn #1#2
{
\@@_draw_literal:x
{ \dim_to_decimal_in_bp:n {#1} ~ \dim_to_decimal_in_bp:n {#2} ~ l }
}
\cs_new_protected:Npn \driver_draw_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_draw_literal:x
{
\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 \driver_draw_rectangle:nnnn #1#2#3#4
{
\@@_draw_literal:x
{
\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}{\driver_draw_evenodd_rule:, \driver_draw_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 \driver_draw_evenodd_rule:
{ \bool_gset_true:N \g_@@_draw_eor_bool }
\cs_new_protected:Npn \driver_draw_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}
% {
% \driver_draw_closepath: ,
% \driver_draw_stroke: ,
% \driver_draw_closestroke: ,
% \driver_draw_fill: ,
% \driver_draw_fillstroke: ,
% \driver_draw_clip: ,
% \driver_draw_discardpath:
% }
% Converting paths to output is again a case of mapping directly to
% PDF operations.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_closepath:
{ \@@_draw_literal:n { h } }
\cs_new_protected:Npn \driver_draw_stroke:
{ \@@_draw_literal:n { S } }
\cs_new_protected:Npn \driver_draw_closestroke:
{ \@@_draw_literal:n { s } }
\cs_new_protected:Npn \driver_draw_fill:
{
\@@_draw_literal:x
{ f \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \driver_draw_fillstroke:
{
\@@_draw_literal:x
{ B \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \driver_draw_clip:
{
\@@_draw_literal:x
{ W \bool_if:NT \g_@@_draw_eor_bool * }
}
\cs_new_protected:Npn \driver_draw_discardpath:
{ \@@_draw_literal:n { n } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_dash_pattern:nn}
% \begin{macro}{\@@_draw_dash:n}
% \begin{macro}{\driver_draw_linewidth:n}
% \begin{macro}{\driver_draw_miterlimit:n}
% \begin{macro}
% {
% \driver_draw_cap_butt:, \driver_draw_cap_round:, \driver_draw_cap_rectangle:,
% \driver_draw_join_miter:, \driver_draw_join_round:, \driver_draw_join_bevel:
% }
% Converting paths to output is again a case of mapping directly to
% PDF operations.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_dash_pattern:nn #1#2
{
\@@_draw_literal:x
{
[
\exp_args:Nf \use:n
{ \clist_map_function:nN {#1} \@@_draw_dash:n }
] ~
\dim_to_decimal_in_bp:n {#2} ~ d
}
}
\cs_new:Npn \@@_draw_dash:n #1
{ ~ \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \driver_draw_linewidth:n #1
{
\@@_draw_literal:x
{ \dim_to_decimal_in_bp:n {#1} ~ w }
}
\cs_new_protected:Npn \driver_draw_miterlimit:n #1
{ \@@_draw_literal:x { \fp_eval:n {#1} ~ M } }
\cs_new_protected:Npn \driver_draw_cap_butt:
{ \@@_draw_literal:n { 0 ~ J } }
\cs_new_protected:Npn \driver_draw_cap_round:
{ \@@_draw_literal:n { 1 ~ J } }
\cs_new_protected:Npn \driver_draw_cap_rectangle:
{ \@@_draw_literal:n { 2 ~ J } }
\cs_new_protected:Npn \driver_draw_join_miter:
{ \@@_draw_literal:n { 0 ~ j } }
\cs_new_protected:Npn \driver_draw_join_round:
{ \@@_draw_literal:n { 1 ~ j } }
\cs_new_protected:Npn \driver_draw_join_bevel:
{ \@@_draw_literal:n { 2 ~ j } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}
% {
% \driver_draw_color_fill_cmyk:nnnn ,
% \driver_draw_color_stroke_cmyk:nnnn
% }
% \begin{macro}
% {
% \driver_draw_color_fill_gray:n ,
% \driver_draw_color_stroke_gray:n
% }
% \begin{macro}
% {
% \driver_draw_color_fill_rgb:nnn ,
% \driver_draw_color_stroke_rgb:nnn
% }
% \begin{macro}{\@@_color_fill_select:n, \@@_color_fill_select:x}
% For the stroke color, all engines here can use the color stack to handle
% the setting. However, that is not the case for fill color: the stack in
% \texttt{(x)dvipdfmx} only covers one type of color. So we have to use
% different approaches for the two sets of engines.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_color_fill_cmyk:nnnn #1#2#3#4
{
\@@_color_fill_select:x
{
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4} ~
k
}
}
\cs_new_protected:Npn \driver_draw_color_stroke_cmyk:nnnn #1#2#3#4
{
\@@_color_select:x
{
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4} ~
k
}
}
\cs_new_protected:Npn \driver_draw_color_fill_gray:n #1
{ \@@_color_fill_select:x { \fp_eval:n {#1} ~ g } }
\cs_new_protected:Npn \driver_draw_color_stroke_gray:n #1
{ \@@_color_select:x { \fp_eval:n {#1} ~ G } }
\cs_new_protected:Npn \driver_draw_color_fill_rgb:nnn #1#2#3
{
\@@_color_fill_select:x
{ \fp_eval:n {#1} ~ \fp_eval:n {#2} ~ \fp_eval:n {#3} ~ rg }
}
\cs_new_protected:Npn \driver_draw_color_stroke_rgb:nnn #1#2#3
{
\@@_color_select:x
{ \fp_eval:n {#1} ~ \fp_eval:n {#2} ~ \fp_eval:n {#3} ~ RG }
}
%<*pdfmode>
\cs_new_eq:NN \@@_color_fill_select:n \@@_color_select:n
%
%<*dvipdfmx|xdvipdfmx>
\cs_new_eq:NN \@@_color_fill_select:n \@@_draw_literal:n
%
\cs_generate_variant:Nn \@@_color_fill_select:n { x }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_cm:nnnn}
% \begin{macro}{\@@_draw_cm:nnnn}
% Another split here between \texttt{pdfmode} and \texttt{(x)dvipdfmx}.
% In the former, we have a direct method to maintain alignment: the driver
% can use a matrix itself. For \texttt{(x)dvipdfmx}, we can to decompose the
% matrix into rotations and a scaling, then use those operations as they
% are handled by the driver. (There is driver support for matrix operations in
% \texttt{(x)dvipdfmx}, but as a matched pair so not suitable for the
% \enquote{stand alone} transformation set up here.)
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_cm:nnnn #1#2#3#4
{
%<*pdfmode>
\@@_matrix:x
{
\fp_eval:n {#1} ~ \fp_eval:n {#2} ~
\fp_eval:n {#3} ~ \fp_eval:n {#4}
}
%
%<*dvipdfmx|xdvipdfmx>
\@@_draw_cm_decompose:nnnnN {#1} {#2} {#3} {#4}
\@@_draw_cm:nnnn
%
}
%<*dvipdfmx|xdvipdfmx>
\cs_new_protected:Npn \@@_draw_cm:nnnn #1#2#3#4
{
\@@_literal:x
{
x:rotate~
\fp_compare:nNnTF {#1} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( -#1 , 5 ) } }
}
\@@_literal:x
{
x:scale~
\fp_eval:n { round ( #2 , 5 ) } ~
\fp_eval:n { round ( #3 , 5 ) }
}
\@@_literal:x
{
x:rotate~
\fp_compare:nNnTF {#4} = \c_zero_fp
{ 0 }
{ \fp_eval:n { round ( -#4 , 5 ) } }
}
}
%
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_draw_cm_decompose:nnnnN}
% \begin{macro}
% {
% \@@_draw_cm_decompose_auxi:nnnnN,
% \@@_draw_cm_decompose_auxii:nnnnN,
% \@@_draw_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|xdvipdfmx>
\cs_new_protected:Npn \@@_draw_cm_decompose:nnnnN #1#2#3#4#5
{
\use:x
{
\@@_draw_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 \@@_draw_cm_decompose_auxi:nnnnN #1#2#3#4#5
{
\use:x
{
\@@_draw_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 \@@_draw_cm_decompose_auxii:nnnnN #1#2#3#4#5
{
\use:x
{
\@@_draw_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 \@@_draw_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}{\driver_draw_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 \driver_draw_box_use:Nnnnn #1#2#3#4#5
{
\@@_scope_begin:
%<*pdfmode>
\driver_draw_cm:nnnn {#2} {#3} {#4} {#5}
%
%<*dvipdfmx|xdvipdfmx>
\@@_literal:x
{
pdf:btrans~matrix~
\fp_eval:n {#2} ~ \fp_eval:n {#3} ~
\fp_eval:n {#4} ~ \fp_eval:n {#5} ~
0 ~ 0
}
%
\hbox_overlap_right:n { \box_use:N #1 }
%<*dvipdfmx|xdvipdfmx>
\@@_literal:n { pdf:etrans }
%
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \subsection{\texttt{dvisvgm} driver}
%
% \begin{macrocode}
%<*dvisvgm>
% \end{macrocode}
%
% \subsubsection{Basics}
%
% \begin{macro}{\@@_literal_svg:n, \@@_literal_svg:x}
% Unlike the other drivers, the requirements for making SVG files mean
% that we can't conveniently transform all operations to the current point.
% That makes life a bit more tricky later as that needs to be accounted for.
% A new line is added after each call to help to keep the output readable
% for debugging.
% \begin{macrocode}
\cs_new_protected:Npn \@@_literal_svg:n #1
{ \@@_literal:n { dvisvgm:raw~ #1 { ?nl } } }
\cs_generate_variant:Nn \@@_literal_svg:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_scope_begin:, \@@_scope_end:}
% A scope in SVG terms is slightly different to the other drivers as
% operations have to be \enquote{tied} to these not simply inside them.
% \begin{macrocode}
\cs_new_protected:Npn \@@_scope_begin:
{ \@@_literal_svg:n { } }
\cs_new_protected:Npn \@@_scope_end:
{ \@@_literal_svg:n { } }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Driver-specific auxiliaries}
%
% \begin{macro}{\@@_scope_begin:n, \@@_scope_begin:x}
% In SVG transformations, clips and so on are attached directly to scopes so
% we need a way or allowing for that. This is rather more useful than
% \cs{@@_scope_begin:} as a result. No assumptions are made about the nature
% of the scoped operation(s).
% \begin{macrocode}
\cs_new_protected:Npn \@@_scope_begin:n #1
{ \@@_literal_svg:n { } }
\cs_generate_variant:Nn \@@_scope_begin:n { x }
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Box operations}
%
% \begin{macro}{\driver_box_use_clip:N}
% \begin{variable}{\g_@@_clip_path_int}
% Clipping in SVG is more involved than with other drivers. 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 \driver_box_use_clip:N #1
{
\int_gincr:N \g_@@_clip_path_int
\@@_literal_svg:x
{ < clipPath~id = " l3cp \int_use:N \g_@@_clip_path_int " > }
\@@_literal_svg:x
{
<
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
"
/>
}
\@@_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}
\@@_scope_begin:n
{
transform =
"
translate ( { ?x } , { ?y } ) ~
scale ( 1 , -1 )
"
}
\@@_scope_begin:x
{
clip-path =
"url ( \c_hash_str l3cp \int_use:N \g_@@_clip_path_int ) "
}
\@@_scope_begin:n
{
transform =
"
scale ( -1 , 1 ) ~
translate ( { ?x } , { ?y } ) ~
scale ( -1 , -1 )
"
}
\box_use:N #1
\@@_scope_end:
\@@_scope_end:
\@@_scope_end:
% \skip_horizontal:n { \box_wd:N #1 }
}
\int_new:N \g_@@_clip_path_int
% \end{macrocode}
% \end{variable}
% \end{macro}
%
% \begin{macro}{\driver_box_use_rotate:Nn}
% Rotation has a dedicated operation which includes a centre-of-rotation
% optional pair. That can be picked up from the driver syntax, so there is
% no need to worry about the transformation matrix.
% \begin{macrocode}
\cs_new_protected:Npn \driver_box_use_rotate:Nn #1#2
{
\@@_scope_begin:x
{
transform =
"
rotate
( \fp_eval:n { round ( -(#2) , 5 ) } , ~ { ?x } , ~ { ?y } )
"
}
\box_use:N #1
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_box_use_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 \driver_box_use_scale:Nnn #1#2#3
{
\@@_scope_begin:x
{
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 }
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \subsubsection{Images}
%
% \begin{macro}{\@@_image_getbb_png:n, \@@_image_getbb_jpg:n}
% These can be included by extracting the bounding box data.
% \begin{macrocode}
\cs_new_eq:NN \@@_image_getbb_png:n \image_extract_bb:n
\cs_new_eq:NN \@@_image_getbb_jpg:n \image_extract_bb:n
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_image_include_png:n, \@@_image_include_jpg:n}
% \begin{macro}{\@@_image_include_bitmap_quote:w}
% The driver here has built-in support for basic image inclusion (see
% \texttt{dvisvgm.def} for a more complex approach, needed if clipping,
% \emph{etc.}, is covered at the image driver level). The only issue is
% that |#1| must be quote-corrected. The \texttt{dvisvgm:img} operation
% quotes the file name, but if it is already quoted (contains spaces)
% then we have an issue: we simply strip off any quotes as a result.
% \begin{macrocode}
\cs_new_protected:Npn \@@_image_include_png:n #1
{
\@@_literal:x
{
dvisvgm:img~
\dim_to_decimal:n { \l_image_ury_dim } ~
\dim_to_decimal:n { \l_image_ury_dim } ~
\@@_image_include_bitmap_quote:w #1 " " \q_stop
}
}
\cs_new_eq:NN \@@_image_include_jpg:n \@@_image_include_png:n
\cs_new:Npn \@@_image_include_bitmap_quote:w #1 " #2 " #3 \q_stop
{ #1#2 }
% \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{PDF Features}
%
% \begin{macro}{\driver_pdf_object_new:n}
% \begin{macro}[EXP]{\driver_pdf_object_ref:n}
% \begin{macro}{\driver_pdf_ojbect_write:nn}
% \begin{macro}{\driver_pdf_compresslevel:n}
% \begin{macro}{\driver_pdf_objects_enable:, \driver_pdf_objects_disable:}
% These are all no-ops.
% \begin{macrocode}
\cs_new_protected:Npn \driver_pdf_object_new:n #1 { }
\cs_new:Npn \driver_pdf_object_ref:n #1 { }
\cs_new_protected:Npn \driver_pdf_object_write:nn #1#2 { }
\cs_new_protected:Npn \driver_pdf_compresslevel:n #1 { }
\cs_new_protected:Npn \driver_pdf_objects_enable: { }
\cs_new_protected:Npn \driver_pdf_objects_disable: { }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsubsection{Drawing}
%
% \begin{macro}{\@@_draw_literal:n, \@@_draw_literal:x}
% The same as the more general literal call.
% \begin{macrocode}
\cs_new_eq:NN \@@_draw_literal:n \@@_literal_svg:n
\cs_generate_variant:Nn \@@_draw_literal:n { x }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_begin:, \driver_draw_end:}
% A drawing needs to be set up such that the co-ordinate system is
% translated. That is done inside a scope, which as described below
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_begin:
{
\driver_draw_scope_begin:
\@@_draw_scope:n { transform="translate({?x},{?y})~scale(1,-1)" }
}
\cs_new_protected:Npn \driver_draw_end:
{ \driver_draw_scope_end: }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_scope_begin:, \driver_draw_scope_end:}
% \begin{macro}{\@@_draw_scope:n, \@@_draw_scope:x}
% \begin{variable}{\g_@@_draw_scope_int, \l_@@_draw_scope_int}
% Several settings that with other drivers are \enquote{stand alone} have
% to be given as part of a scope in SVG. As a result, there is a need to
% provide a mechanism to automatically close these extra scopes. That is
% done using a dedicated function and a pair of tracking variables. Within
% each graphics scope we use a global variable to do the work, with a group
% used to save the value between scopes. The result is that no direct action
% is needed when creating a scope.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_scope_begin:
{
\int_set_eq:NN
\l_@@_draw_scope_int
\g_@@_draw_scope_int
\group_begin:
\int_gzero:N \g_@@_draw_scope_int
}
\cs_new_protected:Npn \driver_draw_scope_end:
{
\prg_replicate:nn
{ \g_@@_draw_scope_int }
{ \@@_draw_literal:n { } }
\group_end:
\int_gset_eq:NN
\g_@@_draw_scope_int
\l_@@_draw_scope_int
}
\cs_new_protected:Npn \@@_draw_scope:n #1
{
\@@_draw_literal:n { }
\int_gincr:N \g_@@_draw_scope_int
}
\cs_generate_variant:Nn \@@_draw_scope:n { x }
\int_new:N \g_@@_draw_scope_int
\int_new:N \l_@@_draw_scope_int
% \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_moveto:nn, \driver_draw_lineto:nn}
% \begin{macro}{\driver_draw_rectangle:nnnn}
% \begin{macro}{\driver_draw_curveto:nnnnnn}
% \begin{macro}{\@@_draw_add_to_path:n}
% \begin{variable}{\g_@@_draw_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 \driver_draw_moveto:nn #1#2
{
\@@_draw_add_to_path:n
{ M ~ \dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2} }
}
\cs_new_protected:Npn \driver_draw_lineto:nn #1#2
{
\@@_draw_add_to_path:n
{ L ~ \dim_to_decimal:n {#1} ~ \dim_to_decimal:n {#2} }
}
\cs_new_protected:Npn \driver_draw_rectangle:nnnn #1#2#3#4
{
\@@_draw_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 \driver_draw_curveto:nnnnnn #1#2#3#4#5#6
{
\@@_draw_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 \@@_draw_add_to_path:n #1
{
\tl_gset:Nx \g_@@_draw_path_tl
{
\g_@@_draw_path_tl
\tl_if_empty:NF \g_@@_draw_path_tl { \c_space_tl }
#1
}
}
\tl_new:N \g_@@_draw_path_tl
% \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_evenodd_rule:, \driver_draw_nonzero_rule:}
% The fill rules here have to be handled as scopes.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_evenodd_rule:
{ \@@_draw_scope:n { fill-rule="evenodd" } }
\cs_new_protected:Npn \driver_draw_nonzero_rule:
{ \@@_draw_scope:n { fill-rule="nonzero" } }
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_draw_path:n}
% \begin{macro}
% {
% \driver_draw_closepath: ,
% \driver_draw_stroke: ,
% \driver_draw_closestroke: ,
% \driver_draw_fill: ,
% \driver_draw_fillstroke: ,
% \driver_draw_clip: ,
% \driver_draw_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 \driver_draw_closepath:
{ \@@_draw_add_to_path:n { Z } }
\cs_new_protected:Npn \@@_draw_path:n #1
{
\bool_if:NTF \g_@@_draw_clip_bool
{
\int_gincr:N \g_@@_clip_path_int
\@@_draw_literal:x
{
< clipPath~id = " l3cp \int_use:N \g_@@_clip_path_int " >
{ ?nl }
{ ?nl }
< /clipPath > { ? nl }
<
use~xlink:href =
"\c_hash_str l3path \int_use:N \g_@@_path_int " ~
#1
/>
}
\@@_draw_scope:x
{
clip-path =
"url( \c_hash_str l3cp \int_use:N \g_@@_clip_path_int)"
}
}
{
\@@_draw_literal:x
{ }
}
\tl_gclear:N \g_@@_draw_path_tl
\bool_gset_false:N \g_@@_draw_clip_bool
}
\int_new:N \g_@@_path_int
\cs_new_protected:Npn \driver_draw_stroke:
{ \@@_draw_path:n { style="fill:none" } }
\cs_new_protected:Npn \driver_draw_closestroke:
{
\driver_draw_closepath:
\driver_draw_stroke:
}
\cs_new_protected:Npn \driver_draw_fill:
{ \@@_draw_path:n { style="stroke:none" } }
\cs_new_protected:Npn \driver_draw_fillstroke:
{ \@@_draw_path:n { } }
\cs_new_protected:Npn \driver_draw_clip:
{ \bool_gset_true:N \g_@@_draw_clip_bool }
\bool_new:N \g_@@_draw_clip_bool
\cs_new_protected:Npn \driver_draw_discardpath:
{
\bool_if:NT \g_@@_draw_clip_bool
{
\int_gincr:N \g_@@_clip_path_int
\@@_draw_literal:x
{
< clipPath~id = " l3cp \int_use:N \g_@@_clip_path_int " >
{ ?nl }
{ ?nl }
< /clipPath >
}
\@@_draw_scope:x
{
clip-path =
"url( \c_hash_str l3cp \int_use:N \g_@@_clip_path_int)"
}
}
\tl_gclear:N \g_@@_draw_path_tl
\bool_gset_false:N \g_@@_draw_clip_bool
}
% \end{macrocode}
% \end{variable}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_dash_pattern:nn}
% \begin{macro}{\@@_draw_dash:n}
% \begin{macro}{\@@_draw_dash_aux:nn}
% \begin{macro}{\driver_draw_linewidth:n}
% \begin{macro}{\driver_draw_miterlimit:n}
% \begin{macro}
% {
% \driver_draw_cap_butt:, \driver_draw_cap_round:, \driver_draw_cap_rectangle:,
% \driver_draw_join_miter:, \driver_draw_join_round:, \driver_draw_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 \driver_draw_dash_pattern:nn #1#2
{
\use:x
{
\@@_draw_dash_aux:nn
{ \clist_map_function:nn {#1} \@@_draw_dash:n }
{ \dim_to_decimal:n {#2} }
}
}
\cs_new:Npn \@@_draw_dash:n #1
{ , \dim_to_decimal_in_bp:n {#1} }
\cs_new_protected:Npn \@@_draw_dash_aux:nn #1#2
{
\@@_draw_scope:x
{
stroke-dasharray =
"
\tl_if_empty:oTF { \use_none:n #1 }
{ none }
{ \use_none:n #1 }
" ~
stroke-offset=" #2 "
}
}
\cs_new_protected:Npn \driver_draw_linewidth:n #1
{ \@@_draw_scope:x { stroke-width=" \dim_to_decimal:n {#1} " } }
\cs_new_protected:Npn \driver_draw_miterlimit:n #1
{ \@@_draw_scope:x { stroke-miterlimit=" \fp_eval:n {#1} " } }
\cs_new_protected:Npn \driver_draw_cap_butt:
{ \@@_draw_scope:n { stroke-linecap="butt" } }
\cs_new_protected:Npn \driver_draw_cap_round:
{ \@@_draw_scope:n { stroke-linecap="round" } }
\cs_new_protected:Npn \driver_draw_cap_rectangle:
{ \@@_draw_scope:n { stroke-linecap="square" } }
\cs_new_protected:Npn \driver_draw_join_miter:
{ \@@_draw_scope:n { stroke-linejoin="miter" } }
\cs_new_protected:Npn \driver_draw_join_round:
{ \@@_draw_scope:n { stroke-linejoin="round" } }
\cs_new_protected:Npn \driver_draw_join_bevel:
{ \@@_draw_scope:n { stroke-linejoin="bevel" } }
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}
% {
% \driver_draw_color_fill_cmyk:nnnn ,
% \driver_draw_color_stroke_cmyk:nnnn
% }
% \begin{macro}
% {
% \driver_draw_color_fill_gray:n ,
% \driver_draw_color_stroke_gray:n
% }
% \begin{macro}
% {
% \driver_draw_color_fill_rgb:nnn ,
% \driver_draw_color_stroke_rgb:nnn
% }
% \begin{macro}{\@@_draw_color_fill:nnn}
% SVG fill color has to be covered outside of the stack, as for
% \texttt{dvips}. Here, we are only allowed RGB colors so there is some
% conversion to do.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_color_stroke_cmyk:nnnn #1#2#3#4
{
\use:x
{
\@@_draw_color_fill:nnn
{ \fp_eval:n { -100 * ( (#1) * ( 1 - (#4) ) - 1 ) } }
{ \fp_eval:n { -100 * ( (#2) * ( 1 - (#4) ) + #4 - 1 ) } }
{ \fp_eval:n { -100 * ( (#3) * ( 1 - (#4) ) + #4 - 1 ) } }
}
}
\cs_new_eq:NN \driver_draw_color_stroke_cmyk:nnnn \driver_color_cmyk:nnnn
\cs_new_protected:Npn \driver_draw_color_gray:n #1
{
\use:x
{
\@@_draw_color_gray_aux:n
{ \fp_eval:n { 100 * (#3) } }
}
}
\cs_new_protected:Npn \@@_draw_color_gray_aux:n #1
{ \@@_draw_color_fill:nnn {#1} {#1} {#1} }
\cs_new_eq:NN \driver_draw_color_stroke_gray:n \driver_color_gray:n
\cs_new_protected:Npn \driver_draw_color_rgb:nnn #1#2#3
{
\use:x
{
\@@_draw_color_fill:nnn
{ \fp_eval:n { 100 * (#1) } }
{ \fp_eval:n { 100 * (#2) } }
{ \fp_eval:n { 100 * (#3) } }
}
}
\cs_new_protected:Npn \@@_draw_color_fill:nnn #1#2#3
{
\@@_draw_scope:x
{
fill =
"
rgb
(
#1 \c_percent_str ,
#2 \c_percent_str ,
#3 \c_percent_str
)
"
}
}
\cs_new_eq:NN \driver_draw_color_stroke_rgb:nnn \driver_color_rgb:nnn
% \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\driver_draw_cm:nnnn}
% The four arguments here are floats (the affine matrix), the last
% two are a displacement vector.
% \begin{macrocode}
\cs_new_protected:Npn \driver_draw_cm:nnnn #1#2#3#4
{
\@@_draw_scope:n
{
transform =
"
matrix
(
\fp_eval:n {#1} , \fp_eval:n {#2} ,
\fp_eval:n {#3} , \fp_eval:n {#4} ,
0pt , 0pt
)
"
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\driver_draw_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 \driver_draw_box_use:Nnnnn #1#2#3#4#5#6#7
{
\@@_scope_begin:
\driver_draw_cm:nnnn {#2} {#3} {#4} {#5}
\@@_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
\@@_literal_svg:n { }
\@@_scope_end:
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \begin{macrocode}
%
% \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex