5
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ @200_success, perhaps this site is not suitable for my question then. I can post the code, but my question is mainly about whether my method of integrating my code with the very large code base of pgf is the right way to go about things. To answer this, the reviewer would need to be reasonably familiar with PGF. \$\endgroup\$ – David Purton Oct 8 '18 at 14:04
  • 1
    \$\begingroup\$ You don't even have that much code in your repository. You could totally paste all of your code here. \$\endgroup\$ – 200_success Oct 8 '18 at 14:08
  • \$\begingroup\$ We got a character limit on questions of over 60k. Your code fits just fine, thank you for including it. \$\endgroup\$ – Mast Oct 8 '18 at 14:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.