%% This is part of OpTeX project, see http://petr.olsak.net/optex \_codedecl \colordef {Colors <2020-03-18>} % loaded in format \_doc ----------------------------- We declare internal boolean value \`\_iflocalcolor` ad do \`\localcolor` as default. \_cod ----------------------------- \_newifi \_iflocalcolor \_localcolortrue \_protected\_def \_localcolor {\_localcolortrue} \_protected\_def \_nolocalcolor {\_localcolorfalse} \_public \localcolor \nolocalcolor ; \_doc ----------------------------- The basic colors in CMYK \`\Blue` \`\Red` \`\Brown` \`\Green` \`\Yellow` \`\Cyan` \`\Magenta` \`\Grey` \`\LightGrey` \`\White` and \`\Black` are declared here. \_cod ----------------------------- \_def\Blue {\_setcmykcolor{1 1 0 0}} \_def\Red {\_setcmykcolor{0 1 1 0}} \_def\Brown {\_setcmykcolor{0 0.67 0.67 0.5}} \_def\Green {\_setcmykcolor{1 0 1 0}} \_def\Yellow {\_setcmykcolor{0 0 1 0}} \_def\Cyan {\_setcmykcolor{1 0 0 0}} \_def\Magenta {\_setcmykcolor{0 1 0 0}} \_def\Grey {\_setcmykcolor{0 0 0 0.5}} \_def\LightGrey {\_setcmykcolor{0 0 0 0.2}} \_def\White {\_setgreycolor{1}} \_def\Black {\_setgreycolor{0}} \_doc ----------------------------- By default, the \`\setcmykcolor` \`\setrgbcolor` and \`\setgreycolor` macros with `{}` parameter expand to `\_setcolor{}` using \`\_formatcmyk` or \`\_formatrgb` or \`\_formatgrey` expandable macros. For example `\setrgbcolor{1 0 0}` expands to `\_setcolor{1 0 0 rg 1 0 0 RG}`.\nl We set both types of colors (for lines (`K` or `RG` or `G`) and for fills (`r` or `rg` or `g`) together in the command. This is the reason why the \`\_fillstroke` uses both its parameters. If only fills are needed you can do `\def\_fillstroke#1#2{#1}`. If only strokes are needed you can do `\def\_fillstroke#1#2{#2}`. \_cod ----------------------------- \_def\_setcmykcolor#1{\_setcolor{\_formatcmyk{#1}}} \_def\_setrgbcolor#1{\_setcolor{\_formatrgb{#1}}} \_def\_setgreycolor#1{\_setcolor{\_formatgrey{#1}}} \_def\_formatcmyk#1{\_fillstroke{#1 k}{#1 K}} \_def\_formatrgb#1{\_fillstroke{#1 rg}{#1 RG}} \_def\_formatgrey#1{\_fillstroke{#1 g}{#1 G}} \_def\_fillstroke#1#2{#1 #2} \_public \setcmykcolor \setrgbcolor \setgreycolor ; \_doc ----------------------------- The \`\onlyrgb` declaration redefines \^`\_formatcmyk` in order it expands to its conversion to RGB . This conversion is done by the \^`\_cmyktorgb` macro. Moreover, `\onlyrgb` re-defines three basic RGB colors for RGB color space and re-declares \^`\colordef` as \^`\rgbcolordef`. The \hbox{\`\onlycmyk`} macro does a similar work, it re-defines \^`\_formatrgb` macro. The Grey color space is unchanged and works in both main settings (RGB or CMYK) without collisions. \_cod ----------------------------- \_def\_onlyrgb{\_def\Red{\_setrgbcolor{1 0 0}}% \_def\Green{\_setrgbcolor{0 1 0}}\_def\Blue{\_setrgbcolor{0 0 1}}% \_let\_colordef=\_rgbcolordef \_def\_formatrgb##1{\_fillstroke{##1 rg}{##1 RG}}% \_def\_formatcmyk##1{\_fillstroke{\_cmyktorgb ##1 ; rg}{\_cmyktorgb ##1 ; RG}}} \_def\_onlycmyk{\_def\_formatcmyk##1{\_fillstroke{##1 k}{##1 K}}% \_def\_formatrgb##1{\_fillstroke{\_rgbtocmyk ##1 ; k}{\_rgbtocmyk ##1 ; K}}} \_public \onlyrgb \onlycmyk ; \_doc ----------------------------- The \`\_setcolor` macro redefines empty `\_ensureblack` macro (used in output routine for headers and footers) to `\_ensureblackA` which sets Black at the start of its parameter and returns to the current color at the end of its parameter. The current color is saved into `\_currentcolor` macro and colorstack is pushed. Finally, the `\_colorstackpop` is initialized by `\aftergroup` if `\localcolor` is declared. You can save current color to your macro by `\let\yourmacro=\_currentcolor` and you can return to this color by the command `\_setcolor\yourmacro`. \_cod ----------------------------- \_protected\_def \_setcolor #1{\_global\_let\_ensureblack=\_ensureblackA \_iflocalcolor \_edef\_currentcolor{#1}\_colorstackpush\_currentcolor \_aftergroup\_colorstackpop \_else \_xdef\_currentcolor{#1}\_colorstackset\_currentcolor \_fi } \_def\_pdfblackcolor{0 g 0 G} \_edef\_currentcolor{\_pdfblackcolor} \_def\_ensureblackA#1{\_global\_let\_openfnotestack=\_openfnotestackA \_colorstackpush\_pdfblackcolor #1\_colorstackpop} \_doc ----------------------------- The colorstack is initialized here and the basic macros \`\_colorstackpush`, \`\_colorstackpop` and \`\_colorstackset` are defined here. \_cod ----------------------------- \_mathchardef\_colorstackcnt=0 % Implicit stack usage \_def\_colorstackpush#1{\_pdfcolorstack\_colorstackcnt push{#1}} \_def\_colorstackpop{\_pdfcolorstack\_colorstackcnt pop} \_def\_colorstackset#1{\_pdfcolorstack\_colorstackcnt set{#1}} \_doc ----------------------------- We need to open a special color stack for footnotes, because footnotes can follow on next pages and their colors are independent on colors used in the main page-body. The \`\_openfnotestack` is defined as \`\_openfnotestackA` when the \^`\_setcolor` is used first. The \`\_fnotestack` is initialized in in `\everyjob` because the initialization is not saved to the format. \_cod ----------------------------- %\_mathchardef\_fnotestack=\_pdfcolorstackinit page {0 g 0 G} % must be in \everyjob \_def \_openfnotestackA {\_pdfcolorstack\_fnotestack current} \_doc ----------------------------- We use lua codes for RGB to CMYK or CMYK to RGB conversions and for addition color components in the \^`\colordef` macro. The \`\_rgbtocmyk` ` ;` expands to ` ` and the \`\_cmyktorgb` ` ;` expands to ` `. The \`\_colorcrop`, \`\_colordefFin` and \`\_douseK` are auxiliary macros used in the \^`\colordef`. The `\_colorcrop` rescales color components in order to they are in $[0,1]$ interval. The `\colordefFin` expands to the values accumulated in Lua code `color_C`, `color_M`, `color_Y` and `color_K`. The `\_douseK` applies \^`\useK` to CMYK components. \_cod ----------------------------- \_def\_rgbtocmyk #1 #2 #3 ;{% \_ea \_stripzeros \_detokenize \_ea{\_directlua{ local kr = math.max(#1,#2,#3) if (kr==0) then tex.print('0. 0. 0. 1 ;') else tex.print(string.format('\_pcent.3f \_pcent.3f \_pcent.3f \_pcent.3f ;', (kr-#1)/kr, (kr-#2)/kr, (kr-#3)/kr, 1-kr)) end }}} \_def\_cmyktorgb #1 #2 #3 #4 ;{% \_ea \_stripzeros \_detokenize \_ea{\_directlua{ local kr = 1-#4 tex.print(string.format('\_pcent.3f \_pcent.3f \_pcent.3f ;', (1-#1)*kr, (1-#2)*kr, (1-#3)*kr)) }}} \_def\_colorcrop{\_directlua{ local m=math.max(color_C, color_M, color_Y, color_K) if (m>1) then color_C=color_C/m color_M=color_M/m color_Y=color_Y/m color_K=color_K/m end }} \_def\_colordefFin{\_colorcrop \_ea \_stripzeros \_detokenize \_ea{\_directlua{ tex.print(string.format('\_pcent.3f \_pcent.3f \_pcent.3f \_pcent.3f ;', color_C, color_M, color_Y, color_K)) }}} \_def\_douseK{\_colorcrop \_directlua{ kr=math.min(color_C, color_M, color_Y) if (kr>=1) then color_C=0 color_M=0 color_Y=0 color_K=1 else color_C=(color_C-kr)/(1-kr) color_M=(color_M-kr)/(1-kr) color_Y=(color_Y-kr)/(1-kr) color_K=math.min(color_K+kr,1) end }} \_doc ----------------------------- We have a problem with the `%.3f` directive in Lua code. It prints trailed zeros: (0.300 instead desired 0.3) but we want to save PDF file space. The macro \`\_stripzeros` removes these trailing zeros at expand processor level. So `\_stripzeros 0.300 0.400 0.560 ;` expands to `.3 .4 .56`. \_cod ----------------------------- \_def\_stripzeros #1.#2 #3{\_ifx0#1\_else#1\_fi.\_stripzeroA #2 0 :% \_ifx;#3\_else \_space \_ea\_stripzeros\_ea#3\_fi} \_def\_stripzeroA #10 #2:{\_ifx^#2^\_stripzeroC#1:\_else \_stripzeroB#1 0 :\_fi} \_def\_stripzeroB #10 #2:{\_ifx^#2^\_stripzeroC#1:\_else #1\_fi} \_def\_stripzeroC #1 #2:{#1} \_doc ----------------------------- The \`\rgbcolordef` and \`\cmykcolordef` use common macro \`\_commoncolordef` with different first four parameters. The `\_commoncolordef {}` does the real work. It initializes the Lua variables for summation. It expands in the group where color selectors have special meaning, then it adjusts the resulting string by \^`\replstring` and runs it. Example shows how the are processed: \begtt input : ".3\Blue + .6^\KhakiC \useK -\Black" expanded to: ".3 !=K 1 1 0 0 +.6^!=R .804 .776 .45 \_useK -!=G 0" adjusted to: "\_addcolor .3!=K 1 1 0 0 \_addcolor .6!^R .804 .776 .45 \_useK \_addcolor -1!=G 0" and this is processed. \endtt \`\_addcolor` `!` expands to `\_addcolor: ` for example it expands to `\_addcolor:=K ` followed by one or three or four numbers (depending on ). is `=` (use as is) or `^` (use complementary color). is `K` for CMYK, `R` for RGB and `G` for GREY color space. Uppercase informs that `\cmykcolordef` is processed and lowercase informs that `\rgbcolordef` is processed. All variants of commands `\_addcolor:` are defined. All of them expand to `\_addcolorA ` which adds the values of Lua variables. The `\rgbcolordef` uses `\_addcolorA 0` and `\cmkykcolordef` uses `\_addcolorA `. So the Lua variable names are a little confusing when `\rgbcolordef` is processed. Next, `\_commoncolordef` saves resulting values from Lua to `\_tmpb` using `\_colordefFin`. If `\rgbcolordef` is processed, then we must to remove the last component which is in the format `.0` in such case. The `\_stripK` macro does it. Finally, the `` is defined as `{}`, for example `\_setcmykclor{1 0 .5 .3}`. \_cod ----------------------------- \_def\_rgbcolordef {\_commoncolordef \_setrgbcolor krg} \_def\_cmykcolordef {\_commoncolordef \_setcmykcolor KRG} \_def\_commoncolordef#1#2#3#4#5#6{% \_begingroup \_directlua{color_C=0 color_M=0 color_Y=0 color_K=0}% \_def\_setcmykcolor##1{!=#2 ##1 }% \_def\_setrgbcolor ##1{!=#3 ##1 }% \_def\_setgreycolor##1{!=#4 ##1 }% \_let\_useK=\_relax \_edef\_tmpb{+#6}% \_replstring\_tmpb{+ }{+}\_replstring\_tmpb{- }{-}% \_replstring\_tmpb{+}{\_addcolor}\_replstring\_tmpb{-}{\_addcolor-}% \_replstring\_tmpb{^!=}{!^}\_replstring\_tmpb{-!}{-1!}% \_ifx K#2\_let\_useK=\_douseK \_fi \_tmpb \_edef\_tmpb{\_colordefFin}% \_ifx k#2\_edef\_tmpb{\_ea\_stripK \_tmpb;}\_fi \_ea\_endgroup \_ea\_def\_ea#5\_ea{\_ea#1\_ea{\_tmpb}}% } \_def\_addcolor#1!#2#3{\_cs{addcolor:#2#3}#1} \_def\_addcolorA #1 #2 #3 #4 #5 {% \_def\_tmpa{#1}\_ifx\_tmpa\_empty \_else \_edef\_tmpa{\_tmpa*}\_fi \_directlua{color_C=math.max(color_C+\_tmpa#2,0) color_M=math.max(color_M+\_tmpa#3,0) color_Y=math.max(color_Y+\_tmpa#4,0) color_K=math.max(color_K+\_tmpa#5,0) }} \_sdef{addcolor:=K}#1 #2 #3 #4 #5 {\_addcolorA #1 #2 #3 #4 #5 } \_sdef{addcolor:^K}#1 #2 #3 #4 #5 {\_addcolorA #1 (1-#2) (1-#3) (1-#4) #5 } \_sdef{addcolor:^G}#1 #2 {\_addcolorA #1 0 0 0 #2 } \_sdef{addcolor:=G}#1 #2 {\_addcolorA #1 0 0 0 (1-#2) } \_sdef{addcolor:=R}#1 #2 #3 #4 {% \_edef\_tmpa{\_noexpand\_addcolorA #1 \_rgbtocmyk #2 #3 #4 ; }\_tmpa } \_sdef{addcolor:^R}#1 #2 #3 #4 {\_cs{addcolor:=R}#1 (1-#2) (1-#3) (1-#4) } \_sdef{addcolor:=k}#1 #2 #3 #4 #5 {% \_edef\_tmpa{\_noexpand\_addcolorA #1 \_cmyktorgb #2 #3 #4 #5 ; 0 }\_tmpa } \_sdef{addcolor:^k}#1 #2 #3 #4 #5 {\_cs{addcolor:=k}#1 (1-#2) (1-#3) (1-#4) #5 } \_sdef{addcolor:^g}#1 #2 {\_addcolorA #1 (1-#2) (1-#2) (1-#2) 0 } \_sdef{addcolor:=g}#1 #2 {\_addcolorA #1 #2 #2 #2 0 } \_sdef{addcolor:=r}#1 #2 #3 #4 {\_addcolorA #1 #2 #3 #4 0 } \_sdef{addcolor:^r}#1 #2 #3 #4 {\_addcolorA #1 (1-#2) (1-#3) (1-#4) 0 } \_def\_stripK#1 .0;{#1} \_let\_colordef=\_cmykcolordef % default \_colordef is \_cmykcolordef \_doc ----------------------------- Public versions of \`\colordef` and \`\useK` macros are declared using `\_def`, because the internal versions `\_colordef` and `\_useK` are changed during processing. \_cod ----------------------------- \_def \useK{\_useK} \_def \colordef {\_colordef} \_public \cmykcolordef \rgbcolordef ; \_doc ----------------------------- The \LaTeX/ file `x11nam.def` is read by \`\morecolors`. The numbers 0,1,2,3,4 are transformed to letters O, , B, C, D in the name of the color. Colors defined already are not re-defined. The empty \`\_showcolor` macro should be re-defined for color catalog printing. For example: \begtt \def\vr{\vrule height10pt depth2pt width20pt} \def\_showcolor{\hbox{\tt\_bslash\_tmpb: \csname\_tmpb\endcsname \vr}\space\space} \begmulti 4 \typosize[11/14] \morecolors \endmulti \endtt \_cod ----------------------------- \_def\_morecolors{% \_long\_def\_tmp##1\preparecolorset##2##3##4##5{\_tmpa ##5;,,,;} \_def\_tmpa##1,##2,##3,##4;{\_ifx,##1,\_else \_def\_tmpb{##1}\_replstring\_tmpb{1}{}\_replstring\_tmpb{2}{B}% \_replstring\_tmpb{3}{C}\_replstring\_tmpb{4}{D}\_replstring\_tmpb{0}{O}% \_ifcsname \_tmpb\_endcsname \_else \_sdef{\_tmpb}{\_setrgbcolor{##2 ##3 ##4}}\_showcolor\_fi \_ea\_tmpa\_fi } \_ea\_tmp\_input x11nam.def } \_let\_showcolor=\_relax % re-define it if you want to print a color catalog \_public \morecolors ; \_endcode % ------------------------------------- The colors have different behavior than fonts. A marks (whatsits) with color information are stored into PDF output and \TeX/ doesn't interpret them. The PDF viewer (or PDF interpreter in a printer) reads these marks and switches colors according to them. This is totally independent on \TeX/ group mechanism. You can declare `\nolocalcolor` at the beginning of the document, if you want this behavior. In this case, if you set a color then you must to return back to black color using `\Black` manually. By default, \OpTeX/ sets `\localcolor`. It means that the typesetting returns back to a previous color at the end of current group, so you cannot write `\Black` explicitly. This is implemented using `\aftergroup` feature. There is a limitation of this feature: when a color selector is used in a group of a box, which is saved by `\setbox`, then the activity or reconstruction of previous color are processed at `\setbox` time, no in the box itself. You must correct it by double group: \begtt \setbox0=\hbox{\Red text} % bad: \Black is done after \setbox \setbox0=\hbox{{\Red text}} % good: \Black is done after group inside the box \endtt The implementation of colors is based on colorstack, so the current color can follow across more pages. It is not so obvious because PDF viewer (or PDF interpreter) manipulates with colors locally at each PDF page and it initializes each PDF page with black on white color. Macros \^`\setcmykcolor``{ }` or \^`\setrgbcolor``{ }` or\nl \^`\setgreycolor``{}` should be used in color selectors or user can specify these macros explicitly. The color mixing processed by the \^`\colordef` is done in the substractive color model CMYK. If the result has a component greater than 1 then all components are multiplied by a coefficient in order to maximal component is equal to 1. You can move a shared amount of CMY components (i.e. their minimum) to the $K$ component. This saves the color tonners and the result is more true. This should be done by \^`\useK` command at the end of a linear combination used in `\colordef`. For example \begtt \colordef \myColor {.3\Green + .4\Blue \useK} \endtt The `\useK` command exactly does: $$ \displaylines{ k' =\min(C,M,Y), \cr \ C=(C-k')/(1-k'), \ M=(M-k')/(1-k'), \ Y=(Y-k')/(1-k'), \cr K = \min(1,K+k'). } $$ You can use minus instead plus in the linear combination in `\colordef`. The given color is substracted in such case and the negative components are rounded to zero immediately. For example \begtt \colordef \Color {\Brown-\Black} \endtt can be used for removing black component from the color. You can use the `-\Black` trick after `\useK` command in order to remove grey components occured during color mixing. Finally, you can use `^` immediately preceeded before macro name of the color. Then the complementary color is used here. \begtt \colordef\mycolor{\Grey+.6^\Blue} % the same as \colordef\mycolor{\Grey+.6\Yellow} \endtt The \^`\rgbcolordef` can be used to mix colors in additive color model RGB. If \^`\onlyrgb` is declared, then \^`\colordef` works as \^`\rgbcolordef`. If a CMYK to RGB or RGB to CMYK conversion is needed then the following simple formulae are used (ICC profiles are not supported): $$ \displaylines{ \hbox{CMYK to RGB:}\cr R = (1-C)(1-K), \ G = (1-M)(1-K), \ B = (1-Y)(1-K). \cr \hbox{RGB to CMYK:}\cr K'=\max(R,G,B), \ C=(K'-R)/K', \ M=(K'-G)/K', \ Y=(K'-B)/K', \ K=1-K'. } $$ The RGB to CMYK conversion is invoked when a color is declared using `\setrgbcolor` and it is used in \^`\colordef` or if it is printed when \^`\onlycmyk` is declared. The CMYK to RGB conversion is invoked when a color is declared using \^`\setcmykcolor` and it is used in \^`\rgbcolordef` or if it is printed when \^`\onlyrgb` is declared. \_endinput 2020-04-22 \replstring\tmpb{+ }{+}, {- }{-} added in \colordef, bug fixed 2020-03-18 introduced