From 70c447916c71f91152858979199b3e3ab6b4dbee Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 15:45:07 +0900 Subject: [PATCH 01/44] annotation types --- src/core/annotation/AnnotationTypes.ts | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/core/annotation/AnnotationTypes.ts diff --git a/src/core/annotation/AnnotationTypes.ts b/src/core/annotation/AnnotationTypes.ts new file mode 100644 index 000000000..d2697672e --- /dev/null +++ b/src/core/annotation/AnnotationTypes.ts @@ -0,0 +1,31 @@ +/** + * Subtypes of PDF annotations from table 169 in the PDF specification. + */ +export enum AnnotationTypes { + Text = 'Text', + Link = 'Link', + FreeText = 'FreeText', + Line = 'Line', + Square = 'Square', + Circle = 'Circle', + Polygon = 'Polygon', + PolyLine = 'PolyLine', + Highlight = 'Highlight', + Underline = 'Underline', + Squiggly = 'Squiggly', + StrikeOut = 'StrikeOut', + Stamp = 'Stamp', + Caret = 'Caret', + Ink = 'Ink', + Popup = 'Popup', + FileAttachment = 'FileAttachment', + Sound = 'Sound', + Movie = 'Movie', + Widget = 'Widget', + Screen = 'Screen', + PrinterMark = 'PrinterMark', + TrapNet = 'TrapNet', + Watermark = 'Watermark', + ThreeD = '3D', + Redact = 'Redact', +} From 0c52eaf4245c00d466f965e80b12a84606d5530a Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 15:47:42 +0900 Subject: [PATCH 02/44] plural --- src/core/annotation/PDFAnnotation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 47aae9f47..0f929ae82 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -14,7 +14,7 @@ class PDFAnnotation { this.dict = dict; } - // This is technically required by the PDF spec + // These is technically required by the PDF spec Rect(): PDFArray | undefined { return this.dict.lookup(PDFName.of('Rect'), PDFArray); } From dbe3184aae77252f39d793f6c6ac53acee29038e Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 15:58:32 +0900 Subject: [PATCH 03/44] get subtype --- src/core/annotation/AnnotationTypes.ts | 52 +++++++++++++------------- src/core/annotation/PDFAnnotation.ts | 17 +++++++++ 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/core/annotation/AnnotationTypes.ts b/src/core/annotation/AnnotationTypes.ts index d2697672e..35ad10816 100644 --- a/src/core/annotation/AnnotationTypes.ts +++ b/src/core/annotation/AnnotationTypes.ts @@ -2,30 +2,30 @@ * Subtypes of PDF annotations from table 169 in the PDF specification. */ export enum AnnotationTypes { - Text = 'Text', - Link = 'Link', - FreeText = 'FreeText', - Line = 'Line', - Square = 'Square', - Circle = 'Circle', - Polygon = 'Polygon', - PolyLine = 'PolyLine', - Highlight = 'Highlight', - Underline = 'Underline', - Squiggly = 'Squiggly', - StrikeOut = 'StrikeOut', - Stamp = 'Stamp', - Caret = 'Caret', - Ink = 'Ink', - Popup = 'Popup', - FileAttachment = 'FileAttachment', - Sound = 'Sound', - Movie = 'Movie', - Widget = 'Widget', - Screen = 'Screen', - PrinterMark = 'PrinterMark', - TrapNet = 'TrapNet', - Watermark = 'Watermark', - ThreeD = '3D', - Redact = 'Redact', + Text = '/Text', + Link = '/Link', + FreeText = '/FreeText', + Line = '/Line', + Square = '/Square', + Circle = '/Circle', + Polygon = '/Polygon', + PolyLine = '/PolyLine', + Highlight = '/Highlight', + Underline = '/Underline', + Squiggly = '/Squiggly', + StrikeOut = '/StrikeOut', + Stamp = '/Stamp', + Caret = '/Caret', + Ink = '/Ink', + Popup = '/Popup', + FileAttachment = '/FileAttachment', + Sound = '/Sound', + Movie = '/Movie', + Widget = '/Widget', + Screen = '/Screen', + PrinterMark = '/PrinterMark', + TrapNet = '/TrapNet', + Watermark = '/Watermark', + ThreeD = '/3D', + Redact = '/Redact', } diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 0f929ae82..22290bd75 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -4,6 +4,7 @@ import PDFStream from '../objects/PDFStream'; import PDFArray from '../objects/PDFArray'; import PDFRef from '../objects/PDFRef'; import PDFNumber from '../objects/PDFNumber'; +import { AnnotationTypes } from './AnnotationTypes'; class PDFAnnotation { readonly dict: PDFDict; @@ -15,6 +16,10 @@ class PDFAnnotation { } // These is technically required by the PDF spec + Subtype(): PDFName | undefined { + return this.dict.lookup(PDFName.of('Subtype'), PDFName); + } + Rect(): PDFArray | undefined { return this.dict.lookup(PDFName.of('Rect'), PDFArray); } @@ -28,6 +33,18 @@ class PDFAnnotation { return this.dict.context.lookupMaybe(numberOrRef, PDFNumber); } + /** + * Get the subtype enum. + * This gives `undefined` if the subtype not written in the PDF. + * @returns The subtype as AnnotationTypes or undefined + */ + getSubtype(): AnnotationTypes | undefined { + const subtypePdfName = this.Subtype(); + if (subtypePdfName instanceof PDFName) + return subtypePdfName.toString() as AnnotationTypes; + return undefined; + } + getRectangle(): { x: number; y: number; width: number; height: number } { const Rect = this.Rect(); return Rect?.asRectangle() ?? { x: 0, y: 0, width: 0, height: 0 }; From f18da38d0fe56de2f5d7327704263ab543f5ce59 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 16:01:06 +0900 Subject: [PATCH 04/44] refac: add table reference --- src/core/annotation/PDFAnnotation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 22290bd75..101be27ad 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -5,6 +5,7 @@ import PDFArray from '../objects/PDFArray'; import PDFRef from '../objects/PDFRef'; import PDFNumber from '../objects/PDFNumber'; import { AnnotationTypes } from './AnnotationTypes'; +import PDFString from '../objects/PDFString'; class PDFAnnotation { readonly dict: PDFDict; @@ -15,7 +16,7 @@ class PDFAnnotation { this.dict = dict; } - // These is technically required by the PDF spec + // These is technically required by the PDF spec (See table 164) Subtype(): PDFName | undefined { return this.dict.lookup(PDFName.of('Subtype'), PDFName); } From 3d7b136cd59fabe4a5ec67af0fda63e365d241a6 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 16:10:43 +0900 Subject: [PATCH 05/44] annotation entries --- src/core/annotation/PDFAnnotation.ts | 95 +++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 101be27ad..e294288b5 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -16,24 +16,113 @@ class PDFAnnotation { this.dict = dict; } - // These is technically required by the PDF spec (See table 164) + // entries common to all annotation dictionaries (See table 164) + + /** + * annotation subtype + * @returns The subtype as a PDFName or undefined if none. + */ Subtype(): PDFName | undefined { return this.dict.lookup(PDFName.of('Subtype'), PDFName); } + /** + * location of annotation on page + * @returns The rectangle as a PDFArray or undefined if none. + */ Rect(): PDFArray | undefined { return this.dict.lookup(PDFName.of('Rect'), PDFArray); } - AP(): PDFDict | undefined { - return this.dict.lookupMaybe(PDFName.of('AP'), PDFDict); + /** + * text to be displayed for the annotation. + * @returns The text as a PDFString or undefined if none content) + */ + Contents(): PDFString | undefined { + return this.dict.lookup(PDFName.of('Contents'), PDFString); + } + + /** + * Indirect reference to the page object with which this annotation is associated. + * @returns The page object as a PDFRef or undefined if none. + */ + P(): PDFRef | undefined { + return this.dict.lookup(PDFName.of('P'), PDFRef); } + /** + * Name of the annotation, typically an identifier. + * @returns The name as a PDFString or undefined if none. + */ + NM(): PDFString | undefined { + return this.dict.lookup(PDFName.of('NM'), PDFString); + } + + /** + * Date and time when the annotation was created. + * @returns The date as a PDFString or undefined if none. + */ + M(): PDFString | undefined { + return this.dict.lookup(PDFName.of('M'), PDFString); + } + + /** + * A set of flags specifying various characteristics of the annotation. + * @returns The flags as a PDFNumber or undefined if none. + */ F(): PDFNumber | undefined { const numberOrRef = this.dict.lookup(PDFName.of('F')); return this.dict.context.lookupMaybe(numberOrRef, PDFNumber); } + /** + * appearance dictionary + * @returns The appearance dictionary as a PDFDict or undefined if none. + */ + AP(): PDFDict | undefined { + return this.dict.lookupMaybe(PDFName.of('AP'), PDFDict); + } + + /** + * Annotation's appearance state + * @returns The appearance state as a PDFName or undefined if none. + */ + AS(): PDFName | undefined { + return this.dict.lookupMaybe(PDFName.of('AS'), PDFName); + } + + /** + * Array specifying annotation's border characteristics + * @returns The border characteristics as a PDFArray or undefined if none. + */ + Boader(): PDFArray | undefined { + return this.dict.lookup(PDFName.of('Boader'), PDFArray); + } + + /** + * The annotation's color + * @returns The color as a PDFArray or undefined if none. + */ + C(): PDFArray | undefined { + return this.dict.lookup(PDFName.of('C'), PDFArray); + } + + /** + * Integer key of annotation's entry in structural parent tree + * @returns + */ + StructParent(): PDFNumber | undefined { + return this.dict.lookup(PDFName.of('StructParent'), PDFNumber); + } + + /** + * Optional content group or optional content membership dictionary + * @returns The optional content group as a PDFDict or undefined if none. + */ + OC(): PDFDict | undefined { + return this.dict.lookup(PDFName.of('OC'), PDFDict); + } + /** * Get the subtype enum. * This gives `undefined` if the subtype not written in the PDF. From befcfe7c7f2127472097e3f3a7bd03f8b5e5ff1c Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 16:31:42 +0900 Subject: [PATCH 06/44] PDF page converting annotations --- src/api/PDFPage.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 5982f6601..a95eacb94 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -45,6 +45,7 @@ import { PDFRef, PDFDict, PDFArray, + PDFAnnotation, } from '../core'; import { assertEachIs, @@ -104,6 +105,9 @@ export default class PDFPage { /** The document to which this page belongs. */ readonly doc: PDFDocument; + /** The annotations associated with this page. */ + readonly annotations: PDFAnnotation[] = []; + private fontKey?: PDFName; private font?: PDFFont; private fontSize = 24; @@ -122,6 +126,8 @@ export default class PDFPage { this.node = leafNode; this.ref = ref; this.doc = doc; + + this.annotations = this.readAnnotations(leafNode); } /** @@ -1691,4 +1697,30 @@ export default class PDFPage { } } } + + /** + * Read the annotation dictionaries from this page and convert to PDFAnnotation class instances + * @param pageLeaf The page leaf node + * @returns {PDFAnnotation[]} The annotations on this page + */ + private readAnnotations(pageLeaf: PDFPageLeaf): PDFAnnotation[] { + const annotsArray = pageLeaf.Annots(); + + // if there are no annotations... + if (!annotsArray || !(annotsArray instanceof PDFArray)) { + // ...return an empty array + return []; + } + + // convert the annotation dictionaries to PDFAnnotation instances + const annotations: PDFAnnotation[] = []; + for (let i = 0; i < annotsArray.size(); i++) { + const annotDict = annotsArray.lookup(i); + if (annotDict instanceof PDFDict) { + const pdfAnnotation = PDFAnnotation.fromDict(annotDict); + annotations.push(pdfAnnotation); + } + } + return annotations; + } } From b4043c6871a6b8511f4febdbb2746a62a074da1f Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 17:05:52 +0900 Subject: [PATCH 07/44] rename operations -> drawingOperations because adding `annotationOperation` --- src/api/PDFPage.ts | 2 +- src/api/{operations.ts => drawingOperations.ts} | 0 src/api/form/PDFField.ts | 2 +- src/api/form/PDFForm.ts | 2 +- src/api/form/appearances.ts | 2 +- src/api/index.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/api/{operations.ts => drawingOperations.ts} (100%) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index a95eacb94..d8f120461 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -7,7 +7,7 @@ import { drawRectangle, drawSvgPath, drawEllipse, -} from './operations'; +} from './drawingOperations'; import { popGraphicsState, pushGraphicsState, diff --git a/src/api/operations.ts b/src/api/drawingOperations.ts similarity index 100% rename from src/api/operations.ts rename to src/api/drawingOperations.ts diff --git a/src/api/form/PDFField.ts b/src/api/form/PDFField.ts index ce9977549..33d02f1b3 100644 --- a/src/api/form/PDFField.ts +++ b/src/api/form/PDFField.ts @@ -25,7 +25,7 @@ import { import { assertIs, assertMultiple, assertOrUndefined } from '../../utils'; import { ImageAlignment } from '../image'; import PDFImage from '../PDFImage'; -import { drawImage, rotateInPlace } from '../operations'; +import { drawImage, rotateInPlace } from '../drawingOperations'; export interface FieldAppearanceOptions { x?: number; diff --git a/src/api/form/PDFForm.ts b/src/api/form/PDFForm.ts index 78d766412..bbecf9a47 100644 --- a/src/api/form/PDFForm.ts +++ b/src/api/form/PDFForm.ts @@ -16,7 +16,7 @@ import { } from '../errors'; import PDFFont from '../PDFFont'; import { StandardFonts } from '../StandardFonts'; -import { rotateInPlace } from '../operations'; +import { rotateInPlace } from '../drawingOperations'; import { drawObject, popGraphicsState, diff --git a/src/api/form/appearances.ts b/src/api/form/appearances.ts index 65e466b0a..cb996dff2 100644 --- a/src/api/form/appearances.ts +++ b/src/api/form/appearances.ts @@ -15,7 +15,7 @@ import { drawButton, drawTextField, drawOptionList, -} from '../operations'; +} from '../drawingOperations'; import { rgb, componentsToColor, diff --git a/src/api/index.ts b/src/api/index.ts index 1df2e8b92..1c1a0d7e0 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -4,7 +4,7 @@ export * from './colors'; export * from './errors'; export * from './image'; export * from './objects'; -export * from './operations'; +export * from './drawingOperations'; export * from './operators'; export * from './rotations'; export * from './sizes'; From 613c8c2dc6883be1ce198d316334042dd9eb45b9 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Tue, 25 Feb 2025 22:55:58 +0900 Subject: [PATCH 08/44] Revert "rename operations -> drawingOperations" This reverts commit b4043c6871a6b8511f4febdbb2746a62a074da1f. --- src/api/PDFPage.ts | 2 +- src/api/form/PDFField.ts | 2 +- src/api/form/PDFForm.ts | 2 +- src/api/form/appearances.ts | 2 +- src/api/index.ts | 2 +- src/api/{drawingOperations.ts => operations.ts} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename src/api/{drawingOperations.ts => operations.ts} (100%) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index d8f120461..a95eacb94 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -7,7 +7,7 @@ import { drawRectangle, drawSvgPath, drawEllipse, -} from './drawingOperations'; +} from './operations'; import { popGraphicsState, pushGraphicsState, diff --git a/src/api/form/PDFField.ts b/src/api/form/PDFField.ts index 33d02f1b3..ce9977549 100644 --- a/src/api/form/PDFField.ts +++ b/src/api/form/PDFField.ts @@ -25,7 +25,7 @@ import { import { assertIs, assertMultiple, assertOrUndefined } from '../../utils'; import { ImageAlignment } from '../image'; import PDFImage from '../PDFImage'; -import { drawImage, rotateInPlace } from '../drawingOperations'; +import { drawImage, rotateInPlace } from '../operations'; export interface FieldAppearanceOptions { x?: number; diff --git a/src/api/form/PDFForm.ts b/src/api/form/PDFForm.ts index bbecf9a47..78d766412 100644 --- a/src/api/form/PDFForm.ts +++ b/src/api/form/PDFForm.ts @@ -16,7 +16,7 @@ import { } from '../errors'; import PDFFont from '../PDFFont'; import { StandardFonts } from '../StandardFonts'; -import { rotateInPlace } from '../drawingOperations'; +import { rotateInPlace } from '../operations'; import { drawObject, popGraphicsState, diff --git a/src/api/form/appearances.ts b/src/api/form/appearances.ts index cb996dff2..65e466b0a 100644 --- a/src/api/form/appearances.ts +++ b/src/api/form/appearances.ts @@ -15,7 +15,7 @@ import { drawButton, drawTextField, drawOptionList, -} from '../drawingOperations'; +} from '../operations'; import { rgb, componentsToColor, diff --git a/src/api/index.ts b/src/api/index.ts index 1c1a0d7e0..1df2e8b92 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -4,7 +4,7 @@ export * from './colors'; export * from './errors'; export * from './image'; export * from './objects'; -export * from './drawingOperations'; +export * from './operations'; export * from './operators'; export * from './rotations'; export * from './sizes'; diff --git a/src/api/drawingOperations.ts b/src/api/operations.ts similarity index 100% rename from src/api/drawingOperations.ts rename to src/api/operations.ts From 39811d18d2bc0eb31afb5014c1ec58453907eb6f Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Thu, 4 Sep 2025 01:12:18 +0700 Subject: [PATCH 09/44] add annotation object into sample.pdf --- tests/utils/data/simple.pdf | Bin 5207 -> 5486 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/utils/data/simple.pdf b/tests/utils/data/simple.pdf index 65e6bcf22e9534b003496eaa8cf295be2d2e0bae..1b0b8a25b1c09fa3dface8d6aac681e335500989 100644 GIT binary patch delta 502 zcmcbv@lI>Q87?6O1%1c7y!?`4g=j+q1p|ek*v+T74ze>D8chDcDOwMfOeR+g)z%yGav8vZf|;qQv8h6u0zwevWe`xvLlU$w!VokvF*n5&G&Q!w6f`$L z2xh}F(23}7GBGyCRB2*rVS%J_@xr$2?i%Ker VQqy?3Oe`$Tc)3(nUH#p7xd12&aRLAU delta 273 zcmaE-bzNh_8LrI_xc0M8t{2X&H{j(mfCB|HQ&VG8g*1eip^*YuRv`}|W{DwYWNKoJ zuFlBJ&=f<=+}r|HEZM-o*wg|;v$2JNIjTaSI!iM&%?1W0rbY$`btOfKnK`LNyj(Um tyj%(j`oWo1sS1V$li!P^N)(qQ7L`;Kr2_qCVqs#=%cZL7>hH$O1pr6qJA?oL From f655fc98db1e1271bdf1b5482dd7a57c34fd71c0 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Fri, 5 Sep 2025 23:02:34 +0700 Subject: [PATCH 10/44] fix: export AnnotationTypes --- src/core/annotation/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/annotation/index.ts b/src/core/annotation/index.ts index 2e42734fc..b8f037625 100644 --- a/src/core/annotation/index.ts +++ b/src/core/annotation/index.ts @@ -2,3 +2,4 @@ export { default as PDFAnnotation } from './PDFAnnotation'; export { default as PDFWidgetAnnotation } from './PDFWidgetAnnotation'; export { default as AppearanceCharacteristics } from './AppearanceCharacteristics'; export * from './flags'; +export * from './AnnotationTypes'; From 4e5645668929ab9c6b74f8987620d20f922a7dd1 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Fri, 5 Sep 2025 23:03:50 +0700 Subject: [PATCH 11/44] refac:fixed by lint --- src/core/annotation/PDFAnnotation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index e294288b5..116b3947d 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -130,8 +130,9 @@ class PDFAnnotation { */ getSubtype(): AnnotationTypes | undefined { const subtypePdfName = this.Subtype(); - if (subtypePdfName instanceof PDFName) + if (subtypePdfName instanceof PDFName) { return subtypePdfName.toString() as AnnotationTypes; + } return undefined; } From fbe291b97c9be2e47efcb9d8ba3f152248f3943a Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Fri, 5 Sep 2025 23:04:26 +0700 Subject: [PATCH 12/44] fix: seperate types of P --- src/core/annotation/PDFAnnotation.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 116b3947d..9d4147e95 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -6,6 +6,7 @@ import PDFRef from '../objects/PDFRef'; import PDFNumber from '../objects/PDFNumber'; import { AnnotationTypes } from './AnnotationTypes'; import PDFString from '../objects/PDFString'; +import PDFPageLeaf from '../structures/PDFPageLeaf'; class PDFAnnotation { readonly dict: PDFDict; @@ -47,7 +48,8 @@ class PDFAnnotation { * @returns The page object as a PDFRef or undefined if none. */ P(): PDFRef | undefined { - return this.dict.lookup(PDFName.of('P'), PDFRef); + const ref = this.dict.get(PDFName.of('P')); + return ref instanceof PDFRef ? ref : undefined; } /** @@ -251,6 +253,17 @@ class PDFAnnotation { if (enable) this.setFlag(flag); else this.clearFlag(flag); } + + getParentPage(): PDFPageLeaf | undefined { + const pageRef = this.P(); + if (!pageRef) return undefined; + + const page = this.dict.context.lookup(pageRef); + if (page instanceof PDFPageLeaf) { + return page; + } + return undefined; + } } export default PDFAnnotation; From 4debc5964d36225abb1a71609f812c8c0a30e9d4 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 00:03:16 +0700 Subject: [PATCH 13/44] feat:test annotation reading --- tests/core/annotation/PDFAnnotation.spec.ts | 73 +++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/core/annotation/PDFAnnotation.spec.ts diff --git a/tests/core/annotation/PDFAnnotation.spec.ts b/tests/core/annotation/PDFAnnotation.spec.ts new file mode 100644 index 000000000..7116e13b0 --- /dev/null +++ b/tests/core/annotation/PDFAnnotation.spec.ts @@ -0,0 +1,73 @@ +import fs from 'fs'; +import { + PDFDocument, + PDFAnnotation, + AnnotationTypes, + PDFNumber, + PDFName, +} from '../../../src/index'; + +const simplePdf = fs.readFileSync('tests/utils/data/simple.pdf'); + +describe('PDFAnnotation', () => { + describe('readAnnotations() method', () => { + it('properly reads highlight annotation objects from simple.pdf', async () => { + // Load simple.pdf which contains a highlight annotation + const pdfDoc = await PDFDocument.load(new Uint8Array(simplePdf)); + const page = pdfDoc.getPage(0); + + // Check that annotations were read correctly + expect(page.annotations).toBeDefined(); + expect(page.annotations.length).toBe(1); + + // Verify the annotation properties + const annotation = page.annotations[0]; + expect(annotation).toBeInstanceOf(PDFAnnotation); + + // Check annotation type + const subtype = annotation.getSubtype(); + expect(subtype).toBeDefined(); + expect(subtype).toBe(AnnotationTypes.Highlight); + + // Check annotation rectangle [100 100 200 124] + const rect = annotation.Rect(); + expect(rect).toBeDefined(); + expect(rect?.size()).toBe(4); + expect((rect?.lookup(0) as PDFNumber)?.asNumber()).toBe(100); + expect((rect?.lookup(1) as PDFNumber)?.asNumber()).toBe(100); + expect((rect?.lookup(2) as PDFNumber)?.asNumber()).toBe(200); + expect((rect?.lookup(3) as PDFNumber)?.asNumber()).toBe(124); + + // Check annotation contents + const contents = annotation.Contents(); + expect(contents).toBeDefined(); + expect(contents?.asString()).toBe('Highlight: Hello'); + + // Check page reference + const pageRef = annotation.getParentPage(); + expect(pageRef).toBeDefined(); + + // Check color array [1 1 0] (yellow) + const color = annotation.dict.lookup(PDFName.of('C')); + expect(color).toBeDefined(); + + // Check title/author + const title = annotation.dict.lookup(PDFName.of('T')); + expect(title).toBeDefined(); + + // Check flags + const flags = annotation.dict.lookup(PDFName.of('F')); + expect(flags).toBeDefined(); + }); + + it('returns empty array when page has no annotations', async () => { + // Create a new document with no annotations + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage(); + + // Check that annotations array is empty + expect(page.annotations).toBeDefined(); + expect(page.annotations.length).toBe(0); + }); + }); +}); From 8652794c4f9c9b809223d92e400aaf5ab065bfcc Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 00:47:03 +0700 Subject: [PATCH 14/44] feat: add creation method for annotation object --- src/core/annotation/PDFAnnotation.ts | 47 ++++++++++++++++++++++ src/core/annotation/PDFAnnotationOption.ts | 16 ++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/core/annotation/PDFAnnotationOption.ts diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 9d4147e95..29ce139e9 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -7,12 +7,59 @@ import PDFNumber from '../objects/PDFNumber'; import { AnnotationTypes } from './AnnotationTypes'; import PDFString from '../objects/PDFString'; import PDFPageLeaf from '../structures/PDFPageLeaf'; +import { AnnotationOptions } from './PDFAnnotationOption'; class PDFAnnotation { readonly dict: PDFDict; static fromDict = (dict: PDFDict): PDFAnnotation => new PDFAnnotation(dict); + static Create = (options: AnnotationOptions): PDFAnnotation => { + const page = options.page; + const context = page.doc.context; + const dict = context.obj({ + Type: 'Annot', + Subtype: options.subtype, + Rect: [ + options.rect.x, + options.rect.y, + options.rect.x + options.rect.width, + options.rect.y + options.rect.height, + ], + }); + + if (options.contents !== undefined) { + dict.set(PDFName.of('Contents'), PDFString.of(options.contents)); + } + + if (options.name !== undefined) { + dict.set(PDFName.of('NM'), PDFString.of(options.name)); + } + + // Set the page reference directly from the provided PDFPage + dict.set(PDFName.of('P'), page.ref); + + if (options.flags !== undefined) { + dict.set(PDFName.of('F'), PDFNumber.of(options.flags)); + } + + if (options.color !== undefined) { + const colorArray = context.obj(options.color); + dict.set(PDFName.of('C'), colorArray); + } + + if (options.border !== undefined) { + const borderArray = context.obj(options.border); + dict.set(PDFName.of('Border'), borderArray); + } + + if (options.modificationDate !== undefined) { + dict.set(PDFName.of('M'), PDFString.of(options.modificationDate)); + } + + return new PDFAnnotation(dict); + }; + protected constructor(dict: PDFDict) { this.dict = dict; } diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts new file mode 100644 index 000000000..815ce7398 --- /dev/null +++ b/src/core/annotation/PDFAnnotationOption.ts @@ -0,0 +1,16 @@ +import PDFPage from '../../api/PDFPage'; +import { AnnotationTypes } from './AnnotationTypes'; + +export interface AnnotationOptions { + subtype: AnnotationTypes; + rect: { x: number; y: number; width: number; height: number }; + page: PDFPage; + contents?: string; + name?: string; + flags?: number; + color?: [number, number, number] | [number, number, number, number]; + border?: + | [number, number, number] + | [number, number, number, number, number, number]; + modificationDate?: string; +} From ea97ecef209b0a20aff2d719f8b60e3cf87bfc32 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 01:32:01 +0700 Subject: [PATCH 15/44] feat: register created Annotation object to PDF --- src/api/PDFPage.ts | 18 +++++++++++++++++ src/core/annotation/PDFAnnotation.ts | 23 ++++++++++++++++------ src/core/annotation/PDFAnnotationOption.ts | 2 -- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index a95eacb94..45034f1d1 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -60,6 +60,7 @@ import { assertIsOneOfOrUndefined, } from '../utils'; import { drawSvg } from './svg'; +import { AnnotationOptions } from 'src/core/annotation/PDFAnnotationOption'; /** * Represents a single page of a [[PDFDocument]]. @@ -662,6 +663,23 @@ export default class PDFPage { } } + /** + * Add an annotation to this page from an existing PDFAnnotation instance. + */ + addAnnotation(AnnotationOptions: AnnotationOptions): void { + const annotation = PDFAnnotation.Create( + this.doc.context, + this.node, + AnnotationOptions, + ); + + // Register the annotation dictionary in the context to get a PDFRef + const annotationRef = this.doc.context.register(annotation.dict); + + // Add the PDFRef to the page's annotations array + this.node.addAnnot(annotationRef); + } + /** * Reset the x and y coordinates of this page to `(0, 0)`. This operation is * often useful after calling [[translateContent]]. For example: diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 29ce139e9..5bc1d0e9e 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -8,15 +8,18 @@ import { AnnotationTypes } from './AnnotationTypes'; import PDFString from '../objects/PDFString'; import PDFPageLeaf from '../structures/PDFPageLeaf'; import { AnnotationOptions } from './PDFAnnotationOption'; +import PDFContext from '../PDFContext'; class PDFAnnotation { readonly dict: PDFDict; static fromDict = (dict: PDFDict): PDFAnnotation => new PDFAnnotation(dict); - static Create = (options: AnnotationOptions): PDFAnnotation => { - const page = options.page; - const context = page.doc.context; + static Create = ( + context: PDFContext, + page: PDFPageLeaf, + options: AnnotationOptions, + ): PDFAnnotation => { const dict = context.obj({ Type: 'Annot', Subtype: options.subtype, @@ -36,8 +39,14 @@ class PDFAnnotation { dict.set(PDFName.of('NM'), PDFString.of(options.name)); } - // Set the page reference directly from the provided PDFPage - dict.set(PDFName.of('P'), page.ref); + // Set the page reference by getting the PDFRef for the PDFPageLeaf + const pageRef = context.getObjectRef(page); + if (!pageRef) { + throw new Error( + 'Could not find PDFRef for the provided PDFPageLeaf. The page must be registered in the PDF context.', + ); + } + dict.set(PDFName.of('P'), pageRef); if (options.flags !== undefined) { dict.set(PDFName.of('F'), PDFNumber.of(options.flags)); @@ -57,7 +66,9 @@ class PDFAnnotation { dict.set(PDFName.of('M'), PDFString.of(options.modificationDate)); } - return new PDFAnnotation(dict); + const annotation = new PDFAnnotation(dict); + + return annotation; }; protected constructor(dict: PDFDict) { diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts index 815ce7398..ed7ca77f6 100644 --- a/src/core/annotation/PDFAnnotationOption.ts +++ b/src/core/annotation/PDFAnnotationOption.ts @@ -1,10 +1,8 @@ -import PDFPage from '../../api/PDFPage'; import { AnnotationTypes } from './AnnotationTypes'; export interface AnnotationOptions { subtype: AnnotationTypes; rect: { x: number; y: number; width: number; height: number }; - page: PDFPage; contents?: string; name?: string; flags?: number; From 4e3d4fb5d6112ae7e0b78cd2e6f588171e429087 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 01:38:23 +0700 Subject: [PATCH 16/44] Revert "add annotation object into sample.pdf" This reverts commit 39811d18d2bc0eb31afb5014c1ec58453907eb6f. --- tests/utils/data/simple.pdf | Bin 5486 -> 5207 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/utils/data/simple.pdf b/tests/utils/data/simple.pdf index 1b0b8a25b1c09fa3dface8d6aac681e335500989..65e6bcf22e9534b003496eaa8cf295be2d2e0bae 100644 GIT binary patch delta 273 zcmaE-bzNh_8LrI_xc0M8t{2X&H{j(mfCB|HQ&VG8g*1eip^*YuRv`}|W{DwYWNKoJ zuFlBJ&=f<=+}r|HEZM-o*wg|;v$2JNIjTaSI!iM&%?1W0rbY$`btOfKnK`LNyj(Um tyj%(j`oWo1sS1V$li!P^N)(qQ7L`;Kr2_qCVqs#=%cZL7>hH$O1pr6qJA?oL delta 502 zcmcbv@lI>Q87?6O1%1c7y!?`4g=j+q1p|ek*v+T74ze>D8chDcDOwMfOeR+g)z%yGav8vZf|;qQv8h6u0zwevWe`xvLlU$w!VokvF*n5&G&Q!w6f`$L z2xh}F(23}7GBGyCRB2*rVS%J_@xr$2?i%Ker VQqy?3Oe`$Tc)3(nUH#p7xd12&aRLAU From 77e6ba8bfb8119f85b0e32fbba96e798ca1e2045 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 01:44:45 +0700 Subject: [PATCH 17/44] fix: move edited pdf sample --- tests/core/data/simple.pdf | Bin 0 -> 5529 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/core/data/simple.pdf diff --git a/tests/core/data/simple.pdf b/tests/core/data/simple.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9b0eed642e5dc89e240f617176df01d3271e0a46 GIT binary patch literal 5529 zcmbtYZExa868?_Fe<zI~0dI`W_+=pX>+h*< z+Ze*k-f1FraF?sAs-Ld6_W8rjN5kn^-A=b-lSS$LuWn~D>2`?B+3QA#sNQi-{dd5}e$B1#itv0|Q<#pT&qC3^$eXO-+E?x`_;V5M&@<8dCj8bs+cxUvl(3V^sxuLw=_F)=GK;~#XpXE7&M@7$}CoK5C*1< zLYO7GT9U3x$X%)!rrR=>=@=J@< z7}q|i+ayS{O`5^?!f#s8mZK~<*2gx4`()Bms=}qc0~Oh9+_ylwk?S-;>Qv`J>4L;D zYK&Izo5r@4DPvC=#s;gjlK+scO};sqlX`R)1rqr@g!faiLJlnF@H8ruP-dh;C;BSs zI$iUY>8dEQXkKzUGoDr=lUH%{BFRI-x=jk1B?=M|6J)U1{{w@Ay=v%MIjq9qe@tzU zoR0G*)($E|o#3>-cDUh6}g!U6Q^UIky%6dV^!n+{`;`5*GG`v-;Zz7pyUL1 zdowM|C>*;|E*yI>Fov${7-By63^#Ob!}nd+>-)o@?;^zk_rtYKrqgfGh7=+RWG!_x zvgbh{eM3s$LU|FcF|vh#)cIUG_RwC0!&+) zwOcaX;;9~vwV1y|VNTB;cXNL#!gX3E1)*O%Z0(riv>dhS0da?ARaXeP_cDychjd5J z7FG^yhuqPi)_|}?X`$yntEItS`D5isIq8lRfA#fW{Y@y{Ay* zD`da60*LTvOE6UyEyV?dN7uZf|}XE#$61Y3|3?~^$!OI!!_U}ZIj?6uOyPnK%oUS=EF>_AD672Lyw<$ z?h~$6tc3@BfqxkB_F!7j?aZ}yz==Lkd*^{yGOpW|C5JBLW70rt%7c-tlM#A0@H@43AmJvv)1E zKhX@A*q^cF4zQ@tCdxdMkl;nBx?{$(p_KK#}q z7&pbDF;dUG38vKpRi2yy0u#Rs1}e$Eh@R7o{rBi71Dvdg(1E5&{HlU4lV8e+GZ;!j z7U3%i=cMY^!jZw#GLh@K3`1~nuMmnXB-_I)u81nED!Fur#x4PfKWN^3}j)PQb0hEzzGXM~Di%&sOaHSG8J{f^> zm80(~$hU`T%8GezIF>qfd_CHHmMYEGEKh}8|BHs2|31pOQmQb)ZctbcLxGF2y6g!q z?A2{%{uinR(!*DvKo=_TFHi-z|~(?aPU2@2JkR^6+057! z?y2SOV2BcRwA>xS!egT6`foYC{tyYaS>Em7mW;bF8ieYz=Tr0_sStpBKc*?V-{x3u zZ|IpOt{wPdD4fAadO?3Q58NR1!-W;%p0IF-!|C61a6GZ6^Y=}hiN@U Date: Sat, 6 Sep 2025 01:45:19 +0700 Subject: [PATCH 18/44] fix: move path --- tests/core/annotation/PDFAnnotation.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/annotation/PDFAnnotation.spec.ts b/tests/core/annotation/PDFAnnotation.spec.ts index 7116e13b0..93b7697af 100644 --- a/tests/core/annotation/PDFAnnotation.spec.ts +++ b/tests/core/annotation/PDFAnnotation.spec.ts @@ -7,7 +7,7 @@ import { PDFName, } from '../../../src/index'; -const simplePdf = fs.readFileSync('tests/utils/data/simple.pdf'); +const simplePdf = fs.readFileSync('tests/core/data/simple.pdf'); describe('PDFAnnotation', () => { describe('readAnnotations() method', () => { From 78aa772df3d53567f8df3ba80ecf38235ad24a9f Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 12:59:04 +0700 Subject: [PATCH 19/44] fix: correct method name from Create to create in PDFAnnotation class --- src/core/annotation/PDFAnnotation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 5bc1d0e9e..17f4ad9a7 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -15,7 +15,7 @@ class PDFAnnotation { static fromDict = (dict: PDFDict): PDFAnnotation => new PDFAnnotation(dict); - static Create = ( + static create = ( context: PDFContext, page: PDFPageLeaf, options: AnnotationOptions, From 9e89bdcf6675acdcacde2bdbbd051aa0272b5297 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 13:02:52 +0700 Subject: [PATCH 20/44] fix: rename PDFWidgetAnnotation.create to PDFWidgetAnnotation.createEmpty for avoid duplication --- src/api/form/PDFField.ts | 2 +- src/core/annotation/PDFWidgetAnnotation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/form/PDFField.ts b/src/api/form/PDFField.ts index ce9977549..4d756ecf1 100644 --- a/src/api/form/PDFField.ts +++ b/src/api/form/PDFField.ts @@ -306,7 +306,7 @@ export default class PDFField { assertMultiple(degreesAngle, 'degreesAngle', 90); // Create a widget for this field - const widget = PDFWidgetAnnotation.create(this.doc.context, this.ref); + const widget = PDFWidgetAnnotation.createEmpty(this.doc.context, this.ref); // Set widget properties const rect = rotateRectangle( diff --git a/src/core/annotation/PDFWidgetAnnotation.ts b/src/core/annotation/PDFWidgetAnnotation.ts index 0e894e2c8..ebeb0ef4a 100644 --- a/src/core/annotation/PDFWidgetAnnotation.ts +++ b/src/core/annotation/PDFWidgetAnnotation.ts @@ -12,7 +12,7 @@ class PDFWidgetAnnotation extends PDFAnnotation { static fromDict = (dict: PDFDict): PDFWidgetAnnotation => new PDFWidgetAnnotation(dict); - static create = (context: PDFContext, parent: PDFRef) => { + static createEmpty = (context: PDFContext, parent: PDFRef) => { const dict = context.obj({ Type: 'Annot', Subtype: 'Widget', From d2e888dd455689013b24a4c2bc05a838668a46e5 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 13:07:35 +0700 Subject: [PATCH 21/44] fix: option typing --- src/core/annotation/PDFAnnotationOption.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts index ed7ca77f6..0bb9e1a2e 100644 --- a/src/core/annotation/PDFAnnotationOption.ts +++ b/src/core/annotation/PDFAnnotationOption.ts @@ -6,9 +6,10 @@ export interface AnnotationOptions { contents?: string; name?: string; flags?: number; - color?: [number, number, number] | [number, number, number, number]; - border?: + color?: + | [number] | [number, number, number] - | [number, number, number, number, number, number]; + | [number, number, number, number]; + border?: [number, number, number]; modificationDate?: string; } From 228f59f33219eb6430eb7b35a5c137c23bc97c40 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 13:08:34 +0700 Subject: [PATCH 22/44] fix: able to set array number freely --- src/core/annotation/PDFAnnotationOption.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts index 0bb9e1a2e..5667407ee 100644 --- a/src/core/annotation/PDFAnnotationOption.ts +++ b/src/core/annotation/PDFAnnotationOption.ts @@ -6,10 +6,7 @@ export interface AnnotationOptions { contents?: string; name?: string; flags?: number; - color?: - | [number] - | [number, number, number] - | [number, number, number, number]; - border?: [number, number, number]; + color?: number[]; + border?: number[]; modificationDate?: string; } From ac3eb234c43e5739fb8f77dc50c1adb86c0eb3bf Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 16:11:58 +0700 Subject: [PATCH 23/44] fix: rename PDFAnnotation.create to PDFAnnotation.createBase and add PDFMarkupAnnotation class --- src/core/annotation/PDFAnnotation.ts | 2 +- src/core/annotation/PDFAnnotationOption.ts | 4 ++++ src/core/annotation/PDFMarkupAnnotation.ts | 27 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/core/annotation/PDFMarkupAnnotation.ts diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 17f4ad9a7..d8fc327e7 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -15,7 +15,7 @@ class PDFAnnotation { static fromDict = (dict: PDFDict): PDFAnnotation => new PDFAnnotation(dict); - static create = ( + protected static createBase = ( context: PDFContext, page: PDFPageLeaf, options: AnnotationOptions, diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts index 5667407ee..39af1c657 100644 --- a/src/core/annotation/PDFAnnotationOption.ts +++ b/src/core/annotation/PDFAnnotationOption.ts @@ -10,3 +10,7 @@ export interface AnnotationOptions { border?: number[]; modificationDate?: string; } + +export interface MarkupAnnotationOptions extends AnnotationOptions { + quadPoints: [number, number, number, number, number, number, number, number]; +} diff --git a/src/core/annotation/PDFMarkupAnnotation.ts b/src/core/annotation/PDFMarkupAnnotation.ts new file mode 100644 index 000000000..9d2db94b4 --- /dev/null +++ b/src/core/annotation/PDFMarkupAnnotation.ts @@ -0,0 +1,27 @@ +import PDFContext from '../PDFContext'; +import PDFPageLeaf from '../structures/PDFPageLeaf'; +import PDFAnnotation from './PDFAnnotation'; +import PDFName from '../objects/PDFName'; +import PDFNumber from '../objects/PDFNumber'; +import { MarkupAnnotationOptions } from './PDFAnnotationOption'; + +export default class PDFMarkupAnnotation extends PDFAnnotation { + static create( + context: PDFContext, + page: PDFPageLeaf, + options: MarkupAnnotationOptions, + ): PDFMarkupAnnotation { + // Create the base annotation using PDFAnnotation.create() + const baseAnnotation = PDFAnnotation.createBase(context, page, options); + + // Create a new PDFMarkupAnnotation with the same dictionary + const markupAnnotation = new PDFMarkupAnnotation(baseAnnotation.dict); + + const quadPointsArray = context.obj( + options.quadPoints.map((point) => PDFNumber.of(point)), + ); + markupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); + + return markupAnnotation; + } +} From 53675ca9c1b40dc20d273ed4b62fbecdf7d9dad9 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 21:30:52 +0700 Subject: [PATCH 24/44] fix: create PDFAnnotation subclasses from Factory class --- src/api/PDFPage.ts | 5 +++-- src/core/annotation/AnnotationFactory.ts | 28 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/core/annotation/AnnotationFactory.ts diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 45034f1d1..293ddd28a 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -61,6 +61,7 @@ import { } from '../utils'; import { drawSvg } from './svg'; import { AnnotationOptions } from 'src/core/annotation/PDFAnnotationOption'; +import AnnotationFactory from 'src/core/annotation/AnnotationFactory'; /** * Represents a single page of a [[PDFDocument]]. @@ -667,7 +668,7 @@ export default class PDFPage { * Add an annotation to this page from an existing PDFAnnotation instance. */ addAnnotation(AnnotationOptions: AnnotationOptions): void { - const annotation = PDFAnnotation.Create( + const annotation = PDFAnnotation.create( this.doc.context, this.node, AnnotationOptions, @@ -1735,7 +1736,7 @@ export default class PDFPage { for (let i = 0; i < annotsArray.size(); i++) { const annotDict = annotsArray.lookup(i); if (annotDict instanceof PDFDict) { - const pdfAnnotation = PDFAnnotation.fromDict(annotDict); + const pdfAnnotation = AnnotationFactory.fromDict(annotDict); annotations.push(pdfAnnotation); } } diff --git a/src/core/annotation/AnnotationFactory.ts b/src/core/annotation/AnnotationFactory.ts new file mode 100644 index 000000000..dc69d6b2e --- /dev/null +++ b/src/core/annotation/AnnotationFactory.ts @@ -0,0 +1,28 @@ +import PDFDict from '../objects/PDFDict'; +import PDFName from '../objects/PDFName'; +import PDFAnnotation from './PDFAnnotation'; +import PDFMarkupAnnotation from './PDFMarkupAnnotation'; +import { AnnotationTypes } from './AnnotationTypes'; + +export default class AnnotationFactory { + static fromDict = (dict: PDFDict): PDFAnnotation => { + switch (this.getSubtype(dict)) { + case AnnotationTypes.Highlight: + case AnnotationTypes.Underline: + case AnnotationTypes.Squiggly: + case AnnotationTypes.StrikeOut: + return PDFMarkupAnnotation.fromDict(dict); + default: + return PDFAnnotation.fromDict(dict); + } + }; + + private static getSubtype(dict: PDFDict): AnnotationTypes | undefined { + const subtypePdfName = dict.get(PDFName.of('Subtype')); + if (subtypePdfName instanceof PDFName) { + return subtypePdfName.toString() as AnnotationTypes; + } else { + return undefined; + } + } +} From 6d7da790f80ffc8e83a922de608e218791e7b8f2 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 22:02:32 +0700 Subject: [PATCH 25/44] refac: easier API to add Text Markup Annotation --- src/api/PDFPage.ts | 43 +++++++++++-------- src/api/PDFPageOptions.ts | 42 ++++++++++++++++++ src/core/annotation/AnnotationFactory.ts | 4 +- src/core/annotation/PDFAnnotation.ts | 4 +- src/core/annotation/PDFAnnotationOption.ts | 16 ------- src/core/annotation/PDFMarkupAnnotation.ts | 27 ------------ .../annotation/PDFTextMarkupAnnotation.ts | 36 ++++++++++++++++ 7 files changed, 107 insertions(+), 65 deletions(-) delete mode 100644 src/core/annotation/PDFAnnotationOption.ts delete mode 100644 src/core/annotation/PDFMarkupAnnotation.ts create mode 100644 src/core/annotation/PDFTextMarkupAnnotation.ts diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 293ddd28a..d8f9b4b00 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -33,6 +33,7 @@ import { PDFPageDrawTextOptions, BlendMode, PDFPageDrawSVGElementOptions, + PDFPageAddTextMarkupAnnotationOptions, } from './PDFPageOptions'; import { degrees, Rotation, toDegrees } from './rotations'; import { StandardFonts } from './StandardFonts'; @@ -60,8 +61,8 @@ import { assertIsOneOfOrUndefined, } from '../utils'; import { drawSvg } from './svg'; -import { AnnotationOptions } from 'src/core/annotation/PDFAnnotationOption'; import AnnotationFactory from 'src/core/annotation/AnnotationFactory'; +import PDFTextMarkupAnnotation from 'src/core/annotation/PDFTextMarkupAnnotation'; /** * Represents a single page of a [[PDFDocument]]. @@ -664,23 +665,6 @@ export default class PDFPage { } } - /** - * Add an annotation to this page from an existing PDFAnnotation instance. - */ - addAnnotation(AnnotationOptions: AnnotationOptions): void { - const annotation = PDFAnnotation.create( - this.doc.context, - this.node, - AnnotationOptions, - ); - - // Register the annotation dictionary in the context to get a PDFRef - const annotationRef = this.doc.context.register(annotation.dict); - - // Add the PDFRef to the page's annotations array - this.node.addAnnot(annotationRef); - } - /** * Reset the x and y coordinates of this page to `(0, 0)`. This operation is * often useful after calling [[translateContent]]. For example: @@ -1742,4 +1726,27 @@ export default class PDFPage { } return annotations; } + + /** + * Add an annotation to this page from an existing PDFAnnotation instance. + */ + addTextMarkupAnnotation( + options: PDFPageAddTextMarkupAnnotationOptions, + ): void { + const context = this.doc.context; + const page = this.node; + + // convert to PDFDict + const annotationDict = PDFTextMarkupAnnotation.create( + context, + page, + options, + ).dict; + + // register PDF object into the PDF + const ref = context.register(annotationDict); + + // add the annotation to the page's Annots array + page.addAnnot(ref); + } } diff --git a/src/api/PDFPageOptions.ts b/src/api/PDFPageOptions.ts index b5013d224..8eff8c183 100644 --- a/src/api/PDFPageOptions.ts +++ b/src/api/PDFPageOptions.ts @@ -3,6 +3,7 @@ import PDFFont from './PDFFont'; import { Rotation } from './rotations'; import { FillRule, LineCapStyle, TextRenderingMode } from './operators'; import type { Space, TransformationMatrix } from '../types'; +import { AnnotationTypes } from '../core/annotation/AnnotationTypes'; interface SvgOptions { matrix?: TransformationMatrix; @@ -178,3 +179,44 @@ export interface PDFPageDrawSVGElementOptions { fonts?: { [fontName: string]: PDFFont }; blendMode?: BlendMode; } + +/** + * Options for adding an annotation to a PDF page. + */ +export interface PDFPageAddAnnotationOptions { + subtype: AnnotationTypes; + rect: { x: number; y: number; width: number; height: number }; + contents?: string; + name?: string; + flags?: number; + color?: number[]; + border?: number[]; + modificationDate?: string; +} + +/** + * Options for adding a markup annotation to a PDF page. + * + * Markup annotations refers: + * - Highlight + * - Underline + * - Squiggly + * - StrikeOut + * annotations. + */ +export interface PDFPageAddTextMarkupAnnotationOptions + extends PDFPageAddAnnotationOptions { + /** + * The quad points that define the region(s) to be marked up. + */ + quadPoints: { + lefttopX: number; + lefttopY: number; + righttopX: number; + righttopY: number; + rightbottomX: number; + rightbottomY: number; + leftbottomX: number; + leftbottomY: number; + }; +} diff --git a/src/core/annotation/AnnotationFactory.ts b/src/core/annotation/AnnotationFactory.ts index dc69d6b2e..7d1b3a6ae 100644 --- a/src/core/annotation/AnnotationFactory.ts +++ b/src/core/annotation/AnnotationFactory.ts @@ -1,7 +1,7 @@ import PDFDict from '../objects/PDFDict'; import PDFName from '../objects/PDFName'; import PDFAnnotation from './PDFAnnotation'; -import PDFMarkupAnnotation from './PDFMarkupAnnotation'; +import PDFTextMarkupAnnotation from './PDFTextMarkupAnnotation'; import { AnnotationTypes } from './AnnotationTypes'; export default class AnnotationFactory { @@ -11,7 +11,7 @@ export default class AnnotationFactory { case AnnotationTypes.Underline: case AnnotationTypes.Squiggly: case AnnotationTypes.StrikeOut: - return PDFMarkupAnnotation.fromDict(dict); + return PDFTextMarkupAnnotation.fromDict(dict); default: return PDFAnnotation.fromDict(dict); } diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index d8fc327e7..0f6fd764a 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -7,7 +7,7 @@ import PDFNumber from '../objects/PDFNumber'; import { AnnotationTypes } from './AnnotationTypes'; import PDFString from '../objects/PDFString'; import PDFPageLeaf from '../structures/PDFPageLeaf'; -import { AnnotationOptions } from './PDFAnnotationOption'; +import { PDFPageAddAnnotationOptions } from '../../api/PDFPageOptions'; import PDFContext from '../PDFContext'; class PDFAnnotation { @@ -18,7 +18,7 @@ class PDFAnnotation { protected static createBase = ( context: PDFContext, page: PDFPageLeaf, - options: AnnotationOptions, + options: PDFPageAddAnnotationOptions, ): PDFAnnotation => { const dict = context.obj({ Type: 'Annot', diff --git a/src/core/annotation/PDFAnnotationOption.ts b/src/core/annotation/PDFAnnotationOption.ts deleted file mode 100644 index 39af1c657..000000000 --- a/src/core/annotation/PDFAnnotationOption.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AnnotationTypes } from './AnnotationTypes'; - -export interface AnnotationOptions { - subtype: AnnotationTypes; - rect: { x: number; y: number; width: number; height: number }; - contents?: string; - name?: string; - flags?: number; - color?: number[]; - border?: number[]; - modificationDate?: string; -} - -export interface MarkupAnnotationOptions extends AnnotationOptions { - quadPoints: [number, number, number, number, number, number, number, number]; -} diff --git a/src/core/annotation/PDFMarkupAnnotation.ts b/src/core/annotation/PDFMarkupAnnotation.ts deleted file mode 100644 index 9d2db94b4..000000000 --- a/src/core/annotation/PDFMarkupAnnotation.ts +++ /dev/null @@ -1,27 +0,0 @@ -import PDFContext from '../PDFContext'; -import PDFPageLeaf from '../structures/PDFPageLeaf'; -import PDFAnnotation from './PDFAnnotation'; -import PDFName from '../objects/PDFName'; -import PDFNumber from '../objects/PDFNumber'; -import { MarkupAnnotationOptions } from './PDFAnnotationOption'; - -export default class PDFMarkupAnnotation extends PDFAnnotation { - static create( - context: PDFContext, - page: PDFPageLeaf, - options: MarkupAnnotationOptions, - ): PDFMarkupAnnotation { - // Create the base annotation using PDFAnnotation.create() - const baseAnnotation = PDFAnnotation.createBase(context, page, options); - - // Create a new PDFMarkupAnnotation with the same dictionary - const markupAnnotation = new PDFMarkupAnnotation(baseAnnotation.dict); - - const quadPointsArray = context.obj( - options.quadPoints.map((point) => PDFNumber.of(point)), - ); - markupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); - - return markupAnnotation; - } -} diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts new file mode 100644 index 000000000..70fbd166f --- /dev/null +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -0,0 +1,36 @@ +import PDFContext from '../PDFContext'; +import PDFPageLeaf from '../structures/PDFPageLeaf'; +import PDFAnnotation from './PDFAnnotation'; +import PDFName from '../objects/PDFName'; +import PDFNumber from '../objects/PDFNumber'; +import { PDFPageAddTextMarkupAnnotationOptions } from '../../api/PDFPageOptions'; + +export default class PDFTextMarkupAnnotation extends PDFAnnotation { + static create( + context: PDFContext, + page: PDFPageLeaf, + options: PDFPageAddTextMarkupAnnotationOptions, + ): PDFTextMarkupAnnotation { + // Create the base annotation using PDFAnnotation.create() + const baseAnnotation = PDFAnnotation.createBase(context, page, options); + + // Create a new PDFMarkupAnnotation with the same dictionary + const markupAnnotation = new PDFTextMarkupAnnotation(baseAnnotation.dict); + + const quadPointsArray = context.obj( + [ + options.quadPoints.leftbottomX, + options.quadPoints.leftbottomY, + options.quadPoints.rightbottomX, + options.quadPoints.rightbottomY, + options.quadPoints.lefttopX, + options.quadPoints.lefttopY, + options.quadPoints.righttopX, + options.quadPoints.righttopY, + ].map((point) => PDFNumber.of(point)), + ); + markupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); + + return markupAnnotation; + } +} From 3cb8171d0d367c059514d0e10f17202073e23bf6 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 22:56:50 +0700 Subject: [PATCH 26/44] fix: fixed error with testing --- src/api/PDFPage.ts | 9 ++---- src/api/form/PDFField.ts | 2 +- src/core/annotation/AnnotationFactory.ts | 2 +- .../annotation/PDFTextMarkupAnnotation.ts | 30 +++++++++++++++++-- src/core/annotation/PDFWidgetAnnotation.ts | 2 +- src/core/annotation/index.ts | 1 + tests/core/annotation/PDFAnnotation.spec.ts | 15 ++++++++-- 7 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index d8f9b4b00..e2f41b66b 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -108,9 +108,6 @@ export default class PDFPage { /** The document to which this page belongs. */ readonly doc: PDFDocument; - /** The annotations associated with this page. */ - readonly annotations: PDFAnnotation[] = []; - private fontKey?: PDFName; private font?: PDFFont; private fontSize = 24; @@ -129,8 +126,6 @@ export default class PDFPage { this.node = leafNode; this.ref = ref; this.doc = doc; - - this.annotations = this.readAnnotations(leafNode); } /** @@ -1706,8 +1701,8 @@ export default class PDFPage { * @param pageLeaf The page leaf node * @returns {PDFAnnotation[]} The annotations on this page */ - private readAnnotations(pageLeaf: PDFPageLeaf): PDFAnnotation[] { - const annotsArray = pageLeaf.Annots(); + annotations(): PDFAnnotation[] { + const annotsArray = this.node.Annots(); // if there are no annotations... if (!annotsArray || !(annotsArray instanceof PDFArray)) { diff --git a/src/api/form/PDFField.ts b/src/api/form/PDFField.ts index 4d756ecf1..ce9977549 100644 --- a/src/api/form/PDFField.ts +++ b/src/api/form/PDFField.ts @@ -306,7 +306,7 @@ export default class PDFField { assertMultiple(degreesAngle, 'degreesAngle', 90); // Create a widget for this field - const widget = PDFWidgetAnnotation.createEmpty(this.doc.context, this.ref); + const widget = PDFWidgetAnnotation.create(this.doc.context, this.ref); // Set widget properties const rect = rotateRectangle( diff --git a/src/core/annotation/AnnotationFactory.ts b/src/core/annotation/AnnotationFactory.ts index 7d1b3a6ae..8e6428235 100644 --- a/src/core/annotation/AnnotationFactory.ts +++ b/src/core/annotation/AnnotationFactory.ts @@ -18,7 +18,7 @@ export default class AnnotationFactory { }; private static getSubtype(dict: PDFDict): AnnotationTypes | undefined { - const subtypePdfName = dict.get(PDFName.of('Subtype')); + const subtypePdfName = dict.lookup(PDFName.of('Subtype'), PDFName); if (subtypePdfName instanceof PDFName) { return subtypePdfName.toString() as AnnotationTypes; } else { diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts index 70fbd166f..1a9232dff 100644 --- a/src/core/annotation/PDFTextMarkupAnnotation.ts +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -3,9 +3,14 @@ import PDFPageLeaf from '../structures/PDFPageLeaf'; import PDFAnnotation from './PDFAnnotation'; import PDFName from '../objects/PDFName'; import PDFNumber from '../objects/PDFNumber'; +import PDFArray from '../objects/PDFArray'; +import PDFDict from '../objects/PDFDict'; import { PDFPageAddTextMarkupAnnotationOptions } from '../../api/PDFPageOptions'; export default class PDFTextMarkupAnnotation extends PDFAnnotation { + static fromDict = (dict: PDFDict): PDFTextMarkupAnnotation => + new PDFTextMarkupAnnotation(dict); + static create( context: PDFContext, page: PDFPageLeaf, @@ -15,7 +20,9 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { const baseAnnotation = PDFAnnotation.createBase(context, page, options); // Create a new PDFMarkupAnnotation with the same dictionary - const markupAnnotation = new PDFTextMarkupAnnotation(baseAnnotation.dict); + const textmarkupAnnotation = new PDFTextMarkupAnnotation( + baseAnnotation.dict, + ); const quadPointsArray = context.obj( [ @@ -29,8 +36,25 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { options.quadPoints.righttopY, ].map((point) => PDFNumber.of(point)), ); - markupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); + textmarkupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); + + return textmarkupAnnotation; + } - return markupAnnotation; + QuadPoints(): PDFNumber[] | undefined { + const quadPoints = this.dict.lookup(PDFName.of('QuadPoints')); + if (quadPoints instanceof PDFArray) { + const numbers: PDFNumber[] = []; + for (let idx = 0, len = quadPoints.size(); idx < len; idx++) { + const num = quadPoints.lookup(idx); + if (num instanceof PDFNumber) { + numbers.push(num); + } else { + return undefined; + } + } + return numbers; + } + return undefined; } } diff --git a/src/core/annotation/PDFWidgetAnnotation.ts b/src/core/annotation/PDFWidgetAnnotation.ts index ebeb0ef4a..0e894e2c8 100644 --- a/src/core/annotation/PDFWidgetAnnotation.ts +++ b/src/core/annotation/PDFWidgetAnnotation.ts @@ -12,7 +12,7 @@ class PDFWidgetAnnotation extends PDFAnnotation { static fromDict = (dict: PDFDict): PDFWidgetAnnotation => new PDFWidgetAnnotation(dict); - static createEmpty = (context: PDFContext, parent: PDFRef) => { + static create = (context: PDFContext, parent: PDFRef) => { const dict = context.obj({ Type: 'Annot', Subtype: 'Widget', diff --git a/src/core/annotation/index.ts b/src/core/annotation/index.ts index b8f037625..7c173aab4 100644 --- a/src/core/annotation/index.ts +++ b/src/core/annotation/index.ts @@ -1,4 +1,5 @@ export { default as PDFAnnotation } from './PDFAnnotation'; +export { default as PDFTextMarkupAnnotation } from './PDFTextMarkupAnnotation'; export { default as PDFWidgetAnnotation } from './PDFWidgetAnnotation'; export { default as AppearanceCharacteristics } from './AppearanceCharacteristics'; export * from './flags'; diff --git a/tests/core/annotation/PDFAnnotation.spec.ts b/tests/core/annotation/PDFAnnotation.spec.ts index 93b7697af..85f804861 100644 --- a/tests/core/annotation/PDFAnnotation.spec.ts +++ b/tests/core/annotation/PDFAnnotation.spec.ts @@ -5,6 +5,7 @@ import { AnnotationTypes, PDFNumber, PDFName, + PDFTextMarkupAnnotation, } from '../../../src/index'; const simplePdf = fs.readFileSync('tests/core/data/simple.pdf'); @@ -17,12 +18,14 @@ describe('PDFAnnotation', () => { const page = pdfDoc.getPage(0); // Check that annotations were read correctly - expect(page.annotations).toBeDefined(); - expect(page.annotations.length).toBe(1); + const annotations = page.annotations(); + expect(annotations).toBeDefined(); + expect(annotations.length).toBe(1); // Verify the annotation properties - const annotation = page.annotations[0]; + const annotation = annotations[0] as PDFTextMarkupAnnotation; expect(annotation).toBeInstanceOf(PDFAnnotation); + expect(annotation).toBeInstanceOf(PDFTextMarkupAnnotation); // Check annotation type const subtype = annotation.getSubtype(); @@ -58,6 +61,12 @@ describe('PDFAnnotation', () => { // Check flags const flags = annotation.dict.lookup(PDFName.of('F')); expect(flags).toBeDefined(); + + // Check quadpoints + const quadPoints = annotation.QuadPoints(); + expect(quadPoints).toBeDefined(); + expect(quadPoints?.length).toBe(8); + expect((quadPoints?.at(0) as PDFNumber)?.asNumber()).toBeDefined(); }); it('returns empty array when page has no annotations', async () => { From ba1e1685ba39544b6c66288c49da8a1a9a17b6f5 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 23:06:30 +0700 Subject: [PATCH 27/44] fix: update addTextMarkupAnnotation to return PDFTextMarkupAnnotation instance --- src/api/PDFPage.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index e2f41b66b..51a5c7a3b 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -1727,21 +1727,20 @@ export default class PDFPage { */ addTextMarkupAnnotation( options: PDFPageAddTextMarkupAnnotationOptions, - ): void { + ): PDFTextMarkupAnnotation { const context = this.doc.context; const page = this.node; // convert to PDFDict - const annotationDict = PDFTextMarkupAnnotation.create( - context, - page, - options, - ).dict; + const annotation = PDFTextMarkupAnnotation.create(context, page, options); + const dict = annotation.dict; // register PDF object into the PDF - const ref = context.register(annotationDict); + const ref = context.register(dict); // add the annotation to the page's Annots array page.addAnnot(ref); + + return annotation; } } From c32f16c5803b933611302157d754bb264b70e2c1 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Sat, 6 Sep 2025 23:09:00 +0700 Subject: [PATCH 28/44] test: testing annotation creation --- tests/core/annotation/PDFAnnotation.spec.ts | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/core/annotation/PDFAnnotation.spec.ts b/tests/core/annotation/PDFAnnotation.spec.ts index 85f804861..e122f699f 100644 --- a/tests/core/annotation/PDFAnnotation.spec.ts +++ b/tests/core/annotation/PDFAnnotation.spec.ts @@ -78,5 +78,36 @@ describe('PDFAnnotation', () => { expect(page.annotations).toBeDefined(); expect(page.annotations.length).toBe(0); }); + + it('can add a highlight annotation to a page', async () => { + // Create a new document and add a page + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage([600, 400]); + + // Add a highlight annotation + const highlight = page.addTextMarkupAnnotation({ + subtype: AnnotationTypes.Highlight, + color: [1, 1, 0], // Yellow + rect: { + x: 50, + y: 300, + width: 150, + height: 20, + }, + contents: 'This is a highlight annotation', + quadPoints: { + leftbottomX: 50, + leftbottomY: 300, + lefttopX: 50, + lefttopY: 320, + righttopX: 200, + righttopY: 320, + rightbottomX: 200, + rightbottomY: 300, + }, + }); + expect(highlight).toBeInstanceOf(PDFAnnotation); + expect(highlight).toBeInstanceOf(PDFTextMarkupAnnotation); + }); }); }); From 2d8b3c6a5bee6ae2afab28cac532f74fddcb0993 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Mon, 8 Sep 2025 05:11:52 +0700 Subject: [PATCH 29/44] sample PDF printing code --- annotationsample-fixed.js | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 annotationsample-fixed.js diff --git a/annotationsample-fixed.js b/annotationsample-fixed.js new file mode 100644 index 000000000..7360adb78 --- /dev/null +++ b/annotationsample-fixed.js @@ -0,0 +1,70 @@ +const fs = require('fs'); + +async function createAnnotatedPDF() { + try { + console.log('Creating PDF with annotation...'); + + // Import pdf-lib + const { PDFDocument, StandardFonts, rgb } = require('./cjs/index'); + const { + AnnotationTypes, + } = require('./cjs/core/annotation/AnnotationTypes'); + + console.log('PDF-lib loaded successfully'); + + // Create a new PDF document + const pdfDoc = await PDFDocument.create(); + const page = pdfDoc.addPage([600, 400]); + console.log('Page created'); + + // Add some text first + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica); + page.drawText('This text will be highlighted', { + x: 50, + y: 300, + size: 12, + font: helveticaFont, + color: rgb(0, 0, 0), + }); + console.log('Text added'); + + // Try to add a highlight annotation + try { + const highlight = page.addTextMarkupAnnotation({ + subtype: AnnotationTypes.Highlight, + color: [1, 1, 0], // Yellow + rect: { + x: 50, + y: 300, + width: 150, + height: 20, + }, + contents: 'This is a highlight annotation', + quadPoints: { + leftbottomX: 50, + leftbottomY: 300, + lefttopX: 50, + lefttopY: 320, + righttopX: 200, + righttopY: 320, + rightbottomX: 200, + rightbottomY: 300, + }, + }); + console.log('Annotation added successfully'); + } catch (annotationError) { + console.log('Could not add annotation:', annotationError.message); + console.log('Proceeding without annotation...'); + } + + // Save the PDF + const pdfBytes = await pdfDoc.save(); + fs.writeFileSync('annotationsample.pdf', pdfBytes); + console.log('PDF created successfully: annotationsample.pdf'); + } catch (error) { + console.error('Error creating PDF:', error); + } +} + +// Run the function +createAnnotatedPDF(); From 50e4d517f64768d124a7af777c78fbf34484b731 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Mon, 8 Sep 2025 05:25:23 +0700 Subject: [PATCH 30/44] test: test output of annotation creation --- tests/core/annotation/PDFAnnotation.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/core/annotation/PDFAnnotation.spec.ts b/tests/core/annotation/PDFAnnotation.spec.ts index e122f699f..7511a0640 100644 --- a/tests/core/annotation/PDFAnnotation.spec.ts +++ b/tests/core/annotation/PDFAnnotation.spec.ts @@ -85,7 +85,7 @@ describe('PDFAnnotation', () => { const page = pdfDoc.addPage([600, 400]); // Add a highlight annotation - const highlight = page.addTextMarkupAnnotation({ + page.addTextMarkupAnnotation({ subtype: AnnotationTypes.Highlight, color: [1, 1, 0], // Yellow rect: { @@ -106,8 +106,13 @@ describe('PDFAnnotation', () => { rightbottomY: 300, }, }); - expect(highlight).toBeInstanceOf(PDFAnnotation); - expect(highlight).toBeInstanceOf(PDFTextMarkupAnnotation); + + const annotationFetched = + page.annotations()[0] as PDFTextMarkupAnnotation; + expect(annotationFetched).toBeDefined(); + expect(annotationFetched).toBeInstanceOf(PDFTextMarkupAnnotation); + + expect(annotationFetched.getSubtype()).toBe(AnnotationTypes.Highlight); }); }); }); From be3a35c0ee5c01e3cc6596a6ed4575610a33aab1 Mon Sep 17 00:00:00 2001 From: konbraphat51 Date: Mon, 8 Sep 2025 05:25:57 +0700 Subject: [PATCH 31/44] fix: fix slash duplication --- src/core/annotation/PDFAnnotation.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 0f6fd764a..78582762f 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -22,7 +22,8 @@ class PDFAnnotation { ): PDFAnnotation => { const dict = context.obj({ Type: 'Annot', - Subtype: options.subtype, + // Remove leading '/' from the subtype string + Subtype: options.subtype.toString().replace(/\//g, ''), Rect: [ options.rect.x, options.rect.y, From cee0754764241b2b43575b7dab503e33847bf4f9 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Fri, 19 Sep 2025 02:59:10 +0900 Subject: [PATCH 32/44] fix dependency --- src/api/PDFPage.ts | 3 +-- src/core/annotation/index.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts index 51a5c7a3b..f8d5ee2ce 100644 --- a/src/api/PDFPage.ts +++ b/src/api/PDFPage.ts @@ -61,8 +61,7 @@ import { assertIsOneOfOrUndefined, } from '../utils'; import { drawSvg } from './svg'; -import AnnotationFactory from 'src/core/annotation/AnnotationFactory'; -import PDFTextMarkupAnnotation from 'src/core/annotation/PDFTextMarkupAnnotation'; +import { AnnotationFactory, PDFTextMarkupAnnotation } from '../core'; /** * Represents a single page of a [[PDFDocument]]. diff --git a/src/core/annotation/index.ts b/src/core/annotation/index.ts index 7c173aab4..ce6e6310b 100644 --- a/src/core/annotation/index.ts +++ b/src/core/annotation/index.ts @@ -1,4 +1,5 @@ export { default as PDFAnnotation } from './PDFAnnotation'; +export { default as AnnotationFactory } from './AnnotationFactory'; export { default as PDFTextMarkupAnnotation } from './PDFTextMarkupAnnotation'; export { default as PDFWidgetAnnotation } from './PDFWidgetAnnotation'; export { default as AppearanceCharacteristics } from './AppearanceCharacteristics'; From a7f8ad54d4851da38b355c1d59750751ccd14841 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Fri, 19 Sep 2025 04:05:35 +0900 Subject: [PATCH 33/44] fix linter it took too much time to run --- eslint.config.js | 16 +++ package.json | 2 +- yarn.lock | 286 ++++++++++++++++++++++++++++------------------- 3 files changed, 185 insertions(+), 119 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index e72188024..260f52077 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,22 @@ const prettier = require('eslint-plugin-prettier'); const globals = require('globals'); module.exports = [ + { + ignores: [ + 'node_modules/**', + 'dist/**', + 'cjs/**', + 'es/**', + 'ts3.4/**', + 'coverage/**', + 'build/**', + 'apps/node-build/**', + '**/*.min.js', + '**/*.js.map', + '**/*.d.ts.map', + 'tsBuildInfo.json', + ], + }, eslint.configs.recommended, { files: ['**/*.{ts,tsx,js,jsx,mjs}'], diff --git a/package.json b/package.json index 90e22ad10..113131621 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "downlevel-dts": "^0.11.0", - "eslint": "^8.56.0", + "eslint": "^9.35.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "flamebearer": "^1.1.3", diff --git a/yarn.lock b/yarn.lock index 8b1ae05e9..e33fe2c83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -297,56 +297,106 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.4.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/eslint-utils@^4.8.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3" + integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1": version "4.12.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/config-array@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/config-helpers@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" + integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== + +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.57.1", "@eslint/js@^8.56.0": +"@eslint/js@9.35.0": + version "9.35.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.35.0.tgz#ffbc7e13cf1204db18552e9cd9d4a8e17c692d07" + integrity sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw== + +"@eslint/js@^8.56.0": version "8.57.1" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" + "@eslint/core" "^0.15.2" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@iarna/toml@2.2.5": version "2.2.5" @@ -633,7 +683,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -1005,6 +1055,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@^1.0.6": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -1044,6 +1099,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/node-fetch@^2.5.7": version "2.6.11" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" @@ -1172,26 +1232,21 @@ "@typescript-eslint/types" "7.18.0" eslint-visitor-keys "^3.4.3" -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + acorn@^8.8.2: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -acorn@^8.9.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== - agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" @@ -1876,7 +1931,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -cross-spawn@^7.0.2: +cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2039,13 +2094,6 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dot-prop@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" @@ -2246,78 +2294,80 @@ eslint-plugin-prettier@^5.1.3: prettier-linter-helpers "^1.0.0" synckit "^0.11.0" -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.56.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.35.0: + version "9.35.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.35.0.tgz#7a89054b7b9ee1dfd1b62035d8ce75547773f47e" + integrity sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.0" + "@eslint/config-helpers" "^0.3.1" + "@eslint/core" "^0.15.2" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.35.0" + "@eslint/plugin-kit" "^0.3.5" + "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" - cross-spawn "^7.0.2" + cross-spawn "^7.0.6" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: - acorn "^8.9.0" + acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.2.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== @@ -2483,12 +2533,12 @@ figures@^5.0.0: escape-string-regexp "^5.0.0" is-unicode-supported "^1.2.0" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" fill-range@^7.0.1: version "7.0.1" @@ -2535,14 +2585,13 @@ flamebearer@^1.1.3: concat-stream "^1.6.2" opn "^5.3.0" -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" + keyv "^4.5.4" flatted@^3.2.9: version "3.3.3" @@ -2762,13 +2811,6 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - globals@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" @@ -3324,7 +3366,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^3.0.2, is-path-inside@^3.0.3: +is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -3949,7 +3991,7 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -keyv@^4.5.3: +keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -4228,7 +4270,7 @@ mimic-response@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-4.0.0.tgz#35468b19e7c75d10f5165ea25e75a5ceea7cf70f" integrity sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg== -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -5003,13 +5045,6 @@ rfdc@^1.4.1: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - rimraf@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.5.tgz#9be65d2d6e683447d2e9013da2bf451139a61ccf" @@ -5326,7 +5361,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5394,7 +5438,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5486,11 +5537,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -5551,11 +5597,6 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -5817,7 +5858,7 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -5835,6 +5876,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From e61dd0fce8a072d39a322e4e49b8f3616bc943a3 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Fri, 19 Sep 2025 04:06:32 +0900 Subject: [PATCH 34/44] rid testing codes --- annotationsample-fixed.js | 3 ++- annotationsample.pdf | Bin 0 -> 1143 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 annotationsample.pdf diff --git a/annotationsample-fixed.js b/annotationsample-fixed.js index 7360adb78..7cdd549a4 100644 --- a/annotationsample-fixed.js +++ b/annotationsample-fixed.js @@ -1,3 +1,4 @@ +//yarn node annotationsample-fixed.js const fs = require('fs'); async function createAnnotatedPDF() { @@ -30,7 +31,7 @@ async function createAnnotatedPDF() { // Try to add a highlight annotation try { - const highlight = page.addTextMarkupAnnotation({ + page.addTextMarkupAnnotation({ subtype: AnnotationTypes.Highlight, color: [1, 1, 0], // Yellow rect: { diff --git a/annotationsample.pdf b/annotationsample.pdf new file mode 100644 index 0000000000000000000000000000000000000000..42062b8bbdd57f8eace6fbb2eb9bf903f8bf06f0 GIT binary patch literal 1143 zcmY!laB3Nn zSiOEwYH@yPQF3ar0?1Wv`9K*E!y`4PEVU#vIZ@Z#z|z9R!ob|ZQo$5vg?@y8QdVj* zSSiqr`mPlv?!hIAC8;32Kmm}LZ)!?rqEmha&|d~XXl7ubU;@NI-#O;x<(Gi`YYs9G z;@`XypqoHqW>7ICuY-hO9&yXeDM>9-(09uL+UAm)oS%})rSFrPmtK;gU}yvmvEq`V z)Wlq_iaDxXhFnaJJg&dzC~Z|p+ca~&qyRVj!9#8*SY5&buQ>^xf4}C_!@uEs)N`9# zp6^|^NQivMoe6t)lop4W zn22f=rRzVK`}Th^=k0=j{bL=+cPh{5IA^KES~Kg=b>YAo6IsC zCM7;j_+MwfHt%!do60TiU+U%->t*tBa4ng%!0{=o&+?x2eG6?(zj0jNwJO`Cx1&`> zq+`*NV#gT*EJs6xlYgmYRI)uS>6>wWWf;H47lX;KYS&wx4=)MIpICKjVR&Er4{!6# zQ0Dd@r_ZMx`%#p^9{R)Jq?`y9WC#qJ$6sk6riuebfKa!#8`DM&GH~ z6ZxR@!^DrrFK53wJmrkUe&e~mq7S^QE4E)WZToo2d|_!Fd(G=Vcgo*Q+L1P=TD~qw z?`~ebLO@>bY>x8_1Tr!e4o~225&z^9b?rEx+w8rs8{(LMpO%?%lK>zkQ8_dl7H z)}gt0xq5}g%reoKMLV9UuW)=8-S?)BY16s&99W{p5-DneGpkY+3=O#SgYxrB6hJvH zh)dryFD+le7{bPy`XF%^5tNz+k17*jtc3#;vmwwBBTxeO%u7jy(v9po+$g1WI5|Wa46?N3y)ch8(d=_G3V`wgC{)NT;#U+VFB^5 Date: Fri, 19 Sep 2025 04:30:03 +0900 Subject: [PATCH 35/44] feat: make contents editable --- src/core/annotation/PDFAnnotation.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 78582762f..21c9b1c14 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -323,6 +323,10 @@ class PDFAnnotation { } return undefined; } + + setContents(contents: string) { + this.dict.set(PDFName.of('Contents'), PDFString.of(contents)); + } } export default PDFAnnotation; From a5280cd030a9218999b680452961e2acd10550f3 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Fri, 19 Sep 2025 04:34:37 +0900 Subject: [PATCH 36/44] refac: reorder methods --- src/core/annotation/PDFAnnotation.ts | 95 ++++++++++++++-------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 21c9b1c14..6723ef1e4 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -202,26 +202,12 @@ class PDFAnnotation { return Rect?.asRectangle() ?? { x: 0, y: 0, width: 0, height: 0 }; } - setRectangle(rect: { x: number; y: number; width: number; height: number }) { - const { x, y, width, height } = rect; - const Rect = this.dict.context.obj([x, y, x + width, y + height]); - this.dict.set(PDFName.of('Rect'), Rect); - } - getAppearanceState(): PDFName | undefined { const AS = this.dict.lookup(PDFName.of('AS')); if (AS instanceof PDFName) return AS; return undefined; } - setAppearanceState(state: PDFName) { - this.dict.set(PDFName.of('AS'), state); - } - - setAppearances(appearances: PDFDict) { - this.dict.set(PDFName.of('AP'), appearances); - } - ensureAP(): PDFDict { let AP = this.AP(); if (!AP) { @@ -239,24 +225,6 @@ class PDFAnnotation { throw new Error(`Unexpected N type: ${N?.constructor.name}`); } - /** @param appearance A PDFDict or PDFStream (direct or ref) */ - setNormalAppearance(appearance: PDFRef | PDFDict) { - const AP = this.ensureAP(); - AP.set(PDFName.of('N'), appearance); - } - - /** @param appearance A PDFDict or PDFStream (direct or ref) */ - setRolloverAppearance(appearance: PDFRef | PDFDict) { - const AP = this.ensureAP(); - AP.set(PDFName.of('R'), appearance); - } - - /** @param appearance A PDFDict or PDFStream (direct or ref) */ - setDownAppearance(appearance: PDFRef | PDFDict) { - const AP = this.ensureAP(); - AP.set(PDFName.of('D'), appearance); - } - removeRolloverAppearance() { const AP = this.AP(); AP?.delete(PDFName.of('R')); @@ -289,15 +257,59 @@ class PDFAnnotation { return this.F()?.asNumber() ?? 0; } - setFlags(flags: number) { - this.dict.set(PDFName.of('F'), PDFNumber.of(flags)); - } - hasFlag(flag: number): boolean { const flags = this.getFlags(); return (flags & flag) !== 0; } + getParentPage(): PDFPageLeaf | undefined { + const pageRef = this.P(); + if (!pageRef) return undefined; + + const page = this.dict.context.lookup(pageRef); + if (page instanceof PDFPageLeaf) { + return page; + } + return undefined; + } + + // Setter methods + setRectangle(rect: { x: number; y: number; width: number; height: number }) { + const { x, y, width, height } = rect; + const Rect = this.dict.context.obj([x, y, x + width, y + height]); + this.dict.set(PDFName.of('Rect'), Rect); + } + + setAppearanceState(state: PDFName) { + this.dict.set(PDFName.of('AS'), state); + } + + setAppearances(appearances: PDFDict) { + this.dict.set(PDFName.of('AP'), appearances); + } + + /** @param appearance A PDFDict or PDFStream (direct or ref) */ + setNormalAppearance(appearance: PDFRef | PDFDict) { + const AP = this.ensureAP(); + AP.set(PDFName.of('N'), appearance); + } + + /** @param appearance A PDFDict or PDFStream (direct or ref) */ + setRolloverAppearance(appearance: PDFRef | PDFDict) { + const AP = this.ensureAP(); + AP.set(PDFName.of('R'), appearance); + } + + /** @param appearance A PDFDict or PDFStream (direct or ref) */ + setDownAppearance(appearance: PDFRef | PDFDict) { + const AP = this.ensureAP(); + AP.set(PDFName.of('D'), appearance); + } + + setFlags(flags: number) { + this.dict.set(PDFName.of('F'), PDFNumber.of(flags)); + } + setFlag(flag: number) { const flags = this.getFlags(); this.setFlags(flags | flag); @@ -313,17 +325,6 @@ class PDFAnnotation { else this.clearFlag(flag); } - getParentPage(): PDFPageLeaf | undefined { - const pageRef = this.P(); - if (!pageRef) return undefined; - - const page = this.dict.context.lookup(pageRef); - if (page instanceof PDFPageLeaf) { - return page; - } - return undefined; - } - setContents(contents: string) { this.dict.set(PDFName.of('Contents'), PDFString.of(contents)); } From e0c1cd53b90ab669842124d7ceee6d031c1767fe Mon Sep 17 00:00:00 2001 From: konbraphat Date: Sat, 20 Sep 2025 12:21:16 +0900 Subject: [PATCH 37/44] feat: add method to set page reference for annotations --- src/core/annotation/PDFAnnotation.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 6723ef1e4..424511de4 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -328,6 +328,17 @@ class PDFAnnotation { setContents(contents: string) { this.dict.set(PDFName.of('Contents'), PDFString.of(contents)); } + + setPage(page: PDFPageLeaf, context: PDFContext) { + // Set the page reference by getting the PDFRef for the PDFPageLeaf + const pageRef = context.getObjectRef(page); + if (!pageRef) { + throw new Error( + 'Could not find PDFRef for the provided PDFPageLeaf. The page must be registered in the PDF context.', + ); + } + this.dict.set(PDFName.of('P'), pageRef); + } } export default PDFAnnotation; From 9ed2b0f4428326134a0da6154cddf268575f0e84 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Sat, 20 Sep 2025 18:48:30 +0900 Subject: [PATCH 38/44] set methods --- src/core/annotation/PDFAnnotation.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 424511de4..fd77c9018 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -339,6 +339,22 @@ class PDFAnnotation { } this.dict.set(PDFName.of('P'), pageRef); } + + setIdentifier(name: string) { + this.dict.set(PDFName.of('NM'), PDFString.of(name)); + } + + setModificationDate(date: Date) { + this.dict.set(PDFName.of('M'), PDFString.fromDate(date)); + } + + setBorder(border: number[]) { + this.dict.set(PDFName.of('Border'), this.dict.context.obj(border)); + } + + setColor(color: number[]) { + this.dict.set(PDFName.of('C'), this.dict.context.obj(color)); + } } export default PDFAnnotation; From e221ad98fa7e2ae1cb3e23d946bcc7f97475b2f2 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Mon, 29 Sep 2025 06:21:35 +0900 Subject: [PATCH 39/44] refac: swap duplicated codes with set methods --- src/core/annotation/PDFAnnotation.ts | 42 ++++++++-------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index fd77c9018..21cd51972 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -20,9 +20,10 @@ class PDFAnnotation { page: PDFPageLeaf, options: PDFPageAddAnnotationOptions, ): PDFAnnotation => { + // Create the minimal base annotation dictionary first const dict = context.obj({ Type: 'Annot', - // Remove leading '/' from the subtype string + // Remove leading '/' from the subtype string (if present) Subtype: options.subtype.toString().replace(/\//g, ''), Rect: [ options.rect.x, @@ -32,43 +33,22 @@ class PDFAnnotation { ], }); - if (options.contents !== undefined) { - dict.set(PDFName.of('Contents'), PDFString.of(options.contents)); - } - - if (options.name !== undefined) { - dict.set(PDFName.of('NM'), PDFString.of(options.name)); - } - - // Set the page reference by getting the PDFRef for the PDFPageLeaf - const pageRef = context.getObjectRef(page); - if (!pageRef) { - throw new Error( - 'Could not find PDFRef for the provided PDFPageLeaf. The page must be registered in the PDF context.', - ); - } - dict.set(PDFName.of('P'), pageRef); - - if (options.flags !== undefined) { - dict.set(PDFName.of('F'), PDFNumber.of(options.flags)); - } - - if (options.color !== undefined) { - const colorArray = context.obj(options.color); - dict.set(PDFName.of('C'), colorArray); - } + const annotation = new PDFAnnotation(dict); - if (options.border !== undefined) { - const borderArray = context.obj(options.border); - dict.set(PDFName.of('Border'), borderArray); + if (options.contents !== undefined) { + annotation.setContents(options.contents); } + if (options.name !== undefined) annotation.setIdentifier(options.name); + annotation.setPage(page, context); + if (options.flags !== undefined) annotation.setFlags(options.flags); + if (options.color !== undefined) annotation.setColor(options.color); + if (options.border !== undefined) annotation.setBorder(options.border); + // modificationDate is a raw string in options; keep direct setting (setter expects Date) if (options.modificationDate !== undefined) { dict.set(PDFName.of('M'), PDFString.of(options.modificationDate)); } - const annotation = new PDFAnnotation(dict); - return annotation; }; From ebed7767aa1c2d84f079df2f1df8ce40234be816 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Mon, 29 Sep 2025 06:23:21 +0900 Subject: [PATCH 40/44] refac: use Date object for modificationDate in PDFAnnotation --- src/api/PDFPageOptions.ts | 2 +- src/core/annotation/PDFAnnotation.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/api/PDFPageOptions.ts b/src/api/PDFPageOptions.ts index 8eff8c183..6cabdcc58 100644 --- a/src/api/PDFPageOptions.ts +++ b/src/api/PDFPageOptions.ts @@ -191,7 +191,7 @@ export interface PDFPageAddAnnotationOptions { flags?: number; color?: number[]; border?: number[]; - modificationDate?: string; + modificationDate?: Date; } /** diff --git a/src/core/annotation/PDFAnnotation.ts b/src/core/annotation/PDFAnnotation.ts index 21cd51972..5ab0365d9 100644 --- a/src/core/annotation/PDFAnnotation.ts +++ b/src/core/annotation/PDFAnnotation.ts @@ -43,10 +43,8 @@ class PDFAnnotation { if (options.flags !== undefined) annotation.setFlags(options.flags); if (options.color !== undefined) annotation.setColor(options.color); if (options.border !== undefined) annotation.setBorder(options.border); - - // modificationDate is a raw string in options; keep direct setting (setter expects Date) if (options.modificationDate !== undefined) { - dict.set(PDFName.of('M'), PDFString.of(options.modificationDate)); + annotation.setModificationDate(options.modificationDate); } return annotation; From 2623aac50139489896d2b07654cfc25a6f180902 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Mon, 29 Sep 2025 06:32:24 +0900 Subject: [PATCH 41/44] feat: add setQuadPoints method --- .../annotation/PDFTextMarkupAnnotation.ts | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts index 1a9232dff..13fd23dfe 100644 --- a/src/core/annotation/PDFTextMarkupAnnotation.ts +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -57,4 +57,68 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { } return undefined; } + + // Overloads: accept a tuple of 8 numbers OR 8 individual number args + setQuadPoints( + quadPoints: [ + number, + number, + number, + number, + number, + number, + number, + number, + ], + ): void; + setQuadPoints( + leftBottomX: number, + leftBottomY: number, + rightBottomX: number, + rightBottomY: number, + leftTopX: number, + leftTopY: number, + rightTopX: number, + rightTopY: number, + ): void; + setQuadPoints( + quadOrLeftBottomX: + | number + | [number, number, number, number, number, number, number, number], + leftBottomY?: number, + rightBottomX?: number, + rightBottomY?: number, + leftTopX?: number, + leftTopY?: number, + rightTopX?: number, + rightTopY?: number, + ): void { + let values: number[]; + + if (Array.isArray(quadOrLeftBottomX)) { + values = quadOrLeftBottomX.slice(0); + } else { + const coords = [ + quadOrLeftBottomX, + leftBottomY, + rightBottomX, + rightBottomY, + leftTopX, + leftTopY, + rightTopX, + rightTopY, + ]; + if (coords.length !== 8 || coords.some((n) => typeof n !== 'number')) { + throw new Error( + 'setQuadPoints requires either a tuple of 8 numbers or 8 individual number arguments', + ); + } + values = coords as number[]; + } + + const quadPointsArray = this.dict.context.obj( + values.map((v) => PDFNumber.of(v)), + ); + this.dict.set(PDFName.of('QuadPoints'), quadPointsArray); + } } From 2f8db96becd7bb55ebc5a890a2c1d18bde60cee5 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Sat, 1 Nov 2025 09:54:17 +0900 Subject: [PATCH 42/44] fix: order of qualpoints --- src/core/annotation/PDFTextMarkupAnnotation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts index 13fd23dfe..3044b140e 100644 --- a/src/core/annotation/PDFTextMarkupAnnotation.ts +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -30,10 +30,10 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { options.quadPoints.leftbottomY, options.quadPoints.rightbottomX, options.quadPoints.rightbottomY, - options.quadPoints.lefttopX, - options.quadPoints.lefttopY, options.quadPoints.righttopX, options.quadPoints.righttopY, + options.quadPoints.lefttopX, + options.quadPoints.lefttopY, ].map((point) => PDFNumber.of(point)), ); textmarkupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); From 0ee1dd80acc1949bc8cd7f047859f6e14c2b7a70 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Sat, 1 Nov 2025 10:03:58 +0900 Subject: [PATCH 43/44] Revert "fix: order of qualpoints" This needs further discussion according on https://stackoverflow.com/questions/9855814/pdf-spec-vs-acrobat-creation-quadpoints This reverts commit 2f8db96becd7bb55ebc5a890a2c1d18bde60cee5. --- src/core/annotation/PDFTextMarkupAnnotation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts index 3044b140e..13fd23dfe 100644 --- a/src/core/annotation/PDFTextMarkupAnnotation.ts +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -30,10 +30,10 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { options.quadPoints.leftbottomY, options.quadPoints.rightbottomX, options.quadPoints.rightbottomY, - options.quadPoints.righttopX, - options.quadPoints.righttopY, options.quadPoints.lefttopX, options.quadPoints.lefttopY, + options.quadPoints.righttopX, + options.quadPoints.righttopY, ].map((point) => PDFNumber.of(point)), ); textmarkupAnnotation.dict.set(PDFName.of('QuadPoints'), quadPointsArray); From da4f8c64fa07ca039ca7181a0c2a19e0585e25f8 Mon Sep 17 00:00:00 2001 From: konbraphat Date: Sat, 1 Nov 2025 10:20:15 +0900 Subject: [PATCH 44/44] refac: add noting comment --- src/core/annotation/PDFTextMarkupAnnotation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/annotation/PDFTextMarkupAnnotation.ts b/src/core/annotation/PDFTextMarkupAnnotation.ts index 13fd23dfe..caf27db67 100644 --- a/src/core/annotation/PDFTextMarkupAnnotation.ts +++ b/src/core/annotation/PDFTextMarkupAnnotation.ts @@ -30,6 +30,9 @@ export default class PDFTextMarkupAnnotation extends PDFAnnotation { options.quadPoints.leftbottomY, options.quadPoints.rightbottomX, options.quadPoints.rightbottomY, + + // NOTE: these order are horizontally inverted with the Spec because of Adobe implementation + // https://stackoverflow.com/questions/9855814/pdf-spec-vs-acrobat-creation-quadpoints options.quadPoints.lefttopX, options.quadPoints.lefttopY, options.quadPoints.righttopX,