\NeedsTeXFormat{LaTeX2e} \ProvidesExplPackage {spotxcolor-legacycolor} {2026-10-09} {1.8} {Shared legacy-compatibility layer for spotxcolor-spotcolor / spotxcolor-xespotcolor} %% %% 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. %% % This file implements the two pieces of machinery that both the % spotxcolor-spotcolor and spotxcolor-xespotcolor compatibility packages % need, but which core spotxcolor does not provide on its own: % % 1. Lazy ("on first use") registration of bulk color tables (HKS, % PANTONE) so that loading a 1089-entry PANTONE table does not create % a PDF object and a page-resource entry for every single color, % regardless of how many are actually used in the document. % % 2. A `spotcolor' xcolor color model, so that legacy code such as % \definecolor{Spots}{spotcolor}{SOMECOLOR,1.0} keeps working. Real % spot color output is obtained by delegating to xcolor's own tinting % (SOMECOLOR!100) once SOMECOLOR is a genuine spotxcolor-registered % color: spotxcolor's \set@color patch auto-detects the resulting % CMYK values and swaps in true Separation operators. \RequirePackage {xcolor} \RequirePackage {spotxcolor} % --- Shared scratch variables (declared up front; used throughout) --- \tl_new:N \l__spotxcolorlegacy_tmpa_tl \tl_new:N \l__spotxcolorlegacy_pdfname_tl \tl_new:N \l__spotxcolorlegacy_cmyk_tl % ===================================================================== % --- 0. Re-flushing the /ColorSpace page resource for body-time colors --- % ===================================================================== % Legacy documents idiomatically call \NewSpotColorSpace/\AddSpotColor % *inside* the document body (see e.g. the original spotcolor package's own % readme.tex example), not in the preamble. Core spotxcolor, when neither % PGF/TikZ nor pdfmanagement is active, only ever copies its accumulated % \g_spotxcolor_resource_tl into \pdfpageresources once, from an % \AtBeginDocument hook -- so a color registered after \begin{document} has % already missed that single flush and its /ColorSpace entry never reaches % the page resources (dvipdfmx/XeTeX are unaffected: their \special-based % resource injection is applied immediately, in stream order). % % The fix: capture the pristine \pdfpageresources value once, as early as % possible (right now, while this file loads, which is necessarily still in % the preamble), and provide an idempotent "recompute \pdfpageresources % from scratch" step that can safely be called again every time a color is % registered, however late. It is a no-op whenever PGF or pdfmanagement is % already handling resources on its own. \tl_new:N \g__spotxcolorlegacy_pdfres_baseline_tl \legacy_if:nT { pdf } { \sys_if_engine_pdftex:T { \tl_gset:Nx \g__spotxcolorlegacy_pdfres_baseline_tl { \the\pdfpageresources } } \sys_if_engine_luatex:T { \tl_gset:Nx \g__spotxcolorlegacy_pdfres_baseline_tl { \the\pdfvariable pageresources } } } \cs_new_protected:Npn \__spotxcolorlegacy_reflush_pdfpageresources: { \bool_if:NF \g__spotxcolor_pdfmanagement_bool { \cs_if_exist:NF \pgfutil@addpdfresource@colorspaces { \legacy_if:nT { pdf } { \tl_if_empty:NF \g_spotxcolor_resource_tl { \sys_if_engine_pdftex:T { \tl_set:Nx \l__spotxcolorlegacy_tmpa_tl { \g__spotxcolorlegacy_pdfres_baseline_tl \space /ColorSpace << \g_spotxcolor_resource_tl >> } \pdfpageresources = \exp_after:wN { \l__spotxcolorlegacy_tmpa_tl } } \sys_if_engine_luatex:T { \tl_set:Nx \l__spotxcolorlegacy_tmpa_tl { \g__spotxcolorlegacy_pdfres_baseline_tl \space /ColorSpace << \g_spotxcolor_resource_tl >> } \pdfvariable pageresources = \exp_after:wN { \l__spotxcolorlegacy_tmpa_tl } } } } } } } % ===================================================================== % --- 1. Lazy registration of bulk color tables --- % ===================================================================== % --- Pending metadata store --- % Maps legacy spot-color name -> PDF name, and -> "c,m,y,k" (comma-separated). % Populated cheaply while \input-ing a HKS/PANTONE table; consumed by % \__spotxcolorlegacy_ensure:n the first time a name is actually used. \prop_new:N \g__spotxcolorlegacy_pending_pdfname_prop \prop_new:N \g__spotxcolorlegacy_pending_cmyk_prop % --- "Cheap" \AddSpotColor: records metadata only --- % Mirrors the legacy signature {colorspace}{name}{pdfname}{cmyk} (the % colorspace argument is, as in spotxcolor's own \AddSpotColor wrapper, % a no-op kept only for source compatibility). \cs_new_protected:Npn \__spotxcolorlegacy_store_pending:nnnn #1#2#3#4 { \tl_set:Nn \l__spotxcolorlegacy_tmpa_tl {#4} \tl_replace_all:Nnn \l__spotxcolorlegacy_tmpa_tl { ~ } { , } \prop_gput:Nnn \g__spotxcolorlegacy_pending_pdfname_prop {#2} {#3} \prop_gput:NnV \g__spotxcolorlegacy_pending_cmyk_prop {#2} \l__spotxcolorlegacy_tmpa_tl } % --- \AddSpotColor, permanently upgraded with a resource re-flush --- % Same eager, full-registration body as core spotxcolor's own \AddSpotColor % (spotxcolor.sty), plus \__spotxcolorlegacy_reflush_pdfpageresources: so % that colors registered inside the document body (the idiomatic legacy % calling convention) still reach the page resources -- see part 0 above. % This *replaces* \AddSpotColor outright rather than saving-and-delegating % to the old one: xparse's "optimized" fast path makes a % NewDocumentCommand-defined macro look itself back up *by name* on every % call, so a save-then-call-old pattern here would recurse forever the same % way it did for \SpotColor (see the note below). \cs_generate_variant:Nn \spotxcolor_define_spotcolor:nnn { nnV } \RenewDocumentCommand \AddSpotColor { m m m m } { \tl_set:Nn \l__spotxcolorlegacy_tmpa_tl {#4} \tl_replace_all:Nnn \l__spotxcolorlegacy_tmpa_tl { ~ } { , } \spotxcolor_define_spotcolor:nnV {#2} {#3} \l__spotxcolorlegacy_tmpa_tl \__spotxcolorlegacy_reflush_pdfpageresources: } % --- Preload helper --- % Temporarily swaps the *public* \AddSpotColor for the cheap variant above, % \input's the given data file (a plain sequence of legacy \AddSpotColor % calls, e.g. spotxcolor-hks.def / spotxcolor-pantone.def), then restores % \AddSpotColor to the (permanent, reflush-enabled) version just defined, % for the rest of the document. This save/restore is safe -- unlike the % \SpotColor case -- because it restores to an *identical* binding rather % than delegating to it from a newly-introduced, different implementation. \cs_new_protected:Npn \spotxcolorlegacy_preload:n #1 { \cs_set_eq:NN \__spotxcolorlegacy_saved_AddSpotColor \AddSpotColor \RenewDocumentCommand \AddSpotColor { m m m m } { \__spotxcolorlegacy_store_pending:nnnn {##1} {##2} {##3} {##4} } \input {#1} \cs_set_eq:NN \AddSpotColor \__spotxcolorlegacy_saved_AddSpotColor } % Plain-name (no `_'/`:') public entry point, so it can be called from % ordinary LaTeX2e package files (spotxcolor-spotcolor.sty, % spotxcolor-xespotcolor.sty) without an \ExplSyntaxOn/Off wrapper. \NewDocumentCommand \spotxcolorlegacypreload { m } { \spotxcolorlegacy_preload:n {#1} } % --- Ensure a legacy spot color name is actually registered --- % No-op if it already is (either because it was registered eagerly via a % direct \AddSpotColor/\definespotcolor call, or because \__spotxcolorlegacy_ensure:n % already ran for it earlier). Otherwise looks it up in the pending table % and performs the real (PDF-object-creating) registration exactly once. \cs_generate_variant:Nn \spotxcolor_define_spotcolor:nnn { nVV } \msg_new:nnnn { spotxcolor-legacycolor } { unknown-spot-color } { Unknown~legacy~spot~color~'#1'. } { The~name~'#1'~was~never~registered~via~\token_to_str:N\AddSpotColor, \token_to_str:N\definespotcolor,~or~a~preloaded~HKS/PANTONE~table.\\ Define~it~first,~or~check~for~a~typo~in~the~color~name. } \cs_new_protected:Npn \__spotxcolorlegacy_ensure:n #1 { \prop_if_in:NnF \g_spotxcolor_db_prop {#1} { \prop_if_in:NnTF \g__spotxcolorlegacy_pending_pdfname_prop {#1} { \prop_get:NnN \g__spotxcolorlegacy_pending_pdfname_prop {#1} \l__spotxcolorlegacy_pdfname_tl \prop_get:NnN \g__spotxcolorlegacy_pending_cmyk_prop {#1} \l__spotxcolorlegacy_cmyk_tl \spotxcolor_define_spotcolor:nVV {#1} \l__spotxcolorlegacy_pdfname_tl \l__spotxcolorlegacy_cmyk_tl \__spotxcolorlegacy_reflush_pdfpageresources: } { \msg_error:nnn { spotxcolor-legacycolor } { unknown-spot-color } {#1} } } } % Plain-name public entry point (see \spotxcolorlegacypreload above for why). \NewDocumentCommand \spotxcolorlegacyensure { m } { \__spotxcolorlegacy_ensure:n {#1} } % ===================================================================== % --- 2. The `spotcolor' xcolor color model --- % ===================================================================== % \definecolor{}{spotcolor}{,} is turned into: % 1. ensure is registered (see above) % 2. \colorlet{}{!} % Step 2 reuses xcolor's own (exactly proportional, in CMYK) tint mixing, % which is precisely what spotxcolor's \set@color patch already knows how % to recognize and convert back into true Separation operators -- so no % driver-specific code is needed here at all. \cs_new_protected:Npn \__spotxcolorlegacy_apply_spotcolor_model:nn #1#2 { \seq_set_split:Nnn \l__spotxcolorlegacy_spec_seq { , } {#2} % \seq_item:Nn is expandable but \tl_trim_spaces:n does not itself force % expansion of its argument, so extract-then-trim must be two separate % x-expanding steps (otherwise the trimmed result is the *unexpanded* % \seq_item:Nn call rather than its value). \tl_set:Nx \l__spotxcolorlegacy_name_tl { \seq_item:Nn \l__spotxcolorlegacy_spec_seq {1} } \tl_set:Nx \l__spotxcolorlegacy_name_tl { \exp_args:NV \tl_trim_spaces:n \l__spotxcolorlegacy_name_tl } \tl_set:Nx \l__spotxcolorlegacy_tint_tl { \seq_item:Nn \l__spotxcolorlegacy_spec_seq {2} } \tl_set:Nx \l__spotxcolorlegacy_tint_tl { \exp_args:NV \tl_trim_spaces:n \l__spotxcolorlegacy_tint_tl } \exp_args:NV \__spotxcolorlegacy_ensure:n \l__spotxcolorlegacy_name_tl \fp_set:Nn \l__spotxcolorlegacy_pct_fp { \l__spotxcolorlegacy_tint_tl * 100 } \tl_set:Nx \l__spotxcolorlegacy_pct_tl { \fp_eval:n { round( \l__spotxcolorlegacy_pct_fp , 4 ) } } \exp_args:Nnx \colorlet {#1} { \l__spotxcolorlegacy_name_tl ! \l__spotxcolorlegacy_pct_tl } } \seq_new:N \l__spotxcolorlegacy_spec_seq \tl_new:N \l__spotxcolorlegacy_name_tl \tl_new:N \l__spotxcolorlegacy_tint_tl \fp_new:N \l__spotxcolorlegacy_pct_fp \tl_new:N \l__spotxcolorlegacy_pct_tl % --- Hook into xcolor's low-level color-definition routine --- % This is the same interception point (and the same technique, via % \XC@sdef\XC@mod@spotcolor{spotcolor} + patching \XC@definec@lor) used by % the xcolor branch of the legacy xespotcolor package since 2014; only the % action taken for a matched `spotcolor' model differs (see above). \ExplSyntaxOff \makeatletter \@ifundefined{XC@mod@spotcolor}{\XC@sdef\XC@mod@spotcolor{spotcolor}}{} \let\spotxcolorlegacy@orig@XCdefineclor\XC@definec@lor \def\XC@definec@lor[#1]#2[#3]#4#5{% \expandafter\ifx\csname XC@mod@#4\endcsname\XC@mod@spotcolor \csname __spotxcolorlegacy_apply_spotcolor_model:nn\endcsname{#2}{#5}% \else \spotxcolorlegacy@orig@XCdefineclor[#1]{#2}[#3]{#4}{#5}% \fi } \makeatother \ExplSyntaxOn % ===================================================================== % --- 3. \SpotColor must also ensure registration before emitting --- % ===================================================================== % Legacy documents call \SpotColor{name}{tint} directly (without ever % going through \definecolor{...}{spotcolor}{...}), most commonly for the % HKS/PANTONE preset tables. \SpotColor emits a raw PDF literal referencing % "/name" as a page-resource name, so the color must already be registered. % % NOTE: this intentionally re-emits the same body as core spotxcolor's own % \SpotColor (spotxcolor.sty) rather than saving-and-delegating to it. % xparse's "optimized" fast path makes a NewDocumentCommand-defined macro % look itself back up *by name* on every call; \cs_set_eq:NN-copying such a % macro before \RenewDocumentCommand-ing the same name therefore does not % preserve the old behavior -- the "saved" copy ends up calling whatever % \SpotColor currently is, i.e. itself, causing infinite recursion. \RenewDocumentCommand \SpotColor { m m } { \__spotxcolorlegacy_ensure:n {#1} \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 } } } \endinput