PGF is a graphics framework for \$\TeX\$ and friends. It supports shadings (gradients), but only in RGB.
There is often a need to output shadings in CMYK and I am having a go at implementing this.
I'm looking for feedback from those who know PGF well as to whether my strategy is good or not and what things I might not have considered.
At present, only axial and radial shadings are supported (not functional ones).
The package is for *\$\LaTeX\$, not ConTeXt or Plain \$\TeX\$.
And not all backends are supported (only pdftex
, luatex
, xetex
, and dvipdfmx
).
This code is currently implemented as a separate package to pgf
, but could possibly be included in the main package in the future.
The code is hosted on GitHub at https://github.com/dcpurton/pgf-cmykshadings
Main style file (pgf-cmykshadings.sty
)
\ProvidesPackage{pgf-cmykshadings}[2018/10/08 CMYK shadings support for PGF (DCP)]
\RequirePackage{pgf}
% macros from pgfcoreshade.code.tex
\def\pgf@parsefunccmyk#1{%
\edef\temp{{#1}}%
\expandafter\pgf@convertcmykstring\temp%
\edef\temp{{\pgf@cmykconv}}%
\expandafter\pgf@@parsefunccmyk\temp}
\def\pgf@@parsefunccmyk#1{%
\let\pgf@bounds=\pgfutil@empty%
\let\pgf@funcs=\pgfutil@empty%
\let\pgf@psfuncs=\pgfutil@empty%
\let\pgf@encode=\pgfutil@empty%
\let\pgf@sys@shading@ranges=\pgfutil@empty%
\pgf@sys@shading@range@num=0\relax%
\pgf@parsefirstcmyk[#1; ]%
\pgf@parselastdomcmyk[#1; ]%
\pgf@parsemidcmyk[#1; ]%
\ifx\pgf@bounds\pgfutil@empty%
\edef\pgf@pdfparseddomain{0 1}%
\edef\pgf@pdfparsedfunction{\pgf@singlefunc\space}%
\else%
\edef\pgf@pdfparseddomain{\pgf@doma\space\pgf@domb}%
\edef\pgf@pdfparsedfunction{%
<< /FunctionType 3 /Domain [\pgf@doma\space\pgf@domb] /Functions
[\pgf@funcs\space] /Bounds [\pgf@bounds] /Encode [0 1 \pgf@encode]
>> }% <<
\fi%
\xdef\pgf@psfuncs{\pgf@psfuncs}%
}
\def\pgf@parsefirstcmyk[cmyk(#1)=(#2,#3,#4,#5)#6]{%
\pgfmathsetlength\pgf@x{#1}%
\edef\pgf@sys@shading@start@pos{\the\pgf@x}%
\pgf@sys@bp@correct\pgf@x%
\edef\pgf@doma{\pgf@sys@tonumber{\pgf@x}}%
\edef\pgf@prevx{\pgf@sys@tonumber{\pgf@x}}%
\pgf@getcmyktuplewithmixin{#2}{#3}{#4}{#5}%
\edef\pgf@sys@shading@start@cmyk{\pgf@sys@cmyk}%
\let\pgf@sys@prevcolor=\pgf@sys@shading@start@cmyk%
\let\pgf@sys@prevpos=\pgf@sys@shading@start@pos%
\edef\pgf@prevcolor{\pgf@cmyk}%
\edef\pgf@firstcolor{\pgf@cmyk}}
\def\pgf@parselastdomcmyk[cmyk(#1)=(#2,#3,#4,#5); {%
\pgfutil@ifnextchar]{%
\pgfmathsetlength\pgf@x{#1}%
\edef\pgf@sys@shading@end@pos{\the\pgf@x}%
\pgf@max=\pgf@x\relax%
\pgf@sys@bp@correct\pgf@x%
\edef\pgf@domb{\pgf@sys@tonumber{\pgf@x}}%
\pgf@getcmyktuplewithmixin{#2}{#3}{#4}{#5}%
\edef\pgf@sys@shading@end@cmyk{\pgf@sys@cmyk}%
\pgfutil@gobble}{\pgf@parselastdomcmyk[}}
\def\pgf@parsemidcmyk[cmyk(#1)=(#2,#3,#4,#5); {\pgf@parserestcmyk[}
\def\pgf@parserestcmyk[cmyk(#1)=(#2,#3,#4,#5); {%
\advance\pgf@sys@shading@range@num by1\relax%
\pgfutil@ifnextchar]{%
\pgf@getcmyktuplewithmixin{#2}{#3}{#4}{#5}%
\edef\pgf@singlefunc{\space%
<< /FunctionType 2 /Domain [0 1] /C0
[\pgf@prevcolor] /C1 [\pgf@cmyk] /N 1 >> }% <<
\edef\pgf@funcs{\pgf@funcs\space%
<< /FunctionType 2 /Domain [\pgf@doma\space\pgf@domb] /C0
[\pgf@prevcolor] /C1 [\pgf@cmyk] /N 1 >> }% <<
\edef\pgf@psfuncs{\pgf@prevx\space \pgf@cmyk\space \pgf@prevcolor\space pgfshade \pgf@psfuncs}%
\pgfmathsetlength\pgf@x{#1}%
\edef\pgf@sys@shading@ranges{\pgf@sys@shading@ranges{{\pgf@sys@prevpos}{\the\pgf@x}{\pgf@sys@prevcolor}{\pgf@sys@cmyk}}}%
\edef\pgf@sys@prevpos{\the\pgf@x}%
\let\pgf@sys@prevcolor=\pgf@sys@cmyk%
\pgfutil@gobble}{%
\pgfmathsetlength\pgf@x{#1}%
\pgf@getcmyktuplewithmixin{#2}{#3}{#4}{#5}%
\edef\pgf@sys@shading@ranges{\pgf@sys@shading@ranges{{\pgf@sys@prevpos}{\the\pgf@x}{\pgf@sys@prevcolor}{\pgf@sys@cmyk}}}%
\edef\pgf@sys@prevpos{\the\pgf@x}%
\let\pgf@sys@prevcolor=\pgf@sys@cmyk%
\edef\pgf@psfuncs{\pgf@prevx\space \pgf@cmyk\space \pgf@prevcolor\space pgfshade \pgf@psfuncs}%
\pgf@sys@bp@correct\pgf@x%
\edef\pgf@prevx{\pgf@sys@tonumber{\pgf@x}}%
\edef\pgf@bounds{\pgf@bounds\space\pgf@sys@tonumber{\pgf@x}}%
\edef\pgf@encode{\pgf@encode\space0 1}%
\edef\pgf@singlefunc{\space%
<< /FunctionType 2 /Domain [0 1] /C0
[\pgf@prevcolor] /C1 [\pgf@cmyk] /N 1 >> }% <<
\edef\pgf@funcs{\pgf@funcs\space%
<< /FunctionType 2 /Domain [\pgf@doma\space\pgf@domb] /C0
[\pgf@prevcolor] /C1 [\pgf@cmyk] /N 1 >> }% <<
\edef\pgf@prevcolor{\pgf@cmyk}%
\pgf@parserestcmyk[}}
\def\pgf@getcmyktuplewithmixin#1#2#3#4{%
\pgfutil@definecolor{pgfshadetemp}{cmyk}{#1,#2,#3,#4}%
\pgfutil@ifundefined{applycolormixins}{}{\applycolormixins{pgfshadetemp}}%
\pgfutil@extractcolorspec{pgfshadetemp}{\pgf@tempcolor}%
\expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}%
\expandafter\pgf@getcmyk@@\pgf@cmykcolor!}
\def\pgf@getcmyk@@#1,#2,#3,#4!{%
\def\pgf@cmyk{#1 #2 #3 #4}%
\def\pgf@sys@cmyk{{#1}{#2}{#3}{#4}}%
}
\def\pgf@convertcmykstring#1{%
\def\pgf@cmykconv{}%
\pgf@converttocmyk#1]%
}
\def\pgf@converttocmyk{%
\pgfutil@ifnextchar]{\pgfutil@gobble}%done!
{%
\pgfutil@ifnextchar;{\pgf@grabsemicolorcmyk}%
{%
\pgfutil@ifnextchar c{\pgf@gobblec}%
{%
\pgfutil@ifnextchar g{\pgf@grabgraycmyk}%
{%
\pgfutil@ifnextchar o{\pgf@grabcolorcmyk}%
{%
\pgfutil@ifnextchar m{\pgf@grabcmyk}%
{%
\pgfutil@ifnextchar r{\pgf@grabrgbcmyk}%
{\pgferror{Illformed shading
specification}\pgf@converttocmyk}%
}%
}%
}%
}%
}%
}%
}
\def\pgf@grabsemicolorcmyk;{%
\edef\pgf@cmykconv{\pgf@cmykconv; }\pgf@converttocmyk}
\def\pgf@gobblec c{\pgf@converttocmyk}
\def\pgf@grabrgbcmyk rgb(#1)=(#2,#3,#4){%
\pgfutil@definecolor{pgf@tempcol}{rgb}{#2,#3,#4}%
\pgfutil@extractcolorspec{pgf@tempcol}{\pgf@tempcolor}%
\expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}%
\expandafter\pgf@convgetcmyk@\expandafter{\pgf@cmykcolor}{#1}%
}
\def\pgf@grabcmyk myk(#1)=(#2,#3,#4,#5){%
\edef\pgf@cmykconv{\pgf@cmykconv cmyk(#1)=(#2,#3,#4,#5)}\pgf@converttocmyk}
\def\pgf@grabgraycmyk gray(#1)=(#2){%
\edef\pgf@cmykconv{\pgf@cmykconv cmyk(#1)=(0,0,0,#2)}\pgf@converttocmyk}
\def\pgf@grabcolorcmyk olor(#1)=(#2){%
\pgfutil@colorlet{pgf@tempcol}{#2}%
\pgfutil@extractcolorspec{pgf@tempcol}{\pgf@tempcolor}%
\expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}%
\expandafter\pgf@convgetcmyk@\expandafter{\pgf@cmykcolor}{#1}%
}
\def\pgf@convgetcmyk@#1#2{%
\edef\pgf@cmykconv{\pgf@cmykconv cmyk(#2)=(#1)}\pgf@converttocmyk}
\def\pgfdeclarehorizontalcmykshading{\pgfutil@ifnextchar[\pgf@declarehorizontalcmykshading{\pgf@declarehorizontalcmykshading[]}}%
\def\pgf@declarehorizontalcmykshading[#1]#2#3#4{%
\expandafter\def\csname pgf@deps@pgfshading#2!\endcsname{#1}%
\expandafter\ifx\csname pgf@deps@pgfshading#2!\endcsname\pgfutil@empty%
\pgfsys@horicmykshading{#2}{#3}{#4}%
\else%
\expandafter\def\csname pgf@func@pgfshading#2!\endcsname{\pgfsys@horicmykshading}%
\expandafter\def\csname pgf@args@pgfshading#2!\endcsname{{#3}{#4}}%
\expandafter\let\csname @pgfshading#2!\endcsname=\pgfutil@empty%
\fi}
\def\pgfdeclareverticalcmykshading{\pgfutil@ifnextchar[\pgf@declareverticalcmykshading{\pgf@declareverticalcmykshading[]}}%
\def\pgf@declareverticalcmykshading[#1]#2#3#4{%
\expandafter\def\csname pgf@deps@pgfshading#2!\endcsname{#1}%
\expandafter\ifx\csname pgf@deps@pgfshading#2!\endcsname\pgfutil@empty%
\pgfsys@vertcmykshading{#2}{#3}{#4}%
\else%
\expandafter\def\csname pgf@func@pgfshading#2!\endcsname{\pgfsys@vertcmykshading}%
\expandafter\def\csname pgf@args@pgfshading#2!\endcsname{{#3}{#4}}%
\expandafter\let\csname @pgfshading#2!\endcsname=\pgfutil@empty%
\fi}
\def\pgfdeclareradialcmykshading{\pgfutil@ifnextchar[\pgf@declareradialcmykshading{\pgf@declareradialcmykshading[]}}%
\def\pgf@declareradialcmykshading[#1]#2#3#4{%
\expandafter\def\csname pgf@deps@pgfshading#2!\endcsname{#1}%
\expandafter\ifx\csname pgf@deps@pgfshading#2!\endcsname\pgfutil@empty%
\pgfsys@radialcmykshading{#2}{#3}{#4}%
\else%
\expandafter\def\csname pgf@func@pgfshading#2!\endcsname{\pgfsys@radialcmykshading}%
\expandafter\def\csname pgf@args@pgfshading#2!\endcsname{{#3}{#4}}%
\expandafter\let\csname @pgfshading#2!\endcsname=\pgfutil@empty%
\fi}
\def\pgfusecmykshading#1{%
\edef\pgf@shadingname{@pgfshading#1}%
\pgf@tryextensions{\pgf@shadingname}{\pgfalternateextension}%
\expandafter\pgfutil@ifundefined\expandafter{\pgf@shadingname}%
{\pgferror{Undefined shading "#1"}}%
{%
{%
\pgfutil@globalcolorsfalse%
\def\pgf@shade@adds{}%
\pgfutil@ifundefined{pgf@deps\pgf@shadingname}%
{}%
{%
\edef\@list{\csname pgf@deps\pgf@shadingname\endcsname}%
\pgfutil@for\@temp:=\@list\do{%
{%
\pgfutil@ifundefined{applycolormixins}{}{\applycolormixins{\@temp}}%
\pgfutil@extractcolorspec{\@temp}{\pgf@tempcolor}%
\expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}%
\xdef\pgf@shade@adds{\pgf@shade@adds,\pgf@cmykcolor}%
}%
}%
}%
\expandafter\pgf@strip@shadename\pgf@shadingname!!%
\pgfutil@ifundefined{@pgfshading\pgf@basename\pgf@shade@adds!}%
{%
{%
\expandafter\def\expandafter\@temp\expandafter{\csname pgf@func\pgf@shadingname\endcsname}%
\edef\@args{{\pgf@basename\pgf@shade@adds}}%
\expandafter\expandafter\expandafter\def%
\expandafter\expandafter\expandafter\@@args%
\expandafter\expandafter\expandafter{\csname pgf@args\pgf@shadingname\endcsname}%
\expandafter\expandafter\expandafter\@temp\expandafter\@args\@@args%
%
}%
}%
{}%
\pgf@invokeshading{\csname @pgfshading\pgf@basename\pgf@shade@adds!\endcsname}%
}%
}%
}
% load the correct driver
\def\pgfutilgetcmykshadingsdriver{%
\expandafter\pgfutil@getcmykshadingsdriver\pgfsysdriver[
}
\def\pgfutil@getcmykshadingsdriver pgfsys-#1[{%
\edef\pgfsyscmykshadingsdriver{pgfsys-cmykshadings-#1}%
}
\pgfutilgetcmykshadingsdriver
\input\pgfsyscmykshadingsdriver
% style options to make use CMYK gradients by default or not
\let\pgfdeclarehorizontalrgbshading\pgfdeclarehorizontalshading
\let\pgfdeclareverticalrgbshading\pgfdeclareverticalshading
\let\pgfdeclareradialrgbshading\pgfdeclareradialshading
\let\pgfusergbshading\pgfuseshading
\DeclareOption{default}{%
\let\pgfdeclarehorizontalshading\pgfdeclarehorizontalcmykshading
\let\pgfdeclareverticalshading\pgfdeclareverticalcmykshading
\let\pgfdeclareradialshading\pgfdeclareradialcmykshading
\let\pgfuseshading\pgfusecmykshading
}
\DeclareOption{nodefault}{%
\let\pgfdeclarehorizontalshading\pgfdeclarehorizontalrgbshading
\let\pgfdeclareverticalshading\pgfdeclareverticalrgbshading
\let\pgfdeclareradialshading\pgfdeclareradialrgbshading
\let\pgfuseshading\pgfusergbshading
}
\ExecuteOptions{default}
\ProcessOptions\relax
pdftex
driver (pgfsys-cmykshadings-pdftex.def
)
Other drivers are analogous.
\ProvidesFile{pgfsys-cmykshadings-pdftex.def}[2018/10/08 CMYK shadings support for PGF pdftex driver (DCP)]
\def\pgfsys@horicmykshading#1#2#3{%
{%
\pgf@parsefunccmyk{#3}%
\pgfmathparse{#2}%
\setbox\pgfutil@tempboxa=\hbox to\pgf@max{\vbox to\pgfmathresult pt{\vfil\pgfsys@invoke{/Sh sh}}\hfil}%
\pgf@process{\pgfpoint{\pgf@max}{#2}}%
\immediate\pdfxform resources {%
/Shading << /Sh << /ShadingType 2
/ColorSpace /DeviceCMYK
/Domain [\pgf@pdfparseddomain]
/Coords [\pgf@doma\space0 \pgf@domb\space0]
/Function \pgf@pdfparsedfunction
/Extend [false false] >> >>}\pgfutil@tempboxa% <<
\expandafter\xdef\csname @pgfshading#1!\endcsname{\leavevmode\noexpand\pdfrefxform\the\pdflastxform}%
}%
}
\def\pgfsys@vertcmykshading#1#2#3{%
{%
\pgf@parsefunccmyk{#3}%
\pgfmathparse{#2}%
\setbox\pgfutil@tempboxa=\hbox to\pgfmathresult pt{\vbox to\pgf@max{\vfil\pgfsys@invoke{/Sh sh}}\hfil}%
\pgf@process{\pgfpoint{#2}{\pgf@max}}%
\immediate\pdfxform resources {%
/Shading << /Sh << /ShadingType 2
/ColorSpace /DeviceCMYK
/Domain [\pgf@pdfparseddomain]
/Coords [0 \pgf@doma\space0 \pgf@domb]
/Function \pgf@pdfparsedfunction
/Extend [false false] >> >>}\pgfutil@tempboxa% <<
\expandafter\xdef\csname @pgfshading#1!\endcsname{\leavevmode\noexpand\pdfrefxform\the\pdflastxform}%
}%
}
\def\pgfsys@radialcmykshading#1#2#3{%
{%
\pgf@parsefunccmyk{#3}%
\setbox\pgfutil@tempboxa=\hbox to2\pgf@max{\vbox to2\pgf@max{\vfil\pgfsys@invoke{/Sh sh}}\hfil}%
\pgf@process{#2}%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\pgf@process{\pgfpoint{\pgf@max}{\pgf@max}}%
\advance\pgf@xa by \pgf@x%
\advance\pgf@ya by \pgf@y%
\pgf@sys@bp@correct{\pgf@x}%
\pgf@sys@bp@correct{\pgf@y}%
\pgf@sys@bp@correct{\pgf@xa}%
\pgf@sys@bp@correct{\pgf@ya}%
\immediate\pdfxform resources {%
/Shading << /Sh << /ShadingType 3
/ColorSpace /DeviceCMYK
/Domain [\pgf@pdfparseddomain]
/Coords [\pgf@sys@tonumber{\pgf@xa} \pgf@sys@tonumber{\pgf@ya} \pgf@doma\space \pgf@sys@tonumber{\pgf@x} \pgf@sys@tonumber{\pgf@y} \pgf@domb]
/Function \pgf@pdfparsedfunction
/Extend [true false] >> >>}\pgfutil@tempboxa% <<
\expandafter\xdef\csname @pgfshading#1!\endcsname{\leavevmode\noexpand\pdfrefxform\the\pdflastxform}%
}%
}
\endinput
pgf
is the right way to go about things. To answer this, the reviewer would need to be reasonably familiar with PGF. \$\endgroup\$