% \iffalse meta-comment % %% File: l3backend-color.dtx % % Copyright (C) 2019-2021 The LaTeX Project % % It may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this % license or (at your option) any later version. The latest version % of this license is in the file % % https://www.latex-project.org/lppl.txt % % This file is part of the "l3backend bundle" (The Work in LPPL) % and all files in that bundle must be distributed together. % % ----------------------------------------------------------------------- % % The development version of the bundle can be found at % % https://github.com/latex3/latex3 % % for those people who are interested. % %<*driver> \documentclass[full,kernel]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3backend-color} package\\ Backend color support^^A % } % % \author{^^A % The \LaTeX{} Project\thanks % {^^A % E-mail: % \href{mailto:latex-team@latex-project.org} % {latex-team@latex-project.org}^^A % }^^A % } % % \date{Released 2021-08-04} % % \maketitle % % \begin{documentation} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3backend-color} Implementation} % % \begin{macrocode} %<*package> %<@@=color> % \end{macrocode} % % Color support is split into parts: collecting data from \LaTeXe{}, the color stack, general color, separations, and color for % drawings. We have different approaches in each backend, and have some choices % to make about \texttt{dvipdfmx}/\XeTeX{} in particular. Whilst it is in some ways % convenient to use the same approach in multiple backends, the fact that % \texttt{dvipdfmx}/\XeTeX{} is PDF-based means it (largely) sticks closer to % direct PDF output. % % \subsection{Collecting information from \LaTeXe{}} % % \subsubsection{\texttt{dvips}-style} % % \begin{macrocode} %<*dvisvgm|dvipdfmx|dvips|xetex> % \end{macrocode} % % \begin{macro}{\@@_backend_pickup:N} % \begin{macro}{\@@_backend_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). The \texttt{x}-type expansion % is there to cover the case where \pkg{xcolor} is in use. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_pickup:N #1 { } \cs_if_exist:cT { ver@color.sty } { \cs_set_protected:Npn \@@_backend_pickup:N #1 { \exp_args:NV \tl_if_head_is_space:nTF \current@color { \tl_set:Nx #1 { { \exp_after:wN \use:n \current@color } { 1 } } } { \exp_last_unbraced:Nx \@@_backend_pickup:w { \current@color } \s_@@_stop #1 } } \cs_new_protected:Npn \@@_backend_pickup:w #1 ~ #2 \s_@@_stop #3 { \tl_set:Nn #3 { {#1} {#2} } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsubsection{\LuaTeX{} and \pdfTeX{}} % % \begin{macrocode} %<*luatex|pdftex> % \end{macrocode} % % \begin{macro}{\@@_backend_pickup:N} % \begin{macro}{\@@_backend_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{@@_backend_pickup:w} breaks it apart, because for instance % \pkg{xcolor} sets it to be instructions to generate a color % \begin{macrocode} \cs_new_protected:Npn \@@_backend_pickup:N #1 { } \cs_if_exist:cT { ver@color.sty } { \cs_set_protected:Npn \@@_backend_pickup:N #1 { \exp_last_unbraced:Nx \@@_backend_pickup:w { \current@color } ~ 0 ~ 0 ~ 0 \s_@@_stop #1 } \cs_new_protected:Npn \@@_backend_pickup:w #1 ~ #2 ~ #3 ~ #4 ~ #5 ~ #6 \s_@@_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 { { \use:n #1 } { #5 } } } { \tl_set:Nn #7 { { gray } { 0 } } } } } } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsection{The color stack} % % For PDF-based engines, we have a color stack available inside the specials. % This is used for concepts beyond color itself: it is needed to manage th graphics % state generally. The exact form depends on the engine, and for % \texttt{dvipdfmx}/\XeTeX{} the backend version. % % \subsubsection{Common code} % % \begin{macrocode} %<*dvipdfmx|luatex|pdftex|xetex> % \end{macrocode} % % \begin{variable}{\l_@@_backend_stack_int} % \pdfTeX{}, \LuaTeX{} and recent \texttt{(x)dvipdfmx} have multiple stacks % available, and to track which one is in use a variable is required. % \begin{macrocode} \int_new:N \l_@@_backend_stack_int % \end{macrocode} % \end{variable} % % \begin{macrocode} % % \end{macrocode} % % \subsubsection{\texttt{dvipdfmx}/\XeTeX{}} % % \begin{macrocode} %<*dvipdfmx|xetex> % \end{macrocode} % % \begin{macro}{\__kernel_color_backend_stack_init:Nnn} % \begin{variable} % {\g_@@_backend_stack_int, \c_@@_backend_main_stack_int} % In \texttt{(x)dvipdfmx}, the base color stack is not set up, so we have to % force that, as well as providing a mechanism more generally. % \begin{macrocode} \int_compare:nNnTF \c__kernel_sys_dvipdfmx_version_int < { 20201111 } { \cs_new_protected:Npn \__kernel_color_backend_stack_init:Nnn #1#2#3 { } } { \int_new:N \g_@@_backend_stack_int \cs_new_protected:Npx \__kernel_color_backend_stack_init:Nnn #1#2#3 { \int_gincr:N \exp_not:N \g_@@_backend_stack_int \int_const:Nn #1 { \exp_not:N \g_@@_backend_stack_int } \use:x { \__kernel_backend_first_shipout:n { \__kernel_backend_literal:n { pdfcolorstackinit ~ \exp_not:N \int_use:N \exp_not:N \g_@@_backend_stack_int \c_space_tl \exp_not:N \tl_if_blank:nF {#2} { #2 ~ } (#3) } } } } \cs_if_exist:cTF { main@pdfcolorstack } { \int_set:Nn \l_@@_backend_stack_int { \int_use:c { main@pdfcolorstack } } } { \__kernel_color_backend_stack_init:Nnn \c_@@_backend_main_stack_int { page ~ direct } { 0 ~ g ~ 0 ~ G } \int_set_eq:NN \l_@@_backend_stack_int \c_@@_backend_main_stack_int \int_const:cn { main@pdfcolorstack } { \c_@@_backend_main_stack_int } } % \end{macrocode} % The backend automatically restores the stack color from the \enquote{classical} % approach (\texttt{pdf:bcolor}) after a scope. That will be an issue for us, so % we manually ensure that the one we are using is inserted. % \begin{macrocode} \cs_gset_protected:Npn \__kernel_backend_scope_end: { \__kernel_backend_literal:n { x:grestore } \__kernel_backend_literal:n { pdfcolorstack ~ \g_@@_backend_stack_int current } } } % \end{macrocode} % \end{variable} % \end{macro} % % \begin{macro}{\__kernel_color_backend_stack_push:nn, \__kernel_color_backend_stack_push:nx} % \begin{macro}{\__kernel_color_backend_stack_pop:n} % Simple enough but needs a version check. % \begin{macrocode} \int_compare:nNnF \c__kernel_sys_dvipdfmx_version_int < { 20201111 } { \cs_new_protected:Npn \__kernel_color_backend_stack_push:nn #1#2 { \__kernel_backend_literal:x { pdfcolorstack ~ \int_eval:n {#1} ~ push ~ (#2) } } \cs_generate_variant:Nn \__kernel_color_backend_stack_push:nn { nx } \cs_new_protected:Npn \__kernel_color_backend_stack_pop:n #1 { \__kernel_backend_literal:x { pdfcolorstack ~ \int_eval:n {#1} ~ pop } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsubsection{\LuaTeX and \pdfTeX{}} % % \begin{macrocode} %<*luatex|pdftex> % \end{macrocode} % % \begin{macro}{\__kernel_color_backend_stack_init:Nnn} % \begin{macrocode} \cs_new_protected:Npn \__kernel_color_backend_stack_init:Nnn #1#2#3 { \int_const:Nn #1 { %<*luatex> \tex_pdffeedback:D colorstackinit ~ % %<*pdftex> \tex_pdfcolorstackinit:D % \tl_if_blank:nF {#2} { #2 ~ } {#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\__kernel_color_backend_stack_push:nn, \__kernel_color_backend_stack_push:nx} % \begin{macro}{\__kernel_color_backend_stack_pop:n} % \begin{macrocode} \cs_new_protected:Npn \__kernel_color_backend_stack_push:nn #1#2 { %<*luatex> \tex_pdfextension:D colorstack ~ % %<*pdftex> \tex_pdfcolorstack:D % \int_eval:n {#1} ~ push ~ {#2} } \cs_generate_variant:Nn \__kernel_color_backend_stack_push:nn { nx } \cs_new_protected:Npn \__kernel_color_backend_stack_pop:n #1 { %<*luatex> \tex_pdfextension:D colorstack ~ % %<*pdftex> \tex_pdfcolorstack:D % \int_eval:n {#1} ~ pop \scan_stop: } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsection{General color} % % \subsubsection{\texttt{dvips}-style} % % \begin{macrocode} %<*dvips|dvisvgm> % \end{macrocode} % % \begin{macro} % { % \@@_backend_select_cmyk:n , % \@@_backend_select_gray:n , % \@@_backend_select_rgb:n , % \@@_backend_select:n % } % \begin{macro}{\@@_backend_reset:} % \begin{macro}{color.sc} % Push the data to the stack. In the case of \texttt{dvips} also saves the % drawing color in raw PostScript. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_select_cmyk:n #1 { \@@_backend_select:n { cmyk ~ #1 } } \cs_new_protected:Npn \@@_backend_select_gray:n #1 { \@@_backend_select:n { gray ~ #1 } } \cs_new_protected:Npn \@@_backend_select_rgb:n #1 { \@@_backend_select:n { rgb ~ #1 } } \cs_new_protected:Npn \@@_backend_select:n #1 { \__kernel_backend_literal:n { color~push~ #1 } %<*dvips> \__kernel_backend_postscript:n { /color.sc ~ { } ~ def } % \group_insert_after:N \@@_backend_reset: } \cs_new_protected:Npn \@@_backend_reset: { \__kernel_backend_literal:n { color~pop } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsubsection{\LuaTeX{} and \pdfTeX{}} % % \begin{macrocode} %<*dvipdfmx|luatex|pdftex|xetex> % \end{macrocode} % % \begin{variable}{\l_@@_backend_fill_tl, \l_@@_backend_stroke_tl} % \begin{macrocode} \tl_new:N \l_@@_backend_fill_tl \tl_new:N \l_@@_backend_stroke_tl % \end{macrocode} % \end{variable} % % \begin{macro} % { % \@@_backend_select_cmyk:n , % \@@_backend_select_gray:n , % \@@_backend_select_rgb:n % } % \begin{macro}{\@@_backend_select:nn} % \begin{macro}{\@@_backend_reset:} % Store the values then pass to the stack. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_select_cmyk:n #1 { \@@_backend_select:nn { #1 ~ k } { #1 ~ K } } \cs_new_protected:Npn \@@_backend_select_gray:n #1 { \@@_backend_select:nn { #1 ~ g } { #1 ~ G } } \cs_new_protected:Npn \@@_backend_select_rgb:n #1 { \@@_backend_select:nn { #1 ~ rg } { #1 ~ RG } } \cs_new_protected:Npn \@@_backend_select:nn #1#2 { \tl_set:Nn \l_@@_backend_fill_tl {#1} \tl_set:Nn \l_@@_backend_stroke_tl {#2} \__kernel_color_backend_stack_push:nn \l_@@_backend_stack_int { #1 ~ #2 } \group_insert_after:N \@@_backend_reset: } \cs_new_protected:Npn \@@_backend_reset: { \__kernel_color_backend_stack_pop:n \l_@@_backend_stack_int } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsubsection{\texttt{dvipmdfx}/\XeTeX{}} % % \begin{macrocode} %<*dvipdfmx|xetex> % \end{macrocode} % % These backends have the most possible approaches: it recognises both % \texttt{dvips}-based color specials and it's own format, plus one can % include PDF statements directly. Recent releases also have a color stack % approach similar to \pdfTeX{}. Of the stack methods, the dedicated % the most versatile is the latter as it can cover all of the use cases % we have. Thus it is used in preference to the \texttt{dvips}-style interface % or the \enquote{native} color specials (which have only one stack). % % \begin{macro} % { % \@@_backend_select_cmyk:n , % \@@_backend_select_gray:n , % \@@_backend_select_rgb:n % } % \begin{macro}{\@@_backend_reset:} % Push the data to the stack. % \begin{macrocode} \int_compare:nNnT \c__kernel_sys_dvipdfmx_version_int < { 20201111 } { \cs_gset_protected:Npn \@@_backend_select_cmyk:n #1 { \__kernel_backend_literal:n { pdf: bc ~ [#1] } \group_insert_after:N \@@_backend_reset: } \cs_gset_eq:NN \@@_backend_select_gray:n \@@_backend_select_cmyk:n \cs_gset_eq:NN \@@_backend_select_rgb:n \@@_backend_select_cmyk:n \cs_gset_protected:Npn \@@_backend_reset: { \__kernel_backend_literal:n { pdf: ec } } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsection{Separations} % % Here, life gets interesting and we need essentially one approach per % backend. % % \begin{macrocode} %<*dvips> % \end{macrocode} % % \begin{macro}{\@@_backend_select_separation:nn, \@@_backend_select_devicen:nn} % \begin{macrocode} \cs_new_protected:Npn \@@_backend_select_separation:nn #1#2 { \@@_backend_select:n { separation ~ #1 ~ #2 } } \cs_new_eq:NN \@@_backend_select_devicen:nn \@@_backend_select_separation:nn % \end{macrocode} % \end{macro} % % \begin{macro} % { % \@@_backend_separation_init:nnnnn, % \@@_backend_separation_init:nxxnn, % \@@_backend_separation_init_aux:nnnnn % } % \begin{macro}[EXP] % { % \@@_backend_separation_init_/DeviceCMYK:nnn , % \@@_backend_separation_init_/DeviceGray:nnn , % \@@_backend_separation_init_/DeviceRGB:nnn % } % \begin{macro}[EXP]{\@@_backend_separation_init_Device:Nn} % \begin{macro}[EXP]{\@@_backend_separation_init:nnn} % \begin{macro}[EXP]{\@@_backend_separation_init_count:n} % \begin{macro}[EXP]{\@@_backend_separation_init_count:w} % \begin{macro}[EXP]{\@@_backend_separation_init:nnnn} % \begin{macro}[EXP]{\@@_backend_separation_init:w} % \begin{macro}[EXP]{\@@_backend_separation_init:n} % \begin{macro}[EXP]{\@@_backend_separation_init:nw} % \begin{macro}{\@@_backend_separation_init_CIELAB:nnn} % Initialising here means creating a small header set up plus massaging % some data. This comes about as we have to deal with PDF-focussed data, % which makes most sense \enquote{higher-up}. The approach is based on % ideas from \url{https://tex.stackexchange.com/q/560093} plus using % the PostScript manual for other aspects. % \begin{macrocode} \cs_new_protected:Npx \@@_backend_separation_init:nnnnn #1#2#3#4#5 { \bool_if:NT \g__kernel_backend_header_bool { \__kernel_backend_first_shipout:n { \exp_not:N \@@_backend_separation_init_aux:nnnnn {#1} {#2} {#3} {#4} {#5} } } } \cs_generate_variant:Nn \@@_backend_separation_init:nnnnn { nxx } \cs_new_protected:Npn \@@_backend_separation_init_aux:nnnnn #1#2#3#4#5 { \__kernel_backend_literal:e { ! TeXDict ~ begin ~ /color \int_use:N \g_@@_model_int { [ ~ /Separation ~ ( \str_convert_pdfname:n {#1} ) ~ [ ~ #2 ~ ] ~ { \cs_if_exist_use:cF { @@_backend_separation_init_ #2 :nnn } { \@@_backend_separation_init:nnn } {#3} {#4} {#5} } ] ~ setcolorspace } ~ def ~ end } } \cs_new:cpn { @@_backend_separation_init_ /DeviceCMYK :nnn } #1#2#3 { \@@_backend_separation_init_Device:Nn 4 {#3} } \cs_new:cpn { @@_backend_separation_init_ /DeviceGray :nnn } #1#2#3 { \@@_backend_separation_init_Device:Nn 1 {#3} } \cs_new:cpn { @@_backend_separation_init_ /DeviceRGB :nnn } #1#2#3 { \@@_backend_separation_init_Device:Nn 2 {#3} } \cs_new:Npn \@@_backend_separation_init_Device:Nn #1#2 { #2 ~ \prg_replicate:nn {#1} { #1 ~ index ~ mul ~ #1 ~ 1 ~ roll ~ } \int_eval:n { #1 + 1 } ~ -1 ~ roll ~ pop } % \end{macrocode} % For the generic case, we cannot use |/FunctionType 2| unfortunately, so % we have to code that idea up in PostScript. Here, we will therefore assume % that a range is \emph{always} given. First, we count values in each argument: % at the backend level, we can assume there are always well-behaved with % spaces present. % \begin{macrocode} \cs_new:Npn \@@_backend_separation_init:nnn #1#2#3 { \exp_args:Ne \@@_backend_separation_init:nnnn { \@@_backend_separation_init_count:n {#2} } {#1} {#2} {#3} } \cs_new:Npn \@@_backend_separation_init_count:n #1 { \int_eval:n { 0 \@@_backend_separation_init_count:w #1 ~ \s_@@_stop } } \cs_new:Npn \@@_backend_separation_init_count:w #1 ~ #2 \s_@@_stop { +1 \tl_if_blank:nF {#2} { \@@_backend_separation_init_count:w #2 \s_@@_stop } } % \end{macrocode} % Now we implement the algorithm. In the terms in the PostScript manual, % we have $\mathbf{N} = 1$ and $\mathbf{Domain} = [0~1]$, with % $\mathbf{Range}$ as |#2|, $\mathbf{C0}$ as |#3| and $\mathbf{C1}$ % as |#4|, with the number of output components in |#1|. So all we have % to do is implement $y_{i} = \mathbf{C0}_{i} + x(\mathbf{C1}_{i} - % \mathbf{C0}_{i})$ with lots of stack manipulation, then check the % ranges. That's done by adding everything to the stack first, then using % the fact we know all of the offsets. As manipulating the stack is tricky, % we start by re-formatting the $\mathbf{C0}$ and $\mathbf{C1}$ arrays to % be interleaved, and add a \texttt{0} to each pair: this is used % to keep the stack of constant length while we are doing the first pass of % mathematics. We then working through that list, calculating from the % last to the first value before tidying up by removing all of the input % values. We do that by first copying all of the final $y$ values to the % end of the stack, then rolling everything so we can pop the now-unneeded % material. % \begin{macrocode} \cs_new:Npn \@@_backend_separation_init:nnnn #1#2#3#4 { \@@_backend_separation_init:w #3 ~ \s_@@_stop #4 ~ \s_@@_stop \prg_replicate:nn {#1} { pop ~ 1 ~ index ~ neg ~ 1 ~ index ~ add ~ \int_eval:n { 3 * #1 } ~ index ~ mul ~ 2 ~ index ~ add ~ \int_eval:n { 3 * #1 } ~ #1 ~ roll ~ } \int_step_function:nnnN {#1} { -1 } { 1 } \@@_backend_separation_init:n \int_eval:n { 4 * #1 + 1 } ~ #1 ~ roll ~ \prg_replicate:nn { 3 * #1 + 1 } { pop ~ } \tl_if_blank:nF {#2} { \@@_backend_separation_init:nw {#1} #2 ~ \s_@@_stop } } \cs_new:Npn \@@_backend_separation_init:w #1 ~ #2 \s_@@_stop #3 ~ #4 \s_@@_stop { #1 ~ #3 ~ 0 ~ \tl_if_blank:nF {#2} { \@@_backend_separation_init:w #2 \s_@@_stop #4 \s_@@_stop } } \cs_new:Npn \@@_backend_separation_init:n #1 { \int_eval:n { #1 * 2 } ~ index ~ } % \end{macrocode} % Finally, we deal with the range limit if required. This is handled % by splitting the range into pairs. It's then just a question of doing % the comparisons, this time dropping everything except the desired % result. % \begin{macrocode} \cs_new:Npn \@@_backend_separation_init:nw #1#2 ~ #3 ~ #4 \s_@@_stop { #2 ~ #3 ~ 2 ~ index ~ 2 ~ index ~ lt ~ { ~ pop ~ exch ~ pop ~ } ~ { ~ 2 ~ index ~ 1 ~ index ~ gt ~ { ~ exch ~ pop ~ exch ~ pop ~ } ~ { ~ pop ~ pop ~ } ~ ifelse ~ } ifelse ~ #1 ~ 1 ~ roll ~ \tl_if_blank:nF {#4} { \@@_backend_separation_init:nw {#1} #4 \s_@@_stop } } % \end{macrocode} % CIELAB support uses the detail from the PostScript reference, page 227; % other than that block of PostScript, this is the same as for PDF-based % routes. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_separation_init_CIELAB:nnn #1#2#3 { \@@_backend_separation_init:nxxnn {#2} { /CIEBasedABC ~ << ~ /RangeABC ~ [ ~ \c_@@_model_range_CIELAB_tl \c_space_tl ] ~ /DecodeABC ~ [ ~ { ~ 16 ~ add ~ 116 ~ div ~ } ~ bind ~ { ~ 500 ~ div ~ } ~ bind ~ { ~ 200 ~ div ~ } ~ bind ~ ] ~ /MatrixABC ~ [ ~ 1 ~ 1 ~ 1 ~ 1 ~ 0 ~ 0 ~ 0 ~ 0 ~ -1 ~ ] ~ /DecodeLMN ~ [ ~ { ~ dup ~ 6 ~ 29 ~ div ~ ge ~ { ~ dup ~ dup ~ mul ~ mul ~ ~ } ~ { ~ 4 ~ 29 ~ div ~ sub ~ 108 ~ 841 ~ div ~ mul ~ } ~ ifelse ~ 0.9505 ~ mul ~ } ~ bind ~ { ~ dup ~ 6 ~ 29 ~ div ~ ge ~ { ~ dup ~ dup ~ mul ~ mul ~ } ~ { ~ 4 ~ 29 ~ div ~ sub ~ 108 ~ 841 ~ div ~ mul ~ } ~ ifelse ~ } ~ bind ~ { ~ dup ~ 6 ~ 29 ~ div ~ ge ~ { ~ dup ~ dup ~ mul ~ mul ~ } ~ { ~ 4 ~ 29 ~ div ~ sub ~ 108 ~ 841 ~ div ~ mul ~ } ~ ifelse ~ 1.0890 ~ mul ~ } ~ bind ] ~ /WhitePoint ~ [ ~ \tl_use:c { c_@@_model_whitepoint_CIELAB_ #1 _tl } ~ ] ~ >> } { \c_@@_model_range_CIELAB_tl } { 100 ~ 0 ~ 0 } {#3} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_backend_devicen_init:nnn} % Trivial as almost all of the work occurs in the shared code. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_devicen_init:nnn #1#2#3 { \__kernel_backend_literal:e { ! TeXDict ~ begin ~ /color \int_use:N \g_@@_model_int { [ ~ /DeviceN ~ [ ~ #1 ~ ] ~ #2 ~ { ~ #3 ~ } ~ ] ~ setcolorspace } ~ def ~ end } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvisvgm> % \end{macrocode} % % \begin{macro}{\@@_backend_select_separation:nn, \@@_backend_select_devicen:nn} % No support at present. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_select_separation:nn #1#2 { } \cs_new_protected:Npn \@@_backend_select_devicen:nn #1#2 { } % \end{macrocode} % \end{macro} % % \begin{macro} % {\@@_backend_separation_init:nnnnn, \@@_backend_separation_init_CIELAB:nnn} % No support at present. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_separation_init:nnnnn #1#2#3#4#5 { } \cs_new_protected:Npn \@@_backend_separation_init_CIELAB:nnnnnn #1#2#3 { } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvipdfmx|luatex|pdftex|xetex> % \end{macrocode} % % \begin{macro}{\@@_backend_select_separation:nn, \@@_backend_select_devicen:nn} % Although \texttt{(x)dvipdfmx} has a built-in approach to color spaces, that % can't be used with the generic color stacks. So we take an approach in which % we share the same code as for \pdfTeX{}. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_select_separation:nn #1#2 { \@@_backend_select:nn { /#1 ~ cs ~ #2 ~ scn } { /#1 ~ CS ~ #2 ~ SCN } } \cs_new_eq:NN \@@_backend_select_devicen:nn \@@_backend_select_separation:nn % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_separation_init:nnnnn} % \begin{macro}{\@@_backend_separation_init:n} % \begin{macro}{\@@_backend_separation_init_CIELAB:nnn} % Initialising the PDF structures needs two parts: creating an object % containing the \enquote{real} name of the Separation, then adding a reference % to that to each page. We use a separate object for the tint transformation % following the model in the PDF reference. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_separation_init:nnnnn #1#2#3#4#5 { \pdf_object_unnamed_write:nx { dict } { /FunctionType ~ 2 /Domain ~ [0 ~ 1] \tl_if_blank:nF {#3} { /Range ~ [#3] } /C0 ~ [#4] ~ /C1 ~ [#5] /N ~ 1 } \@@_backend_separation_init:n { /Separation ~ / \str_convert_pdfname:n {#1} ~ #2 ~ \pdf_object_ref_last: } \bool_lazy_and:nnT { \cs_if_exist_p:N \pdfmanagement_if_active_p: } { \pdfmanagement_if_active_p:} { \use:x { \pdfmanagement_add:nnn { Page / Resources / ColorSpace } { color \int_use:N \g_@@_model_int } { \pdf_object_ref_last: } } } } \cs_new_protected:Npn \@@_backend_separation_init:n #1 { \pdf_object_unnamed_write:nx { array } {#1} } % \end{macrocode} % For CIELAB colors, we need one object per document for the illuminant, % plus initialisation of the color space referencing that object. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_separation_init_CIELAB:nnn #1#2#3 { \pdf_object_if_exist:nF { @@_illuminant_CIELAB_ #1 } { \pdf_object_new:nn { @@_illuminant_CIELAB_ #1 } { array } \pdf_object_write:nx { @@_illuminant_CIELAB_ #1 } { /Lab ~ << /WhitePoint ~ [ \tl_use:c { c_@@_model_whitepoint_CIELAB_ #1 _tl } ] /Range ~ [ \c_@@_model_range_CIELAB_tl ] >> } } \@@_backend_separation_init:nnnnn {#2} { \pdf_object_ref:n { @@_illuminant_CIELAB_ #1 } } { \c_@@_model_range_CIELAB_tl } { 100 ~ 0 ~ 0 } {#3} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_backend_devicen_init:nnn} % \begin{macro}[EXP]{\@@_backend_devicen_init:w} % \begin{macro}{\@@_backend_devicen_init:n} % Similar to the Separations case, but with an arbitrary function for % the alternative space work. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_devicen_init:nnn #1#2#3 { \pdf_object_unnamed_write:nx { stream } { { /FunctionType ~ 4 ~ /Domain ~ [ ~ \prg_replicate:nn { 0 \@@_backend_devicen_init:w #1 ~ \s_@@_stop } { 0 ~ 1 ~ } ~ ] ~ /Range ~ [ ~ \str_case:nn {#2} { { /DeviceCMYK } { 0 ~ 1 ~ 0 ~ 1 ~ 0 ~ 1 ~ 0 ~ 1 } { /DeviceGray } { 0 ~ 1 } { /DeviceRGB } { 0 ~ 1 ~ 0 ~ 1 ~ 0 ~ 1 } } ~ ] } {#3} } \@@_backend_separation_init:n { /DeviceN ~ [ ~ #1 ~ ] ~ #2 ~ \pdf_object_ref_last: } \bool_lazy_and:nnT { \cs_if_exist_p:N \pdfmanagement_if_active_p: } { \pdfmanagement_if_active_p:} { \use:x { \pdfmanagement_add:nnn { Page / Resources / ColorSpace } { color \int_use:N \g_@@_model_int } { \pdf_object_ref_last: } } } } \cs_new:Npn \@@_backend_devicen_init:w #1 ~ #2 \s_@@_stop { + 1 \tl_if_blank:nF {#2} { \@@_backend_devicen_init:w #2 \s_@@_stop } } \cs_new_eq:NN \@@_backend_devicen_init:n \@@_backend_separation_init:n % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvipdfmx|xetex> % \end{macrocode} % % \begin{macro}{\@@_backend_select_separation:nn, \@@_backend_select_devicen:nn} % For older \texttt{(x)dvipdfmx}, we \emph{could} support separations using a % dedicated mechanism, but it was not added that long before the color % stacks. So instead of having two complex paths, just disable here. % \begin{macrocode} \int_compare:nNnT \c__kernel_sys_dvipdfmx_version_int < { 20201111 } { \cs_gset_protected:Npn \@@_backend_select_separation:nn #1#2 { } \cs_gset_eq:NN \@@_backend_select_devicen:nn \@@_backend_select_separation:nn } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \subsection{Fill and stroke color} % % Here, \texttt{dvipdfmx}/\XeTeX{} follows \LuaTeX{} and \pdfTeX{}, % while for \texttt{dvips} % we have to manage fill and stroke color ourselves. We also handle % \texttt{dvisvgm} independently, as there we can create SVG directly. % % \begin{macrocode} %<*dvipdfmx|luatex|pdftex|xetex> % \end{macrocode} % % \begin{macro} % { % \@@_backend_fill_cmyk:n , % \@@_backend_fill_gray:n , % \@@_backend_fill_rgb:n , % \@@_backend_fill:n , % \@@_backend_stroke_cmyk:n , % \@@_backend_stroke_gray:n , % \@@_backend_stroke_rgb:n , % \@@_backend_stroke:n % } % Drawing (fill/stroke) color is handled in \texttt{dvipdfmx}/\XeTeX{} in the % same way as \LuaTeX{}/\pdfTeX{}. We use the same approach as earlier, except the % color stack is not involved so the generic direct PDF operation is used. % There is no worry about the nature of strokes: everything is handled % automatically. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_cmyk:n #1 { \@@_backend_fill:n { #1 ~ k } } \cs_new_protected:Npn \@@_backend_fill_gray:n #1 { \@@_backend_fill:n { #1 ~ g } } \cs_new_protected:Npn \@@_backend_fill_rgb:n #1 { \@@_backend_fill:n { #1 ~ rg } } \cs_new_protected:Npn \@@_backend_fill:n #1 { \tl_set:Nn \l_@@_backend_fill_tl {#1} \__kernel_color_backend_stack_push:nn \l_@@_backend_stack_int { #1 ~ \l_@@_backend_stroke_tl } \group_insert_after:N \@@_backend_reset: } \cs_new_protected:Npn \@@_backend_stroke_cmyk:n #1 { \@@_backend_stroke:n { #1 ~ K } } \cs_new_protected:Npn \@@_backend_stroke_gray:n #1 { \@@_backend_stroke:n { #1 ~ G } } \cs_new_protected:Npn \@@_backend_stroke_rgb:n #1 { \@@_backend_stroke:n { #1 ~ RG } } \cs_new_protected:Npn \@@_backend_stroke:n #1 { \tl_set:Nn \l_@@_backend_stroke_tl {#1} \__kernel_color_backend_stack_push:nn \l_@@_backend_stack_int { \l_@@_backend_fill_tl \c_space_tl #1 } \group_insert_after:N \@@_backend_reset: } % \end{macrocode} % \end{macro} % % \begin{macro} % { % \@@_backend_fill_separation:nn, % \@@_backend_stroke_separation:nn, % \@@_backend_fill_devicen:nn, % \@@_backend_stroke_devicen:nn % } % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_separation:nn #1#2 { \@@_backend_fill:n { /#1 ~ cs ~ #2 ~ scn } } \cs_new_protected:Npn \@@_backend_stroke_separation:nn #1#2 { \@@_backend_stroke:n { /#1 ~ CS ~ #2 ~ SCN } } \cs_new_eq:NN \@@_backend_fill_devicen:nn \@@_backend_fill_separation:nn \cs_new_eq:NN \@@_backend_stroke_devicen:nn \@@_backend_stroke_separation:nn % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvipdfmx|xetex> % \end{macrocode} % % \begin{macro} % { % \@@_backend_fill_cmyk:n , % \@@_backend_fill_gray:n , % \@@_backend_fill_rgb:n % } % \begin{macro}{\@@_backend_reset:} % \begin{macro}{\@@_backend_stroke:n} % \begin{macro}{\@@_backend_fill_separation:nn, \@@_backend_stroke_separation:nn} % Deal with older \texttt{(x)dvipdfmx}. % \begin{macrocode} \int_compare:nNnT \c__kernel_sys_dvipdfmx_version_int < { 20201111 } { \cs_gset_protected:Npn \@@_backend_fill_cmyk:n #1 { \__kernel_backend_literal:n { pdf: bc ~ [#1] } \group_insert_after:N \@@_backend_reset: } \cs_gset_eq:NN \@@_backend_fill_gray:n \@@_backend_fill_cmyk:n \cs_gset_eq:NN \@@_backend_fill_rgb:n \@@_backend_fill_cmyk:n \cs_gset_protected:Npn \@@_backend_reset: { \__kernel_backend_literal:n { pdf: ec } } \cs_gset_protected:Npn \@@_backend_stroke:n #1 { \__kernel_backend_literal:n {#1} } \cs_gset_protected:Npn \@@_backend_fill_separation:nn #1#2 { } \cs_gset_eq:NN \@@_backend_fill_devicen:nn \@@_backend_fill_separation:nn \cs_gset_eq:NN \@@_backend_stroke_separation:nn \@@_backend_fill_separation:nn \cs_gset_eq:NN \@@_backend_stroke_devicen:nn \@@_backend_stroke_separation:nn } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvips> % \end{macrocode} % % \begin{macro} % { % \@@_backend_fill_cmyk:n , % \@@_backend_fill_gray:n , % \@@_backend_fill_rgb:n , % \@@_backend_fill:n , % \@@_backend_stroke_cmyk:n , % \@@_backend_stroke_gray:n , % \@@_backend_stroke_rgb:n % } % Fill color here is the same as general color \emph{except} we skip the % stroke part. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_cmyk:n #1 { \@@_backend_fill:n { cmyk ~ #1 } } \cs_new_protected:Npn \@@_backend_fill_gray:n #1 { \@@_backend_fill:n { gray ~ #1 } } \cs_new_protected:Npn \@@_backend_fill_rgb:n #1 { \@@_backend_fill:n { rgb ~ #1 } } \cs_new_protected:Npn \@@_backend_fill:n #1 { \__kernel_backend_literal:n { color~push~ #1 } \group_insert_after:N \@@_backend_reset: } \cs_new_protected:Npn \@@_backend_stroke_cmyk:n #1 { \__kernel_backend_postscript:n { /color.sc { #1 ~ setcmykcolor } def } } \cs_new_protected:Npn \@@_backend_stroke_gray:n #1 { \__kernel_backend_postscript:n { /color.sc { #1 ~ setgray } def } } \cs_new_protected:Npn \@@_backend_stroke_rgb:n #1 { \__kernel_backend_postscript:n { /color.sc { #1 ~ setrgbcolor } def } } % \end{macrocode} % \end{macro} % % \begin{macro} % { % \@@_backend_fill_separation:nn, % \@@_backend_stroke_separation:nn, % \@@_backend_fill_devicen:nn, % \@@_backend_stroke_devicen:nn % } % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_separation:nn #1#2 { \@@_backend_fill:n { separation ~ #1 ~ #2 } } \cs_new_protected:Npn \@@_backend_stroke_separation:nn #1#2 { \__kernel_backend_postscript:n { /color.sc { separation ~ #1 ~ #2 } def } } \cs_new_eq:NN \@@_backend_fill_devicen:nn \@@_backend_fill_separation:nn \cs_new_eq:NN \@@_backend_stroke_devicen:nn \@@_backend_stroke_separation:nn % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} %<*dvisvgm> % \end{macrocode} % % \begin{macro} % { % \@@_backend_fill_cmyk:n , % \@@_backend_fill_gray:n , % \@@_backend_fill_rgb:n , % \@@_backend_fill:n % } % Fill color here is the same as general color \emph{except} we skip the % stroke part. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_cmyk:n #1 { \@@_backend_fill:n { cmyk ~ #1 } } \cs_new_protected:Npn \@@_backend_fill_gray:n #1 { \@@_backend_fill:n { gray ~ #1 } } \cs_new_protected:Npn \@@_backend_fill_rgb:n #1 { \@@_backend_fill:n { rgb ~ #1 } } \cs_new_protected:Npn \@@_backend_fill:n #1 { \__kernel_backend_literal:n { color~push~ #1 } \group_insert_after:N \@@_backend_reset: } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_backend_stroke_cmyk:n} % \begin{macro}{\@@_backend_stroke_cmyk:w} % \begin{macro}{\@@_backend_stroke_gray:n, \@@_backend_stroke_gray_aux:n} % \begin{macro}{\@@_backend_stroke_rgb:n} % \begin{macro}{\@@_backend_stroke_rgb:w} % \begin{macro}{\@@_backend:nnn} % For drawings in SVG, we use scopes for all stroke colors. That % requires using \texttt{RGB} values, which luckily are easy to % convert here (|cmyk| to |RGB| is a fixed function). % \begin{macrocode} \cs_new_protected:Npn \@@_backend_stroke_cmyk:n #1 { \@@_backend_cmyk:w #1 \s_@@_stop } \cs_new_protected:Npn \@@_backend_stroke_cmyk:w #1 ~ #2 ~ #3 ~ #4 \s_@@_stop { \use:x { \@@_backend:nnn { \fp_eval:n { -100 * ( 1 - min ( 1 , #1 + #4 ) ) } } { \fp_eval:n { -100 * ( 1 - min ( 1 , #2 + #4 ) ) } } { \fp_eval:n { -100 * ( 1 - min ( 1 , #3 + #4 ) ) } } } } \cs_new_protected:Npn \@@_backend_stroke_gray:n #1 { \use:x { \@@_backend_stroke_gray_aux:n { \fp_eval:n { 100 * (#1) } } } } \cs_new_protected:Npn \@@_backend_stroke_gray_aux:n #1 { \@@_backend:nnn {#1} {#1} {#1} } \cs_new_protected:Npn \@@_backend_stroke_rgb:n #1 { \@@_backend_rgb:w #1 \s_@@_stop } \cs_new_protected:Npn \@@_backend_stroke_rgb:w #1 ~ #2 ~ #3 \s_@@_stop { \use:x { \@@_backend:nnn { \fp_eval:n { 100 * (#1) } } { \fp_eval:n { 100 * (#2) } } { \fp_eval:n { 100 * (#3) } } } } \cs_new_protected:Npx \@@_backend:nnn #1#2#3 { \__kernel_backend_scope:n { stroke = " rgb ( #1 \c_percent_str , #2 \c_percent_str , #3 \c_percent_str ) " } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % { % \@@_backend_fill_separation:nn, % \@@_backend_stroke_separation:nn, % \@@_backend_fill_devicen:nn, % \@@_backend_stroke_devicen:nn % } % At present, these are no-ops. % \begin{macrocode} \cs_new_protected:Npn \@@_backend_fill_separation:nn #1#2 { } \cs_new_protected:Npn \@@_backend_stroke_separation:nn #1#2 { } \cs_new_eq:NN \@@_backend_fill_devicen:nn \@@_backend_fill_separation:nn \cs_new_eq:NN \@@_backend_stroke_devicen:nn \@@_backend_stroke_separation:nn % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex