Files
klz-cables.com/scripts/inject-pantone.ts
Marc Mintel b826b5b741
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 15s
Build & Deploy / 🧪 QA (push) Successful in 1m50s
Build & Deploy / 🏗️ Build (push) Successful in 2m51s
Build & Deploy / 🚀 Deploy (push) Successful in 18s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m24s
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(pdf): change pantone fallback function to type 2 for better viewer compatibility
2026-06-12 15:44:54 +02:00

106 lines
3.6 KiB
TypeScript

import { PDFDocument, PDFName, decodePDFRawStream, PDFRawStream } from 'pdf-lib';
import fs from 'fs';
import path from 'path';
async function run() {
const inputPath = process.argv[2];
const outputPath = process.argv[3] || inputPath;
if (!inputPath || !fs.existsSync(inputPath)) {
console.error(`[ERROR] Input file not found: ${inputPath}`);
process.exit(1);
}
console.log(`[INFO] Injecting PANTONE 289 C into: ${path.basename(inputPath)}`);
const pdfBytes = fs.readFileSync(inputPath);
const pdfDoc = await PDFDocument.load(pdfBytes);
// CMYK tint transform for PANTONE 289 C.
// Approximation: 100C, 64M, 0Y, 60K (1.0 0.64 0.0 0.60)
// The function maps an input x (0 to 1) to CMYK: x*1.0, x*0.64, x*0.0, x*0.60
const tintTransform = pdfDoc.context.obj({
FunctionType: 2,
Domain: [0, 1],
C0: [0, 0, 0, 0],
C1: [1.0, 0.64, 0.0, 0.6],
N: 1,
});
// Create the /Separation color space array
const pantoneCS = pdfDoc.context.obj([
PDFName.of('Separation'),
PDFName.of('PANTONE#20289#20C'),
PDFName.of('DeviceCMYK'),
tintTransform,
]);
const csRef = pdfDoc.context.register(pantoneCS);
const pages = pdfDoc.getPages();
for (const page of pages) {
let resources = page.node.Resources();
if (!resources) {
resources = pdfDoc.context.obj({});
page.node.set(PDFName.of('Resources'), resources);
}
// Ensure ColorSpace dict exists
let colorSpaces = resources.get(PDFName.of('ColorSpace')) as any;
if (!colorSpaces) {
colorSpaces = pdfDoc.context.obj({});
resources.set(PDFName.of('ColorSpace'), colorSpaces);
}
// Register the PANTONE color space as /CSPantone in the page's resources
colorSpaces.set(PDFName.of('CSPantone'), csRef);
// Read all content streams
const contents = page.node.Contents();
let streams: any[] = [];
if (contents) {
const resolvedContents = pdfDoc.context.lookup(contents);
if (resolvedContents instanceof PDFRawStream) {
streams = [resolvedContents];
} else if (resolvedContents.constructor.name === 'PDFArray') {
const arr = resolvedContents as any;
for (let j = 0; j < arr.size(); j++) {
streams.push(pdfDoc.context.lookup(arr.get(j)));
}
}
}
let newStr = '';
for (let i = 0; i < streams.length; i++) {
const stream = streams[i];
if (stream instanceof PDFRawStream) {
const decoded = decodePDFRawStream(stream).decode();
const str = Buffer.from(decoded).toString('utf-8');
newStr += str + '\n';
}
}
// The CMYK values for the KLZ Blue created by Ghostscript from #000a66
// is '1 0.98 0.224 0.298'. We use a slightly dynamic regex to catch small precision diffs.
const dynamicRegexK = /1 0\.98[0-9]* 0\.224[0-9]* 0\.298[0-9]* k/g;
const dynamicRegexStrokeK = /1 0\.98[0-9]* 0\.224[0-9]* 0\.298[0-9]* K/g;
let modified = false;
if (dynamicRegexK.test(newStr) || dynamicRegexStrokeK.test(newStr)) {
newStr = newStr.replace(dynamicRegexK, '/CSPantone cs 1 scn');
newStr = newStr.replace(dynamicRegexStrokeK, '/CSPantone CS 1 SCN');
modified = true;
}
if (modified) {
const newStream = pdfDoc.context.flateStream(newStr);
const newRef = pdfDoc.context.register(newStream);
page.node.set(PDFName.of('Contents'), newRef);
console.log(`[INFO] Spot color injected into page ${pages.indexOf(page) + 1}.`);
}
}
const finalPdfBytes = await pdfDoc.save();
fs.writeFileSync(outputPath, finalPdfBytes);
console.log(`[SUCCESS] Saved 5-color PDF to ${outputPath}`);
}
run().catch(console.error);