%#!./run.bash test.tex \NeedsTeXFormat{LaTeX2e} \ProvidesExplPackage {spotxcolor} {2026-03-14} {1.0} {Modern Spot Color Support for xcolor} %% %% Copyright (c) 2026 Munehiro Yamamoto %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3c %% of this license or any later version. %% The latest version of this license is in %% http://www.latex-project.org/lppl.txt %% and version 1.3c or later is part of all distributions of LaTeX %% version 2005/12/01 or later. %% \RequirePackage{xcolor} \RequirePackage{iftex} % --- Variable Declarations --- \prop_new:N \g_spotxcolor_db_prop \tl_new:N \l_spotxcolor_pdfname_tl \tl_const:Nx \c_spotxcolor_hash_str { \string # } \tl_new:N \g_spotxcolor_resource_tl % --- Database Registration --- \cs_new_protected:Npn \spotxcolor_register_db:nnn #1#2#3 { \prop_gput:Nnn \g_spotxcolor_db_prop {#1} { {#2} {#3} } } % --- Safe Resource Management Helper --- \cs_new_protected:Npn \spotxcolor_add_colorspace_resource:nn #1#2 { % Avoid collision with pgfcolorspaces. % Instead of putting it directly into @resources << /ColorSpace ... >>, % we use a generic pdf:put command that dvipdfmx handles more gracefully % for merging dictionaries. % Check if TikZ/PGF is loaded and use its official hook to avoid dictionary overwrites \cs_if_exist:NTF \pgfutil@addpdfresource@colorspaces { % If TikZ/PGF is loaded, inject into its dictionary \pgfutil@addpdfresource@colorspaces { /#1~#2 } } { % Fallback if PGF is NOT loaded \legacy_if:nTF { pdf } { \tl_gput_right:Nx \g_spotxcolor_resource_tl { /#1 \space #2 \space } } { % use the standard approach \special { pdf:put~@resources~<<~/ColorSpace~<<~/#1~#2~>>~>> } } } } % --- PDF Object Generation --- \cs_new_protected:Npn \spotxcolor_create_pdf_obj:nnn #1#2#3 { % Replace spaces in PDF name (#2) with #20 \tl_set:Nn \l_spotxcolor_pdfname_tl { #2 } \tl_replace_all:Nnx \l_spotxcolor_pdfname_tl { ~ } { \c_spotxcolor_hash_str 20 } \legacy_if:nTF { pdf } { \sys_if_engine_pdftex:T { \immediate \pdfobj {<>} \tl_set:Nx \l_tmpa_tl { \the\pdflastobj } \pdfrefobj \l_tmpa_tl \immediate \pdfobj { [/Separation~/\l_spotxcolor_pdfname_tl~/DeviceCMYK~\l_tmpa_tl\space 0~R] } \tl_set:Nx \l_tmpb_tl { \the\pdflastobj } \pdfrefobj \l_tmpb_tl \spotxcolor_add_colorspace_resource:nn {#1} { \l_tmpb_tl \space 0~R } } \sys_if_engine_luatex:T { \immediate \pdfextension obj {<>} \tl_set:Nx \l_tmpa_tl { \pdffeedback lastobj } \pdfextension refobj \l_tmpa_tl \immediate \pdfextension obj { [/Separation~/\l_spotxcolor_pdfname_tl~/DeviceCMYK~\l_tmpa_tl\space 0~R] } \tl_set:Nx \l_tmpb_tl { \pdffeedback lastobj } \pdfextension refobj \l_tmpb_tl \spotxcolor_add_colorspace_resource:nn {#1} { \l_tmpb_tl \space 0~R } } } { % For dvipdfmx, XeLaTeX \special { pdf:obj~@spot_func_#1~<> } \special { pdf:obj~@spot_space_#1~[/Separation~/\l_spotxcolor_pdfname_tl~/DeviceCMYK~@spot_func_#1] } \spotxcolor_add_colorspace_resource:nn {#1} { @spot_space_#1 } } } \cs_generate_variant:Nn \spotxcolor_create_pdf_obj:nnn { nnV } % --- Native Registration for xcolor --- \cs_new_protected:Npn \spotxcolor_bind_xcolor:nnn #1#2#3 { \definecolor {#1} {cmyk} {#3} \legacy_if:nTF { pdf } { % For pdfTeX / LuaTeX, pass the "raw spot color operator" as the 2nd argument to \xcolor@ to fully support \textcolor. \cs_set:cpn { color@#1 } { \xcolor@ {}{/#1~cs~/#1~CS~1~sc~1~SC}{cmyk}{#3} } } { % DO NOTHING MORE for dvipdfmx! % Breaking xcolor's parser by appending \special causes fallback to black. % In dvipdfmx, \textcolor will safely fallback to CMYK representation. } } % --- Core Definition Command --- \cs_new_protected:Npn \spotxcolor_define_spotcolor:nnn #1#2#3 { \spotxcolor_register_db:nnn {#1} {#2} {#3} \tl_set:Nn \l_tmpa_tl { #3 } \tl_replace_all:Nnn \l_tmpa_tl { , } { ~ } \spotxcolor_create_pdf_obj:nnV {#1} {#2} \l_tmpa_tl \spotxcolor_bind_xcolor:nnn {#1} {#2} {#3} } \cs_generate_variant:Nn \spotxcolor_define_spotcolor:nnn { nnV } % --- User Interface --- \NewDocumentCommand{\definespotcolor}{ m m m } { \spotxcolor_define_spotcolor:nnn {#1} {#2} {#3} } % --- Manual Command to Ensure True Spot Color Output in dvipdfmx --- \NewDocumentCommand{\SpotColor}{ m m } { \legacy_if:nTF { pdf } { \sys_if_engine_pdftex:T { \pdfliteral { /#1~cs~/#1~CS~#2~sc~#2~SC } } \sys_if_engine_luatex:T { \pdfextension literal { /#1~cs~/#1~CS~#2~sc~#2~SC } } } { \special { pdf:code~/#1~cs~/#1~CS~#2~sc~#2~SC } } } % --- Safe Bulk Expansion and Registration of Page Resources --- \AtBeginDocument { \legacy_if:nTF { pdf } { \tl_if_empty:NF \g_spotxcolor_resource_tl { \sys_if_engine_pdftex:T { \edef \spotxcolor_temp_tl { \the\pdfpageresources \space /ColorSpace << \g_spotxcolor_resource_tl >> } \pdfpageresources = \exp_after:wN { \spotxcolor_temp_tl } } \sys_if_engine_luatex:T { \edef \spotxcolor_temp_tl { \the\pdfvariable~pageresources \space /ColorSpace << \g_spotxcolor_resource_tl >> } \pdfvariable~pageresources = \exp_after:wN { \spotxcolor_temp_tl } } } } {} } % ======================================================= % --- Backward Compatibility Wrappers for spotcolor package --- % ======================================================= \NewDocumentCommand{\NewSpotColorSpace}{ m } { } \NewDocumentCommand{\SetPageColorSpace}{ m } { } \tl_const:Nx \SpotSpace { \c_spotxcolor_hash_str 20 } \NewDocumentCommand{\AddSpotColor}{ m m m m } { \tl_set:Nn \l_tmpa_tl { #4 } \tl_replace_all:Nnn \l_tmpa_tl { ~ } { , } \spotxcolor_define_spotcolor:nnV {#2} {#3} \l_tmpa_tl } % ======================================================= % --- Backward Compatibility Wrappers for colorspace package --- % ======================================================= \NewDocumentCommand{\pagecolorspace}{ m } { } \NewDocumentCommand{\resetpagecolorspace}{ } { } \ExplSyntaxOff % ======================================================= % --- PGF Pattern CMYK Patch (v0.17) --- % Force PGF/TikZ uncolored patterns to use CMYK instead of hardcoded RGB. % This ensures safe CMYK fallback for spot colors in pattern fills. % Both legacy `patterns` and modern `patterns.meta` are supported. % ======================================================= \makeatletter \AtBeginDocument{ \@ifpackageloaded{pgfcore}{% % 1. Register CMYK Pattern Colorspace \pgfutil@addpdfresource@colorspaces{ /pgfpcmyk [/Pattern /DeviceCMYK] }% % 2. Override driver command to expect 5 parameters (PatternName, C, M, Y, K) \def\pgfsys@setpatternuncolored#1#2#3#4#5{% \pgfsysprotocol@literal{/pgfpcmyk cs #2 #3 #4 #5 /pgfpat#1\space scn}% }% % 3. Patch for legacy `patterns` library (pgfcorepatterns) \def\pgf@set@fillpattern#1#2{% \pgfutil@ifundefined{pgf@pattern@name@#1}{% \pgferror{Undefined pattern `#1'}% }{% \csname pgf@pattern@instantiate@#1\endcsname \expandafter\global\expandafter\let\csname pgf@pattern@instantiate@#1\endcsname=\relax \pgf@ifpatternisinherentlycolored{#1}{% \pgfsys@setpatterncolored{\csname pgf@pattern@name@#1\endcsname}% }{% \pgfutil@colorlet{pgf@tempcolor}{#2}% \pgfutil@ifundefined{applycolormixins}{}{\applycolormixins{pgf@tempcolor}}% \pgfutil@extractcolorspec{pgf@tempcolor}{\pgf@tempcolor}% \expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}% <-- convert to CMYK \expandafter\pgf@set@fill@patternuncolored\pgf@cmykcolor\relax{#1}% }% }% }% \def\pgf@set@fill@patternuncolored#1,#2,#3,#4\relax#5{% \pgfsys@setpatternuncolored{\csname pgf@pattern@name@#5\endcsname}{#1}{#2}{#3}{#4}% }% % 4. Patch for modern `patterns.meta` library (pgflibrarypatterns.meta) \def\pgf@pat@setpatternuncolored#1#2{% \pgfutil@colorlet{pgf@tempcolor}{#2}% \pgfutil@ifundefined{applycolormixins}{}{\applycolormixins{pgf@tempcolor}}% \pgfutil@extractcolorspec{pgf@tempcolor}{\pgf@tempcolor}% \expandafter\pgfutil@convertcolorspec\pgf@tempcolor{cmyk}{\pgf@cmykcolor}% <-- Force CMYK \expandafter\pgf@pat@set@fill@patternuncolored\pgf@cmykcolor\relax{#1}% }% \def\pgf@pat@set@fill@patternuncolored#1,#2,#3,#4\relax#5{% <-- Expect 4 color components \pgfsys@setpatternuncolored{#5}{#1}{#2}{#3}{#4}% }% }\relax } \makeatother \endinput