From 3b1ce337f9579b1cde0f00e50e5f08b9ebd089af Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Fri, 11 Oct 2024 19:03:26 +0000 Subject: [PATCH] monkey patch pdf-js to support even-odd rule --- src/pdf.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/pdf.ts b/src/pdf.ts index 332dc4a..7cdc6b7 100644 --- a/src/pdf.ts +++ b/src/pdf.ts @@ -1,7 +1,7 @@ -import { PDFDocument, PDFImage, rgb } from "pdf-lib"; +import { PDFDocument, PDFImage, PDFOperator, PDFOperatorNames, PDFPage, rgb } from "pdf-lib"; import { QR } from "./qr-base.js"; import { ImageOptions, Matrix } from "./typing/types"; -import { getOptions, getDotsSVGPath } from "./utils.js"; +import { getOptions, getDotsSVGPath, getFindersSVGPath } from "./utils.js"; import colorString from "color-string"; import { clearMatrixCenter, zeroFillFinders } from "./matrix.js"; @@ -34,6 +34,43 @@ function getOpacity(color: string | number): number { return (color % 256) / 255; } +/** + * This code is a piece of monkey patching to change the fill rule of the QR code. As pdf-lib does not support the even-odd fill rule, we need to patch the content stream of the page to change the fill rule from non-zero to even-odd. + * @param page + */ +function patchContentStream(page: PDFPage) { + // @ts-expect-error patching private method + page.prevGetContentStream = page.getContentStream; + // @ts-expect-error patching private method + page.getContentStream = (...args) => { + // @ts-expect-error patching private method + const contentStream = page.prevGetContentStream(...args); + contentStream.prevPush = contentStream.push; + contentStream.push = (...operators: PDFOperator[]) => { + contentStream.prevPush(...operators.map((op: any) => { + if (op.name == PDFOperatorNames.FillNonZeroAndStroke) { + return PDFOperator.of(PDFOperatorNames.FillEvenOddAndStroke, op.args); + } + if (op.name == PDFOperatorNames.FillNonZero) { + return PDFOperator.of(PDFOperatorNames.FillEvenOdd, op.args); + } + if (op.name == PDFOperatorNames.FillNonZeroAndStroke) { + return PDFOperator.of(PDFOperatorNames.FillEvenOddAndStroke, op.args); + } + return op; + })); + } + return contentStream; + } +} + +function revertContentStream(page: PDFPage) { + // @ts-expect-error patching private method + page.getContentStream = page.prevGetContentStream; + // @ts-expect-error patching private method + page.contentStream.push = page.contentStream.prevPush; +} + async function PDF({ matrix, margin, @@ -66,6 +103,15 @@ async function PDF({ borderColor: rgb(...colorToRGB(color)), borderOpacity: getOpacity(color), }); + const findersPath = getFindersSVGPath(matrix, size, marginPx, borderRadius); + patchContentStream(page); + page.drawSvgPath(findersPath, { + color: rgb(...colorToRGB(color)), + opacity: getOpacity(color), + borderColor: rgb(...colorToRGB(color)), + borderOpacity: getOpacity(color), + }); + revertContentStream(page); if (logo) { let logoData: PDFImage; const header = new Uint8Array(logo.slice(0, 4));