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