diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/l3experimental/l3color.dtx |
Initial commit
Diffstat (limited to 'macros/latex/contrib/l3experimental/l3color.dtx')
-rw-r--r-- | macros/latex/contrib/l3experimental/l3color.dtx | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/macros/latex/contrib/l3experimental/l3color.dtx b/macros/latex/contrib/l3experimental/l3color.dtx new file mode 100644 index 0000000000..107d6684d2 --- /dev/null +++ b/macros/latex/contrib/l3experimental/l3color.dtx @@ -0,0 +1,902 @@ +% \iffalse meta-comment +% +%% File: l3color.dtx +% +% Copyright (C) 2017-2019 The LaTeX3 Project +% +% It may be distributed and/or modified under the conditions of the +% LaTeX Project Public License (LPPL), either version 1.3c of this +% license or (at your option) any later version. The latest version +% of this license is in the file +% +% http://www.latex-project.org/lppl.txt +% +% This file is part of the "l3experimental 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|package> +\RequirePackage{expl3} +%</driver|package> +%<*driver> +\documentclass[full]{l3doc} +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +%</driver> +% \fi +% +% \title{^^A +% The \textsf{l3color} package\\ Experimental color support^^A +% } +% +% \author{^^A +% The \LaTeX3 Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% +% \date{Released 2019-08-25} +% +% \maketitle +% +% \begin{documentation} +% +% \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. +% +% The 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 +% \item \texttt{spot} Spot color, with one value, the name of the color +% (see \url{https://helpx.adobe.com/indesign/using/spot-process-colors.html} +% for details of the use of spot colors in print) +% \end{itemize} +% +% Additional models may be created to allow mixing of spot colors +% with each other or with those from other models. See +% Section~\ref{l3color:sec:spot} for more detail of spot color support. +% +% 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}}. 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} +% <intermediate>!10!yellow +% \end{verbatim} +% where |<intermediate>| 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} \Arg{value(s)} +% \end{syntax} +% Stores the color specification equivalent to the \meta{model} 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}{\color_show:n} +% \begin{syntax} +% \cs{color_show:n} \Arg{name} +% \end{syntax} +% Displays the color specification stored in the \meta{name} on the +% terminal +% \end{function} +% +% \section{Selecting colors} +% +% \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} \Arg{value(s)} +% \end{syntax} +% Activates the color specification equivalent to the \meta{model} 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{Core color representation} +% +% To allow data to be handled internally, \pkg{l3color} uses a simple +% representation of color, based on that used by the \pkg{dvips} program. +% This is a token list made up of the model name followed by one or more +% data entries, each separated by a \emph{space}. The valid forms are thus +% \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}}, +% each of which falls in the range $[0,1]$ +% \item \texttt{rgb \meta{red} \meta{green} \meta{blue}}, +% each of which falls in the range $[0,1]$ +% \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} +% +% This core representation is produced when parsing color expressions. +% +% \begin{function}{\color_parse:nN} +% \begin{syntax} +% \cs{color_parse:nN} \Arg{color expression} \Arg{tl} +% \end{syntax} +% Parses the \meta{color expression} as described above, and sets the +% \meta{tl} the the equivalent \meta{core color representation}. +% \end{function} +% +% \section{Spot colors} +% \label{l3color:sec:spot} +% +% \end{documentation} +% +% \begin{implementation} +% +% \section{\pkg{l3color} Implementation} +% +% \begin{macrocode} +%<*initex|package> +% \end{macrocode} +% +% \begin{macrocode} +%<@@=color> +% \end{macrocode} +% +% \begin{macrocode} +%<*package> +\ProvidesExplPackage{l3color}{2019-08-25}{} + {L3 Experimental color support} +%</package> +% \end{macrocode} +% +% \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 separate token list variable, rather than the alternative of using a +% single property list for all names. +% +% \subsection{Setup} +% +% \begin{variable}{\l_@@_internal_tl} +% \begin{macrocode} +\tl_new:N \l_@@_internal_tl +% \end{macrocode} +% \end{variable} +% +% \subsection{Utility functions} +% +% \begin{macro}[int, TF, EXP]{\@@_if_defined:n} +% A simple wrapper to avoid needing to have the lookup repeated in too many +% places. +% \begin{macrocode} +\prg_new_conditional:Npnn \@@_if_defined:n #1 { T, F, TF } + { + \tl_if_exist:cTF { l_@@_named_ #1 _tl } + \prg_return_true: + \prg_return_false: + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_extract:nNN, \@@_extract:VNN} +% \begin{macro}{\@@_extract:NNw} +% Split the model and color from a named color, and store the two. No test +% for the existence of the color: that is assumed to be the case (this +% is internal only). Somewhat \enquote{old-fashioned} but should be quite +% fast. +% \begin{macrocode} +\cs_new_protected:Npn \@@_extract:nNN #1#2#3 + { + \exp_after:wN \exp_after:wN \exp_after:wN + \@@_extract:NNw + \exp_after:wN \exp_after:wN \exp_after:wN #2 + \exp_after:wN \exp_after:wN \exp_after:wN #3 + \cs:w l_@@_named_ #1 _tl \cs_end: \q_stop + } +\cs_generate_variant:Nn \@@_extract:nNN { V } +\cs_new_protected:Npn \@@_extract:NNw #1#2 #3 ~ #4 \q_stop + { + \tl_set:Nn #1 {#3} + \tl_set:Nn #2 {#4} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Model conversion} +% +% \begin{macro}{\@@_convert:nnN, \@@_convert:VVN} +% \begin{macro}{\@@_convert:nnnN} +% \begin{macro}[aux, EXP] +% { +% \@@_convert_gray_rgb:w +% \@@_convert_gray_cmyk:w +% \@@_convert_cmyk_gray:w +% \@@_convert_cmyk_rgb:w +% \@@_convert_rgb_gray:w +% \@@_convert_rgb_cmyk:w +% } +% \begin{macro}[aux, EXP]{\@@_convert_rgb_cmyk:nnnn} +% Model conversion is carried out using standard formulae, as described in +% the manual for \pkg{xcolor} (see also the \emph{PostScript Language +% Reference Manual}). +% \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_new_protected:Npn \@@_convert:nnnN #1#2#3#4 + { + \str_if_eq:nnT {#1} { spot } % TO DO!!! + { } + \tl_set:Nx #4 + { \use:c { @@_convert_ #1 _ #2 :w } #3 ~ 0 ~ 0 ~ 0 \q_stop } + } +\cs_generate_variant:Nn \@@_convert:nnnN { nnV } +\cs_new:Npn \@@_convert_gray_rgb:w #1 ~ #2 \q_stop + { #1 ~ #1 ~ #1 } +\cs_new:Npn \@@_convert_gray_cmyk:w #1 ~ #2 \q_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 ~ #4 \q_stop + { \fp_eval:n { 0.3 * #1 + 0.59 * #2 + 0.11 * #3 } } +% \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. +% \begin{macrocode} +\cs_new:Npn \@@_convert_rgb_cmyk:w #1 ~ #2 ~ #3 ~ #4 \q_stop + { + \exp_args:Nf \@@_convert_rgb_cmyk:nnnn + { \fp_eval:n { min ( 1 - #1 , 1 - #2 , 1 - #3 ) } } {#1} {#2} {#3} + } +\cs_new:Npn \@@_convert_rgb_cmyk:nnnn #1#2#3#4 + { + \fp_eval:n { min ( 1 , max ( 0 , 1 - #2 - #1 ) ) } \c_space_tl + \fp_eval:n { min ( 1 , max ( 0 , 1 - #3 - #1 ) ) } \c_space_tl + \fp_eval:n { min ( 1 , max ( 0 , 1 - #4 - #1 ) ) } \c_space_tl + #1 + } +\cs_new:Npn \@@_convert_cmyk_gray:w #1 ~ #2 ~ #3 ~ #4 ~ #5 \q_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 ~ #5 \q_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 ) } + } +% \end{macrocode} +% \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}{\color_parse:nN} +% \begin{macro}{\@@_parse:nN} +% \begin{macro}{\@@_parse:Nw} +% \begin{macro}{\@@_parse_loop_init:Nnn} +% \begin{macro}{\@@_parse_loop:w} +% \begin{macro}{\@@_parse_loop:nn} +% \begin{macro}{\@@_parse_break:w} +% \begin{macro}{\@@_parse_end:} +% \begin{macro}[aux, EXP]{\@@_parse_mix:Nnnn, \@@_parse_mix:NVVn} +% \begin{macro}[aux, EXP]{\@@_parse_mix:nNnn} +% \begin{macro}[aux, 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:Npn \color_parse:nN #1#2 + { +%<*package> + \@@_backend_pickup:N \l_@@_current_tl + \tl_set_eq:cN { l_@@_named_ . _tl } \l_@@_current_tl +%</package> + \exp_args:Ne \@@_parse:nN { \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:nN #1#2 + { + \tl_if_exist:cTF { l_@@_named_ #1 _tl } + { \tl_set_eq:Nc #2 { l_@@_named_ #1 _tl } } + { \@@_parse:Nw #2#1 ! \q_stop } + \@@_check_model:N #2 + } +\cs_new_protected:Npn \@@_parse:Nw #1#2 ! #3 \q_stop + { + \@@_if_defined:nTF {#2} + { + \tl_if_blank:nTF {#3} + { \tl_set_eq:Nc #1 { l_@@_named_ #2 _tl } } + { \@@_parse_loop_init:Nnn #1 {#2} {#3} } + } + { + \__kernel_msg_error:nnn { color } { unknown-color } {#3} + \tl_set_eq:NN \l_@@_current_tl \l_@@_named_black_tl + } + } +% \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 ! ! ! ! \q_stop + \tl_set:Nx \l_@@_internal_tl + { \l_@@_model_tl \c_space_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 \q_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 \q_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 + { + \@@_if_defined: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:VnT \l_@@_model_tl { gray } + { + \use:x + { + \tl_set:Nn \exp_not:N \l_@@_model_tl + { \l_@@_next_model_tl } + \tl_set:Nn \exp_not:N \l_@@_value_tl + { \l_@@_next_value_tl } + \tl_set:Nn \exp_not:N \l_next_@@_model_tl + { \l_@@_model_tl } + \tl_set:Nn \exp_not:N \l_next_@@_value_tl + { \l_@@_value_tl } + } + } + \@@_convert:VVN + \l_@@_next_model_tl + \l_@@_model_tl + \l_@@_next_value_tl + } + \tl_set:Nx \l_@@_value_tl + { + \@@_parse_mix:NVVn + \l_@@_model_tl \l_@@_value_tl \l_@@_next_value_tl {#1} + } + } + { + \__kernel_msg_error:nnn { color } { unknown-color } {#2} + \@@_extract:nNN { black } \l_@@_model_tl \l_@@_value_tl + \@@_parse_break:w + } + } +\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 \q_mark #4 \q_stop + } +\cs_new:Npn \@@_parse_mix_gray:nw #1#2 \q_mark #3 \q_stop + { \fp_eval:n { #2 * #1 + #3 * ( 1 - #1 ) } } +\cs_new:Npn \@@_parse_mix_rgb:nw + #1#2 ~ #3 ~ #4 \q_mark #5 ~ #6 ~ #7 \q_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 \q_mark #6 ~ #7 ~ #8 ~ #9 \q_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} +% +% \begin{macro}[EXP] +% { +% \@@_parse_model_gray:w, \@@_parse_model_rgb:w, +% \@@_parse_model_cmyk:w, \@@_parse_model_spot:w +% } +% \begin{macro}[EXP]{\@@_parse_number:n} +% \begin{macro}[EXP]{\@@_parse_number:w} +% \begin{macro}[EXP]{\@@_parse_spot_aux:w} +% Turn the input into internal form, also tidying up the number quickly. +% \begin{macrocode} +\cs_new:Npn \@@_parse_model_gray:w #1 , #2 \q_stop { \@@_parse_number:n {#1} } +\cs_new:Npn \@@_parse_model_rgb:w #1 , #2 , #3 , #4 \q_stop + { + \@@_parse_number:n {#1} ~ + \@@_parse_number:n {#2} ~ + \@@_parse_number:n {#3} + } +\cs_new:Npn \@@_parse_model_cmyk:w #1 , #2 , #3 , #4 , #5 \q_stop + { + \@@_parse_number:n {#1} ~ + \@@_parse_number:n {#2} ~ + \@@_parse_number:n {#3} ~ + \@@_parse_number:n {#4} + } +\cs_new:Npn \@@_parse_model_spot:w #1 , #2 \q_stop + { \@@_parse_spot_aux:w #1 ! 100 ! \q_stop } +\cs_new:Npn \@@_parse_spot_aux:w #1 ! #2 ! #3 \q_stop + { #1 ~ \fp_eval:n { #2 / 100 } } +\cs_new:Npn \@@_parse_number:n #1 + { \@@_parse_number:w #1 . 0 . \q_stop } +\cs_new:Npn \@@_parse_number:w #1 . #2 . #3 \q_stop + { \tl_if_blank:nTF {#1} { 0 } {#1} . #2 } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% \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:w} +% 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:w #1 \q_stop + \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 \c_space_tl \l_@@_value_tl } + } + } +\cs_new_protected:Npn \@@_check_model:w #1 ~ #2 \q_stop + { + \tl_set:Nn \l_@@_model_tl {#1} + \tl_set:Nn \l_@@_value_tl {#2} + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\@@_select:} +% A driver-neutral location for \enquote{last minute} manipulations before +% handing off to the driver 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:Npn \@@_select: + { + \tl_set_eq:cN { l_@@_named_ . _tl } \l_@@_current_tl + \@@_select:V \l_@@_current_tl + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\color_select:n} +% \begin{macro}{\color_select:nn} +% Parse the input expressions then get the driver to actually activate +% them. +% \begin{macrocode} +\cs_new_protected:Npn \color_select:n #1 + { + \color_parse:nN {#1} \l_@@_current_tl + \@@_select: + } +\cs_new_protected:Npn \color_select:nn #1#2 + { + \@@_direct:nnN {#1} {#2} \l_@@_current_tl + \@@_select: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \subsection{Direct model use} +% +% \begin{macro}{\@@_direct:nnN} +% Directly set a color based on a model/value combination. +% \begin{macrocode} +\cs_new_protected:Npn \@@_direct:nnN #1#2#3 + { + \cs_if_exist:cTF { @@_parse_model_ #1 :w } + { + \tl_set:Nx #3 + { #1 ~ \use:c { @@_parse_model_ #1 :w } #2 , 0 , 0 , 0 , 0 \q_stop } + \@@_check_model:N #3 + } + { + \__kernel_msg_error:nnn { color } { invalid-model } {#1} + } + } +% \end{macrocode} +% \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}{\@@_store:Nn} +% Store the named color unless it has an invalid name. +% \begin{macrocode} +\cs_new_protected:Npn \@@_store:Nn #1#2 + { + \str_if_eq:nnF {#2} { . } + { + \tl_clear_new:c { l_@@_named_ #2 _tl } + \tl_set_eq:cN { l_@@_named_ #2 _tl } #1 + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\color_set:nn} +% \begin{macro}{\color_set:nnn} +% \begin{macro}{\color_set_eq:nn} +% Defining named colors has to include a step to force creation of the +% underlying token list to avoid errors when checking is enabled. +% \begin{macrocode} +\cs_new_protected:Npn \color_set:nn #1#2 + { + \color_parse:nN {#2} \l_@@_named_tl + \@@_store:Nn \l_@@_named_tl {#1} + } +\cs_new_protected:Npn \color_set:nnn #1#2#3 + { + \@@_direct:nnN {#2} {#3} \l_@@_named_tl + \@@_store:Nn \l_@@_named_tl {#1} + } +\cs_new_protected:Npn \color_set_eq:nn #1#2 + { + \@@_if_defined:nTF {#2} + { + \tl_clear_new:c { l_@@_named_ #1 _tl } + \str_if_eq:nnTF {#2} { . } + { \tl_set_eq:cN { l_@@_named_ #1 _tl } \l_@@_current_tl } + { \tl_set_eq:cc { l_@@_named_ #1 _tl } { l_@@_named_ #2 _tl } } + } + { + \__kernel_msg_error:nnn { color } { unknown-color } {#2} + } + } +% \end{macrocode} +% \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_._tl} +% A special named color: this is always defined though not fixed in +% definition. +% \begin{macrocode} +\tl_new:c { l_@@_named_._tl } +\tl_set_eq:cN { l_@@_named_._tl } \l_@@_current_tl +% \end{macrocode} +% \end{variable} +% +% \subsection{Diagnostics} +% +% \begin{macro}{\color_show:n} +% \begin{macro}{\@@_show:w} +% \begin{macro}{\@@_show_gray:w, \@@_show_rgb:w, \@@_show_cmyk:w} +% 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 #1 + { + \msg_show:nnxxxx { LaTeX / color } { show } + {#1} + { + \@@_if_defined:nTF {#1} + { \exp_last_unbraced:Nv \@@_show:w { l_@@_named_ #1 _tl } \q_stop } + { } + } + { } + { } + } +\cs_new:Npn \@@_show:w #1 ~ #2 \q_stop + { + \msg_show_item_unbraced:nn { model } {#1} + \exp_args:Nnf \msg_show_item_unbraced:nn { value } + { \use:c { @@_show_ #1 :w } #2 \q_stop } + } +\cs_new:Npn \@@_show_gray:w #1 \q_stop { #1 } +\cs_new:Npn \@@_show_rgb:w #1 ~ #2 ~ #3 \q_stop { #1 ,~ #2 ,~ #3 } +\cs_new:Npn \@@_show_cmyk:w #1 ~ #2 ~ #3 ~ #4 \q_stop { #1 ,~ #2 ,~ #3 ,~ #4 } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \subsection{Messages} +% +% \begin{macrocode} +\__kernel_msg_new:nnnn { color } { invalid-model } + { Invalid~color~model~'#1'. } + { + LaTeX~has~been~asked~to~use~a~color~model~called~'#1',~ + but~this~model~is~not~set~up. + } +\__kernel_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. + } +% \end{macrocode} +% +% \begin{macrocode} +\__kernel_msg_new:nnn { color } { show } + { + The~color~#1~ + \tl_if_empty:nTF {#2} + { is~undefined. } + { has~the~properties: #2 } + } +% \end{macrocode} +% +% \begin{macrocode} +%</initex|package> +% \end{macrocode} +% +% \end{implementation} +% +% \PrintIndex |