%#!./run.bash test.tex
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage {spotxcolor} {2026-03-14} {1.0} {Modern Spot Color Support for xcolor}

%%
%% Copyright (c) 2026 Munehiro Yamamoto <munepixyz@gmail.com>
%%
%% 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 {<</C0[0~0~0~0]/FunctionType~2/C1[#3]/Domain[0~1]/N~1>>}
            \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 {<</C0[0~0~0~0]/FunctionType~2/C1[#3]/Domain[0~1]/N~1>>}
            \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~<</C0[0~0~0~0]/FunctionType~2/C1[#3]/Domain[0~1]/N~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
