%#!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}}
\providecommand\meta[1]{\ensuremath{\langle}\textit{#1}\ensuremath{\rangle}}
\providecommand\marg[1]{\texttt{\{}\meta{#1}\texttt{\}}}
\title{\textsf{spotxcolor} Technical Note}
\author{Munehiro Yamamoto}
\date{2026/10/09 v1.8}

\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 Core Principle}

The design philosophy of \pkg{spotxcolor} can be stated simply:
\pkg{xcolor} handles color arithmetic,
and PDF-specific Separation color spaces are written directly as PDF literals.
No custom color models, no \pkg{xcolor} internal hacking.

Other spot color packages (\pkg{spotcolor}, \pkg{xespotcolor}, \pkg{colorspace})
attempt to work around \pkg{xcolor}'s lack of Separation awareness
by declaring custom color models deep inside \pkg{xcolor}'s internals.
But \pkg{xcolor}'s internal API is not stable across versions,
making this approach inherently fragile.

\subsubsection{Why \cs{color@} is fragile}

To understand this fragility, one must examine the internal representation of colors in \pkg{xcolor}.
When |\definecolor{Plum}{rgb}{.5,0,1}| is called, \pkg{xcolor} creates a macro |\\color@Plum| containing the \emph{internal representation}:
\begin{quote}
|\xcolor@|\marg{type-spec}\marg{driver-spec}\marg{n-model}\marg{n-color-spec}
\end{quote}
where \meta{type-spec} is empty or |named|, \meta{driver-spec} is a driver-dependent string, \meta{n-model} is the color model name, and \meta{n-color-spec} is the normalised color values.
The crucial point is the definition of |\xcolor@| itself:
\begin{quote}
\begin{verbatim}
\def\xcolor@#1#2#3#4{#2}   % returns only driver-spec
\end{verbatim}
\end{quote}
When |\\color@Plum| is expanded, \emph{only the driver-spec is returned}; the model and color values are discarded.
This is a deliberate design for backward compatibility with the \pkg{color} package, where |\\color@Plum| expanded directly to a driver string.

The driver-spec format differs completely between drivers:
\begin{center}
\small
\renewcommand{\arraystretch}{1.3}
\begin{tabular}{ll}
\hline
Driver & \texttt{\textbackslash\textbackslash color@Plum} expands to \\
\hline
dvips   & |rgb 0.5 0 1| \\
pdftex  & |0.5 0 1 rg 0.5 0 1 RG| \\
\hline
\end{tabular}
\end{center}

This creates three structural barriers for custom color models:
\begin{enumerate}
\item Driver-spec opacity:
  Even if a custom model stores a Separation identifier in \meta{driver-spec},
  the downstream |\set@color| mechanism simply pushes this string verbatim.
  The \pkg{pdfcol}/driver layer (e.g., \texttt{dvipdfmx.def}) interprets it
  as numeric CMYK/RGB/Gray values.
  There is no protocol for passing ``this is a Separation color'' through this channel.

\item Type dispatch fragility:
  \pkg{xcolor}'s internal |\XC@type| inspects whether the first token of |\\color@|\meta{name}
  is |\xcolor@| to classify colors into types (0--4: undefined, unidentified, named, declared, undeclared).
  Any modification to the internal structure risks breaking this token-level dispatch.

\item Historical instability:
  Prior to \pkg{xcolor} v1.10, the internal representation used a different command
  (|\xcolor@|\meta{name} with a different syntax).
  The format has changed before, and the current structure is not a public API contract.
\end{enumerate}

\noindent
This is why approaches that inject custom color models into \pkg{xcolor}'s internals
are fragile across versions and driver combinations.

\pkg{spotxcolor} takes the opposite approach:
if \pkg{xcolor} converts spot colors to CMYK,
then detect the proportional tint from the CMYK values
and emit the PDF Separation operators directly as PDF literals.
This keeps the spot color logic entirely outside of \pkg{xcolor}'s color model system.

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

Based on this principle, \pkg{spotxcolor} takes a non-invasive approach:

\begin{enumerate}
\item 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 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 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)}

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 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 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})}

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 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 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)}

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}

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 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 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}

In commercial printing practice, non-proportional spot color mixes
have no meaningful plate separation:
a mix like |DIC161s!80!black| does not correspond to
a single spot ink at a recoverable tint percentage,
so CMYK process printing is the correct output for such colors.
The fallback to CMYK is therefore not a practical limitation.

Implementing DeviceN support would be 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.

\subsection{Why Not \texttt{pdf:bcolor}/\texttt{pdf:ecolor}?}

The \texttt{dvipdfmx} driver provides a ``balanced color'' mechanism:
|\special{pdf:bcolor [c m y k]}| pushes a color onto the driver's internal stack,
and |\special{pdf:ecolor}| pops it.
This is the standard path used by the \pkg{pdfcol}/\texttt{dvipdfmx.def} driver layer.

However, this mechanism only accepts numeric CMYK/RGB/Gray values.
The \pkg{xcolor} $\to$ \pkg{pdfcol} $\to$ \texttt{dvipdfmx} pipeline
converts everything to numeric color values before reaching the driver layer,
so there is no way to carry ``this is a spot color'' information through the bcolor path.

This is why \pkg{spotxcolor} uses
|\special{color push cmyk ...}| followed by |\special{pdf:code ...}|
(the dvips-compatible color stack path),
rather than |\special{pdf:bcolor}|/|\special{pdf:ecolor}|.
The |pdf:code| special allows writing arbitrary PDF operators,
including Separation color space operators,
which the bcolor interface cannot express.


\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 No dvipdfmx/XeTeX support: The package directly uses |\pdfobj| and |\pdfpageresources|, which are pdfTeX/LuaTeX primitives.
There is no |\special|-based fallback.

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

\item 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 DeviceN support: Uniquely among these packages, \textsf{colorspace} supports |/DeviceN| color spaces for multi-ink combinations.
\end{itemize}

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

As described in Section~1.2 (``The Core Principle''), \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.
Four interception layers (|\set@color|, |\set@page@color|, PGF driver macros, and |\pgfsys@setpatternuncolored|) cover all output paths; see Section~2 for details.

This approach is 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
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 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 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 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 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}
