% \iffalse meta-comment % %% File: l3color.dtx % % Copyright (C) 2017-2020 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> \documentclass[full]{l3doc} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \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 2020-05-18} % % \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} % !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} \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} to the equivalent \meta{core color representation} % (used at the backend level and based on \texttt{dvips} 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}{2020-05-18}{} {L3 Experimental color support} % % \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} % % \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}[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: \s_@@_stop } \cs_generate_variant:Nn \@@_extract:nNN { V } \cs_new_protected:Npn \@@_extract:NNw #1#2 #3 ~ #4 \s_@@_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 \s_@@_stop } } \cs_generate_variant:Nn \@@_convert:nnnN { nnV } \cs_new:Npn \@@_convert_gray_rgb:w #1 ~ #2 \s_@@_stop { #1 ~ #1 ~ #1 } \cs_new:Npn \@@_convert_gray_cmyk:w #1 ~ #2 \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 ~ #4 \s_@@_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 \s_@@_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 \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 ~ #5 \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 ) } } % \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 % \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 ! \s_@@_stop } \@@_check_model:N #2 } \cs_new_protected:Npn \@@_parse:Nw #1#2 ! #3 \s_@@_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 } {#2} \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 ! ! ! ! \s_@@_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 \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 { \@@_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 \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} % % \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 \s_@@_stop { \@@_parse_number:n {#1} } \cs_new:Npn \@@_parse_model_rgb:w #1 , #2 , #3 , #4 \s_@@_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 \s_@@_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 \s_@@_stop { \@@_parse_spot_aux:w #1 ! 100 ! \s_@@_stop } \cs_new:Npn \@@_parse_spot_aux:w #1 ! #2 ! #3 \s_@@_stop { #1 ~ \fp_eval:n { #2 / 100 } } \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} % \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 \s_@@_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 \s_@@_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 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: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 backend 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 \s_@@_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 } \s_@@_stop } { } } { } { } } \cs_new:Npn \@@_show:w #1 ~ #2 \s_@@_stop { \msg_show_item_unbraced:nn { model } {#1} \exp_args:Nnf \msg_show_item_unbraced:nn { value } { \use:c { @@_show_ #1 :w } #2 \s_@@_stop } } \cs_new:Npn \@@_show_gray:w #1 \s_@@_stop { #1 } \cs_new:Npn \@@_show_rgb:w #1 ~ #2 ~ #3 \s_@@_stop { #1 ,~ #2 ,~ #3 } \cs_new:Npn \@@_show_cmyk:w #1 ~ #2 ~ #3 ~ #4 \s_@@_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} % % \end{macrocode} % % \end{implementation} % % \PrintIndex