%#!lualatex -synctex=1 spotxcolor-technote
\documentclass[luatex]{article}
\usepackage{shortvrb}\AtBeginDocument{\MakeShortVerb{\|}}
\usepackage{amsmath}
\usepackage{hyperref}
\usepackage{xcolor}
\usepackage{tikz}
\usepackage{tcolorbox}
\tcbuselibrary{skins}

\usepackage{spotxcolor}
% Define Triadic Spot Colors for Examples
\definespotcolor{DIC161s}{DIC 161s*}{0, 0.64, 1, 0}   % Vermilion
\definespotcolor{DIC256s}{DIC 256s*}{0.9, 0, 0.4, 0}  % Green
\definespotcolor{DIC200s}{DIC 200s*}{0.5, 0.8, 0, 0}  % Purple

\newcommand\pkg[1]{\textsf{#1}}
\newcommand\cs[1]{\texttt{\textbackslash #1}}
\newcommand\op[1]{\texttt{#1}}

\title{\textsf{spotxcolor} Technical Note}
\author{Munehiro Yamamoto}
\date{2026/03/29 v1.7}

\begin{document}
\maketitle

\begin{abstract}
This document describes the design rationale and internal architecture of the \pkg{spotxcolor} package.
It is intended for developers, contributors, and advanced users who wish to understand how spot color output is achieved across all \TeX\ engines and graphics layers, and why certain limitations exist.
For usage instructions, see the companion document \texttt{spotxcolor.tex}.
\end{abstract}

\tableofcontents

% =====================================================================
\section{Design Philosophy}
% =====================================================================

\subsection{The Problem}

PDF spot colors (Separation color spaces) require a fundamentally different PDF structure from process colors (DeviceCMYK/DeviceRGB).
While \pkg{xcolor} provides an excellent color mixing and tinting engine, it has no awareness of PDF Separation color spaces.
Every color---including spot colors registered via |\definecolor{...}{cmyk}{...}|---is ultimately emitted as CMYK (or RGB) operators in the PDF content stream.

The legacy \pkg{spotcolor} package and the more modern \pkg{colorspace} package each attempted to solve this, but with significant limitations:
\pkg{spotcolor} suffered from structural PDF issues with modern \pkg{expl3}/\pkg{xcolor} updates,
and \pkg{colorspace} lacked full support for \texttt{dvipdfmx}-based workflows ((u)p\LaTeX).

\subsection{The Strategy: Intercept, Detect, Replace}

Rather than modifying \pkg{xcolor}'s internal color model (which would break its mixing engine),
\pkg{spotxcolor} takes a non-invasive approach:

\begin{enumerate}
\item \textbf{Register normally.}
  Each spot color is registered in \pkg{xcolor}'s database as standard CMYK via |\definecolor|.
  This preserves full compatibility with \pkg{xcolor}'s tinting (|DIC161s!50|), mixing, and color expression engine.

\item \textbf{Create PDF objects.}
  At definition time, \pkg{spotxcolor} creates the PDF Separation color space objects (Tint Transform Function + Separation array) and registers them in the page's |/ColorSpace| resource dictionary.

\item \textbf{Intercept at output time.}
  At the point where CMYK values are about to be written into the PDF content stream, \pkg{spotxcolor} checks whether those values are a proportional scalar multiple of any registered spot color's base CMYK.
  If so, the CMYK operators are replaced with Separation color space operators.
\end{enumerate}

This ``late interception'' strategy means \pkg{spotxcolor} is transparent to \pkg{xcolor}, \pkg{PGF}/\pkg{TikZ}, \pkg{tcolorbox}, and \pkg{colortbl}---they all continue to work with standard CMYK values, unaware that the final PDF output uses spot colors.

\subsection{Proportional Tint Detection}

The core matching algorithm is simple:
given the current CMYK values $(c, m, y, k)$ and a registered spot color with base CMYK $(b_c, b_m, b_y, b_k)$,
\pkg{spotxcolor} computes the tint factor $t$ from the dominant (largest) base component and verifies that all four components satisfy:
\[
  \lvert c_i - t \cdot b_i \rvert < \varepsilon \quad (i \in \{c, m, y, k\})
\]
where $\varepsilon = 0.005$.
If all four pass, the color is recognized as tint $t$ of that spot color.

This detects all expressions of the form |DIC161s!N| (where $N$ is a percentage).
Expressions like |DIC161s!80!black| produce non-proportional CMYK values (the K component is introduced by the black mix) and correctly fall back to CMYK.

% =====================================================================
\section{Interception Points}
% =====================================================================

\pkg{spotxcolor} patches multiple interception points to achieve comprehensive spot color coverage.
Each point addresses a different code path through which colors reach the PDF content stream.

\subsection{Layer 1: \cs{set@color} (xcolor/LaTeX color stack)}

\textbf{Covers:} |\color|, |\textcolor|, \pkg{colortbl} row colors, \pkg{tcolorbox} elements.

When \pkg{xcolor} processes a color command, it computes the final CMYK values, stores them in |\current@color|, and calls |\set@color| to push the color onto the PDF color stack.

\pkg{spotxcolor} patches |\set@color| at |\AtBeginDocument|:

\begin{itemize}
\item \textbf{pdfTeX/LuaTeX:}
  Before the original |\set@color| executes, |\current@color| is parsed.
  If a spot color match is found, |\current@color| is replaced with the spot color operator string (e.g., |/DIC161s cs /DIC161s CS 0.5 sc 0.5 SC|).
  The original |\set@color| then pushes this modified string onto the color stack via |\pdfliteral|.

\item \textbf{dvipdfmx/XeTeX:}
  |\current@color| cannot be replaced with raw PDF operators because |\special{color push cmyk ...}| expects model-prefixed format.
  Instead, the original |\set@color| runs first (CMYK color push), then |\special{pdf:code ...}| is emitted to override with spot color operators.
\end{itemize}

\medskip
\noindent\textbf{Technical pitfall---ExplSyntax catcodes:}
All references to |\set@color|, |\current@color|, and |\set@page@color| in \pkg{expl3} code must use |c|-type access (e.g., |\cs_set_eq:cc{...}{set@color}|) because |@| is catcode~12 (other) in \pkg{expl3} but catcode~11 (letter) in these \LaTeX\ kernel macros.
Attempting to write |\set@color| directly in \pkg{expl3} code causes \TeX\ to parse it as |\set| followed by |@color|.

\subsection{Layer 2: \cs{set@page@color} (\cs{pagecolor})}

\textbf{Covers:} |\pagecolor|, |\nopagecolor|.

\pkg{xcolor}'s |\pagecolor| calls |\set@page@color| \emph{without} going through |\set@color|, so the Layer~1 patch does not affect it.

\begin{itemize}
\item \textbf{pdfTeX/LuaTeX:}
  |\set@page@color| copies |\current@color| to |\current@page@color|.
  \pkg{spotxcolor} intercepts \emph{before} this copy, modifying |\current@color| so the fill rectangle uses spot operators.

\item \textbf{dvipdfmx/XeTeX:}
  |\special{background cmyk ...}| only supports standard color models.
  \pkg{spotxcolor} lets the original run (CMYK fallback), stores the spot color operators, and uses a |shipout/background| hook to overdraw with a spot-colored full-page rectangle.
  A reverse-CTM translation (|q 1 0 0 1 -TX -TY cm ... Q|) maps coordinates from the content stream's translated origin back to absolute PDF coordinates.
\end{itemize}

\subsection{Layer 3: PGF Driver Macros (TikZ fill/stroke)}

\textbf{Covers:} |\fill|, |\draw|, |\pgfsetfillcolor|, |\pgfsetstrokecolor|, all TikZ color operations.

PGF's internal color management resolves colors through its own code path and calls low-level driver macros defined in \texttt{pgfsys-common-pdf.def}:

\begin{quote}
\begin{verbatim}
\pgfsys@color@cmyk@fill#1#2#3#4
  → \pgfsysprotocol@literal{#1 #2 #3 #4 k}
\pgfsys@color@cmyk@stroke#1#2#3#4
  → \pgfsysprotocol@literal{#1 #2 #3 #4 K}
\end{verbatim}
\end{quote}

These macros bypass |\set@color| entirely.
\pkg{spotxcolor} patches all four CMYK driver macros (|cmyk@fill|, |cmyk@stroke|, |cmy@fill|, |cmy@stroke|) to check the four component arguments against registered spot colors.
If matched, fill uses |/NAME cs TINT sc| and stroke uses |/NAME CS TINT SC|.

Since \texttt{pgfsys-common-pdf.def} is shared by all engines, a single set of patches covers pdfTeX, LuaTeX, XeTeX, and dvipdfmx.

\subsection{Layer 4: PGF Uncolored Patterns}

\textbf{Covers:} |pattern color=DIC161s!60|, |pattern={Lines[...]}, pattern color=...|.

PGF uncolored patterns use a dedicated color space |[/Pattern /DeviceCMYK]| (registered as |/pgfpcmyk|) with the operator format:
\begin{quote}
  |/pgfpcmyk cs C M Y K /pgfpatN scn|
\end{quote}

For spot colors, a different color space is needed: |[/Pattern [/Separation ...]]|.
\pkg{spotxcolor} creates these objects (one per registered spot color) at |\AtBeginDocument| and registers them as |/pgfpspot_NAME| in page resources.

Then |\pgfsys@setpatternuncolored| is wrapped: when the CMYK arguments match a spot color, the output becomes:
\begin{quote}
  |/pgfpspot_DIC161s cs 0.6 /pgfpat21 scn|
\end{quote}

\medskip
\noindent\textbf{Technical pitfall---catcode boundary crossing:}
The pattern patch must be installed inside a |\makeatletter| / |\AtBeginDocument| block (to run after PGF defines |\pgfsys@setpatternuncolored|), but the patch code uses \pkg{expl3} functions.
Writing |\ExplSyntaxOn| inside |\AtBeginDocument{...}| has \emph{no effect} because the argument is already tokenized at file-read time with |_| as catcode~8 (subscript).

The solution: define a wrapper function |\__spotxcolor_install_pattern_spot_patch:| in the \pkg{expl3} region (correct catcodes), then call it from the |\makeatletter| block via |\csname __spotxcolor_install_pattern_spot_patch:\endcsname|.
|\csname| constructs control sequence names from character codes, ignoring catcodes, and thus correctly bridges the catcode boundary.

% =====================================================================
\section{What Cannot Be Intercepted}
% =====================================================================

\subsection{Hatching vs.\ Shading: A Structural Difference}

Both hatching (patterns) and shading (gradients) are PDF features used by PGF/TikZ, but they differ fundamentally in \emph{where the color information lives} in the PDF structure.

\subsubsection{Hatching (Uncolored Patterns)}

Color is specified by \textbf{operators in the content stream}:
\begin{quote}
\begin{verbatim}
/pgfpcmyk cs 0 0.64 1 0 /pgfpat3 scn   % CMYK hatching
\end{verbatim}
\end{quote}

The pattern tile itself (defined in a separate Pattern object) is color-independent---it describes only geometry (line angles, spacing, dot radii).
The color is applied at the point of use via the |cs|/|scn| operators.

\pkg{spotxcolor} intercepts at this point: it wraps |\pgfsys@setpatternuncolored| and replaces the operator with a spot color pattern color space:
\begin{quote}
\begin{verbatim}
/pgfpspot_DIC161s cs 0.6 /pgfpat21 scn  % spot color hatching
\end{verbatim}
\end{quote}

This required creating |[/Pattern [/Separation ...]]| color space objects but \emph{no changes} to PGF's pattern generation code.

\subsubsection{Shading (Gradients)}

Color is \textbf{frozen into a standalone PDF object} (Shading dictionary) at definition time:
\begin{quote}
\begin{verbatim}
<< /ShadingType 2
   /ColorSpace /DeviceRGB          % hardcoded by PGF
   /Function << /C0 [r g b]        % endpoint colors as RGB
                /C1 [r g b] ... >>
>>
\end{verbatim}
\end{quote}

This Shading dictionary is wrapped in a Form XObject (|/Fm16|, |/Fm17|, etc.) and referenced from the content stream with a single operator:
\begin{quote}
\begin{verbatim}
/Fm16 Do    % "paint this shading" — no color info here
\end{verbatim}
\end{quote}

The content stream contains \emph{no color operators} to intercept.
The color has already been resolved to RGB and embedded in the object.

True spot color gradients would require:
\begin{enumerate}
\item Generating Shading objects with |/ColorSpace [/Separation /DIC#20161s* /DeviceCMYK funcRef]|
\item Using tint values (|/C0 [1]|, |/C1 [0.1]|) instead of RGB triples
\item Rewriting PGF's shading generation code in \texttt{pgfcoreshading.code.tex}
\end{enumerate}

This is architecturally infeasible at the driver-patch level and constitutes a PGF core modification.

\subsection{Non-Proportional Color Mixes and DeviceN}

Expressions like |DIC161s!80!black| produce CMYK values $(0, 0.512, 0.8, 0.2)$.
The $K=0.2$ component breaks proportionality with the base $(0, 0.64, 1, 0)$, so no single-spot tint can represent this color.

The PDF specification provides |/DeviceN| color spaces for multi-ink combinations:
\begin{quote}
\begin{verbatim}
[/DeviceN [/DIC#20161s* /Black] /DeviceCMYK ...]
\end{verbatim}
\end{quote}

Implementing DeviceN support would require:
\begin{itemize}
\item Parsing non-proportional CMYK values to decompose them into spot + process components
\item Creating DeviceN color space objects with multi-dimensional tint transform functions
\item Extending the matching engine to handle multi-spot combinations
\end{itemize}

This is a substantial extension and is considered out of scope for \pkg{spotxcolor}.

% =====================================================================
\section{Operator String Construction}
% =====================================================================

A recurring technical challenge in \pkg{spotxcolor} is constructing PDF operator strings with correct spacing in the \pkg{expl3} environment.

\subsection{The Space Swallowing Problem}

In \pkg{expl3}, |~| (tilde) has catcode~10 (space).
However, after \TeX\ reads a control sequence name, it enters ``state~S'' (skipping spaces).
In this state, a catcode-10 token is silently discarded:

\begin{quote}
\begin{verbatim}
% BROKEN: space after \g__spotxcolor_matched_name_tl is swallowed
\tl_set:Nx \l_tmpa_tl
  { / \g__spotxcolor_matched_name_tl ~ cs ~ ... }
% Produces: /DIC161scs (no space!)
\end{verbatim}
\end{quote}

\subsection{The Solution: n-type Alternation}

\pkg{spotxcolor} builds operator strings using |\tl_put_right:Nn| (n-type, literal tokens) alternating with |\tl_put_right:NV| (V-type, variable expansion):

\begin{quote}
\begin{verbatim}
\tl_clear:N \l_tmpb_tl
\tl_put_right:Nn \l_tmpb_tl { / }
\tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_name_tl
\tl_put_right:Nn \l_tmpb_tl { ~cs~/ }   % ~ is in braces → state M
\tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_name_tl
\tl_put_right:Nn \l_tmpb_tl { ~CS~ }
...
\end{verbatim}
\end{quote}

The |~| inside braces is read in state~M (middle of line), where catcode-10 tokens are preserved.
This guarantees correct spacing: |/DIC161s cs /DIC161s CS 1 sc 1 SC|.

% =====================================================================
\section{Engine-Specific Considerations}
% =====================================================================

\subsection{pdfTeX and LuaTeX}

These engines support |\pdfliteral| (or |\pdfextension literal|) which writes raw strings directly into the content stream.
|\current@color| is stored as a raw operator string (e.g., |0 0.64 1 0 k 0 0.64 1 0 K|).
\pkg{spotxcolor} can modify |\current@color| in-place before it is pushed, making interception clean and complete.

\subsection{dvipdfmx and XeTeX}

These engines use |\special{color push cmyk c m y k}| for color stack management.
The |cmyk| model prefix is required; raw PDF operators cannot be passed through this channel.

\pkg{spotxcolor}'s approach for these engines:
\begin{enumerate}
\item Let the original |\set@color| execute (CMYK color push via |\special{color push ...}|).
\item Immediately emit |\special{pdf:code /NAME cs /NAME CS TINT sc TINT SC}| to override.
\end{enumerate}

This produces content streams where CMYK operators appear followed by spot color operators.
The spot color operators override the CMYK state, so the visual result is correct.

For |\pagecolor|, |\special{background cmyk ...}| generates a separate background content stream that cannot be overridden.
\pkg{spotxcolor} uses the |shipout/background| hook to emit a spot-colored full-page rectangle in the main content stream, which paints over the CMYK background.


\section{Comparison of Implementation Approaches}

This section compares the internal architecture of the four spot color packages.
Understanding these differences explains why each package has its particular set of capabilities and limitations.

\subsection{\textsf{spotcolor} --- Direct PDF Literal Injection}

The \textsf{spotcolor} package (2006) takes the simplest possible approach.
Designed exclusively for pdfTeX, its |\SpotColor| command directly emits PDF literal operators via |\pdfliteral|:

\begin{quote}
\begin{verbatim}
\pdfliteral{/NAME cs /NAME CS TINT sc TINT SC}
\end{verbatim}
\end{quote}

This bypasses the \LaTeX\ color stack entirely, so the spot color state is lost at page breaks.
The package does define a custom \pkg{xcolor} color model (|spotcolor|) via |\xcolor@{}{}{spotcolor}{...}|, but \pkg{xcolor}'s driver code does not know how to render this model, so standard commands like |\textcolor| do not produce spot color output.

There is no PGF integration, no pattern support, and no interaction with |\set@color|.

\subsection{\textsf{xespotcolor} --- XeTeX/dvipdfmx Focused}

The \textsf{xespotcolor} package (2014--2021) targets Xe\LaTeX\ and \texttt{dvipdfmx}.
Its core command |\SpotColor| uses |\special{pdf:literal ...}| followed by |\aftergroup\reset@color| to partially manage the color state.

Its most notable contribution is defining custom PGF driver macros:

\begin{quote}
\begin{verbatim}
\gdef\pgfsys@color@spotcolor@stroke#1#2{
  \special{pdf:literal /#1 CS #2 SC}}
\gdef\pgfsys@color@spotcolor@fill#1#2{
  \special{pdf:literal /#1 cs #2 sc}}
\end{verbatim}
\end{quote}

However, these macros are only called when the user explicitly invokes spot color commands.
Standard \pkg{xcolor} operations (|\color|, |\textcolor|) go through the normal CMYK driver macros and produce process color output.
The package also disables PGF's color space management (|\pgf@sys@pdf@colorspaces@existsfalse|) when TikZ or \pkg{tcolorbox} is loaded, which is a workaround for resource dictionary conflicts.

\subsection{\textsf{colorspace} --- xcolor Internal Hook}

The \textsf{colorspace} package (2015--2019) takes a more sophisticated approach.
It defines custom \pkg{xcolor} color models and hooks into \pkg{xcolor}'s internal color representation by storing a ``spot color reference'' (the PDF color space name) alongside the standard color data.

When |\set@color| processes a color, \textsf{colorspace} extracts this reference from |\current@color| and uses it to emit the correct Separation operators.
It also provides PGF driver macros (|\pgfsys@color@&spot@fill|, etc.) that retrieve the spot color reference from PGF's internal fill/stroke color variables.

Key limitations:

\begin{itemize}
\item \textbf{No dvipdfmx/XeTeX support:} The package directly uses |\pdfobj| and |\pdfpageresources|, which are pdfTeX/LuaTeX primitives.
There is no |\special|-based fallback.

\item \textbf{Manual page resource management:} Users must call |\pagecolorspace{...}| and |\resetpagecolorspace| to manage the |/ColorSpace| dictionary, which is fragile in modern \LaTeX.

\item \textbf{No pattern support:} While the package provides fill/stroke PGF driver macros, uncolored patterns still use the default |[/Pattern /DeviceCMYK]| color space with CMYK values.

\item \textbf{DeviceN support:} Uniquely among these packages, \textsf{colorspace} supports |/DeviceN| color spaces for multi-ink combinations.
\end{itemize}

\subsection{\textsf{spotxcolor} --- Late-Stage Interception}

\pkg{spotxcolor} (2026) takes a fundamentally different approach: rather than modifying \pkg{xcolor}'s internal color model, it lets \pkg{xcolor} operate entirely in CMYK and intercepts the output at multiple stages.

The key insight is that for proportional tints (|DIC161s!N|), the resulting CMYK values are always a scalar multiple of the base spot color's CMYK values.
By checking this proportionality at output time, \pkg{spotxcolor} can transparently convert CMYK to Separation operators without any cooperation from \pkg{xcolor}'s mixing engine.

Four interception layers cover all output paths:

\begin{enumerate}
\item |\set@color| (xcolor color stack) --- covers |\color|, |\textcolor|, \pkg{colortbl}, \pkg{tcolorbox}
\item |\set@page@color| --- covers |\pagecolor|
\item PGF driver macros (|\pgfsys@color@cmyk@fill|, etc.) --- covers TikZ fill/stroke
\item |\pgfsys@setpatternuncolored| --- covers pattern fills via |[/Pattern [/Separation ...]]|
\end{enumerate}

This approach has the advantage of being completely transparent to all packages that use \pkg{xcolor} or PGF for color management, requiring no special commands or manual resource management.
The trade-off is that non-proportional mixes (|DIC161s!80!black|) cannot be detected and fall back to CMYK, whereas \textsf{colorspace}'s DeviceN support can theoretically handle such cases.

\subsection{Summary of Architectural Differences}

\begin{center}
\renewcommand{\arraystretch}{1.3}
\small
\begin{tabular}{lllll}
\hline
\textbf{Aspect}
  & \textsf{spotcolor}
  & \textsf{xespotcolor}
  & \textsf{colorspace}
  & \textsf{spotxcolor} \\
\hline
Color model
  & custom
  & custom
  & custom
  & CMYK (native) \\
Interception
  & none
  & explicit cmd
  & |\current@color|
  & |\set@color| + PGF \\
Color stack
  & no
  & partial
  & yes
  & yes \\
Resource mgmt
  & manual
  & manual
  & manual
  & automatic \\
Engine support
  & pdf only
  & xe/dvipdfmx
  & pdf/lua
  & all four \\
Code style
  & \LaTeXe
  & \LaTeXe
  & \LaTeXe
  & expl3 + \LaTeXe \\
\hline
\end{tabular}
\end{center}


% =====================================================================
\section{Compatibility with pdfmanagement (v1.6+)}
% =====================================================================

\subsection{The Resource Registration Conflict}

When pdfmanagement (|\DocumentMetadata{}|) is active, it takes exclusive control of all PDF page resources.
It redefines PGF's |\pgfutil@addpdfresource@colorspaces| with its own parser (|\__pdfmanagement_patch_pgfcolorspaces:w|), which scans for a |\q_stop| delimiter in a specific argument format.

\pkg{spotxcolor} passed arguments like |/DIC161s \l_tmpb_tl \space 0 R| where |\l_tmpb_tl| was an already-expanded object number.
This token list did not contain the delimiter expected by the pdfmanagement parser, causing a ``Runaway argument'' error:

\begin{quote}
\begin{verbatim}
Runaway argument?
DIC161 \l_tmpb_tl \space 0 R\q_stop ...
! File ended while scanning use of
  \__pdfmanagement_patch_pgfcolorspaces:w.
\end{verbatim}
\end{quote}

Similarly, direct |\pdfpageresources| access (the fallback path when PGF is not loaded) is explicitly forbidden when pdfmanagement is active.

\subsection{Centralized Resource Dispatch}

A boolean flag |\g__spotxcolor_pdfmanagement_bool| is detected at package load time via |\IfPDFManagementActiveTF|, and rewires the centralized resource helper |\spotxcolor_add_colorspace_resource:nn|:

\begin{quote}
\begin{verbatim}
\bool_if:NTF \g__spotxcolor_pdfmanagement_bool
  { \pdfmanagement_add:nne
      { Page/Resources/ColorSpace } {#1} {#2} }
  { % traditional path:
    %   \pgfutil@addpdfresource@colorspaces
    %   or \pdfpageresources / \special{pdf:put}
  }
\end{verbatim}
\end{quote}

Note the use of the |nne| variant (not |nnn|): the third argument (object reference, e.g., |42 0 R|) must be e-expanded at call time.
The |nnn| variant stores the unexpanded token list, and since |\spotxcolor_add_colorspace_resource:nn| is called multiple times (once per |\definespotcolor|), the last call's object reference overwrites earlier ones---causing all spot colors to share a single Separation CS.

This completely bypasses the pdfmanagement-patched |\pgfutil@addpdfresource@colorspaces|, eliminating the argument format mismatch.

The path format |Page/Resources/ColorSpace| (no spaces around slashes) is confirmed from \texttt{colorspace-patches-tmp-ltx.sty} in the \TeX\ Live distribution, which uses |\pdfmanagement_add:nee {Page/Resources/ColorSpace}| for the same purpose.

\subsection{Affected Code Paths}

Four code paths were updated to use centralized dispatch:

\begin{enumerate}
\item \textbf{Separation CS registration} (|\spotxcolor_create_pdf_obj:nnn|):
    Called at |\definespotcolor| time.
    Each spot color's |/NAME objRef| entry is registered via |\spotxcolor_add_colorspace_resource:nn|, which now branches internally.

\item \textbf{Bulk resource fallback} (|\AtBeginDocument|, |\g_spotxcolor_resource_tl|):
    When PGF is not loaded in traditional mode, accumulated resources are injected via |\pdfpageresources|.
    This block is now guarded by |\bool_if:NF \g__spotxcolor_pdfmanagement_bool|.
    (When pdfmanagement is active, resources were already registered individually via path~1.)

\item \textbf{Pattern CS creation} (|\__spotxcolor_create_pattern_cs:|):
    Creates |[/Pattern [/Separation ...]]| objects and registers them as |/pgfpspot_NAME|.
    Previously called |\pgfutil@addpdfresource@colorspaces| directly; now uses the centralized helper with |\exp_args:NnV| to safely pass expanded object references.

\item \textbf{CMYK Pattern CS} (|\AtBeginDocument|, |/pgfpcmyk|):
    The |/pgfpcmyk [/Pattern /DeviceCMYK]| registration previously used |\pgfutil@addpdfresource@colorspaces|; now uses |\csname spotxcolor_add_colorspace_resource:nn\endcsname| to bridge the catcode boundary from |\makeatletter| context.
\end{enumerate}

\subsection{Interaction with PGF's Own pdfmanagement Awareness}

Recent PGF versions (3.1.10+) include their own pdfmanagement patches, which also rewrite |\pgfutil@addpdfresource@colorspaces|.
Because \pkg{spotxcolor} completely bypasses |\pgfutil@addpdfresource@colorspaces| when pdfmanagement is active, there is no risk of double-patching or argument format conflicts between \pkg{spotxcolor} and PGF's pdfmanagement layer.

\subsection{Note on l3color}

\pkg{spotxcolor} registers spot colors via \pkg{xcolor}'s |\definecolor{...}{cmyk}{...}|.
The \textsf{expl3} color system (\textsf{l3color}) maintains a separate color database and does not automatically import \pkg{xcolor} definitions, so |\color_select:n{DIC161s}| will fail with ``Unknown color''.

This is not a limitation of \pkg{spotxcolor} but a consequence of the current \pkg{xcolor}--\textsf{l3color} boundary:
\pkg{xcolor} does not yet provide a bridge to register its colors in the \textsf{l3color} database.
Package developers building on \pkg{spotxcolor} should use the standard \pkg{xcolor} interface (|\color|, |\textcolor|, etc.)\ rather than \textsf{l3color} commands.


\end{document}
