% \iffalse meta-comment % %% File: l3color.dtx % % Copyright (C) 2017-2022 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 % % http://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]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{^^A % The \textsf{l3color} package\\ 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 2022-11-02} % % \maketitle % % \begin{documentation} % % \section{Color in boxes} % % Controlling the color of text in boxes requires a small number of control % functions, so that the boxed material uses the color at the point where % it is set, rather than where it is used. % % \begin{function}[added = 2011-09-03]{\color_group_begin:, \color_group_end:} % \begin{syntax} % \cs{color_group_begin:} % \ldots % \cs{color_group_end:} % \end{syntax} % Creates a color group: one used to \enquote{trap} color settings. % This grouping is built in to for example \cs{hbox_set:Nn}. % \end{function} % % \begin{function}[added = 2011-09-03]{\color_ensure_current:} % \begin{syntax} % \cs{color_ensure_current:} % \end{syntax} % Ensures that material inside a box uses the foreground color % at the point where the box is set, rather than that in force when the % box is used. This function should usually be used within a % \cs{color_group_begin:} \ldots \cs{color_group_end:} group. % \end{function} % % \section{Color models} % % A color \emph{model} is a way to represent sets of colors. Different models % are particularly suitable for different output methods, \emph{e.g.}~screen % or print. Parameter-based models can describe a very large number of unique % colors, and have a varying number of \emph{axes} which define a color % space. In contrast, various proprietary models are available which define % \emph{spot} colors (more formally separations). % % Core models are used to pass color information to output; these are % \enquote{native} to \pkg{l3color}. Core models use real numbers in the range % $[0,1]$ to represent values. The core models supported here are % \begin{itemize} % \item \texttt{gray} Grayscale color, with a single axis running from % $0$ (fully black) to $1$ (fully white) % \item \texttt{rgb} Red-green-blue color, with three axes, one for each of % the components % \item \texttt{cmyk} Cyan-magenta-yellow-black color, with four axes, one for % each of the components % \end{itemize} % % There are also interface models: these are convenient for users but have % to be manipulated before storing/passing to the backend. Interface models % are primarily integer-based: see below for more detail. The supported % interface models are % \begin{itemize} % \item \texttt{Gray} Grayscale color, with a single axis running from % $0$ (fully black) to $15$ (fully white) % \item \texttt{hsb} Hue-saturation-brightness color, with three axes,all % real values in the range $[0,1]$ for hue saturation and brightness % \item \texttt{Hsb} Hue-saturation-brightness color, with three axes, integer % in the range $[0,360]$ for hue, real values in the range $[0,1]$ for % saturation and brightness % \item \texttt{HSB} Hue-saturation-brightness color, with three axes, integers % in the range $[0,240]$ for hue, saturation and brightness % \item \texttt{HTML} HTML format representation of RGB color given as a % single six-digit hexadecimal number % \item \texttt{RGB} Red-green-blue color, with three axes, one for each of % the components, values as integers from $0$ to $255$ % \item \texttt{wave} Light wavelength, a real number in the range % $380$ to $780$ (nanometres) % \end{itemize} % All interface models are internally stored as |rgb|. % % Finally, there are a small number of models which are parsed to allow % data transfer from \pkg{xcolor} but which should not be used by end-users. % These are % \begin{itemize} % \item \texttt{cmy} Cyan-magenta-yellow color with three axes, one for % each of the components; converted to |cmyk| % \item \texttt{tHsb} \enquote{Tuned} hue-saturation-brightness color with three % axes, integer in the range $[0,360]$ for hue, real values in the range % $[0,1]$ for saturation and brightness; converted to |rgb| using the % standard tuning map defined by \pkg{xcolor} % \item \texttt{\&spot} Spot color tint with one value; treated as a gray % tint as spot color data is not available for extraction % \end{itemize} % % To allow parsing of data from \pkg{xcolor}, any leading model up the first % \texttt{:} will be discarded; the approach of selecting an internal form % for data is \emph{not} used in \pkg{l3color}. % % Additional models may be created to allow mixing of separation colors % with each other or with those from other models. See % Section~\ref{l3color:sec:new-models} for more detail of color support % for additional models. % % When color is selected by model, the \meta{values} given are specified as % a comma-separated list. The length of the list will therefore be determined % by the detail of the model involved. % % Color models (and interconversion) are complex, and more details are given % in the manual to the \LaTeXe{} \pkg{xcolor} package and in the % \emph{PostScript Language Reference Manual}, published by Addison--Wesley. % % \section{Color expressions} % % In addition to allowing specification of color by model and values, % \pkg{l3color} also supports color expressions. These are created % by combining one or more color names, with the amount of each specified % as a percentage. The latter is given between |!| symbols in the expression. % Thus for example % \begin{verbatim} % red!50!green % \end{verbatim} % is a mixture of $50\,\%$ red and $50\,\%$ green. A trailing percentage is % interpreted as implicitly followed by |white|, and so % \begin{verbatim} % red!25 % \end{verbatim} % specifies $25\,\%$ red mixed with $75\,\%$ white. % % Where the models for the mixed colors are different, the model of the first % color is used. Thus % \begin{verbatim} % red!50!cyan % \end{verbatim} % will result in a color specification using the |rgb| model, made up of % $50\,\%$ red and $50\,\%$ of cyan \emph{expressed in \texttt{rgb}}. % This may be important as color model interconversion is not exact. % % The one exception to the above is where the first model in an expression is % |gray|. In this case, the order of mixing is \enquote{swapped} internally, so % that for example % \begin{verbatim} % black!50!red % \end{verbatim} % has the same result as % \begin{verbatim} % red!50!black % \end{verbatim} % (the predefined colors |black| and |white| use the |gray| model). % % Where more than two colors are mixed in an expression, evaluation takes place % in a stepwise fashion. Thus in % \begin{verbatim} % cyan!50!magenta!10!yellow % \end{verbatim} % the sub-expression % \begin{verbatim} % cyan!50!magenta % \end{verbatim} % is first evaluated to give an intermediate color specification, before % the second step % \begin{verbatim} % !10!yellow % \end{verbatim} % where || represents this transitory calculated value. % % Within a color expression, |.| may be used to represent the color active % for typesetting (the current color). This allows for example % \begin{verbatim} % .!50 % \end{verbatim} % to mean a mixture of $50\,\%$ of current color with white. % % (Color expressions supported here are a subset of those provided by % the \LaTeXe{} \pkg{xcolor} package. At present, only such features as are % clearly useful have been added here.) % % \section{Named colors} % % Color names are stored in a single namespace, which makes them accessible % as part of color expressions. Whilst they are not reserved in a technical % sense, the names |black|, |white|, |red|, |green|, |blue|, |cyan|, |magenta| % and |yellow| have special meaning and should not be redefined. Color names % should be made up of letters, numbers and spaces only: other characters are % reserved for use in color expressions. In particular, |.| represents the % current color at the start of a color expression. % % \begin{function}{\color_set:nn} % \begin{syntax} % \cs{color_set:nn} \Arg{name} \Arg{color expression} % \end{syntax} % Evaluates the \meta{color expression} and stores the resulting % color specification as the \meta{name}. % \end{function} % % \begin{function}{\color_set:nnn} % \begin{syntax} % \cs{color_set:nnn} \Arg{name} \Arg{model(s)} \Arg{value(s)} % \end{syntax} % Stores the color specification equivalent to the \meta{model(s)} and % \meta{values} as the \meta{name}. % \end{function} % % \begin{function}{\color_set_eq:nn} % \begin{syntax} % \cs{color_set_eq:nn} \Arg{name1} \Arg{name2} % \end{syntax} % Copies the color specification in \meta{name2} to \meta{name1}. The % special name |.| may be used to represent the current color, allowing % it to be saved to a name. % \end{function} % % \begin{function}[EXP, pTF, added = 2022-08-12]{\color_if_exist:n} % \begin{syntax} % \cs{color_if_exist:n} \Arg{name} % \cs{color_if_exist:n} \Arg{name} \Arg{true code} \Arg{false code} % \end{syntax} % Tests whether \meta{name} is currently defined to provide a color % specification. % \end{function} % % \begin{function}[added = 2021-05-11]{\color_show:n, \color_log:n} % \begin{syntax} % \cs{color_show:n} \Arg{name} % \cs{color_log:n} \Arg{name} % \end{syntax} % Displays the color specification stored in the \meta{name} on the % terminal or log file. % \end{function} % % \section{Selecting colors} % % General selection of color is safe when split across pages: a stack is % used to ensure that the correct color is re-selected on the new page. % % These commands set the current color (|.|): other more specialised functions % such as fill and stroke selectors do \emph{not} adjust this value. % % \begin{function}{\color_select:n} % \begin{syntax} % \cs{color_select:n} \Arg{color expression} % \end{syntax} % Parses the \meta{color expression} and then activates the resulting % color specification for typeset material. % \end{function} % % \begin{function}{\color_select:nn} % \begin{syntax} % \cs{color_select:nn} \Arg{model(s)} \Arg{value(s)} % \end{syntax} % Activates the color specification equivalent to the \meta{model(s)} and % \meta{value(s)} for typeset material. % \end{function} % % \begin{variable}{\l_color_fixed_model_tl} % When this is set to a non-empty value, colors will be converted to % the specified model when they are selected. Note that included images % and similar are not influenced by this setting. % \end{variable} % % \section{Colors for fills and strokes} % % Colors for drawing operations and so forth are split into strokes and fills % (the latter may also be referred to as non-stroke color). The fill color is % used for text under normal circumstances. Depending on the backend, stroke % color may use a \emph{stack}, in which case it exhibits the same page breaking % behavior as general color. However, \texttt{dvips}/\texttt{dvisvgm} do not % support this, and so color will need to be contained within a scope, such % as \cs{draw_begin:}/\cs{draw_end:}. % % \begin{function}{\color_fill:n, \color_stroke:n} % \begin{syntax} % \cs{color_fill:n} \Arg{color expression} % \end{syntax} % Parses the \meta{color expression} and then activates the resulting % color specification for filling or stroking. % \end{function} % % \begin{function}{\color_fill:nn, \color_stroke:nn} % \begin{syntax} % \cs{color_fill:nn} \Arg{model(s)} \Arg{value(s)} % \end{syntax} % Activates the color specification equivalent to the \meta{model(s)} and % \meta{value(s)} for filling or stroking. % \end{function} % % \begin{variable}[module = color]{color.sc} % When using \texttt{dvips}, this PostScript variables hold the stroke color. % \end{variable} % % \subsection{Coloring math mode material} % % Coloring math mode material using \cs{color_select:nn(n)} has some restrictions % and often leads to spacing issues and/or poor input syntax. Avoiding generating % \tn{mathord} atoms whilst coloring only those parts of the input which are % required needs careful handling. The functionality here covers this important % use case. % % \begin{function}[added = 2022-01-26]{\color_math:nn, \color_math:nnn} % \begin{syntax} % \cs{color_math:nn} \Arg{color expression}\Arg{content} % \cs{color_math:nnn} \Arg{model(s)} \Arg{value(s)} \Arg{content} % \end{syntax} % Works as for \cs{color_select:n(n)} but applies color only to the math mode % \meta{content}. The function does not generate a group and the \meta{content} % therefore retains its math atom states. Sub/superscripts are also properly % handled. % \end{function} % % \begin{variable}[added = 2022-01-26]{\l_color_math_active_tl} % This list controls which tokens are considered as math active and % should therefore be replaced by their definition during searching for % sub/superscripts. % \end{variable} % % \section{Multiple color models} % % When selecting or setting a color with an explicit model, it is possible % to give values for more than one model at one time. This is particularly % useful where automated conversion between models does not give the desired % outcome. To do this, the list of models and list of values are both subdivided % using |/| characters (as for the similar function in \pkg{xcolor}). For % example, to save a color with explicit |cmyk| and |rgb| values, one could % use % \begin{verbatim} % \color_set:nnn { foo } { cmyk / rgb } % { 0.1 , 0.2 , 0.3 , 0.4 / 0.1, 0.2 , 0.3 } % \end{verbatim} % The manually-specified conversion will be used in preference to automated % calculation whenever the model(s) listed are used: both in expressions and % when a fixed model is active. % % Similarly, the same syntax can be applied to directly selecting a color. % \begin{verbatim} % \color_select:nn { cmyk / rgb } % { 0.1 , 0.2 , 0.3 , 0.4 / 0.1, 0.2 , 0.3 } % \end{verbatim} % Again, this list is used when a fixed model is active: the first entry is used % unless there is a fixed model matching one of the other entries. % % \section{Exporting color specifications} % % The major use of color expressions is in setting typesetting output, but there % are other places in which some form of color information is required. These % may need data in a different format or using a different model to the internal % representation. Thus a set of functions are available to export colors in % different formats. % % Valid export targets are % \begin{itemize} % \item \texttt{backend} Two brace groups: the first containing the % model, the second containing space-separated values appropriate % for the model; this is the format required by backend functions % of \pkg{expl3} % \item \texttt{comma-sep-cmyk} Comma-separated cyan-magenta-yellow-black % values % \item \texttt{comma-sep-rgb} Comma-separated red-green-blue values % suitable for use as a PDF annotation color % \item \texttt{HTML} Uppercase two-digit hexadecimal values, expressing % a red-green-blue color; the digits are \emph{not} separated % \item \texttt{space-sep-cmyk} Space-separated cyan-magenta-yellow-black % values % \item \texttt{space-sep-rgb} Space-separated red-green-blue values % suitable for use as a PDF annotation color % \end{itemize} % % \begin{function}{\color_export:nnN} % \begin{syntax} % \cs{color_export:nnN} \Arg{color expression} \Arg{format} \Arg{tl} % \end{syntax} % Parses the \meta{color expression} as described earlier, % then converts to the \meta{format} specified and assigns the data to the % \meta{tl}. % \end{function} % % \begin{function}{\color_export:nnnN} % \begin{syntax} % \cs{color_export:nnnN} \Arg{model} \Arg{value(s)} \Arg{format} \Arg{tl} % \end{syntax} % Expresses the combination of \meta{model} and \meta{value(s)} in an % internal representation, then converts to the \meta{format} specified and % assigns the data to the \meta{tl}. % \end{function} % % \section{Creating new color models} % \label{l3color:sec:new-models} % % Additional color models are required to support specialist workflows, for % example those involving separations (see % \url{https://helpx.adobe.com/indesign/using/spot-process-colors.html} % for details of the use of separations in print). Color models may be split % into families; for the standard device-based color models (\texttt{DeviceCMYK}, % \texttt{DeviceRGB}, \texttt{DeviceGray}), these are synonymous. This % is not generally the case: see the PDF reference for more details. (Note that % \pkg{l3color} uses the shorter names \texttt{cmyk}, etc.) % % \begin{function}{\color_model_new:nnn} % \begin{syntax} % \cs{color_model_new:nnn} \Arg{model} \Arg{family} \Arg{params} % \end{syntax} % Creates a new \meta{model} which is derived from the color model \meta{family}. % The latter should be one of % \begin{itemize} % \item \texttt{DeviceN} % \item \texttt{ICCBased} % \item \texttt{Separation} % \end{itemize} % (The \meta{family} may be given in mixed case as-in the PDF reference: % internally, case of these strings is folded.) % Depending on the \meta{family}, one or more \meta{params} are mandatory or % optional. % \end{function} % % For a \texttt{Separation} space, there are three \emph{compulsory} keys. % \begin{itemize} % \item \texttt{name} The name of the Separation, for example the formal % name of a spot color ink. Such a \meta{name} may contain spaces, etc., % which are not permitted in the \meta{model}. % \item \texttt{alternative-model} An alternative device colorspace, one of % \texttt{cmyk}, \texttt{rgb}, \texttt{gray} or \texttt{CIELAB}. The three % parameter-based models work as described above; see below for % details of CIELAB colors. % \item \texttt{alternative-values} A comma-separated list of values % appropriate to the \texttt{alternative-model}. This information is used by % the PDF application if the \texttt{Separation} is not available. % \end{itemize} % % CIELAB color separations are created using the % \texttt{alternative-model = CIELAB} setting. These colors must also have an % \texttt{illuminant} key, one of \texttt{a}, \texttt{c}, \texttt{e}, % \texttt{d50}, \texttt{d55}, \texttt{d65} or \texttt{d75}. The % \texttt{alternative-values} in this case are the three parameters $L*$, $a*$ % and $b*$ of the CIELAB model. Full details of this device-independent color % approach are given in the documentation to the \pkg{colorspace} package. % % CIELAB colors \emph{cannot} be converted into other device-dependent color % spaces, and as such, mixing can only occur if colors set up using the CIELAB % model are also given with an alternative parameter-based model. If that is % not the case, \pkg{l3color} will fallback to using black as the colorant in % any mixing. % % For a \texttt{DeviceN} space, there is one \emph{compulsory} key. % \begin{itemize} % \item \texttt{names} The names of the components of the \texttt{DeviceN} % space. Each should be either the \meta{name} of a \texttt{Separation} model, % a process color name (\texttt{cyan}, etc.) or the special name \texttt{none}. % \end{itemize} % % For a \texttt{ICCBased} space, there is one \emph{compulsory} key. % \begin{itemize} % \item \texttt{file} The name of the file containing the profile. % \end{itemize} % % \subsection{Color profiles} % % Color profiles are used to ensure color accuracy by linking to collaboration. % Applying a profile can be used to standardise color which is otherwise % device-dependence. % % \begin{function}[added = 2021-02-23]{\color_profile_apply:nn} % \begin{syntax} % \cs{color_profile_apply:nn} \Arg{profile} \Arg{model} % \end{syntax} % This function applies a \meta{profile} to one of the device \meta{models}. % The profile will then apply to all color of the selected \meta{model}. The % \meta{profile} should specify an ICC profile file. The \meta{model} has to % be one the standard device models: \texttt{cmyk}, \texttt{gray} or % \texttt{rgb}. % \end{function} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{l3color} Implementation} % % \begin{macrocode} %<*package> % \end{macrocode} % % \begin{macrocode} %<@@=color> % \end{macrocode} % % \subsection{Basics} % % \begin{variable} % {\l_@@_current_tl} % The color currently active for foreground (text, \emph{etc.}) material. % This is stored in the form of a color model followed by one or more % values. There are four pre-defined models, three of which take numerical % values in the range $[0,1]$: % \begin{itemize} % \item \texttt{gray \meta{gray}} Grayscale color with the \meta{gray} % value running from $0$ (fully black) to $1$ (fully white) % \item \texttt{cmyk \meta{cyan} \meta{magenta} \meta{yellow} \meta{black}} % \item \texttt{rgb \meta{red} \meta{green} \meta{blue}} % \end{itemize} % Notice that the value are separated by spaces. There is a fourth pre-defined % model using a string value and a numerical one: % \begin{itemize} % \item \texttt{spot \meta{name} \meta{tint}} A pre-defined spot color, % where the \meta{name} should be a pre-defined string color name and the % \meta{tint} should be in the range $[0,1]$. % \end{itemize} % % Additional models may be created to allow mixing of spot colors. The % number of data entries these require will depend on the number of % colors to be mixed. % \begin{texnote} % The content of \cs{l_@@_current_tl} comprises two brace groups, the % first containing the color model and the second containing the value(s) % applicable in that model. % \end{texnote} % \end{variable} % % \begin{macro}{\color_group_begin:, \color_group_end:} % Grouping for color is the same as using the basic \cs{group_begin:} % and \cs{group_end:} functions. However, for semantic reasons, they % are renamed here. % \begin{macrocode} \cs_new_eq:NN \color_group_begin: \group_begin: \cs_new_eq:NN \color_group_end: \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}{\color_ensure_current:} % A driver-independent wrapper for setting the foreground color to the % current color \enquote{now}. % \begin{macrocode} \cs_new_protected:Npn \color_ensure_current: { \@@_select:N \l_@@_current_tl } % \end{macrocode} % \end{macro} % % \begin{variable}{\s_@@_stop} % Internal scan marks. % \begin{macrocode} \scan_new:N \s_@@_stop % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_select:N, \@@_select_math:N} % \begin{macro}{\@@_select:nn} % Take an internal color specification and pass it to the driver. This code % is needed to ensure the current color but will also be used by the % higher-level material. % \begin{macrocode} \cs_new_protected:Npn \@@_select:N #1 { \exp_after:wN \@@_select:nn #1 \group_insert_after:N \@@_backend_reset: } \cs_new_protected:Npn \@@_select_math:N #1 { \exp_after:wN \@@_select:nn #1 } \cs_new_protected:Npn \@@_select:nn #1#2 { \use:c { @@_backend_select_ #1 :n } {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{variable}{\l_@@_current_tl} % The current color, with the model and % \begin{macrocode} \tl_new:N \l_@@_current_tl \tl_set:Nn \l_@@_current_tl { { gray } { 0 } } % \end{macrocode} % \end{variable} % % \subsection{Predefined color names} % % The ability to predefine colors with a name is a key part of this module and % means there has to be a method for storing the results. At first sight, it % seems natural to follow the usual \pkg{expl3} model and create a % \texttt{color} variable type for the process. That would then allow both % local and global colors, constant colors and the like. However, these names % need to be accessible in some form at the user level, for selection of colors % either simply by name or as part of a more complex expression. This does not % require that the full name is exposed but does require that they can be % looked up in a predictable way. As such, it is more useful to expose just the % color names as part of the interface, with the result that only local color % names can be created. (This is also seen for example in key creation in % \pkg{l3keys}.) As a result, color names are declarative (no \texttt{new} % functions). % % Since there is no need to manipulate colors \emph{en masse}, each is stored % in a two-part structure: a \texttt{prop} for the colors themselves, and a % \texttt{tl} for the default model for each color. % % \subsection{Setup} % % \begin{variable}{\l_@@_internal_int, \l_@@_internal_tl} % \begin{macrocode} \int_new:N \l_@@_internal_int \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \begin{variable}{\s_@@_mark} % Internal scan marks. \cs{s_@@_stop} is already defined in \pkg{l3color-base}. % \begin{macrocode} \scan_new:N \s_@@_mark % \end{macrocode} % \end{variable} % % \subsection{Utility functions} % % \begin{macro}[pTF, EXP]{\color_if_exist:n} % A simple wrapper to avoid needing to have the lookup repeated in too many % places.To guard against a color created in a group, we need to test for % entries in the |prop|. % \begin{macrocode} \prg_new_conditional:Npnn \color_if_exist:n #1 { p , T, F, TF } { \prop_if_exist:cTF { l_@@_named_ #1 _prop } { \prop_if_empty:cTF { l_@@_named_ #1 _prop } \prg_return_false: \prg_return_true: } \prg_return_false: } % \end{macrocode} % \end{macro} % % \begin{macro}[EXP]{\@@_model:N, \@@_values:N} % Simple abstractions. % \begin{macrocode} \cs_new:Npn \@@_model:N #1 { \exp_after:wN \use_i:nn #1 } \cs_new:Npn \@@_values:N #1 { \exp_after:wN \use_ii:nn #1 } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_extract:nNN, \@@_extract:VNN} % Recover the values for the standard model for a color. % \begin{macrocode} \cs_new_protected:Npn \@@_extract:nNN #1#2#3 { \tl_set_eq:Nc #2 { l_@@_named_ #1 _tl } \prop_get:cVN { l_@@_named_ #1 _prop } #2 #3 } \cs_generate_variant:Nn \@@_extract:nNN { V } % \end{macrocode} % \end{macro} % % \subsection{Model conversion} % % \begin{macro}{\@@_convert:nnN, \@@_convert:VVN} % \begin{macro}{\@@_convert:nnnN, \@@_convert:nVnN, \@@_convert:nnVN} % \begin{macro}[EXP] % { % \@@_convert_gray_gray:w % \@@_convert_gray_rgb:w % \@@_convert_gray_cmyk:w % \@@_convert_cmyk_gray:w % \@@_convert_cmyk_rgb:w % \@@_convert_cmyk_cmyk:w % \@@_convert_rgb_gray:w % \@@_convert_rgb_rgb:w % \@@_convert_rgb_cmyk:w % } % \begin{macro}[EXP]{\@@_convert_rgb_cmyk:nnn} % \begin{macro}[EXP]{\@@_convert_rgb_cmyk:nnnn} % Model conversion is carried out using standard formulae for base models, % as described in the manual for \pkg{xcolor} (see also the \emph{PostScript % Language Reference Manual}). For other models direct conversion might not % be defined, so we go through the fallback models if necessary. % \begin{macrocode} \cs_new_protected:Npn \@@_convert:nnN #1#2#3 { \@@_convert:nnVN {#1} {#2} #3 #3 } \cs_generate_variant:Nn \@@_convert:nnN { VV } \cs_generate_variant:Nn \exp_last_unbraced:Nf { c } \cs_new_protected:Npn \@@_convert:nnnN #1#2#3#4 { \tl_set:Nx #4 { \cs_if_exist_use:cTF { @@_convert_ #1 _ #2 :w } { #3 \s_@@_stop } { \cs_if_exist:cTF { @@_convert_ \use:c { c_@@_fallback_ #1 _tl } _ #2 :w } { \exp_last_unbraced:cf { @@_convert_ \use:c { c_@@_fallback_ #1 _tl } _ #2 :w } { \use:c { @@_convert_ #1 _ \use:c { c_@@_fallback_ #1 _tl } :w } #3 \s_@@_stop } \s_@@_stop } { \exp_last_unbraced:cf { @@_convert_ \use:c { c_@@_fallback_ #2 _tl } _ #2 :w } { \cs_if_exist_use:cTF { @@_convert_ #1 _ \use:c { c_@@_fallback_ #2 _tl } :w } { #3 \s_@@_stop } { \exp_last_unbraced:cf { @@_convert_ \use:c { c_@@_fallback_ #1 _tl } _ \use:c { c_@@_fallback_ #2 _tl } :w } { \use:c { @@_convert_ #1 _ \use:c { c_@@_fallback_ #1 _tl } :w } #3 \s_@@_stop } \s_@@_stop } } \s_@@_stop } } } } \cs_generate_variant:Nn \@@_convert:nnnN { nV , nnV } \cs_new:Npn \@@_convert_gray_gray:w #1 \s_@@_stop { #1 } \cs_new:Npn \@@_convert_gray_rgb:w #1 \s_@@_stop { #1 ~ #1 ~ #1 } \cs_new:Npn \@@_convert_gray_cmyk:w #1 \s_@@_stop { 0 ~ 0 ~ 0 ~ \fp_eval:n { 1 - #1 } } % \end{macrocode} % These rather odd values are based on \textsc{ntsc} television: the set are % used for the |cmyk| conversion. % \begin{macrocode} \cs_new:Npn \@@_convert_rgb_gray:w #1 ~ #2 ~ #3 \s_@@_stop { \fp_eval:n { 0.3 * #1 + 0.59 * #2 + 0.11 * #3 } } \cs_new:Npn \@@_convert_rgb_rgb:w #1 \s_@@_stop { #1 } % \end{macrocode} % The conversion from |rgb| to |cmyk| is the most complex: a two-step % procedure which requires \emph{black generation} and \emph{undercolor % removal} functions. The PostScript reference describes them as % device-dependent, but following \pkg{xcolor} we assume they are linear. % Moreover, as the likelihood of anyone using a non-unitary matrix here is % tiny, we simplify and treat those two concepts as no-ops. To allow code % sharing with parsing of |cmy| values, we have an intermediate function % here (\cs{@@_convert_rgb_cmyk:nnn}) which actually takes |cmy| values % as input. % \begin{macrocode} \cs_new:Npn \@@_convert_rgb_cmyk:w #1 ~ #2 ~ #3 \s_@@_stop { \exp_args:Neee \@@_convert_rgb_cmyk:nnn { \fp_eval:n { 1 - #1 } } { \fp_eval:n { 1 - #2 } } { \fp_eval:n { 1 - #3 } } } \cs_new:Npn \@@_convert_rgb_cmyk:nnn #1#2#3 { \exp_args:Ne \@@_convert_rgb_cmyk:nnnn { \fp_eval:n { min( #1, #2 , #3 ) } } {#1} {#2} {#3} } \cs_new:Npn \@@_convert_rgb_cmyk:nnnn #1#2#3#4 { \fp_eval:n { min ( 1 , max ( 0 , #2 - #1 ) ) } \c_space_tl \fp_eval:n { min ( 1 , max ( 0 , #3 - #1 ) ) } \c_space_tl \fp_eval:n { min ( 1 , max ( 0 , #4 - #1 ) ) } \c_space_tl #1 } \cs_new:Npn \@@_convert_cmyk_gray:w #1 ~ #2 ~ #3 ~ #4 \s_@@_stop { \fp_eval:n { 1 - min ( 1 , 0.3 * #1 + 0.59 * #2 + 0.11 * #3 + #4 ) } } \cs_new:Npn \@@_convert_cmyk_rgb:w #1 ~ #2 ~ #3 ~ #4 \s_@@_stop { \fp_eval:n { 1 - min ( 1 , #1 + #4 ) } \c_space_tl \fp_eval:n { 1 - min ( 1 , #2 + #4 ) } \c_space_tl \fp_eval:n { 1 - min ( 1 , #3 + #4 ) } } \cs_new:Npn \@@_convert_cmyk_cmyk:w #1 \s_@@_stop { #1 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Color expressions} % % \begin{variable} % {\l_@@_model_tl, \l_@@_value_tl, \l_@@_next_model_tl, \l_@@_next_value_tl} % Working space to store the color data whilst doing calculations: keeping % it on the stack is attractive but gets tricky (return is non-trivial). % \begin{macrocode} \tl_new:N \l_@@_model_tl \tl_new:N \l_@@_value_tl \tl_new:N \l_@@_next_model_tl \tl_new:N \l_@@_next_value_tl % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_parse:nN} % \begin{macro}{\@@_parse_aux:nN} % \begin{macro}{\@@_parse_eq:Nn} % \begin{macro}{\@@_parse_eq:nNn} % \begin{macro}{\@@_parse:Nw} % \begin{macro}{\@@_parse_loop_init:Nnn} % \begin{macro}{\@@_parse_loop:w} % \begin{macro}{\@@_parse_loop:nn} % \begin{macro}{\@@_parse_gray:n, \@@_parse_std:n} % \begin{macro}{\@@_parse_break:w} % \begin{macro}{\@@_parse_end:} % \begin{macro}[EXP]{\@@_parse_mix:Nnnn, \@@_parse_mix:NVVn} % \begin{macro}[EXP]{\@@_parse_mix:nNnn} % \begin{macro}[EXP] % { % \@@_parse_mix_gray:nw , % \@@_parse_mix_rgb:nw , % \@@_parse_mix_cmyk:nw % } % The main function for parsing color expressions removes actives but % otherwise expands, then starts working through the expression itself. % At the end, we apply the payload. % \begin{macrocode} \cs_new_protected:Npx \@@_parse:nN #1#2 { \tl_set:Nx \exp_not:c { l_@@_named_ . _tl } { \exp_not:N \@@_model:N \exp_not:N \l_@@_current_tl } \prop_put:NVx \exp_not:c { l_@@_named_ . _prop } \exp_not:c { l_@@_named_ . _tl } { \exp_not:N \@@_values:N \exp_not:N \l_@@_current_tl } \exp_not:N \exp_args:Ne \exp_not:N \@@_parse_aux:nN { \exp_not:N \tl_to_str:n {#1} } #2 } % \end{macrocode} % Before going to all of the effort of parsing an expression, these two % precursor functions look for a pre-defined name, either on its own or % with a trailing |!| (which is the same thing). % \begin{macrocode} \cs_new_protected:Npn \@@_parse_aux:nN #1#2 { \color_if_exist:nTF {#1} { \@@_parse_set_eq:Nn #2 {#1} } { \@@_parse:Nw #2#1 ! \s_@@_stop } \@@_check_model:N #2 } \cs_new_protected:Npn \@@_parse_set_eq:Nn #1#2 { \tl_if_empty:NTF \l_color_fixed_model_tl { \exp_args:Nv \@@_parse_set_eq:nNn { l_@@_named_ #2 _tl } } { \exp_args:NV \@@_parse_set_eq:nNn \l_color_fixed_model_tl } #1 {#2} } % \end{macrocode} % Here, we have to allow for the case where there is a fixed model: % that can't be swept up by generic conversion as we are dealing with a % named color. % \begin{macrocode} \cs_new_protected:Npn \@@_parse_set_eq:nNn #1#2#3 { \prop_get:cnNTF { l_@@_named_ #3 _prop } {#1} \l_@@_value_tl { \tl_set:Nx #2 { {#1} { \l_@@_value_tl } } } { \tl_set_eq:Nc \l_@@_model_tl { l_@@_named_ #3 _tl } \prop_get:cVN { l_@@_named_ #3 _prop } \l_@@_model_tl \l_@@_value_tl \@@_convert:nnN \l_@@_model_tl {#1} \l_@@_value_tl \tl_set:Nx #2 { {#1} { \l_@@_value_tl } } } } \cs_new_protected:Npn \@@_parse:Nw #1#2 ! #3 \s_@@_stop { \color_if_exist:nTF {#2} { \tl_if_blank:nTF {#3} { \@@_parse_set_eq:Nn #1 {#2} } { \@@_parse_loop_init:Nnn #1 {#2} {#3} } } { \msg_error:nnn { color } { unknown-color } {#2} \tl_set:Nn \l_@@_current_tl { { gray } { 0 } } } } % \end{macrocode} % Once we establish that a full parse is needed, the next job is to get the % detail of the first color. That will determine the model we use for the % calculation: splitting here makes checking that a bit easier. % \begin{macrocode} \cs_new_protected:Npn \@@_parse_loop_init:Nnn #1#2#3 { \group_begin: \@@_extract:nNN {#2} \l_@@_model_tl \l_@@_value_tl \@@_parse_loop:w #3 ! ! ! ! \s_@@_stop \tl_set:Nx \l_@@_internal_tl { { \l_@@_model_tl } { \l_@@_value_tl } } \exp_args:NNNV \group_end: \tl_set:Nn #1 \l_@@_internal_tl } % \end{macrocode} % This is the loop proper: there can be an open-ended set of colors to parse, % separated by |!| tokens. There are a few cases to look out for. At the end % of the expression and with we find a mix of $100$ then we simply skip the % next color entirely (we can't stop the loop as there might be a further % valid color to mix in). On the other hand, if we get a mix of $0$ then % drop everything so far and start again. There is also a trailing % |white| to \enquote{read in} if the final explicit data is a mix. % Those conditions are separate from actually looping, which is therefore % sorted out by checking if we have further data to process: in contrast % to \pkg{xcolor}, we don't allow |!!| so the test can be simplified. % \begin{macrocode} \cs_new_protected:Npn \@@_parse_loop:w #1 ! #2 ! #3 ! #4 ! #5 \s_@@_stop { \bool_lazy_or:nnF { \tl_if_blank_p:n {#1} } { \int_compare_p:nNn {#1} = { 100 } } { \int_compare:nNnTF {#1} = { 0 } { \tl_if_blank:nTF {#2} { \@@_extract:nNN { white } } { \@@_extract:nNN {#2} } \l_@@_model_tl \l_@@_value_tl } { \use:x { \@@_parse_loop:nn {#1} { \tl_if_blank:nTF {#2} { white } {#2} } } } } \tl_if_blank:nF {#3} { \@@_parse_loop:w #3 ! #4 ! #5 \s_@@_stop } \@@_parse_end: } % \end{macrocode} % The \enquote{payload} of calculation in the loop first. If the model for % the upcoming color is different from that of the existing (partial) color, % convert the model. For |gray| the two are flipped round so that the outcome % is something with \enquote{real} color. We are then in a position to do the % actual calculation itself. The two auxiliaries here give us a way to break % the loop should an invalid name be found. % \begin{macrocode} \cs_new_protected:Npn \@@_parse_loop:nn #1#2 { \color_if_exist:nTF {#2} { \@@_extract:nNN {#2} \l_@@_next_model_tl \l_@@_next_value_tl \tl_if_eq:NNF \l_@@_model_tl \l_@@_next_model_tl { \str_if_eq:VnTF \l_@@_model_tl { gray } { \@@_parse_gray:n {#2} } { \@@_parse_std:n {#2} } } \tl_set:Nx \l_@@_value_tl { \@@_parse_mix:NVVn \l_@@_model_tl \l_@@_value_tl \l_@@_next_value_tl {#1} } } { \msg_error:nnn { color } { unknown-color } {#2} \@@_extract:nNN { black } \l_@@_model_tl \l_@@_value_tl \@@_parse_break:w } } % \end{macrocode} % The \texttt{gray} model needs special handling: the models need to be % swapped: we do that using a dedicated function. % \begin{macrocode} \cs_new_protected:Npn \@@_parse_gray:n #1 { \tl_set_eq:NN \l_@@_model_tl \l_@@_next_model_tl \tl_set:Nn \l_@@_next_model_tl { gray } \exp_args:NnV \@@_convert:nnN { gray } \l_@@_model_tl \l_@@_value_tl \prop_get:cVN { l_@@_named_ #1 _prop } \l_@@_model_tl \l_@@_next_value_tl } \cs_new_protected:Npn \@@_parse_std:n #1 { \prop_get:cVNF { l_@@_named_ #1 _prop } \l_@@_model_tl \l_@@_next_value_tl { \@@_convert:VVN \l_@@_next_model_tl \l_@@_model_tl \l_@@_next_value_tl } } \cs_new_protected:Npn \@@_parse_break:w #1 \@@_parse_end: { } \cs_new_protected:Npn \@@_parse_end: { } % \end{macrocode} % Do the vector arithmetic: mainly a question of shuffling input, along % with one pre-calculation to keep down the use of division. % \begin{macrocode} \cs_new:Npn \@@_parse_mix:Nnnn #1#2#3#4 { \exp_args:Nf \@@_parse_mix:nNnn { \fp_eval:n { #4 / 100 } } #1 {#2} {#3} } \cs_generate_variant:Nn \@@_parse_mix:Nnnn { NVV } \cs_new:Npn \@@_parse_mix:nNnn #1#2#3#4 { \use:c { @@_parse_mix_ #2 :nw } {#1} #3 \s_@@_mark #4 \s_@@_stop } \cs_new:Npn \@@_parse_mix_gray:nw #1#2 \s_@@_mark #3 \s_@@_stop { \fp_eval:n { #2 * #1 + #3 * ( 1 - #1 ) } } \cs_new:Npn \@@_parse_mix_rgb:nw #1#2 ~ #3 ~ #4 \s_@@_mark #5 ~ #6 ~ #7 \s_@@_stop { \fp_eval:n { #2 * #1 + #5 * ( 1 - #1 ) } \c_space_tl \fp_eval:n { #3 * #1 + #6 * ( 1 - #1 ) } \c_space_tl \fp_eval:n { #4 * #1 + #7 * ( 1 - #1 ) } } \cs_new:Npn \@@_parse_mix_cmyk:nw #1#2 ~ #3 ~ #4 ~ #5 \s_@@_mark #6 ~ #7 ~ #8 ~ #9 \s_@@_stop { \fp_eval:n { #2 * #1 + #6 * ( 1 - #1 ) } \c_space_tl \fp_eval:n { #3 * #1 + #7 * ( 1 - #1 ) } \c_space_tl \fp_eval:n { #4 * #1 + #8 * ( 1 - #1 ) } \c_space_tl \fp_eval:n { #5 * #1 + #9 * ( 1 - #1 ) } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP] % { % \@@_parse_model_gray:w, \@@_parse_model_rgb:w, % \@@_parse_model_cmyk:w % } % \begin{macro}[EXP]{\@@_parse_number:n} % \begin{macro}[EXP]{\@@_parse_number:w} % Turn the input into internal form, also tidying up the number quickly. % \begin{macrocode} \cs_new:Npn \@@_parse_model_gray:w #1 , #2 \s_@@_stop { { gray } { \@@_parse_number:n {#1} } } \cs_new:Npn \@@_parse_model_rgb:w #1 , #2 , #3 , #4 \s_@@_stop { { rgb } { \@@_parse_number:n {#1} ~ \@@_parse_number:n {#2} ~ \@@_parse_number:n {#3} } } \cs_new:Npn \@@_parse_model_cmyk:w #1 , #2 , #3 , #4 , #5 \s_@@_stop { { cmyk } { \@@_parse_number:n {#1} ~ \@@_parse_number:n {#2} ~ \@@_parse_number:n {#3} ~ \@@_parse_number:n {#4} } } \cs_new:Npn \@@_parse_number:n #1 { \@@_parse_number:w #1 . 0 . \s_@@_stop } \cs_new:Npn \@@_parse_number:w #1 . #2 . #3 \s_@@_stop { \tl_if_blank:nTF {#1} { 0 } {#1} . #2 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}[EXP] % { % \@@_parse_model_Gray:w, \@@_parse_model_hsb:w, % \@@_parse_model_Hsb:w, \@@_parse_model_HSB:w, % \@@_parse_model_HTML:w, \@@_parse_model_RGB:w % } % \begin{macro}[EXP]{\@@_parse_model_hsb:nnn, \@@_parse_model_hsb_aux:nnn} % \begin{macro}[EXP]{\@@_parse_model_hsb:nnnn} % \begin{macro}[EXP]{\@@_parse_model_hsb:nnnnn} % \begin{macro}[EXP] % { % \@@_parse_model_hsb_0:nnnn , % \@@_parse_model_hsb_1:nnnn , % \@@_parse_model_hsb_2:nnnn , % \@@_parse_model_hsb_3:nnnn , % \@@_parse_model_hsb_4:nnnn , % \@@_parse_model_hsb_5:nnnn % } % \begin{macro}[EXP]{\@@_parse_model_wave:w} % \begin{macro}[EXP] % {\@@_parse_model_wave_auxi:nn, \@@_parse_model_wave_auxii:nn} % \begin{macro}[EXP]{\@@_parse_model_wave_rho:n} % \begin{macrocode} \cs_new:Npn \@@_parse_model_Gray:w #1 , #2 \s_@@_stop { { gray } { \fp_eval:n { #1 / 15 } } } \cs_new:Npn \@@_parse_model_hsb:w #1 , #2 , #3 , #4 \s_@@_stop { \@@_parse_model_hsb:nnn {#1} {#2} {#3} } \cs_new:Npn \@@_parse_model_Hsb:w #1 , #2 , #3 , #4 \s_@@_stop { \exp_args:Ne \@@_parse_model_hsb:nnn { \fp_eval:n { #1 / 360 } } {#2} {#3} } % \end{macrocode} % The conversion here is non-trivial but is described at length % in the \pkg{xcolor} manual. For ease, we calculate the integer % and fractional parts of the hue first, then use them to work out the % possible values for $r$, $g$ and $b$ before putting them in the correct % places. % \begin{macrocode} \cs_new:Npn \@@_parse_model_hsb:nnn #1#2#3 { { rgb } { \exp_args:Ne \@@_parse_model_hsb_aux:nnn { \fp_eval:n { 6 * #1 } } {#2} {#3} } } \cs_new:Npn \@@_parse_model_hsb_aux:nnn #1#2#3 { \exp_args:Nee \@@_parse_model_hsb_aux:nnnn { \fp_eval:n { floor(#1) } } { \fp_eval:n { #1 - floor(#1) } } {#2} {#3} } \cs_new:Npn \@@_parse_model_hsb_aux:nnnn #1#2#3#4 { \use:e { \exp_not:N \@@_parse_model_hsb_aux:nnnnn { \@@_parse_number:n {#4} } { \fp_eval:n { round(#4 * (1 - #3) ,5) } } { \fp_eval:n { round(#4 * ( 1 - #3 * #2 ) ,5) } } { \fp_eval:n { round(#4 * ( 1 - #3 * (1 - #2) ) ,5) } } {#1} } } \cs_new:Npn \@@_parse_model_hsb_aux:nnnnn #1#2#3#4#5 { \use:c { @@_parse_model_hsb_ #5 :nnnn } {#1} {#2} {#3} {#4} } \cs_new:cpn { @@_parse_model_hsb_0:nnnn } #1#2#3#4 { #1 ~ #4 ~ #2 } \cs_new:cpn { @@_parse_model_hsb_1:nnnn } #1#2#3#4 { #3 ~ #1 ~ #2 } \cs_new:cpn { @@_parse_model_hsb_2:nnnn } #1#2#3#4 { #2 ~ #1 ~ #4 } \cs_new:cpn { @@_parse_model_hsb_3:nnnn } #1#2#3#4 { #2 ~ #3 ~ #1 } \cs_new:cpn { @@_parse_model_hsb_4:nnnn } #1#2#3#4 { #4 ~ #2 ~ #1 } \cs_new:cpn { @@_parse_model_hsb_5:nnnn } #1#2#3#4 { #1 ~ #2 ~ #3 } \cs_new:cpn { @@_parse_model_hsb_6:nnnn } #1#2#3#4 { #1 ~ #2 ~ #2 } \cs_new:Npn \@@_parse_model_HSB:w #1 , #2 , #3 , #4 \s_@@_stop { \exp_args:Neee \@@_parse_model_hsb:nnn { \fp_eval:n {#1 / 240} } { \fp_eval:n {#2 / 240} } { \fp_eval:n {#3 / 240} } } \cs_new:Npn \@@_parse_model_HTML:w #1 , #2 \s_@@_stop { \@@_parse_model_HTML_aux:w #1 0 0 0 0 0 0 \s_@@_stop } \cs_new:Npn \@@_parse_model_HTML_aux:w #1#2#3#4#5#6#7 \s_@@_stop { { rgb } { \fp_eval:n { round(\int_from_hex:n {#1#2} / 255,5) } ~ \fp_eval:n { round(\int_from_hex:n {#3#4} / 255,5) } ~ \fp_eval:n { round(\int_from_hex:n {#5#6} / 255,5) } } } \cs_new:Npn \@@_parse_model_RGB:w #1 , #2 , #3 , #4 \s_@@_stop { { rgb } { \fp_eval:n { round(#1 / 255,5) } ~ \fp_eval:n { round(#2 / 255,5) } ~ \fp_eval:n { round(#3 / 255,5) } } } % \end{macrocode} % Following the description in the \pkg{xcolor} manual. As we always use |rgb|, % there is no need to find the sixth, we just pas the information straight % to the |hsb| auxiliary defined earlier. % \begin{macrocode} \cs_new:Npn \@@_parse_model_wave:w #1 , #2 \s_@@_stop { { rgb } { \fp_compare:nNnTF {#1} < { 420 } { \@@_parse_model_wave_auxi:nn {#1} { 0.3 + 0.7 * (#1 - 380) / 40 } } { \fp_compare:nNnTF {#1} > { 700 } { \@@_parse_model_wave_auxi:nn {#1} { 0.3 + 0.7 * (#1 - 780) / -80 } } { \@@_parse_model_wave_auxi:nn {#1} { 1 } } } } } \cs_new:Npn \@@_parse_model_wave_auxi:nn #1#2 { \fp_compare:nNnTF {#1} < { 440 } { \@@_parse_model_wave_auxii:nn { 4 + \@@_parse_model_wave_rho:n { (#1 - 440) / -60 } } {#2} } { \fp_compare:nNnTF {#1} < { 490 } { \@@_parse_model_wave_auxii:nn { 4 - \@@_parse_model_wave_rho:n { (#1 - 440) / 50 } } {#2} } { \fp_compare:nNnTF {#1} < { 510 } { \@@_parse_model_wave_auxii:nn { 2 + \@@_parse_model_wave_rho:n { (#1 - 510) / -20 } } {#2} } { \fp_compare:nNnTF {#1} < { 580 } { \@@_parse_model_wave_auxii:nn { 2 - \@@_parse_model_wave_rho:n { (#1 - 510) / 70 } } {#2} } { \fp_compare:nNnTF {#1} < { 645 } { \@@_parse_model_wave_auxii:nn { \@@_parse_model_wave_rho:n { (#1 - 645) / -65 } } {#2} } { \@@_parse_model_wave_auxii:nn { 0 } {#2} } } } } } } \cs_new:Npn \@@_parse_model_wave_auxii:nn #1#2 { \exp_args:Neee \@@_parse_model_hsb_aux:nnn { \fp_eval:n {#1} } { 1 } { \@@_parse_model_wave_rho:n {#2} } } \cs_new:Npn \@@_parse_model_wave_rho:n #1 { \fp_eval:n { min(1, max(0,#1) ) } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_parse_model_cmy:w} % Simply pass data to the conversion functions. % \begin{macrocode} \cs_new:Npn \@@_parse_model_cmy:w #1 , #2 , #3 , #4 \s_@@_stop { { cmyk } { \@@_convert_rgb_cmyk:nnn {#1} {#2} {#3} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_parse_model_tHsb:w} % \begin{macro}{\@@_parse_model_tHsb:n} % \begin{macro}{\@@_parse_model_tHsb:nw} % There are three stages to the process here: bring the |tH| argument into % the normal range, divide through to get to |hsb| and finally convert that % to |rgb|. The final stage can be delegated to the parsing function for % |hsb|, and the conversion from |Hsb| to |hsb| is trivial, so the main focus % here is the first stage. We use a simple expandable loop to do the work, % and we implement the equation given in the \pkg{xcolor} manual % (number~85 there) as a simple expression. % \begin{macrocode} \cs_new:Npn \@@_parse_model_tHsb:w #1 , #2 , #3 , #4 \s_@@_stop { \exp_args:Ne \@@_parse_model_hsb:nnn { \@@_parse_model_tHsb:n {#1} } {#2} {#3} } \cs_new:Npn \@@_parse_model_tHsb:n #1 { \@@_parse_model_tHsb:nw {#1} 0 , 0 ; 60 , 30 ; 120 , 60 ; 180 , 120 ; 210 , 180 ; 240 , 240 ; 360 , 360 ; \q_recursion_tail , ; \q_recursion_stop } \cs_new:Npn \@@_parse_model_tHsb:nw #1 #2 , #3 ; #4 , #5 ; { \quark_if_recursion_tail_stop_do:nn {#4} { 0 } \fp_compare:nNnTF {#1} > {#4} { \@@_parse_model_tHsb:nw {#1} #4 , #5 ; } { \use_i_delimit_by_q_recursion_stop:nw { \fp_eval:n { ((#1 - #2) / (#4 - #2) * (#5 - #3) + #3) / 360 } } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_parse_model_&spot:w} % We cannot extract data here from that passed by \pkg{xcolor}, so % we fall back on a black tint. % \begin{macrocode} \cs_new:cpn { @@_parse_model_&spot:w } #1 , #2 \s_@@_stop { { gray } { #1 } } % \end{macrocode} % \end{macro} % % \subsection{Selecting colors (and color models)} % % \begin{variable}{\l_color_fixed_model_tl} % For selecting a single fixed model. % \begin{macrocode} \tl_new:N \l_color_fixed_model_tl % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_check_model:N} % \begin{macro}{\@@_check_model:nn} % Check that the model in use is the one required. % \begin{macrocode} \cs_new_protected:Npn \@@_check_model:N #1 { \tl_if_empty:NF \l_color_fixed_model_tl { \exp_after:wN \@@_check_model:nn #1 \tl_if_eq:NNF \l_@@_model_tl \l_color_fixed_model_tl { \@@_convert:VVN \l_@@_model_tl \l_color_fixed_model_tl \l_@@_value_tl } \tl_set:Nx #1 { { \l_color_fixed_model_tl } { \l_@@_value_tl } } } } \cs_new_protected:Npn \@@_check_model:nn #1#2 { \tl_set:Nn \l_@@_model_tl {#1} \tl_set:Nn \l_@@_value_tl {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}{\@@_finalise_current:} % A backend-neutral location for \enquote{last minute} manipulations before % handing off to the backend code. We set the special |.| syntax here: this % will therefore always be available. The finalisation is separate from the % main function so it can also be applied to \emph{e.g.}~page color. % \begin{macrocode} \cs_new_protected:Npx \@@_finalise_current: { \tl_set:Nx \exp_not:c { l_@@_named_ . _tl } { \exp_not:N \@@_model:N \exp_not:N \l_@@_current_tl } \prop_clear:N \exp_not:c { l_@@_named_ . _prop } \prop_put:NVx \exp_not:c { l_@@_named_ . _prop } \exp_not:c { l_@@_named_ . _tl } { \exp_not:N \@@_values:N \exp_not:N \l_@@_current_tl } } % \end{macrocode} % \end{macro} % % \begin{macro}{\color_select:n} % \begin{macro}{\color_select:nn} % \begin{macro}{\@@_select_main:Nw, \@@_select_loop:Nw} % \begin{macro}{\@@_select:nnN} % \begin{macro}{\@@_select_swap:Nnn} % Parse the input expressions then get the backend to actually activate % them. The main complexity here is the need to check through multiple models. % That is done \enquote{locally} here as the approach is subtly different to % when different models are being stored. % \begin{macrocode} \cs_new_protected:Npn \color_select:n #1 { \@@_parse:nN {#1} \l_@@_current_tl \@@_finalise_current: \@@_select:N \l_@@_current_tl } \cs_new_protected:Npn \color_select:nn #1#2 { \@@_select_main:Nw \l_@@_current_tl #1 / / \s_@@_mark #2 / / \s_@@_stop \@@_finalise_current: \@@_select:N \l_@@_current_tl } % \end{macrocode} % If the first color model is the fixed one, or if there is no fixed % model, we don't need most of the data: just set up and apply the backend % function. % \begin{macrocode} \cs_new_protected:Npn \@@_select_main:Nw #1 #2 / #3 / #4 \s_@@_mark #5 / #6 / #7 \s_@@_stop { \@@_select:nnN {#2} {#5} #1 \bool_lazy_or:nnF { \tl_if_empty_p:N \l_color_fixed_model_tl } { \str_if_eq_p:nV {#2} \l_color_fixed_model_tl } { \@@_select_loop:Nw #1 #3 / #4 \s_@@_mark #6 / #7 \s_@@_stop } } % \end{macrocode} % If a fixed model applies, we need to check each possible value in order. % If there is no hit at all, fall back on the generic formula-based % interchange. % \begin{macrocode} \cs_new_protected:Npn \@@_select_loop:Nw #1 #2 / #3 \s_@@_mark #4 / #5 \s_@@_stop { \str_if_eq:nVTF {#2} \l_color_fixed_model_tl { \@@_select:nnN {#2} {#4} #1 } { \tl_if_blank:nTF {#2} { \exp_after:wN \@@_select_swap:Nnn \exp_after:wN #1 #1 } { \@@_select_loop:Nw #1 #3 \s_@@_mark #5 \s_@@_stop } } } \cs_new_protected:Npn \@@_select:nnN #1#2#3 { \cs_if_exist:cTF { @@_parse_model_ #1 :w } { \tl_set:Nx #3 { \use:c { @@_parse_model_ #1 :w } #2 , 0 , 0 , 0 , 0 \s_@@_stop } } { \msg_error:nnn { color } { unknown-model } {#1} } } \cs_new_protected:Npn \@@_select_swap:Nnn #1#2#3 { \@@_convert:nVnN {#2} \l_color_fixed_model_tl {#3} \l_@@_value_tl \tl_set:Nx #1 { { \l_color_fixed_model_tl } { \l_@@_value_tl } } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Math color} % % The approach here is the same as for the \LaTeXe{} \cs{mathcolor} command, % but as we are working at the \pkg{expl3} level we can make some minor % changes. % % \begin{macro}{\l_color_math_active_tl} % Tokens representing active sub/superscripts. % \begin{macrocode} \tl_new:N \l_color_math_active_tl \tl_set:Nn \l_color_math_active_tl { ' } % \end{macrocode} % \end{macro} % % \begin{macro}{\g_@@_math_seq} % Not all engines have multiple color stacks, and at the same time we are % not expecting breaking within a colored math fragment. So we track the % color stack ourselves. % \begin{macrocode} \seq_new:N \g_@@_math_seq % \end{macrocode} % \end{macro} % % \begin{macro}{\color_math:nn} % \begin{macro}{\color_math:nnn} % \begin{macro}{\@@_math:nn} % The basic set up here is relatively simple: store the current color, % parse the new color as-normal, then switch color before inserting the % tokens we are asked to change. The tricky part is right at the end, % handling the reset. % \begin{macrocode} \cs_new_protected:Npn \color_math:nn #1#2 { \@@_math:nn {#2} { \@@_parse:nN {#1} \l_@@_current_tl } } \cs_new_protected:Npn \color_math:nnn #1#2#3 { \@@_math:nn {#3} { \@@_select_main:Nw \l_@@_current_tl #1 / / \s_@@_mark #2 / / \s_@@_stop } } \cs_new_protected:Npn \@@_math:nn #1#2 { \seq_gpush:NV \g_@@_math_seq \l_@@_current_tl #2 \@@_select_math:N \l_@@_current_tl #1 \@@_math_scan:w } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro} % { % \@@_math_scan:w , % \@@_math_scan_auxi: , % \@@_math_scan_auxii: , % \@@_math_scan_end: % } % The complication when changing the color back is due to the fact % that the \cs{color_math:nn(n)} may be followed by \verb=^= or \verb=_= % or the hidden superscript (for example \texttt{'}) and its argument may % end in a \tn{mathop} in which case the sub- and superscripts may be % attached as \cs{limits} instead of after the material. All cases % need separate treatment. To avoid repeatedly collecting the same % token, we first check for an alignment tab: assuming we don't have % one of those, we can \enquote{recycle} \cs{l_peek_token} safely. % As we have an explicit \cs{c_alignment_token}, there needs to be % an align-safe group present. % \begin{macrocode} \cs_new_protected:Npn \@@_math_scan:w { \peek_remove_filler:n { \group_align_safe_begin: \peek_catcode:NTF \c_alignment_token { \group_align_safe_end: \@@_math_scan_end: } { \group_align_safe_end: \@@_math_scan_auxi: } } } % \end{macrocode} % Dealing with literal |_| and |^| is easy, and as we have exactly two cases, % we can hard-code this. We use a hard-coded list for limits: these are all % primitives. The \cs{use_none:n} here also removes the test token so it is % left just in the right place. % \begin{macrocode} \cs_new_protected:Npn \@@_math_scan_auxi: { \token_case_catcode:NnTF \l_peek_token { \c_math_subscript_token { } \c_math_superscript_token { } } { \@@_math_scripts:Nw } { \token_case_meaning:NnTF \l_peek_token { \tex_limits:D { \tex_limits:D } \tex_nolimits:D { \tex_nolimits:D } \tex_displaylimits:D { \tex_displaylimits:D } } { \@@_math_scan:w \use_none:n } { \@@_math_scan_auxii: } } } % \end{macrocode} % The one final case to handle is math-active tokens, most obviously % \texttt{'}, as these won't be covered earlier. % \begin{macrocode} \cs_new_protected:Npn \@@_math_scan_auxii: { \tl_map_inline:Nn \l_color_math_active_tl { \token_if_eq_meaning:NNT \l_peek_token ##1 { \tl_map_break:n { \use_i:nn { \@@_math_scan_auxiii:N ##1 } } } \@@_math_scan_end: } } \cs_new_protected:Npn \@@_math_scan_auxiii:N #1 { \exp_after:wN \exp_after:wN \exp_after:wN \@@_math_scan:w \char_generate:nn { `#1 } { 13 } } \cs_new_protected:Npn \@@_math_scan_end: { \@@_backend_reset: \seq_gpop:NN \g_@@_math_seq \l_@@_current_tl } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_math_scripts:Nw} % \begin{macro}{\@@_math_script_aux:N} % The tricky part of handling sub and superscripts is that we have % to reset color to the one that is on the stack but reset it back % to what it was before to allow for cases like % \begin{verbatim} % \[ \color_math:n { red } { a + \sum } _ { i = 1 } ^ { n } \] % \end{verbatim} % Here, \TeX{} constructs a \cs{vbox} stacking subscript, summation % sign, and superscript. So technically the superscript comes first % and the \cs{sum} that should get colored red is the middle. % % The approach here is to set up a brace group immediately after the % script token, then to set the color appropriately in that argument. % We need an extra group to keep the color contained, and as we % need to allow for an explicit closing brace in the source, the % inner group also is a brace one rather than \cs{group_begin:}-based. % At the end of the outer group we need to insert \cs{@@_math_scan:w} % to continue the search for a second script token. % % Notice that here we \emph{don't} need to use the math-specific % color selector as we can allow the % |\group_insert_after:N \@@_backend_reset:| to operate normally. % \begin{macrocode} \cs_new_protected:Npn \@@_math_scripts:Nw #1 { #1 \c_group_begin_token \c_group_begin_token \seq_get:NN \g_@@_math_seq \l_@@_current_tl \@@_select:N \l_@@_current_tl \group_insert_after:N \c_group_end_token \group_insert_after:N \@@_math_scan:w \peek_remove_filler:n { \peek_catcode_remove:NF \c_group_begin_token { \@@_math_script_aux:N } } } % \end{macrocode} % Deal with the case where we do not have an explicit brace pair in the % source. % \begin{macrocode} \cs_new_protected:Npn \@@_math_script_aux:N #1 { #1 \c_group_end_token } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Fill and stroke color} % % \begin{macro}{\color_fill:n, \color_stroke:n} % \begin{macro}{\color_fill:nn, \color_stroke:nn} % \begin{macro}{\@@_draw:nnn} % \begin{macrocode} \cs_new_protected:Npn \color_fill:n #1 { \@@_parse:nN {#1} \l_@@_current_tl \exp_after:wN \@@_draw:nnn \l_@@_current_tl { fill } } \cs_new_protected:Npn \color_stroke:n #1 { \@@_parse:nN {#1} \l_@@_current_tl \exp_after:wN \@@_draw:nnn \l_@@_current_tl { stroke } } \cs_new_protected:Npn \color_fill:nn #1#2 { \@@_select_main:Nw \l_@@_current_tl #1 / / \s_@@_mark #2 / / \s_@@_stop \exp_after:wN \@@_draw:nnn \l_@@_current_tl { fill } } \cs_new_protected:Npn \color_stroke:nn #1#2 { \@@_select_main:Nw \l_@@_current_tl #1 / / \s_@@_mark #2 / / \s_@@_stop \exp_after:wN \@@_draw:nnn \l_@@_current_tl { stroke } } \cs_new_protected:Npn \@@_draw:nnn #1#2#3 { \use:c { @@_backend_ #3 _ #1 :n } {#2} \exp_args:Nc \group_insert_after:N { @@_backend_ #3 _ reset: } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Defining named colors} % % \begin{variable}{\l_@@_named_tl} % Space to store the detail of the named color. % \begin{macrocode} \tl_new:N \l_@@_named_tl % \end{macrocode} % \end{variable} % % \begin{macro}{\color_set:nn} % \begin{macro}{\@@_set:nnn} % \begin{macro}{\@@_set:nn} % \begin{macro}{\@@_set:nnw} % \begin{macro}{\color_set:nnn, \@@_set_aux:nnn} % \begin{macro}{\@@_set_colon:nnw} % \begin{macro}{\@@_set_loop:nw} % \begin{macro}{\color_set_eq:nn} % Defining named colors means working through the model list and saving % both the \enquote{main} color and any equivalents in other models. Even % if there is only one model, we store a |prop| as well as a |tl|, as there % could be grouping weirdness, etc. When setting using an expression, % we need to avoid any fixed model issues, which is done without a group as % in \pkg{l3keys}. % \begin{macrocode} \cs_new_protected:Npn \color_set:nn #1#2 { \exp_args:NV \@@_set:nnn \l_color_fixed_model_tl {#1} {#2} } \cs_new_protected:Npn \@@_set:nnn #1#2#3 { \tl_clear:N \l_color_fixed_model_tl \@@_set:nn {#2} {#3} \tl_set:Nn \l_color_fixed_model_tl {#1} } \cs_new_protected:Npn \@@_set:nn #1#2 { \str_if_eq:nnF {#1} { . } { \@@_parse:nN {#2} \l_@@_named_tl \tl_clear_new:c { l_@@_named_ #1 _tl } \tl_set:cx { l_@@_named_ #1 _tl } { \@@_model:N \l_@@_named_tl } \prop_clear_new:c { l_@@_named_ #1 _prop } \prop_put:cvx { l_@@_named_ #1 _prop } { l_@@_named_ #1 _tl } { \@@_values:N \l_@@_named_tl } \@@_set:nnw {#1} {#2} #2 ! \s_@@_stop } } % \end{macrocode} % When setting an expression-based color, there could be multiple model % data available for one or more of the input colors. Where that is true for % the \emph{first} named color in an expression, we re-parse the expression % when they are also parameter-based: only |cmyk|, |gray| and |rgb| make % any sense here. There is a bit of a performance hit but this should be % rare and taking place during set-up. % \begin{macrocode} \cs_new_protected:Npn \@@_set:nnw #1#2#3 ! #4 \s_@@_stop { \clist_map_inline:nn { cmyk , gray , rgb } { \prop_get:cnNT { l_@@_named_ #3 _prop } {##1} \l_@@_internal_tl { \prop_if_in:cnF { l_@@_named_ #1 _prop } {##1} { \group_begin: \tl_set:cn { l_@@_named_ #3 _tl } {##1} \@@_parse:nN {#2} \l_@@_internal_tl \exp_args:NNNV \group_end: \tl_set:Nn \l_@@_internal_tl \l_@@_internal_tl \prop_put:cxx { l_@@_named_ #1 _prop } { \@@_model:N \l_@@_internal_tl } { \@@_values:N \l_@@_internal_tl } } } } } \cs_new_protected:Npn \color_set:nnn #1#2#3 { \str_if_eq:nnF {#1} { . } { \tl_clear_new:c { l_@@_named_ #1 _tl } \prop_clear_new:c { l_@@_named_ #1 _prop } \exp_args:Ne \@@_set_aux:nnn { \tl_to_str:n {#2} } {#1} {#3} } } \cs_new_protected:Npx \@@_set_aux:nnn #1#2#3 { \exp_not:N \@@_set_colon:nnw {#2} {#3} #1 \c_colon_str \c_colon_str \exp_not:N \s_@@_stop } \use:x { \cs_new_protected:Npn \exp_not:N \@@_set_colon:nnw ##1##2 ##3 \c_colon_str ##4 \c_colon_str ##5 \exp_not:N \s_@@_stop } { \tl_if_blank:nTF {#4} { \@@_set_loop:nw {#1} #3 } { \@@_set_loop:nw {#1} #4 } / / \s_@@_mark #2 / / \s_@@_stop } \cs_new_protected:Npn \@@_set_loop:nw #1#2 / #3 \s_@@_mark #4 / #5 \s_@@_stop { \tl_if_blank:nF {#2} { \@@_select:nnN {#2} {#4} \l_@@_named_tl \tl_set:Nx \l_@@_internal_tl { \@@_model:N \l_@@_named_tl } \tl_if_empty:cT { l_@@_named_ #1 _tl } { \tl_set_eq:cN { l_@@_named_ #1 _tl } \l_@@_internal_tl } \prop_put:cVx { l_@@_named_ #1 _prop } \l_@@_internal_tl { \@@_values:N \l_@@_named_tl } \@@_set_loop:nw {#1} #3 \s_@@_mark #5 \s_@@_stop } } \cs_new_protected:Npn \color_set_eq:nn #1#2 { \color_if_exist:nTF {#2} { \tl_clear_new:c { l_@@_named_ #1 _tl } \prop_clear_new:c { l_@@_named_ #1 _prop } \str_if_eq:nnTF {#2} { . } { \tl_set:cx { l_@@_named_ #1 _tl } { \@@_model:N \l_@@_current_tl } \prop_put:cvx { l_@@_named_ #1 _prop } { l_@@_named_ #1 _tl } { \@@_values:N \l_@@_current_tl } } { \tl_set_eq:cc { l_@@_named_ #1 _tl } { l_@@_named_ #2 _tl } \prop_set_eq:cc { l_@@_named_ #1 _prop } { l_@@_named_ #2 _prop } } } { \msg_error:nnn { color } { unknown-color } {#2} } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % A small set of colors are always defined. % \begin{macrocode} \color_set:nnn { black } { gray } { 0 } \color_set:nnn { white } { gray } { 1 } \color_set:nnn { cyan } { cmyk } { 1 , 0 , 0 , 0 } \color_set:nnn { magenta } { cmyk } { 0 , 1 , 0 , 0 } \color_set:nnn { yellow } { cmyk } { 0 , 0 , 1 , 0 } \color_set:nnn { red } { rgb } { 1 , 0 , 0 } \color_set:nnn { green } { rgb } { 0 , 1 , 0 } \color_set:nnn { blue } { rgb } { 0 , 0 , 1 } % \end{macrocode} % % \begin{variable}{\l_@@_named_._prop, \l_@@_named_._tl} % A special named color: this is always defined though not fixed in % definition. % \begin{macrocode} \prop_new:c { l_@@_named_._prop } \tl_new:c { l_@@_named_._tl } \tl_set:cx { l_@@_named_._tl } { \@@_model:N \l_@@_current_tl } % \end{macrocode} % \end{variable} % % \subsection{Exporting colors} % % \begin{macro}{\color_export:nnN} % \begin{macro}{\color_export:nnnN} % \begin{macro}{\@@_export:nN} % \begin{macro}{\@@_export:nnnN} % \begin{macrocode} \cs_new_protected:Npn \color_export:nnN #1#2#3 { \group_begin: \tl_if_exist:cT { c_@@_export_ #2 _tl } { \tl_set_eq:Nc \l_color_fixed_model_tl { c_@@_export_ #2 _tl } } \@@_parse:nN {#1} #3 \@@_export:nN {#2} #3 \exp_args:NNNV \group_end: \tl_set:Nn #3 #3 } \cs_new_protected:Npn \color_export:nnnN #1#2#3#4 { \@@_select_main:Nw #4 #1 / / \s_@@_mark #2 / / \s_@@_stop \@@_export:nN {#3} #4 } \cs_new_protected:Npn \@@_export:nN #1#2 { \exp_after:wN \@@_export:nnnN #2 {#1} #2 } \cs_new:Npn \@@_export:nnnN #1#2#3#4 { \cs_if_exist_use:cF { @@_export_format_ #3 :nnN } { \msg_error:nnn { color } { unknown-export-format } {#3} \use_none:nnn } {#1} {#2} #4 } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_export_format_backend:nnN} % Simple. % \begin{macrocode} \cs_new_protected:Npn \@@_export_format_backend:nnN #1#2#3 { \tl_set:Nn #3 { {#1} {#2} } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_export:nnnNN} % A generic auxiliary for cases where only one model is appropriate. % \begin{macrocode} \cs_new_protected:Npn \@@_export:nnnNN #1#2#3#4#5 { \str_if_eq:nnTF {#2} {#1} { #5 #4 #3 \s_@@_stop } { \@@_convert:nnnN {#2} {#1} {#3} #4 \exp_after:wN #5 \exp_after:wN #4 #4 \s_@@_stop } } % \end{macrocode} % \end{macro} % % \begin{variable} % { % \c_@@_export_comma-sep-cmyk_tl , % \c_@@_export_comma-sep-rgb_tl , % \c_@@_export_HTML_tl , % \c_@@_export_space-sep-cmyk_tl , % \c_@@_export_space-sep-rgb_tl % } % \begin{macrocode} \tl_const:cn { c_@@_export_comma-sep-cmyk_tl } { cmyk } \tl_const:cn { c_@@_export_comma-sep-rgb_tl } { rgb } \tl_const:Nn \c_@@_export_HTML_tl { rgb } \tl_const:cn { c_@@_export_space-sep-cmyk_tl } { cmyk } \tl_const:cn { c_@@_export_space-sep-rgb_tl } { rgb } % \end{macrocode} % \end{variable} % % \begin{macro} % { % \@@_export_format_comma-sep-cmyk:nnN , % \@@_export_format_comma-sep-rgb:nnN , % \@@_export_format_space-sep-cmyk:nnN , % \@@_export_format_space-sep-rgb:nnN % } % \begin{macrocode} \group_begin: \cs_set_protected:Npn \@@_tmp:w #1#2 { \cs_new_protected:cpx { @@_export_format_ #1 :nnN } ##1##2##3 { \exp_not:N \@@_export:nnnNN {#2} {##1} {##2} ##3 \exp_not:c { @@_export_ #1 :Nw } } } \@@_tmp:w { comma-sep-cmyk } { cmyk } \@@_tmp:w { comma-sep-rgb } { rgb } \@@_tmp:w { HTML } { rgb } \@@_tmp:w { space-sep-cmyk } { cmyk } \@@_tmp:w { space-sep-rgb } { rgb } \group_end: % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_export_space-sep-cmyk:Nw, \@@_export_comma-sep-cmyk:Nw} % \begin{macrocode} \cs_new_protected:cpn { @@_export_comma-sep-cmyk:Nw } #1#2 ~ #3 ~ #4 ~ #5 \s_@@_stop { \tl_set:Nn #1 { #2 , #3 , #4 , #5 } } \cs_new_protected:cpn { @@_export_space-sep-cmyk:Nw } #1#2 \s_@@_stop { \tl_set:Nn #1 {#2} } % \end{macrocode} % \end{macro} % % \begin{macro} % { % \@@_export_comma-sep-rgb:Nw , % \@@_export_HTML:Nw , % \@@_export_space-sep-rgb:Nw % } % \begin{macro}[EXP]{\@@_export_HTML:n} % \textsc{html} values must be given in |rgb|: we force conversion if % required, then do some simple maths. % \begin{macrocode} \cs_new_protected:cpn { @@_export_comma-sep-rgb:Nw } #1#2 ~ #3 ~ #4 \s_@@_stop { \tl_set:Nx #1 { #2 , #3 , #4 } } \cs_new_protected:Npn \@@_export_HTML:Nw #1#2 ~ #3 ~ #4 \s_@@_stop { \tl_set:Nx #1 { \@@_export_HTML:n {#2} \@@_export_HTML:n {#3} \@@_export_HTML:n {#4} } } \cs_new:Npn \@@_export_HTML:n #1 { \fp_compare:nNnTF {#1} = { 0 } { 00 } { \fp_compare:nNnT { #1 * 255 } < { 16 } { 0 } \int_to_Hex:n { \fp_to_int:n { #1 * 255 } } } } \cs_new_protected:cpn { @@_export_space-sep-rgb:Nw } #1#2 \s_@@_stop { \tl_set:Nn #1 {#2} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Additional color models} % % \begin{variable}{\l_@@_internal_prop} % \begin{macrocode} \prop_new:N \l_@@_internal_prop % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_model_int} % A tracker for the total number of new models. % \begin{macrocode} \int_new:N \g_@@_model_int % \end{macrocode} % \end{variable} % % \begin{variable} % {\c_@@_fallback_cmyk_tl, \c_@@_fallback_gray_tl, \c_@@_fallback_rgb_tl} % For every colorspace, we define one of the base colorspaces as a fallback. % The base colorspaces themselves are their own fallback. % \begin{macrocode} \tl_const:Nn \c_@@_fallback_cmyk_tl { cmyk } \tl_const:Nn \c_@@_fallback_gray_tl { gray } \tl_const:Nn \c_@@_fallback_rgb_tl { rgb } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_colorants_prop} % Mapping from names to colorants. % \begin{macrocode} \prop_new:N \g_@@_colorants_prop \prop_gput:Nnn \g_@@_colorants_prop { black } { Black } \prop_gput:Nnn \g_@@_colorants_prop { blue } { Blue } \prop_gput:Nnn \g_@@_colorants_prop { cyan } { Cyan } \prop_gput:Nnn \g_@@_colorants_prop { green } { Green } \prop_gput:Nnn \g_@@_colorants_prop { magenta } { Magenta } \prop_gput:Nnn \g_@@_colorants_prop { none } { None } \prop_gput:Nnn \g_@@_colorants_prop { red } { Red } \prop_gput:Nnn \g_@@_colorants_prop { yellow } { Yellow } % \end{macrocode} % \end{variable} % % \begin{variable} % { % \c_@@_model_whitepoint_CIELAB_a_tl , % \c_@@_model_whitepoint_CIELAB_b_tl , % \c_@@_model_whitepoint_CIELAB_e_tl , % \c_@@_model_whitepoint_CIELAB_d50_tl , % \c_@@_model_whitepoint_CIELAB_d55_tl , % \c_@@_model_whitepoint_CIELAB_d65_tl , % \c_@@_model_whitepoint_CIELAB_d75_tl % } % Whitepoint data for the CIELAB profiles. % \begin{macrocode} \tl_const:Nn \c_@@_model_whitepoint_CIELAB_a_tl { 1.0985 ~ 1 ~ 0.3558 } \tl_const:Nn \c_@@_model_whitepoint_CIELAB_b_tl { 0.9807 ~ 1 ~ 1.1822 } \tl_const:Nn \c_@@_model_whitepoint_CIELAB_e_tl { 1 ~ 1 ~ 1 } \tl_const:cn { c_@@_model_whitepoint_CIELAB_d50_tl } { 0.9642 ~ 1 ~ 0.8251 } \tl_const:cn { c_@@_model_whitepoint_CIELAB_d55_tl } { 0.9568 ~ 1 ~ 0.9214 } \tl_const:cn { c_@@_model_whitepoint_CIELAB_d65_tl } { 0.9504 ~ 1 ~ 1.0888 } \tl_const:cn { c_@@_model_whitepoint_CIELAB_d75_tl } { 0.9497 ~ 1 ~ 1.2261 } % \end{macrocode} % \end{variable} % % \begin{variable}{\c_@@_model_range_CIELAB_tl} % The range for CIELAB color spaces. % \begin{macrocode} \tl_const:Nn \c_@@_model_range_CIELAB_tl { 0 ~ 100 ~ -128 ~ 127 ~ -128 ~ 127 } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_alternative_model_prop} % For tracking the alternative model set up for separations, etc. % \begin{macrocode} \prop_new:N \g_@@_alternative_model_prop \clist_map_inline:nn { cyan , magenta , yellow , black } { \prop_gput:Nnn \g_@@_alternative_model_prop {#1} { cmyk } } \clist_map_inline:nn { red , green , blue } { \prop_gput:Nnn \g_@@_alternative_model_prop {#1} { rgb } } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_alternative_values_prop} % Same for the values: a bit more involved. % \begin{macrocode} \prop_new:N \g_@@_alternative_values_prop \prop_gput:Nnn \g_@@_alternative_values_prop { cyan } { 1 , 0 , 0 , 0 } \prop_gput:Nnn \g_@@_alternative_values_prop { magenta } { 0 , 1 , 0 , 0 } \prop_gput:Nnn \g_@@_alternative_values_prop { yellow } { 0 , 0 , 1 , 0 } \prop_gput:Nnn \g_@@_alternative_values_prop { black } { 0 , 0 , 0 , 1 } \prop_gput:Nnn \g_@@_alternative_values_prop { red } { 1 , 0 , 0 } \prop_gput:Nnn \g_@@_alternative_values_prop { green } { 0 , 1 , 0 } \prop_gput:Nnn \g_@@_alternative_values_prop { blue } { 0 , 0 , 1 } % \end{macrocode} % \end{variable} % % \begin{macro}{\color_model_new:nnn, \@@_model_new:nnn} % Set up a new model: in general this has to be handled by a family-dependent % function. To avoid some \enquote{interesting} questions with casing, we % fold the case of the family name. The key--value list should always be % present, so we convert it up-front to a |prop|, then deal with the detail % on a per-family basis. % \begin{macrocode} \cs_new_protected:Npn \color_model_new:nnn #1#2#3 { \exp_args:Nee \@@_model_new:nnn { \tl_to_str:n {#1} } { \str_foldcase:n {#2} } {#3} } \cs_new_protected:Npn \@@_model_new:nnn #1#2#3 { \cs_if_exist:cTF { @@_parse_model_ #1 :w } { \msg_error:nnn { color } { model-already-defined } {#1} } { \cs_if_exist:cTF { @@_model_ #2 :n } { \prop_set_from_keyval:Nn \l_@@_internal_prop {#3} \use:c { @@_model_ #2 :n } {#1} } { \msg_error:nnn { color } { unknown-model-type } {#2} } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_model_init:nnn, \@@_model_init:nnx} % A shared auxiliary to do the basics of setting up a new model: reserve a % number, create a white-equivalent, set up links to the backend. % \begin{macrocode} \cs_new_protected:Npn \@@_model_init:nnn #1#2#3 { \int_gincr:N \g_@@_model_int \clist_map_inline:nn { fill , stroke , select } { \cs_new_protected:cpx { @@_backend_ ##1 _ #1 :n } ####1 { \exp_not:c { @@_backend_ ##1 _ #2 :nn } { color \int_use:N \g_@@_model_int } {####1} } } \cs_new_protected:cpx { @@_model_ #1 _white: } { \prop_put:Nnn \exp_not:N \l_@@_named_white_prop {#1} { \exp_not:n {#3} } \exp_not:N \int_compare:nNnF { \tex_currentgrouplevel:D } = 0 { \group_insert_after:N \exp_not:c { @@_model_ #1 _ white: } } } \use:c { @@_model_ #1 _white: } } \cs_generate_variant:Nn \@@_model_init:nnn { nnx } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_model_separation:n} % \begin{macro}{\@@_model_separation:nn} % \begin{macro}{\@@_model_separation:nnn} % \begin{macro}{\@@_model_separation:w} % \begin{macro} % { % \@@_model_separation_cmyk:nnnnnn , % \@@_model_separation_gray:nnnnnn , % \@@_model_separation_rgb:nnnnnn % } % \begin{macro}{\@@_model_convert:nnn} % \begin{macro}{\@@_model_separation_CIELAB:nnnnnn} % \begin{macro}{\@@_model_separation_CIELAB:nnnnnnn} % Separations must have a \enquote{real} name, which is pretty easy to find. % \begin{macrocode} \cs_new_protected:Npn \@@_model_separation:n #1 { \prop_get:NnNTF \l_@@_internal_prop { name } \l_@@_internal_tl { \exp_args:NV \@@_model_separation:nn \l_@@_internal_tl {#1} } { \msg_error:nnn { color } { separation-requires-name } {#1} } } % \end{macrocode} % We have two keys to find at this stage: the alternative space model % and linked values. % \begin{macrocode} \cs_new_protected:Npn \@@_model_separation:nn #1#2 { \prop_get:NnNTF \l_@@_internal_prop { alternative-model } \l_@@_internal_tl { \exp_args:NV \@@_model_separation:nnn \l_@@_internal_tl {#2} {#1} } { \msg_error:nnn { color } { separation-alternative-model } {#2} } } \cs_new_protected:Npn \@@_model_separation:nnn #1#2#3 { \cs_if_exist:cTF { @@_model_separation_ #1 :nnnnnn } { \prop_get:NnNTF \l_@@_internal_prop { alternative-values } \l_@@_internal_tl { \exp_after:wN \@@_model_separation:w \l_@@_internal_tl , 0 , 0 , 0 , 0 \s_@@_stop {#2} {#3} {#1} } { \msg_error:nnn { color } { separation-alternative-values } {#2} } } { \msg_error:nnn { color } { unknown-alternative-model } {#1} } } % \end{macrocode} % As each alternative space leads to a different requirement for conversion, % and as there are only a small number of choices, we manually split the data % and then set up. Notice that mixing tints is really just the same % as mixing \texttt{gray}. The \texttt{white} color is special, as it allows % tints to be adjusted without an additional color space. To make sure the % data is set for that at all group levels, we need to work on a per-level % basis. Within the output, only the set-up needs the \enquote{real} name % of the colorspace: we use a simple tracking number for general usage % as this is a clear namespace without issues of escaping chars. % \begin{macrocode} \cs_new_protected:Npn \@@_model_separation:w #1 , #2 , #3 , #4 , #5 \s_@@_stop #6#7#8 { \@@_model_init:nnn {#6} { separation } { 0 } \cs_new_eq:cN { @@_parse_mix_ #6 :nw } \@@_parse_mix_gray:nw \cs_new:cpn { @@_parse_model_ #6 :w } ##1 , ##2 \s_@@_stop { {#6} { \@@_parse_number:n {##1} } } \use:c { @@_model_separation_ #8 :nnnnnn } {#6} {#7} {#1} {#2} {#3} {#4} \prop_gput:Nnn \g_@@_alternative_model_prop {#6} {#8} \prop_gput:Nnx \g_@@_colorants_prop {#6} { \str_convert_pdfname:n {#7} } } \cs_new_protected:Npn \@@_model_separation_cmyk:nnnnnn #1#2#3#4#5#6 { \tl_const:cn { c_@@_fallback_ #1 _tl } { cmyk } \cs_new:cpn { @@_convert_ #1 _cmyk:w } ##1 \s_@@_stop { \fp_eval:n {##1 * #3} ~ \fp_eval:n {##1 * #4} ~ \fp_eval:n {##1 * #5} ~ \fp_eval:n {##1 * #6} } \cs_new:cpn { @@_convert_cmyk_ #1 :w } ##1 \s_@@_stop { 1 } \prop_gput:Nnn \g_@@_alternative_values_prop {#1} { #3 , #4 , #5 , #6 } \@@_backend_separation_init:nnnnn {#2} { /DeviceCMYK } { } { 0 ~ 0 ~ 0 ~ 0 } { #3 ~ #4 ~ #5 ~ #6 } } \cs_new_protected:Npn \@@_model_separation_rgb:nnnnnn #1#2#3#4#5#6 { \tl_const:cn { c_@@_fallback_ #1 _tl } { rgb } \cs_new:cpn { @@_convert_ #1 _rgb:w } ##1 \s_@@_stop { \fp_eval:n {##1 * #3} ~ \fp_eval:n {##1 * #4} ~ \fp_eval:n {##1 * #5} } \cs_new:cpn { @@_convert_rgb_ #1 :w } ##1 \s_@@_stop { 1 } \prop_gput:Nnn \g_@@_alternative_values_prop {#1} { #3 , #4 , #5 } \@@_backend_separation_init:nnnnn {#2} { /DeviceRGB } { } { 0 ~ 0 ~ 0 } { #3 ~ #4 ~ #5 } } \cs_new_protected:Npn \@@_model_separation_gray:nnnnnn #1#2#3#4#5#6 { \tl_const:cn { c_@@_fallback_ #1 _tl } { gray } \cs_new:cpn { @@_convert_ #1 _gray:w } ##1 \s_@@_stop { \fp_eval:n {##1 * #3} } \cs_new:cpn { @@_convert_gray_ #1 :w } ##1 \s_@@_stop { 1 } \prop_gput:Nnn \g_@@_alternative_values_prop {#1} {#3} \@@_backend_separation_init:nnnnn {#2} { /DeviceGray } { } { 0 } {#3} } % \end{macrocode} % Generic model conversion \emph{via} an alternative intermediate. % \begin{macrocode} \cs_new_protected:Npn \@@_model_convert:nnn #1#2#3 { \cs_new:cpx { @@_convert_ #1 _ #3 :w } ##1 \s_@@_stop { \exp_not:N \exp_args:NNe \exp_not:N \use:nn \exp_not:c { @@_convert_ #2 _ #3 :w } { \exp_not:c { @@_convert_ #1 _ #2 :w } ##1 \s_@@_stop } \c_space_tl \exp_not:N \s_@@_stop } } % \end{macrocode} % Setting up for CIELAB needs a bit more work: there is the illuminant and % the need for an appropriate object. % \begin{macrocode} \cs_new_protected:Npn \@@_model_separation_CIELAB:nnnnnn #1#2#3#4#5#6 { \prop_get:NnNF \l_@@_internal_prop { illuminant } \l_@@_internal_tl { \msg_error:nnn { color } { CIELAB-requires-illuminant } {#1} \tl_set:Nn \l_@@_internal_tl { d50 } } \exp_args:NV \@@_model_separation_CIELAB:nnnnnnn \l_@@_internal_tl {#1} {#2} {#3} {#4} {#5} {#6} } % \end{macrocode} % If a CIELAB space is being set up, we need the illuminant, then create % the appropriate set up. At present, this doesn't include \texttt{BlackPoint} % or \texttt{Range} data, but that may be added later. As CIELAB colors % cannot be converted to anything else, we fallback to producing black in the % gray colorspace: the user should set up a second model for colors set up this way. % \begin{macrocode} \cs_new_protected:Npn \@@_model_separation_CIELAB:nnnnnnn #1#2#3#4#5#6#7 { \tl_if_exist:cTF { c_@@_model_whitepoint_CIELAB_ #1 _tl } { \@@_backend_separation_init_CIELAB:nnn {#1} {#3} { #4 ~ #5 ~ #6 } \tl_const:cn { c_@@_fallback_ #2 _tl } { gray } \cs_new:cpn { @@_convert_ #2 _gray:w } ##1 \s_@@_stop { 0 } \cs_new:cpn { @@_convert_gray_ #2 :w } ##1 \s_@@_stop { 1 } } { \msg_error:nnn { color } { unknown-CIELAB-illuminant } {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{macro}{\@@_model_devicen:n} % \begin{macro}{\@@_model_devicen:nn} % \begin{macro}{\@@_model_devicen:nnn} % \begin{macro}{\@@_model_devicen:nnnn} % \begin{macro} % { % \@@_model_devicen_parse_1:nn , % \@@_model_devicen_parse_2:nn , % \@@_model_devicen_parse_3:nn , % \@@_model_devicen_parse_4:nn , % \@@_model_devicen_parse_generic:nn % } % \begin{macro}[EXP]{\@@_model_devicen_parse:nw} % \begin{macro}[EXP]{\@@_model_devicen_mix:nw} % \begin{macro}{\@@_model_devicen_init:nnn} % \begin{macro}{\@@_model_devicen_init:nnnn} % \begin{macro}{\@@_model_devicen_tranform:w} % \begin{macro} % { % \@@_model_devicen_tranform_1:nnnnn , % \@@_model_devicen_tranform_3:nnnnn , % \@@_model_devicen_tranform_4:nnnnn , % } % \begin{macro}{\@@_model_devicen_tranform:nnn} % \begin{macro}[EXP]{\@@_model_devicen_colorant:n} % \begin{macro}{\@@_model_devicen_convert:nnn} % \begin{macro} % { % \@@_model_devicen_convert_cmyk:n , % \@@_model_devicen_convert_gray:n , % \@@_model_devicen_convert_rgb:n % } % \begin{macro}{\@@_model_devicen_convert:nnnn} % \begin{macro}[EXP]{\@@_model_devicen_convert:n, \@@_model_devicen_convert_aux:n} % \begin{macro}[EXP]{\@@_model_devicen_convert:w} % \begin{macro}[EXP]{\@@_convert_devicen_cmyk:nnnnw} % \begin{macro}[EXP]{\@@_convert_devicen_cmyk:nnnnnnnnn} % \begin{macro}[EXP]{\@@_convert_devicen_cmyk_aux:nnnnw} % \begin{macro}[EXP]{\@@_convert_devicen_gray:nw} % \begin{macro}[EXP]{\@@_convert_devicen_gray:nnn} % \begin{macro}[EXP]{\@@_convert_devicen_gray_aux:nw} % \begin{macro}[EXP]{\@@_convert_devicen_rgb:nnnw} % \begin{macro}[EXP]{\@@_convert_devicen_rgb:nnnnnnn} % \begin{macro}[EXP]{\@@_convert_devicen_rgb_aux:nnnw} % We require a list of component names here: one might call them colorants, % but it's convenient to use \TeX{} names instead so we slightly adjust the % terminology. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen:n #1 { \prop_get:NnNTF \l_@@_internal_prop { names } \l_@@_internal_tl { \exp_args:NV \@@_model_devicen:nn \l_@@_internal_tl {#1} } { \msg_error:nnn { color } { DeviceN-requires-names } {#1} } } % \end{macrocode} % All valid models will have an alternative listed, either hard-coded for % the core device ones, or dynamically added for Separations, etc. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen:nn #1#2 { \tl_clear:N \l_@@_model_tl \clist_map_inline:nn {#1} { \prop_get:NnNTF \g_@@_alternative_model_prop {##1} \l_@@_internal_tl { \tl_if_empty:NTF \l_@@_model_tl { \tl_set_eq:NN \l_@@_model_tl \l_@@_internal_tl } { \str_if_eq:VVF \l_@@_model_tl \l_@@_internal_tl { \msg_error:nnn { color } { DeviceN-inconsistent-alternative } {#2} \clist_map_break:n { \use_none:nnnn } } } } { \str_if_eq:nnF {##1} { none } { \msg_error:nnn { color } { DeviceN-no-alternative } {#2} } } } \tl_if_empty:NTF \l_@@_model_tl { \msg_error:nnn { color } { DeviceN-no-alternative } {#2} } { \exp_args:NV \@@_model_devicen:nnn \l_@@_model_tl {#1} {#2} } } % \end{macrocode} % We now complete the data we require by first finding out how many % colorants there are, then moving on to begin constructing the function % required to map to the alternative color space. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen:nnn #1#2#3 { \exp_args:Nx \@@_model_devicen:nnnn { \clist_count:n {#2} } {#1} {#2} {#3} } % \end{macrocode} % At this stage, we have checked everything is in place, so we can set up % the \TeX{} and backend data structures. As for separations, it's not really % possible in general to have a fallback, so we simply provide % \enquote{black} for each element. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen:nnnn #1#2#3#4 { \@@_model_init:nnx {#4} { devicen } { 0 \prg_replicate:nn { #1 - 1 } { ~ 0 } } \cs_if_exist_use:cF { @@_model_devicen_parse_ #1 :nn } { \@@_model_devicen_parse_generic:nn } {#4} {#1} \@@_model_devicen_init:nnn {#1} {#2} {#3} \@@_model_devicen_convert:nnnx {#4} {#2} {#3} { 1 \prg_replicate:nn { #1 - 1 } { ~ 1 } } } % \end{macrocode} % For short lists of DeviceN colors, we can use hand-tuned parsing. This % lines up with other models, where we allow for up to four components. For % larger spaces, rather than limit artificially, we use a somewhat slow % approach based on open-ended commas-lists. % \begin{macrocode} \cs_new_protected:cpn { @@_model_devicen_parse_1:nn } #1#2 { \cs_new:cpn { @@_parse_model_ #1 :w } ##1 , ##2 \s_@@_stop { {#1} { \@@_parse_number:n {##1} } } \cs_new_eq:cN { @@_parse_mix_ #1 :nw } \@@_parse_mix_gray:nw } \cs_new_protected:cpn { @@_model_devicen_parse_2:nn } #1#2 { \cs_new:cpn { @@_parse_model_ #1 :w } ##1 , ##2 , ##3 \s_@@_stop { {#1} { \@@_parse_number:n {##1} ~ \@@_parse_number:n {##2} } } \cs_new:cpn { @@_parse_mix_ #1 :nw } ##1##2 ~ ##3 \s_@@_mark ##4 ~ ##5 \s_@@_stop { \fp_eval:n { ##2 * ##1 + ##4 * ( 1 - ##1 ) } \c_space_tl \fp_eval:n { ##3 * ##1 + ##5 * ( 1 - ##1 ) } } } \cs_new_protected:cpn { @@_model_devicen_parse_3:nn } #1#2 { \cs_new:cpn { @@_parse_model_ #1 :w } ##1 , ##2 , ##3 , ##4 \s_@@_stop { {#1} { \@@_parse_number:n {##1} ~ \@@_parse_number:n {##2} ~ \@@_parse_number:n {##3} } } \cs_new_eq:cN { @@_parse_mix_ #1 :nw } \@@_parse_mix_rgb:nw } \cs_new_protected:cpn { @@_model_devicen_parse_4:nn } #1#2 { \cs_new:cpn { @@_parse_model_ #1 :w } ##1 , ##2 , ##3 , ##4 , ##5 \s_@@_stop { {#1} { \@@_parse_number:n {##1} ~ \@@_parse_number:n {##2} ~ \@@_parse_number:n {##3} ~ \@@_parse_number:n {##4} } } \cs_new_eq:cN { @@_parse_mix_ #1 :nw } \@@_parse_mix_cmyk:nw } \cs_new_protected:Npn \@@_model_devicen_parse_generic:nn #1#2 { \cs_new:cpn { @@_parse_model_ #1 :w } ##1 , ##2 \s_@@_stop { {#1} { \@@_model_devicen_parse:nw {#2} ##1 , ##2 , \q_nil , \s_@@_stop } } \cs_new:cpx { @@_parse_mix_ #1 :nw } ##1 ##2 \s_@@_mark ##3 \s_@@_stop { \exp_not:N \@@_model_devicen_mix:nw {##1} ##2 \c_space_tl \exp_not:N \q_nil \c_space_tl \exp_not:N \s_@@_mark ##3 \c_space_tl \exp_not:N \q_nil \c_space_tl \exp_not:N \s_@@_stop } } \cs_new:Npn \@@_model_devicen_parse:nw #1#2 , #3 \s_@@_stop { \int_compare:nNnT {#1} > 0 { \quark_if_nil:nTF {#2} { \prg_replicate:nn {#1} { 0 ~ } } { \@@_parse_number:n {#2} \int_compare:nNnT {#1} > 1 { ~ } \exp_args:Nf \@@_model_devicen_parse:nw { \int_eval:n { #1 - 1 } } #3 \s_@@_stop } } } \cs_new:Npn \@@_model_devicen_mix:nw #1#2 ~ #3 \s_@@_mark #4 ~ #5 \s_@@_stop { \fp_eval:n { #2 * #1 + #4 * ( 1 - #1 ) } \quark_if_nil:oF { \tl_head:w #3 \q_stop } { \c_space_tl \@@_model_devicen_mix:nw {#1} #3 \s_@@_mark #5 \s_@@_stop } } % \end{macrocode} % To construct the tint transformation, we have to use PostScript. The % aim is to have the final tint for each device colorant as % \[ % 1 - \prod_{n} (1 - X_{n} D_{X_{n}}) % \] % where $X$ is a DeviceN colorant and $D$ is the amount of device colorant % that the DeviceN colorant maps to. At the start of the process, the % PostScript stack will contain the $X_{n}$ values, whilst we have the % $D$ values on a per-DeviceN colorant basis. The more convenient approach % for us is therefore to take each DeviceN colorant in turn and find the % value $1 - X_{n} D_{X_{n}}$, multiplying as we go, and finalise with the % subtraction. That contrasts to \pkg{colorspace}: it splits the process % up by process color, which works better when you have a fixed list % of colorants. (\pkg{colorspace} only supports up to $4$ DeviceN colors, % and only \texttt{cmyk} as the alternative space.) To set this up, % we first need to know the number of values in the target color space: % this is easily handled as there are a very small range of possibles. % Once we have that information, it's relatively easy to build the required % PostScript using some generic code. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen_init:nnn #1#2#3 { \exp_args:Ne \@@_model_devicen_init:nnnn { \str_case:nn {#2} { { cmyk } { 4 } { gray } { 1 } { rgb } { 3 } } } {#1} {#2} {#3} } % \end{macrocode} % As we always need to split the alternative values into parts, we use a % shared auxiliary and only use a minimal difference between code paths. % Construction of the tint transformation is as far as possible done using % loops, which means there are some inefficiencies for device colors in % the \texttt{DeviceN} space: we roll the stack one-at-a-time even if there % is a potential shortcut. However, that way there is nothing to special-case. % Once this is sorted, we can write the tint transform object, which will % remain as the last object until we sort out the final step: the colorant % list. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen_init:nnnn #1#2#3#4 { \tl_set:Nx \l_@@_internal_tl { \prg_replicate:nn {#1} { 1.0 ~ } } \int_zero:N \l_@@_internal_int \clist_map_inline:nn {#4} { \int_incr:N \l_@@_internal_int \prop_get:NnN \g_@@_alternative_values_prop {##1} \l_@@_value_tl \exp_after:wN \@@_model_devicen_transform:w \l_@@_value_tl , 0 , 0 , 0 , \s_@@_stop {#1} {#2} } \tl_put_right:Nx \l_@@_internal_tl { \prg_replicate:nn {#1} { neg ~ 1.0 ~ add ~ #1 ~ -1 ~ roll ~ } \int_eval:n { #2 + #1 } ~ #1 ~ roll \prg_replicate:nn {#2} { ~ pop } ~ #1 ~ 1 ~ roll } \use:x { \@@_backend_devicen_init:nnn { \clist_map_function:nN {#4} \@@_model_devicen_colorant:n } { \str_case:nn {#3} { { cmyk } { /DeviceCMYK } { gray } { /DeviceGray } { rgb } { /DeviceRGB } } } { \exp_not:V \l_@@_internal_tl } } } \cs_new_protected:Npn \@@_model_devicen_transform:w #1 , #2 , #3 , #4 , #5 \s_@@_stop #6#7 { \use:c { @@_model_devicen_transform_ #6 :nnnnn } {#1} {#2} {#3} {#4} {#7} } \cs_new_protected:cpn { @@_model_devicen_transform_1:nnnnn } #1#2#3#4#5 { \@@_model_devicen_transform:nnn {#5} { 1 } {#1} } \cs_new_protected:cpn { @@_model_devicen_transform_3:nnnnn } #1#2#3#4#5 { \clist_map_inline:nn { #1 , #2 , #3 } { \@@_model_devicen_transform:nnn {#5} { 3 } {##1} } } \cs_new_protected:cpn { @@_model_devicen_transform_4:nnnnn } #1#2#3#4#5 { \clist_map_inline:nn { #1 , #2 , #3 , #4 } { \@@_model_devicen_transform:nnn {#5} { 4 } {##1} } } \cs_new_protected:Npn \@@_model_devicen_transform:nnn #1#2#3 { \tl_put_right:Nx \l_@@_internal_tl { \fp_compare:nNnF {#3} = \c_zero_fp { \int_eval:n { #1 - \l_@@_internal_int + #2 } ~ index ~ -#3 ~ mul ~ 1.0 ~ add ~ mul ~ } #2 ~ -1 ~ roll ~ } } \cs_new:Npn \@@_model_devicen_colorant:n #1 { / \prop_item:Nn \g_@@_colorants_prop {#1} ~ } % \end{macrocode} % Here we need to set up conversion from the DeviceN space to the alternative % at the \TeX{} level. This also means supplying methods for inter-converting % to other parameter-based spaces. Essentially the approach is exactly the same % as the PostScript, just expressed in \TeX{} terms. % \begin{macrocode} \cs_new_protected:Npn \@@_model_devicen_convert:nnnn #1#2#3 { \use:c { @@_model_devicen_convert_ #2 :nnn } {#1} {#3} } \cs_generate_variant:Nn \@@_model_devicen_convert:nnnn { nnnx } \cs_new_protected:Npn \@@_model_devicen_convert_cmyk:nnn #1#2 { \tl_const:cn { c_@@_fallback_ #1 _tl } { cmyk } \@@_model_devicen_convert:nnnnn {#1} { cmyk } { 4 } {#2} } \cs_new_protected:Npn \@@_model_devicen_convert_gray:nnn #1#2 { \tl_const:cn { c_@@_fallback_ #1 _tl } { gray } \@@_model_devicen_convert:nnnnn {#1} { gray } { 1 } {#2} } \cs_new_protected:Npn \@@_model_devicen_convert_rgb:nnn #1#2 { \tl_const:cn { c_@@_fallback_ #1 _tl } { rgb } \@@_model_devicen_convert:nnnnn {#1} { rgb } { 3 } {#2} } \cs_new_protected:Npn \@@_model_devicen_convert:nnnnn #1#2#3#4#5 { \cs_new:cpn { @@_convert_ #2 _ #1 :w } ##1 \s_@@_stop {#5} \cs_new:cpx { @@_convert_ #1 _ #2 :w } ##1 \s_@@_stop { \exp_not:c { @@_convert_devicen_ #2 : \prg_replicate:nn {#3} { n } w } \prg_replicate:nn {#3} { { 1 } } ##1 ~ \exp_not:N \s_@@_mark \clist_map_function:nN {#4} \@@_model_devicen_convert:n {} \exp_not:N \s_@@_stop } } \cs_new:Npn \@@_model_devicen_convert:n #1 { { \exp_args:Ne \@@_model_devicen_convert_aux:n { \prop_item:Nn \g_@@_alternative_values_prop {#1} } } } \cs_new:Npn \@@_model_devicen_convert_aux:n #1 { \@@_model_devicen_convert_aux:w #1 , , , , \s_@@_stop } \cs_new:Npn \@@_model_devicen_convert_aux:w #1 , #2 , #3 , #4 , #5 \s_@@_stop { {#1} \tl_if_blank:nF {#2} { {#2} \tl_if_blank:nF {#3} { {#3} \tl_if_blank:nF {#4} { {#4} } } } } \cs_new:Npn \@@_convert_devicen_cmyk:nnnnw #1#2#3#4#5 ~ #6 \s_@@_mark #7#8 \s_@@_stop { \@@_convert_devicen_cmyk:nnnnnnnnn {#5} {#1} {#2} {#3} {#4} #7 #6 \s_@@_mark #8 \s_@@_stop } \cs_new:Npn \@@_convert_devicen_cmyk:nnnnnnnnn #1#2#3#4#5#6#7#8#9 { \use:e { \exp_not:N \@@_convert_devicen_cmyk_aux:nnnnw { \fp_eval:n { #2 * (1 - (#1 * #6)) } } { \fp_eval:n { #3 * (1 - (#1 * #7)) } } { \fp_eval:n { #4 * (1 - (#1 * #8)) } } { \fp_eval:n { #5 * (1 - (#1 * #9)) } } } } \cs_new:Npn \@@_convert_devicen_cmyk_aux:nnnnw #1#2#3#4 #5 \s_@@_mark #6 \s_@@_stop { \tl_if_blank:nTF {#5} { \fp_eval:n { 1 - #1 } ~ \fp_eval:n { 1 - #2 } ~ \fp_eval:n { 1 - #3 } ~ \fp_eval:n { 1 - #4 } } { \@@_convert_devicen_cmyk:nnnnw {#1} {#2} {#3} {#4} #5 \s_@@_mark #6 \s_@@_stop } } \cs_new:Npn \@@_convert_devicen_gray:nw #1#2 ~ #3 \s_@@_mark #4#5 \s_@@_stop { \@@_convert_devicen_gray:nnn {#2} {#1} #4 #3 \s_@@_mark #5 \s_@@_stop } \cs_new:Npn \@@_convert_devicen_gray:nnn #1#2#3 { \exp_arsgs:Ne \@@_convert_devicen_gray_aux:nw { \fp_eval:n { #2 * (1 - (#1 * #3)) } } } \cs_new:Npn \@@_convert_devicen_gray_aux:nw #1 #2 \s_@@_mark #3 \s_@@_stop { \tl_if_blank:nTF {#2} { \fp_eval:n { 1 - #1 } } { \@@_convert_devicen_gray:nw {#1} #2 \s_@@_mark #3 \s_@@_stop } } \cs_new:Npn \@@_convert_devicen_rgb:nnnw #1#2#3#4 ~ #5 \s_@@_mark #6#7 \s_@@_stop { \@@_convert_devicen_rgb:nnnnnnn {#4} {#1} {#2} {#3} #6 #5 \s_@@_mark #7 \s_@@_stop } \cs_new:Npn \@@_convert_devicen_rgb:nnnnnnn #1#2#3#4#5#6#7 { \use:e { \exp_not:N \@@_convert_devicen_rgb_aux:nnnw { \fp_eval:n { #2 * (1 - (#1 * #5)) } } { \fp_eval:n { #3 * (1 - (#1 * #6)) } } { \fp_eval:n { #4 * (1 - (#1 * #7)) } } } } \cs_new:Npn \@@_convert_devicen_rgb_aux:nnnw #1#2#3 #4 \s_@@_mark #5 \s_@@_stop { \tl_if_blank:nTF {#4} { \fp_eval:n { 1 - #1 } ~ \fp_eval:n { 1 - #2 } ~ \fp_eval:n { 1 - #3 } } { \@@_convert_devicen_rgb:nnnw {#1} {#2} {#3} #4 \s_@@_mark #5 \s_@@_stop } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \begin{variable}{\c_@@_icc_colorspace_signatures_prop} % The signatures in the ICC file header indicating the underlying % colorspace. We map it to three values: The number of components, % the values corresponding to white, and the range. % \begin{macrocode} \prop_const_from_keyval:Nn \c_@@_icc_colorspace_signatures_prop { % Gray 47524159 = {1} {1} {0} {}, % RGB 52474220 = {3} {0~0~0} {1~1~1} {}, % CMYK 434D594B = {4} {0~0~0~1} {0~0~0~0} {}, % Lab 4C616220 = {3} {0~0~0} {100~0~0} {0~100~-128~127~-128~127} } % \end{macrocode} % \end{variable} % \begin{macro}{\@@_model_iccbased:n} % \begin{macro}{\@@_model_iccbased:nn} % \begin{macro}{\@@_model_iccbased:nnn, \@@_model_iccbased_aux:nnn} % For an ICC profile, we need a file name and a number of components. The % file name is processed here so the backend can treat it as a string. % \begin{macrocode} \cs_new_protected:Npn \@@_model_iccbased:n #1 { \prop_get:NnNTF \l_@@_internal_prop { file } \l_@@_internal_tl { \exp_args:NV \@@_model_iccbased:nn \l_@@_internal_tl {#1} } { \msg_error:nnn { color } { ICCBased-requires-file } {#1} } } \cs_new_protected:Npn \@@_model_iccbased:nn #1#2 { \exp_args:NNx \prop_get:NnNTF \c_@@_icc_colorspace_signatures_prop { \file_hex_dump:nnn { #1 } { 17 } { 20 } } \l_@@_internal_tl { \exp_last_unbraced:NV \@@_model_iccbased_aux:nnnnnn \l_@@_internal_tl { #2 } { #1 } } { \msg_error:nnn { color } { ICCBased-unsupported-colorspace } {#2} } } % \end{macrocode} % Here, we can use the same internals as for DeviceN approach as we know the % number of components. No conversion is possible, so there is no need % to worry about that at all. % \begin{macrocode} \cs_new_protected:Npn \@@_model_iccbased_aux:nnnnnn #1#2#3#4#5#6 { \@@_model_init:nnn {#5} { iccbased } {#3} \tl_const:cn { c_@@_fallback_ #5 _tl } { gray } \cs_new:cpn { @@_convert_ #5 _gray:w } ##1 \s_@@_stop { 0 } \cs_new:cpn { @@_convert_gray_ #5 :w } ##1 \s_@@_stop { #2 } \use:c { @@_model_devicen_parse_ #1 :nn } {#5} {#1} \exp_args:Nx \@@_backend_iccbased_init:nnn { \file_full_name:n {#6} } {#1} {#4} } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % % \subsection{Applying profiles} % % \begin{macro}{\color_profile_apply:nn, \@@_profile_apply:nn} % \begin{macro} % { % \@@_profile_apply_gray:n , % \@@_profile_apply_rgb:n , % \@@_profile_apply_cmyk:n % } % With a limited range of outcomes, this is largely about getting data to the % backend. % \begin{macrocode} \cs_new_protected:Npn \color_profile_apply:nn #1#2 { \exp_args:Ne \@@_profile_apply:nn { \file_full_name:n {#1} } {#2} } \cs_new_protected:Npn \@@_profile_apply:nn #1#2 { \cs_if_exist_use:cF { @@_profile_apply_ \tl_to_str:n {#2} :n } { \msg_error:nnn { color } { ICC-Device-unknown } {#2} \use_none:n } {#1} } \cs_new_protected:Npn \@@_profile_apply_gray:n #1 { \int_gincr:N \g_@@_model_int \@@_backend_iccbased_device:nnn {#1} { Gray } { 1 } } \cs_new_protected:Npn \@@_profile_apply_rgb:n #1 { \int_gincr:N \g_@@_model_int \@@_backend_iccbased_device:nnn {#1} { RGB } { 3 } } \cs_new_protected:Npn \@@_profile_apply_cmyk:n #1 { \int_gincr:N \g_@@_model_int \@@_backend_iccbased_device:nnn {#1} { CMYK } { 4 } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Diagnostics} % % \begin{macro}{\color_show:n, \color_log:n, \@@_show:Nn} % \begin{macro}[EXP]{\@@_show:n} % Extract the information about a color and format for the user: the approach % is similar to the keys module here. % \begin{macrocode} \cs_new_protected:Npn \color_show:n { \@@_show:Nn \msg_show:nnxxxx } \cs_new_protected:Npn \color_log:n { \@@_show:Nn \msg_log:nnxxxx } \cs_new_protected:Npn \@@_show:Nn #1#2 { #1 { color } { show } {#2} { \color_if_exist:nT {#2} { \exp_args:Nv \@@_show:n { l_@@_named_ #2 _tl } \prop_map_function:cN { l_@@_named_ #2 _prop } \msg_show_item_unbraced:nn } } { } { } } \cs_new:Npn \@@_show:n #1 { \msg_show_item_unbraced:nn { model } {#1} } % \end{macrocode} % \end{macro} % \end{macro} % % \subsection{Messages} % % \begin{macrocode} \msg_new:nnnn { color } { CIELAB-requires-illuminant } { CIELAB~color~space~'#1'~require~an~illuminant. } { LaTeX~has~been~asked~to~create~a~separation~color~space~using~ CIELAB~specifications,~but~no~\\ \\ \iow_indent:n { illuminant~=~ } \\ \\ key~was~given~with~the~correct~information.~LaTeX~will~use~illuminant~ 'd50'~for~recovery. } \msg_new:nnnn { color } { conversion-not-available } { No~model~conversion~available~from~'#1'~to~'#2'. } { LaTeX~has~been~asked~to~convert~a~color~from~model~'#1'~ to~model'#2',~but~there~is~no~method~available~to~do~that. } \msg_new:nnnn { color } { DeviceN-inconsistent-alternative } { DeviceN~color~spaces~require~a~single~alternative~space. } { LaTeX~has~been~asked~to~create~a~DeviceN~color~space~'#1',~ but~the~constituent~colors~do~not~have~a~common~alternative~ color. } \msg_new:nnnn { color } { DeviceN-no-alternative } { DeviceN~color~spaces~require~an~alternative~space. } { LaTeX~has~been~asked~to~create~a~DeviceN~color~space~'#1',~ but~the~constituent~colors~do~not~all~have~a~device-based~alternative. } \msg_new:nnnn { color } { DeviceN-requires-names } { DeviceN~color~space~'#1'~require~a~list~of~names. } { LaTeX~has~been~asked~to~create~a~DeviceN~color~space,~ but~no~\\ \\ \iow_indent:n { names~=~ } \\ \\ key~was~given~with~the~correct~information. } \msg_new:nnnn { color } { ICC-Device-unknown } { Unknown~device~color~space~'#1'. } { LaTeX~has~been~asked~to~apply~an~ICC~profile~but~the~device~color~space~ '#1'~is~unknown. } \msg_new:nnnn { color } { ICCBased-unsupported-colorspace } { ICCBased~color~space~'#1'~uses~an~unsupported~data~color~space. } { LaTeX~has~been~asked~to~create~a~ICCBased~colorspace,~but~the~ used~data~colorspace~is~not~supported.~ICC~profiles~used~for~ defining~a~ICCBased~colorspace~should~use~a~Lab,~RGB,~or~ CMYK~data~colorspace.~LaTeX~will~ignore~this~request. } \msg_new:nnnn { color } { ICCBased-requires-file } { ICCBased~color~space~'#1'~require~an~file. } { LaTeX~has~been~asked~to~create~an~ICCBased~color~space,~but~no~\\ \\ \iow_indent:n { file~=~ } \\ \\ key~was~given~with~the~correct~information.~LaTeX~will~ignore~this~ request. } \msg_new:nnnn { color } { model-already-defined } { Color~model~'#1'~already~defined. } { LaTeX~was~asked~to~define~a~new~color~model~called~'#1',~but~ this~color~model~already~exists. } \msg_new:nnnn { color } { separation-alternative-model } { Separation~color~space~'#1'~require~an~alternative~model. } { LaTeX~has~been~asked~to~create~a~separation~color~space,~ but~no~\\ \\ \iow_indent:n { alternative-model~=~ } \\ \\ key~was~given~with~the~correct~information. } \msg_new:nnnn { color } { separation-alternative-values } { Separation~color~space~'#1'~require~values~for~the~alternative~space. } { LaTeX~has~been~asked~to~create~a~separation~color~space,~ but~no~\\ \\ \iow_indent:n { alternative-values~=~ } \\ \\ key~was~given~with~the~correct~information. } \msg_new:nnnn { color } { separation-requires-name } { Separation~color~space~'#1'~require~a~formal~name. } { LaTeX~has~been~asked~to~create~a~separation~color~space,~ but~no~\\ \\ \iow_indent:n { name~=~ } \\ \\ key~was~given~with~the~correct~information. } \msg_new:nnn { color } { unhandled-model } { Unhandled~color~model~in~LaTeX2e~value~"#1": \\ \\ falling~back~on~grayscale. } \msg_new:nnnn { color } { unknown-color } { Unknown~color~'#1'. } { LaTeX~has~been~asked~to~use~a~color~named~'#1',~ but~this~has~never~been~defined. } \msg_new:nnnn { color } { unknown-alternative-model } { Separation~color~space~'#1'~require~an~valid~alternative~space. } { LaTeX~has~been~asked~to~create~a~separation~color~space,~ but~the~model~given~as\\ \\ \iow_indent:n { alternative-model~=~ } \\ \\ is~unknown. } \msg_new:nnnn { color } { unknown-export-format } { Unknown~export~format~'#1'. } { LaTeX~has~been~asked~to~export~a~color~in~format~'#1',~ but~this~has~never~been~defined. } \msg_new:nnnn { color } { unknown-CIELAB-illuminant } { Unknown~illuminant~model~'#1'. } { LaTeX~has~been~asked~to~use~create~a~color~space~using~CIELAB~ illuminant~'#1',~but~this~does~not~exist. } \msg_new:nnnn { color } { unknown-model } { Unknown~color~model~'#1'. } { LaTeX~has~been~asked~to~use~a~color~model~called~'#1',~ but~this~model~is~not~set~up. } \msg_new:nnnn { color } { unknown-model-type } { Unknown~color~model~type~'#1'. } { LaTeX~has~been~asked~to~create~a~new~color~model~called~'#1',~ but~this~type~of~model~was~never~set~up. } \prop_gput:Nnn \g_msg_module_name_prop { color } { LaTeX } \prop_gput:Nnn \g_msg_module_type_prop { color } { } % \end{macrocode} % % \begin{macrocode} \msg_new:nnn { color } { show } { The~color~#1~ \tl_if_empty:nTF {#2} { is~undefined. } { has~the~properties: #2 } } % \end{macrocode} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \PrintIndex