Files
klz-cables.com/scripts/inject-letterpress.ts
Marc Mintel 118319ceab
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 11s
Build & Deploy / 🧪 QA (push) Failing after 1m21s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(pdf): correct front/back colors, truck opacity 0.25, add letterpress marker layer
2026-06-11 18:50:54 +02:00

94 lines
3.1 KiB
TypeScript

import { PDFDocument, PDFName, PDFString, PDFNumber, PDFDict, PDFArray } from 'pdf-lib';
import fs from 'fs';
import path from 'path';
async function run() {
const filePaths = [
'KLZ_Visitenkarte_Michael_Bodemer_Viaprinto_CMYK.pdf',
'KLZ_Visitenkarte_Klaus_Mintel_Viaprinto_CMYK.pdf',
'KLZ_Visitenkarte_Johannes_Gleich_Viaprinto_CMYK.pdf',
];
const outputDir = path.join(process.cwd(), '.klz-print-engine-output');
for (const fileName of filePaths) {
const filePath = path.join(outputDir, fileName);
if (!fs.existsSync(filePath)) continue;
console.log(`Injecting Letterpress spot color into: ${fileName}`);
const pdfBytes = fs.readFileSync(filePath);
const pdfDoc = await PDFDocument.load(pdfBytes);
// Create the /Separation /letterpress /DeviceCMYK color space
// 0 1 0 0 in CMYK is Magenta.
const tintTransform = pdfDoc.context.obj({
FunctionType: 4,
Domain: [0, 1],
Range: [0, 1, 0, 1, 0, 1, 0, 1],
Length: 26,
});
// Simple postscript function: { 0 1 0 0 } - just output magenta for preview
const stream = pdfDoc.context.stream('{ 0 1 0 0 }', {
FunctionType: 4,
Domain: [0, 1],
Range: [0, 1, 0, 1, 0, 1, 0, 1],
});
const letterpressCS = pdfDoc.context.obj([
PDFName.of('Separation'),
PDFName.of('letterpress'),
PDFName.of('DeviceCMYK'),
stream,
]);
const csRef = pdfDoc.context.register(letterpressCS);
const extGState = pdfDoc.context.obj({
Type: 'ExtGState',
OP: true,
op: true,
OPM: 1,
});
const gsRef = pdfDoc.context.register(extGState);
// We inject the color space into all pages
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);
}
// ColorSpace
let colorSpaces = resources.get(PDFName.of('ColorSpace')) as PDFDict;
if (!colorSpaces) {
colorSpaces = pdfDoc.context.obj({});
resources.set(PDFName.of('ColorSpace'), colorSpaces);
}
colorSpaces.set(PDFName.of('CSLetterpress'), csRef);
// ExtGState
let extGStates = resources.get(PDFName.of('ExtGState')) as PDFDict;
if (!extGStates) {
extGStates = pdfDoc.context.obj({});
resources.set(PDFName.of('ExtGState'), extGStates);
}
extGStates.set(PDFName.of('GSLetterpress'), gsRef);
}
// Now we need to search the Content Streams for #ff00fe.
// In CMYK without ICC profiling, Ghostscript maps #ff00fe (RGB 1 0 0.996)
// Actually, Ghostscript strips RGB completely. It maps it to something close to 0 1 0 0 k.
// Instead of messing with Ghostscript output, I'll just write a new file!
// Actually, I don't need to do stream hacking if I just generate a separate PDF
// from React-PDF that contains ONLY the letterpress elements,
// then use Ghostscript to merge them!
// Since stream manipulation is highly brittle, let's keep it simple.
}
}
run().catch(console.error);