Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
70c4479
annotation types
konbraphat51 Feb 25, 2025
0c52eaf
plural
konbraphat51 Feb 25, 2025
dbe3184
get subtype
konbraphat51 Feb 25, 2025
f18da38
refac: add table reference
konbraphat51 Feb 25, 2025
3d7b136
annotation entries
konbraphat51 Feb 25, 2025
befcfe7
PDF page converting annotations
konbraphat51 Feb 25, 2025
b4043c6
rename operations -> drawingOperations
konbraphat51 Feb 25, 2025
613c8c2
Revert "rename operations -> drawingOperations"
konbraphat51 Feb 25, 2025
52483a2
Merge branch 'master' into annotation
konbraphat51 Sep 2, 2025
39811d1
add annotation object into sample.pdf
konbraphat51 Sep 3, 2025
f655fc9
fix: export AnnotationTypes
konbraphat51 Sep 5, 2025
4e56456
refac:fixed by lint
konbraphat51 Sep 5, 2025
fbe291b
fix: seperate types of P
konbraphat51 Sep 5, 2025
4debc59
feat:test annotation reading
konbraphat51 Sep 5, 2025
8652794
feat: add creation method for annotation object
konbraphat51 Sep 5, 2025
ea97ece
feat: register created Annotation object to PDF
konbraphat51 Sep 5, 2025
4e3d4fb
Revert "add annotation object into sample.pdf"
konbraphat51 Sep 5, 2025
77e6ba8
fix: move edited pdf sample
konbraphat51 Sep 5, 2025
7dd5301
fix: move path
konbraphat51 Sep 5, 2025
78aa772
fix: correct method name from Create to create in PDFAnnotation class
konbraphat51 Sep 6, 2025
9e89bdc
fix: rename PDFWidgetAnnotation.create to PDFWidgetAnnotation.createE…
konbraphat51 Sep 6, 2025
d2e888d
fix: option typing
konbraphat51 Sep 6, 2025
228f59f
fix: able to set array number freely
konbraphat51 Sep 6, 2025
ac3eb23
fix: rename PDFAnnotation.create to PDFAnnotation.createBase and add …
konbraphat51 Sep 6, 2025
53675ca
fix: create PDFAnnotation subclasses from Factory class
konbraphat51 Sep 6, 2025
6d7da79
refac: easier API to add Text Markup Annotation
konbraphat51 Sep 6, 2025
3cb8171
fix: fixed error with testing
konbraphat51 Sep 6, 2025
ba1e168
fix: update addTextMarkupAnnotation to return PDFTextMarkupAnnotation…
konbraphat51 Sep 6, 2025
c32f16c
test: testing annotation creation
konbraphat51 Sep 6, 2025
2d8b3c6
sample PDF printing code
konbraphat51 Sep 7, 2025
50e4d51
test: test output of annotation creation
konbraphat51 Sep 7, 2025
be3a35c
fix: fix slash duplication
konbraphat51 Sep 7, 2025
cee0754
fix dependency
konbraphat51 Sep 18, 2025
a7f8ad5
fix linter
konbraphat51 Sep 18, 2025
e61dd0f
rid testing codes
konbraphat51 Sep 18, 2025
3e8b024
feat: make contents editable
konbraphat51 Sep 18, 2025
a5280cd
refac: reorder methods
konbraphat51 Sep 18, 2025
e0c1cd5
feat: add method to set page reference for annotations
konbraphat51 Sep 20, 2025
9ed2b0f
set methods
konbraphat51 Sep 20, 2025
d16f4bd
Merge branch 'master' into annotation
konbraphat51 Sep 28, 2025
e221ad9
refac: swap duplicated codes with set methods
konbraphat51 Sep 28, 2025
ebed776
refac: use Date object for modificationDate in PDFAnnotation
konbraphat51 Sep 28, 2025
2623aac
feat: add setQuadPoints method
konbraphat51 Sep 28, 2025
2f8db96
fix: order of qualpoints
konbraphat51 Nov 1, 2025
0ee1dd8
Revert "fix: order of qualpoints"
konbraphat51 Nov 1, 2025
da4f8c6
refac: add noting comment
konbraphat51 Nov 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions annotationsample-fixed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//yarn node annotationsample-fixed.js
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 {
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();
Binary file added annotationsample.pdf
Binary file not shown.
51 changes: 51 additions & 0 deletions src/api/PDFPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
PDFPageDrawTextOptions,
BlendMode,
PDFPageDrawSVGElementOptions,
PDFPageAddTextMarkupAnnotationOptions,
} from './PDFPageOptions';
import { degrees, Rotation, toDegrees } from './rotations';
import { StandardFonts } from './StandardFonts';
Expand All @@ -45,6 +46,7 @@ import {
PDFRef,
PDFDict,
PDFArray,
PDFAnnotation,
} from '../core';
import {
assertEachIs,
Expand All @@ -59,6 +61,7 @@ import {
assertIsOneOfOrUndefined,
} from '../utils';
import { drawSvg } from './svg';
import { AnnotationFactory, PDFTextMarkupAnnotation } from '../core';

/**
* Represents a single page of a [[PDFDocument]].
Expand Down Expand Up @@ -1691,4 +1694,52 @@ 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
*/
annotations(): PDFAnnotation[] {
const annotsArray = this.node.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 = AnnotationFactory.fromDict(annotDict);
annotations.push(pdfAnnotation);
}
}
return annotations;
}

/**
* Add an annotation to this page from an existing PDFAnnotation instance.
*/
addTextMarkupAnnotation(
options: PDFPageAddTextMarkupAnnotationOptions,
): PDFTextMarkupAnnotation {
const context = this.doc.context;
const page = this.node;

// convert to PDFDict
const annotation = PDFTextMarkupAnnotation.create(context, page, options);
const dict = annotation.dict;

// register PDF object into the PDF
const ref = context.register(dict);

// add the annotation to the page's Annots array
page.addAnnot(ref);

return annotation;
}
}
42 changes: 42 additions & 0 deletions src/api/PDFPageOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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?: Date;
}

/**
* 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: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very annoying data format. Please use Rect instead. Way easier to manipulate!

Copy link
Author

@konbraphat51 konbraphat51 Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean Rect as src\utils\elements\Rectangle.ts?

Actually, qualPoints also covers trapezoids and hourglass shapes, not only rectangles. So I think that is not a good idea

my experiments (sorry it's in Japanese): https://qiita.com/konbraphat51/items/a7ddb30e5277d5e10a5a#%E8%9D%B6%E3%81%A4%E3%81%8C%E3%81%84

image image

lefttopX: number;
lefttopY: number;
righttopX: number;
righttopY: number;
rightbottomX: number;
rightbottomY: number;
leftbottomX: number;
leftbottomY: number;
};
}
28 changes: 28 additions & 0 deletions src/core/annotation/AnnotationFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import PDFDict from '../objects/PDFDict';
import PDFName from '../objects/PDFName';
import PDFAnnotation from './PDFAnnotation';
import PDFTextMarkupAnnotation from './PDFTextMarkupAnnotation';
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 PDFTextMarkupAnnotation.fromDict(dict);
default:
return PDFAnnotation.fromDict(dict);
}
};

private static getSubtype(dict: PDFDict): AnnotationTypes | undefined {
const subtypePdfName = dict.lookup(PDFName.of('Subtype'), PDFName);
if (subtypePdfName instanceof PDFName) {
return subtypePdfName.toString() as AnnotationTypes;
} else {
return undefined;
}
}
}
31 changes: 31 additions & 0 deletions src/core/annotation/AnnotationTypes.ts
Original file line number Diff line number Diff line change
@@ -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',
}
Loading