%#!./run.bash test.tex \NeedsTeXFormat{LaTeX2e} \ProvidesExplPackage {spotxcolor} {2026-03-17} {1.4} {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 % --- Variables for \set@color patch (spot color auto-detection) --- % Base CMYK values for each spot color: name -> "c m y k" (space-separated) \prop_new:N \g__spotxcolor_base_prop % Detection result flags \bool_new:N \g__spotxcolor_matched_bool \tl_new:N \g__spotxcolor_matched_name_tl \tl_new:N \g__spotxcolor_matched_tint_tl % Working variables \seq_new:N \l__spotxcolor_parts_seq \seq_new:N \l__spotxcolor_base_seq \tl_new:N \l__spotxcolor_cc_tl \tl_new:N \l__spotxcolor_cm_tl \tl_new:N \l__spotxcolor_cy_tl \tl_new:N \l__spotxcolor_ck_tl \tl_new:N \l__spotxcolor_bc_tl \tl_new:N \l__spotxcolor_bm_tl \tl_new:N \l__spotxcolor_by_tl \tl_new:N \l__spotxcolor_bk_tl \fp_new:N \l__spotxcolor_maxbase_fp \fp_new:N \l__spotxcolor_tint_fp % --- Variables for dvipdfmx/XeTeX \pagecolor support --- % Flag: spot color page background is active (dvipdfmx/XeTeX only) \bool_new:N \g__spotxcolor_page_active_bool % Operator string for the page background (e.g., "/DIC161s cs 1 sc") \tl_new:N \g__spotxcolor_page_ops_tl % --- Separation object reference per spot color --- % Maps xcolor name -> Separation array reference (e.g., "6 0 R" for pdfTeX, % "@spot_space_NAME" for dvipdfmx). Used to create [/Pattern sepRef] objects. \prop_new:N \g__spotxcolor_sep_ref_prop % --- 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 } % Store Separation reference for Pattern CS creation \tl_set:Nx \l_tmpa_tl { \l_tmpb_tl \space 0~R } \prop_gput:NnV \g__spotxcolor_sep_ref_prop {#1} \l_tmpa_tl } \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 } % Store Separation reference for Pattern CS creation \tl_set:Nx \l_tmpa_tl { \l_tmpb_tl \space 0~R } \prop_gput:NnV \g__spotxcolor_sep_ref_prop {#1} \l_tmpa_tl } } { % 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 } % Store Separation reference for Pattern CS creation \prop_gput:Nnn \g__spotxcolor_sep_ref_prop {#1} { @spot_space_#1 } } } \cs_generate_variant:Nn \spotxcolor_create_pdf_obj:nnn { nnV } % Ensure NnV variant exists for safe prop storage \cs_generate_variant:Nn \prop_gput:Nnn { NnV } % --- Store base CMYK values for \set@color auto-detection --- \cs_new_protected:Npn \__spotxcolor_store_base:nn #1#2 { % #1 = xcolor name, #2 = "c, m, y, k" (comma-separated, possibly with spaces) % Use clist to normalize: "0, 0.64, 1, 0" → clist {0}{0.64}{1}{0} % then rejoin with single spaces: "0 0.64 1 0" \clist_set:Nn \l_tmpa_clist {#2} \tl_set:Nx \l_tmpa_tl { \clist_use:Nn \l_tmpa_clist { ~ } } \prop_gput:NnV \g__spotxcolor_base_prop {#1} \l_tmpa_tl } % --- Native Registration for xcolor --- %% Try to override \color@ with raw PDF operators or with \xcolor@'s raw field. Both approaches fail: %% - Raw operators in \color@ break xcolor's internal parser (\xcolor@ format is expected). %% - \xcolor@'s 2nd arg (raw field) is ignored by pdfTeX/LuaTeX drivers. %% %% Leave \color@ as standard CMYK (via \definecolor). %% The \set@color patch (installed at \AtBeginDocument) intercepts ALL color pushes, %% detects spot color CMYK values, and converts them to spot color operators. %% This handles \color, \textcolor, \pagecolor, and tinted expressions uniformly across all engines. \cs_new_protected:Npn \spotxcolor_bind_xcolor:nnn #1#2#3 { % Register as standard CMYK in xcolor's database. % xcolor's mixing engine uses this for tinting (DIC161s!50, etc.). % The \set@color patch converts the resulting CMYK to spot color operators. \definecolor {#1} {cmyk} {#3} } % --- 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} % Register base CMYK for \set@color auto-detection \__spotxcolor_store_base:nn {#1} {#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 --- \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 } } } % ===================================================================== % --- \set@color Patch for Spot Color Auto-Detection --- % ===================================================================== % When xcolor evaluates tinted expressions like "DIC161s!50", % it computes new CMYK values from the base color model, bypassing \color@. % The resulting \current@color contains plain CMYK operators. % % This patch intercepts \set@color, parses the CMYK values from \current@color, % and checks if they are a scalar multiple of any registered spot color's base CMYK values. % If a match is found: % - pdfTeX/LuaTeX: \current@color is replaced with spot color operators % - dvipdfmx/XeTeX: \special{pdf:code} is emitted after the color push % % Note: Only proportional tints (DIC161s!N) are auto-detected. % Complex mixes like "DIC161s!50!black" produce non-proportional CMYK and correctly fall back to CMYK representation. % ===================================================================== % --- Parse pdfTeX/LuaTeX format: "c m y k k c m y k K" --- \cs_new_protected:Npn \__spotxcolor_parse_pdftex: { \int_compare:nNnT { \seq_count:N \l__spotxcolor_parts_seq } = { 10 } { \str_if_eq:eeTF { \seq_item:Nn \l__spotxcolor_parts_seq { 5 } } { k } { \tl_set:Nx \l__spotxcolor_cc_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 1 } } \tl_set:Nx \l__spotxcolor_cm_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 2 } } \tl_set:Nx \l__spotxcolor_cy_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 3 } } \tl_set:Nx \l__spotxcolor_ck_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 4 } } \__spotxcolor_try_all_spots: } { } } } % --- Parse dvipdfmx/XeTeX format: "cmyk c m y k" --- \cs_new_protected:Npn \__spotxcolor_parse_dvipdfmx: { \int_compare:nNnT { \seq_count:N \l__spotxcolor_parts_seq } = { 5 } { \str_if_eq:eeTF { \seq_item:Nn \l__spotxcolor_parts_seq { 1 } } { cmyk } { \tl_set:Nx \l__spotxcolor_cc_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 2 } } \tl_set:Nx \l__spotxcolor_cm_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 3 } } \tl_set:Nx \l__spotxcolor_cy_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 4 } } \tl_set:Nx \l__spotxcolor_ck_tl { \seq_item:Nn \l__spotxcolor_parts_seq { 5 } } \__spotxcolor_try_all_spots: } { } } } % --- Iterate registered spot colors and try matching --- \cs_new_protected:Npn \__spotxcolor_try_all_spots: { % Skip all-zero CMYK (= white) to avoid false positives \fp_compare:nNnT { abs( \l__spotxcolor_cc_tl ) + abs( \l__spotxcolor_cm_tl ) + abs( \l__spotxcolor_cy_tl ) + abs( \l__spotxcolor_ck_tl ) } > { 0.001 } { \prop_map_inline:Nn \g__spotxcolor_base_prop { \bool_if:NF \g__spotxcolor_matched_bool { \__spotxcolor_check_one:nn {##1} {##2} } } } } % --- Check if current CMYK is a proportional tint of one spot color --- \cs_new_protected:Npn \__spotxcolor_check_one:nn #1#2 { % #1 = spot color name, #2 = "bc bm by bk" (space-separated) \seq_set_split:Nnn \l__spotxcolor_base_seq { ~ } {#2} \tl_set:Nx \l__spotxcolor_bc_tl { \seq_item:Nn \l__spotxcolor_base_seq { 1 } } \tl_set:Nx \l__spotxcolor_bm_tl { \seq_item:Nn \l__spotxcolor_base_seq { 2 } } \tl_set:Nx \l__spotxcolor_by_tl { \seq_item:Nn \l__spotxcolor_base_seq { 3 } } \tl_set:Nx \l__spotxcolor_bk_tl { \seq_item:Nn \l__spotxcolor_base_seq { 4 } } % Find the maximum base component (used as tint reference) \fp_set:Nn \l__spotxcolor_maxbase_fp { max( \l__spotxcolor_bc_tl , \l__spotxcolor_bm_tl , \l__spotxcolor_by_tl , \l__spotxcolor_bk_tl ) } \fp_compare:nNnT { \l__spotxcolor_maxbase_fp } > { 0 } { % Compute tint = current_dominant / base_dominant \fp_compare:nNnTF { \l__spotxcolor_bc_tl } = { \l__spotxcolor_maxbase_fp } { \fp_set:Nn \l__spotxcolor_tint_fp { \l__spotxcolor_cc_tl / \l__spotxcolor_bc_tl } } { \fp_compare:nNnTF { \l__spotxcolor_bm_tl } = { \l__spotxcolor_maxbase_fp } { \fp_set:Nn \l__spotxcolor_tint_fp { \l__spotxcolor_cm_tl / \l__spotxcolor_bm_tl } } { \fp_compare:nNnTF { \l__spotxcolor_by_tl } = { \l__spotxcolor_maxbase_fp } { \fp_set:Nn \l__spotxcolor_tint_fp { \l__spotxcolor_cy_tl / \l__spotxcolor_by_tl } } { \fp_set:Nn \l__spotxcolor_tint_fp { \l__spotxcolor_ck_tl / \l__spotxcolor_bk_tl } } } } % Validate: tint must be in [0, 1] (with tolerance) \bool_set_true:N \l_tmpa_bool \fp_compare:nNnF { \l__spotxcolor_tint_fp } > { -0.005 } { \bool_set_false:N \l_tmpa_bool } \fp_compare:nNnF { \l__spotxcolor_tint_fp } < { 1.005 } { \bool_set_false:N \l_tmpa_bool } % Validate each CMYK component: |current_i - tint * base_i| < epsilon \bool_if:NT \l_tmpa_bool { \fp_compare:nNnF { abs( \l__spotxcolor_cc_tl - \l__spotxcolor_tint_fp * \l__spotxcolor_bc_tl ) } < { 0.005 } { \bool_set_false:N \l_tmpa_bool } } \bool_if:NT \l_tmpa_bool { \fp_compare:nNnF { abs( \l__spotxcolor_cm_tl - \l__spotxcolor_tint_fp * \l__spotxcolor_bm_tl ) } < { 0.005 } { \bool_set_false:N \l_tmpa_bool } } \bool_if:NT \l_tmpa_bool { \fp_compare:nNnF { abs( \l__spotxcolor_cy_tl - \l__spotxcolor_tint_fp * \l__spotxcolor_by_tl ) } < { 0.005 } { \bool_set_false:N \l_tmpa_bool } } \bool_if:NT \l_tmpa_bool { \fp_compare:nNnF { abs( \l__spotxcolor_ck_tl - \l__spotxcolor_tint_fp * \l__spotxcolor_bk_tl ) } < { 0.005 } { \bool_set_false:N \l_tmpa_bool } } % All checks passed → match found \bool_if:NT \l_tmpa_bool { % Clamp tint to [0, 1] \fp_compare:nNnT { \l__spotxcolor_tint_fp } < { 0 } { \fp_set:Nn \l__spotxcolor_tint_fp { 0 } } \fp_compare:nNnT { \l__spotxcolor_tint_fp } > { 1 } { \fp_set:Nn \l__spotxcolor_tint_fp { 1 } } % Store results globally (to survive \prop_map_inline grouping) \bool_gset_true:N \g__spotxcolor_matched_bool \tl_gset:Nn \g__spotxcolor_matched_name_tl {#1} \tl_gset:Nx \g__spotxcolor_matched_tint_tl { \fp_eval:n { round( \l__spotxcolor_tint_fp , 5 ) } } \prop_map_break: } } } % --- Helper: build spot color operator string in \l_tmpb_tl --- % Result: "/NAME cs /NAME CS TINT sc TINT SC" % Uses n-type (no expansion) for literal parts to guarantee correct spacing, % and V-type for variable substitution. % NOTE: We intentionally avoid x-type expansion (\edef / \tl_set:Nx) % because ~ (catcode 10 space) gets lost during x-expansion % in certain expl3 contexts. \cs_new_protected:Npn \__spotxcolor_build_operators: { \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~/ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_name_tl \tl_put_right:Nn \l_tmpb_tl { ~CS~ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_tint_tl \tl_put_right:Nn \l_tmpb_tl { ~sc~ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_tint_tl \tl_put_right:Nn \l_tmpb_tl { ~SC } } % --- Main interception: detect spot color in \current@color --- % If a registered spot color is detected, the matched flag is set, % \l_tmpb_tl contains the spot color operator string, and (for % pdfTeX/LuaTeX) \current@color is replaced in-place. % NOTE: All references to \current@color use c-type access because % @ is catcode 12 in expl3 but catcode 11 in these LaTeX internals. \cs_new_protected:Npn \__spotxcolor_intercept: { \bool_gset_false:N \g__spotxcolor_matched_bool \prop_if_empty:NF \g__spotxcolor_base_prop { % Copy \current@color to a temp tl (catcode-safe) \tl_set_eq:Nc \l_tmpa_tl { current@color } % Split into space-separated tokens \seq_set_split:NnV \l__spotxcolor_parts_seq { ~ } \l_tmpa_tl \legacy_if:nTF { pdf } { \__spotxcolor_parse_pdftex: } { \__spotxcolor_parse_dvipdfmx: } \bool_if:NT \g__spotxcolor_matched_bool { % Build the spot color operator string in \l_tmpb_tl \__spotxcolor_build_operators: \legacy_if:nT { pdf } { % pdfTeX/LuaTeX: replace \current@color with spot operators. % \tl_set_eq makes \current@color a copy of \l_tmpb_tl. \tl_set_eq:cN { current@color } \l_tmpb_tl } } } } % Generate needed variant \cs_generate_variant:Nn \seq_set_split:Nnn { NnV } % --- Helper: emit \special{pdf:code ...} for dvipdfmx/XeTeX --- \cs_new_protected:Npn \__spotxcolor_emit_special:n #1 { \special { pdf:code~ #1 } } % --- Install patches at \AtBeginDocument --- \AtBeginDocument { % ============================================================ % Patch \set@color (handles \color, \textcolor) % ============================================================ \cs_set_eq:cc { __spotxcolor_orig_set@color } { set@color } \cs_gset_protected:cpn { set@color } { % 1. Detect spot color and modify \current@color (pdfTeX/LuaTeX) \__spotxcolor_intercept: % 2. Call the original \set@color (push to color stack) \use:c { __spotxcolor_orig_set@color } % 3. dvipdfmx/XeTeX: emit raw spot color operators after the push \bool_if:NT \g__spotxcolor_matched_bool { \legacy_if:nF { pdf } { \exp_args:NV \__spotxcolor_emit_special:n \l_tmpb_tl } } } % ============================================================ % Patch \set@page@color (handles \pagecolor) % ============================================================ % xcolor's \pagecolor calls \set@page@color WITHOUT going through % \set@color, so the \set@color patch above doesn't affect it. % % pdfTeX/LuaTeX: intercept \current@color BEFORE the original % copies it to \current@page@color. The fill rectangle then % uses spot color operators natively. % % dvipdfmx/XeTeX: \special{background ...} only supports standard % color models, so we let the original run (CMYK fallback) and % ALSO store the spot color operators. A shipout/background hook % then overdraws the CMYK background with a spot-colored rectangle % using raw PDF operators with a CTM reverse-translation. \cs_if_exist:cT { set@page@color } { \cs_set_eq:cc { __spotxcolor_orig_set@page@color } { set@page@color } \cs_gset_protected:cpn { set@page@color } { % Detect spot color in \current@color \__spotxcolor_intercept: \bool_if:NTF \g__spotxcolor_matched_bool { % Spot color detected \legacy_if:nF { pdf } { % dvipdfmx/XeTeX: store operators for shipout hook \__spotxcolor_build_operators: \tl_gset_eq:NN \g__spotxcolor_page_ops_tl \l_tmpb_tl \bool_gset_true:N \g__spotxcolor_page_active_bool } % pdfTeX/LuaTeX: \current@color already modified by intercept } { % Not a spot color: clear dvipdfmx page overlay flag \bool_gset_false:N \g__spotxcolor_page_active_bool } % Always call original (pdfTeX: uses modified \current@color; % dvipdfmx: emits \special{background cmyk ...} as CMYK fallback) \use:c { __spotxcolor_orig_set@page@color } } } % ============================================================ % Patch \nopagecolor (clears page background) % ============================================================ \cs_if_exist:cT { nopagecolor } { \cs_set_eq:cc { __spotxcolor_orig_nopagecolor } { nopagecolor } \cs_gset_protected:cpn { nopagecolor } { \bool_gset_false:N \g__spotxcolor_page_active_bool \use:c { __spotxcolor_orig_nopagecolor } } } } % ===================================================================== % --- v1.2: dvipdfmx/XeTeX \pagecolor — shipout/background hook --- % ===================================================================== % dvipdfmx wraps the main content stream with: q 1 0 0 1 TX TY cm ... Q % where TX = 1in + \hoffset (in bp), TY = \paperheight - 1in - \voffset (in bp). % % This hook emits a full-page rectangle using spot color operators with % a reverse-CTM translation so the fill uses absolute PDF coordinates: % q 1 0 0 1 -TX -TY cm /NAME cs TINT sc 0 0 W H re f Q % % This overdraws the CMYK background from \special{background}, giving % the correct spot color appearance while keeping CMYK as a fallback. % ===================================================================== % --- Helper: emit the page background special --- \cs_new_protected:Npn \__spotxcolor_emit_page_bg: { % Build the operator string piece by piece (n-type for literals, % x-type for computed values) to guarantee correct spacing. % Conversion: TeX pt to PDF bp → multiply by 72/72.27 \tl_clear:N \l_tmpa_tl \tl_put_right:Nn \l_tmpa_tl { pdf:code~q~1~0~0~1~ } % -TX (negative x-offset to reach PDF origin) \tl_put_right:Nx \l_tmpa_tl { \fp_eval:n { -round( \dim_to_fp:n { 1in + \hoffset } * 72 / 72.27 , 3 ) } } \tl_put_right:Nn \l_tmpa_tl { ~ } % -TY (negative y-offset to reach PDF origin) \tl_put_right:Nx \l_tmpa_tl { \fp_eval:n { -round( ( \dim_to_fp:n { \paperheight } - \dim_to_fp:n { 1in + \voffset } ) * 72 / 72.27 , 3 ) } } \tl_put_right:Nn \l_tmpa_tl { ~cm~ } % Spot color operators (e.g., "/DIC161s cs /DIC161s CS 1 sc 1 SC") \tl_put_right:NV \l_tmpa_tl \g__spotxcolor_page_ops_tl % Full-page rectangle: 0 0 W H re f \tl_put_right:Nn \l_tmpa_tl { ~0~0~ } \tl_put_right:Nx \l_tmpa_tl { \fp_eval:n { round( \dim_to_fp:n { \paperwidth } * 72 / 72.27 , 3 ) } } \tl_put_right:Nn \l_tmpa_tl { ~ } \tl_put_right:Nx \l_tmpa_tl { \fp_eval:n { round( \dim_to_fp:n { \paperheight } * 72 / 72.27 , 3 ) } } \tl_put_right:Nn \l_tmpa_tl { ~re~f~Q } % Emit the special \exp_args:NV \special \l_tmpa_tl } % --- Register the shipout hook --- \AddToHook { shipout/background } { \bool_if:NT \g__spotxcolor_page_active_bool { \__spotxcolor_emit_page_bg: } } % --- 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}{ } { } % ===================================================================== % --- PGF Driver Macro Patches for Spot Color --- % ===================================================================== % PGF's low-level color macros (\pgfsys@color@cmyk@fill, etc.) bypass xcolor's \set@color entirely and emit CMYK operators directly via \pgfsysprotocol@literal. % This means TikZ drawings using spot colors fall back to CMYK even though \set@color is patched. % % This patches the 4 PGF CMYK driver macros (defined in pgfsys-common-pdf.def, shared by ALL engines): % \pgfsys@color@cmyk@fill#1#2#3#4 → #1 #2 #3 #4 k % \pgfsys@color@cmyk@stroke#1#2#3#4 → #1 #2 #3 #4 K % \pgfsys@color@cmy@fill#1#2#3 → #1 #2 #3 0 k % \pgfsys@color@cmy@stroke#1#2#3 → #1 #2 #3 0 K % % Each patched macro checks the CMYK values against registered spot colors. % If a match is found, spot color operators are emitted instead. % ===================================================================== % --- Helper: build FILL-only spot color operators in \l_tmpb_tl --- % Result: "/NAME cs TINT sc" \cs_new_protected:Npn \__spotxcolor_build_fill_operators: { \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~ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_tint_tl \tl_put_right:Nn \l_tmpb_tl { ~sc } } % --- Helper: build STROKE-only spot color operators in \l_tmpb_tl --- % Result: "/NAME CS TINT SC" \cs_new_protected:Npn \__spotxcolor_build_stroke_operators: { \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~ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_tint_tl \tl_put_right:Nn \l_tmpb_tl { ~SC } } % --- Helper: check 4 CMYK args against registered spot colors --- % Sets \g__spotxcolor_matched_bool, \g__spotxcolor_matched_name_tl, % \g__spotxcolor_matched_tint_tl if a match is found. \cs_new_protected:Npn \__spotxcolor_pgf_check:nnnn #1#2#3#4 { \tl_set:Nn \l__spotxcolor_cc_tl {#1} \tl_set:Nn \l__spotxcolor_cm_tl {#2} \tl_set:Nn \l__spotxcolor_cy_tl {#3} \tl_set:Nn \l__spotxcolor_ck_tl {#4} \bool_gset_false:N \g__spotxcolor_matched_bool \__spotxcolor_try_all_spots: } % --- Wrapper for \pgfsysprotocol@literal (catcode-safe) --- \cs_new_protected:Npn \__spotxcolor_pgf_literal:n #1 { \use:c { pgfsysprotocol@literal } {#1} } \cs_generate_variant:Nn \__spotxcolor_pgf_literal:n { V } % --- Install PGF driver patches --- \AtBeginDocument { \cs_if_exist:cT { pgfsys@color@cmyk@fill } { % ---- Save originals ---- \cs_set_eq:cc { __spotxcolor_orig_pgf_cmyk_fill } { pgfsys@color@cmyk@fill } \cs_set_eq:cc { __spotxcolor_orig_pgf_cmyk_stroke } { pgfsys@color@cmyk@stroke } \cs_set_eq:cc { __spotxcolor_orig_pgf_cmy_fill } { pgfsys@color@cmy@fill } \cs_set_eq:cc { __spotxcolor_orig_pgf_cmy_stroke } { pgfsys@color@cmy@stroke } % ---- Patch \pgfsys@color@cmyk@fill ---- \cs_gset_protected:cpn { pgfsys@color@cmyk@fill } #1#2#3#4 { \__spotxcolor_pgf_check:nnnn {#1}{#2}{#3}{#4} \bool_if:NTF \g__spotxcolor_matched_bool { \__spotxcolor_build_fill_operators: \__spotxcolor_pgf_literal:V \l_tmpb_tl } { \use:c { __spotxcolor_orig_pgf_cmyk_fill } {#1}{#2}{#3}{#4} } } % ---- Patch \pgfsys@color@cmyk@stroke ---- \cs_gset_protected:cpn { pgfsys@color@cmyk@stroke } #1#2#3#4 { \__spotxcolor_pgf_check:nnnn {#1}{#2}{#3}{#4} \bool_if:NTF \g__spotxcolor_matched_bool { \__spotxcolor_build_stroke_operators: \__spotxcolor_pgf_literal:V \l_tmpb_tl } { \use:c { __spotxcolor_orig_pgf_cmyk_stroke } {#1}{#2}{#3}{#4} } } % ---- Patch \pgfsys@color@cmy@fill ---- \cs_gset_protected:cpn { pgfsys@color@cmy@fill } #1#2#3 { \__spotxcolor_pgf_check:nnnn {#1}{#2}{#3}{0} \bool_if:NTF \g__spotxcolor_matched_bool { \__spotxcolor_build_fill_operators: \__spotxcolor_pgf_literal:V \l_tmpb_tl } { \use:c { __spotxcolor_orig_pgf_cmy_fill } {#1}{#2}{#3} } } % ---- Patch \pgfsys@color@cmy@stroke ---- \cs_gset_protected:cpn { pgfsys@color@cmy@stroke } #1#2#3 { \__spotxcolor_pgf_check:nnnn {#1}{#2}{#3}{0} \bool_if:NTF \g__spotxcolor_matched_bool { \__spotxcolor_build_stroke_operators: \__spotxcolor_pgf_literal:V \l_tmpb_tl } { \use:c { __spotxcolor_orig_pgf_cmy_stroke } {#1}{#2}{#3} } } } } % ===================================================================== % --- Pattern Color Space for Spot Colors --- % ===================================================================== % PGF uncolored patterns use /pgfpcmyk [/Pattern /DeviceCMYK] as the % color space, with operands: C M Y K /pgfpatN scn % % For spot colors, we need [/Pattern [/Separation ...]] as the color % space, with operands: TINT /pgfpatN scn % % This section creates [/Pattern sepRef] objects for each registered spot color and registers them as /pgfpspot_NAME in page resources. % Then \pgfsys@setpatternuncolored is patched to use spot color pattern CS when the CMYK values match a registered spot color. % ===================================================================== % --- Helper: create Pattern CS objects for all registered spot colors --- \cs_new_protected:Npn \__spotxcolor_create_pattern_cs: { \prop_map_inline:Nn \g__spotxcolor_sep_ref_prop { % ##1 = spot color name, ##2 = Separation reference \legacy_if:nTF { pdf } { \sys_if_engine_pdftex:T { \immediate \pdfobj { [/Pattern~ ##2 ] } \use:c { pgfutil@addpdfresource@colorspaces } { /pgfpspot_ ##1 ~ \the\pdflastobj \space 0~R } } \sys_if_engine_luatex:T { \immediate \pdfextension obj { [/Pattern~ ##2 ] } \use:c { pgfutil@addpdfresource@colorspaces } { /pgfpspot_ ##1 ~ \pdffeedback lastobj \space 0~R } } } { % dvipdfmx / XeTeX: use named references \special { pdf:obj~@pgfpspot_ ##1 ~[/Pattern~ ##2 ] } \use:c { pgfutil@addpdfresource@colorspaces } { /pgfpspot_ ##1 ~ @pgfpspot_ ##1 } } } } % --- Helper: build pattern fill operators for spot color in \l_tmpb_tl --- % Result: "/pgfpspot_NAME cs TINT /pgfpat PATNAME scn" % #1 = pattern name (from PGF) \cs_new_protected:Npn \__spotxcolor_build_pattern_operators:n #1 { \tl_clear:N \l_tmpb_tl \tl_put_right:Nn \l_tmpb_tl { /pgfpspot_ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_name_tl \tl_put_right:Nn \l_tmpb_tl { ~cs~ } \tl_put_right:NV \l_tmpb_tl \g__spotxcolor_matched_tint_tl \tl_put_right:Nn \l_tmpb_tl { ~/pgfpat } \tl_put_right:Nn \l_tmpb_tl {#1} \tl_put_right:Nn \l_tmpb_tl { ~scn } } % --- v1.4: Wrapper to install pattern spot detection from makeatletter --- % This must be defined HERE (in ExplSyntax) so that _ and : tokens are % correctly catcoded. It is called from the makeatletter \AtBeginDocument % block AFTER the CMYK baseline \pgfsys@setpatternuncolored is defined. \cs_new_protected:Npn \__spotxcolor_install_pattern_spot_patch: { % Create /pgfpspot_NAME color space resources \__spotxcolor_create_pattern_cs: % Save the CMYK baseline and wrap with spot detection \cs_set_eq:cc { __spotxcolor_orig_setpatternuncolored } { pgfsys@setpatternuncolored } \cs_gset_protected:cpn { pgfsys@setpatternuncolored } ##1##2##3##4##5 { \__spotxcolor_pgf_check:nnnn {##2}{##3}{##4}{##5} \bool_if:NTF \g__spotxcolor_matched_bool { \__spotxcolor_build_pattern_operators:n {##1} \__spotxcolor_pgf_literal:V \l_tmpb_tl } { \use:c { __spotxcolor_orig_setpatternuncolored } {##1}{##2}{##3}{##4}{##5} } } } \ExplSyntaxOff % ======================================================= % --- PGF Pattern CMYK→Spot Patch --- % Force PGF/TikZ uncolored patterns to use CMYK instead of hardcoded RGB. % After defining the CMYK baseline \pgfsys@setpatternuncolored, % this block also creates [/Pattern [/Separation ...]] color space objects for each registered spot color (step 5) and wraps \pgfsys@setpatternuncolored with spot color auto-detection. % ======================================================= \makeatletter \AtBeginDocument{ \@ifpackageloaded{pgfcore}{% % 1. Register CMYK Pattern Colorspace \pgfutil@addpdfresource@colorspaces{ /pgfpcmyk [/Pattern /DeviceCMYK] }% % 2. Define CMYK baseline of \pgfsys@setpatternuncolored % (5 parameters: PatternName, C, M, Y, K) % Step 5 below wraps this with spot color detection. \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}% }% % % 5. Create [/Pattern [/Separation ...]] objects for each registered spot color, % and wrap \pgfsys@setpatternuncolored with spot color detection. % NOTE: The wrapper function is defined in ExplSyntax (above \ExplSyntaxOff) where _ and : have correct catcodes. We call it here via \csname to bridge the catcode boundary. \csname __spotxcolor_install_pattern_spot_patch:\endcsname }\relax } \makeatother \endinput