Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
8 changes: 7 additions & 1 deletion MODIFICATIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Modifications

## [Unreleased]
## [1.17.1-mod.2025.8]

### Feature Removals

Expand All @@ -11,6 +11,12 @@
- Remove Base64 output by removing the `PDFDocument#saveAsBase64` method;
use `PDFDocument#save` which resolves a `Uint8Array` instead.

### Error Handling

- Introduced `PDFLibError` (exported from `src/core`) with a machine-readable `type: PDFLibErrorType` to classify errors.
- Refactored API/core/utils errors so that all errors extend `PDFLibError` instead of throwing bare `Error`/`TypeError`.
- Normalize external failure surfaces by catching `@denkiyagi/fontkit` and `@pdf-lib/upng` exceptions and rethrowing corresponding `PDFLibError` subclasses (e.g., `InvalidFontDataError`) instead of leaking third-party errors.

### Internal Changes

- Update several devDependencies including `typescript` to their latest versions.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@denkiyagi/pdf-lib",
"version": "1.17.1-mod.2025.7",
"version": "1.17.1-mod.2025.8",
"description": "A modified version of pdf-lib. Create and modify PDF files with JavaScript",
"author": "DenkiYagi Inc. (modified version author), Andrew Dillon <andrew.dillon.j@gmail.com> (orignal version author)",
"contributors": [
Expand Down
50 changes: 34 additions & 16 deletions src/api/PDFDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Embeddable } from 'src/api/Embeddable';
import {
EncryptedPDFError,
ForeignPageError,
InvalidFontSubsetOptionError,
RemovePageFromEmptyDocumentError,
} from 'src/api/errors';
import { PDFEmbeddedPage } from 'src/api/PDFEmbeddedPage';
Expand All @@ -11,7 +12,7 @@ import { PDFImage } from 'src/api/PDFImage';
import { PDFPage } from 'src/api/PDFPage';
import { PDFForm } from 'src/api/form/PDFForm';
import { PageSizes } from 'src/api/sizes';
import type { StandardFonts } from 'src/api/StandardFonts';
import { StandardFonts } from 'src/api/StandardFonts';
import {
AbstractCustomFontEmbedder,
CustomFontEmbedder,
Expand All @@ -37,6 +38,7 @@ import {
StandardFontEmbedder,
UnexpectedObjectTypeError,
} from 'src/core';
import { mapFontkitError } from 'src/core/embedders/fontkit-helpers';
import {
ParseSpeeds,
AttachmentOptions,
Expand All @@ -50,6 +52,8 @@ import type { PDFObject } from 'src/core/objects/PDFObject';
import type { PDFRef } from 'src/core/objects/PDFRef';
import type { TransformationMatrix } from 'src/types/matrix';
import {
InvalidOptionPassedError,
InvalidTypePassedError,
assertIs,
assertIsOneOfOrUndefined,
assertOrUndefined,
Expand All @@ -60,6 +64,7 @@ import {
pluckIndices,
range,
toUint8Array,
values,
} from 'src/utils';
import { FileEmbedder, AFRelationship } from 'src/core/embedders/FileEmbedder';
import { PDFEmbeddedFile } from 'src/api/PDFEmbeddedFile';
Expand Down Expand Up @@ -876,12 +881,20 @@ export class PDFDocument {
embedder = StandardFontEmbedder.for(font, customName);
} else if (canBeConvertedToUint8Array(font)) {
const bytes = toUint8Array(font);
embedder = subset
? CustomFontSubsetEmbedder.for(bytes, customName, vertical, advanced)
: CustomFontEmbedder.for(bytes, customName, vertical, advanced);
try {
embedder = subset
? CustomFontSubsetEmbedder.for(bytes, customName, vertical, advanced)
: CustomFontEmbedder.for(bytes, customName, vertical, advanced);
} catch (error) {
const mappedError = mapFontkitError(error);
if (mappedError) throw mappedError;
throw error;
}
} else {
throw new TypeError(
'`font` must be one of `StandardFonts | Uint8Array | ArrayBuffer`',
throw new InvalidTypePassedError(
'font',
['string', Uint8Array, ArrayBuffer],
font,
);
}

Expand All @@ -906,17 +919,22 @@ export class PDFDocument {
embedTTFFont(font: TTFFont, options: EmbedFontOptions): PDFFont {
const { subset, customName, vertical, advanced } = options;
if (subset !== true) {
throw new TypeError(
'`subset` must explicitly be true when embedding a TTFFont',
);
throw new InvalidFontSubsetOptionError(subset);
}

const embedder = CustomFontSubsetEmbedder.forTTFFont(
font,
customName,
vertical,
advanced,
);
let embedder: AbstractCustomFontEmbedder;
try {
embedder = CustomFontSubsetEmbedder.forTTFFont(
font,
customName,
vertical,
advanced,
);
} catch (error) {
const mappedError = mapFontkitError(error);
if (mappedError) throw mappedError;
throw error;
}

const ref = this.context.nextRef();
const pdfFont = PDFFont.of(ref, this, embedder);
Expand All @@ -939,7 +957,7 @@ export class PDFDocument {
embedStandardFont(font: StandardFonts, customName?: string): PDFFont {
assertIs(font, 'font', ['string']);
if (!isStandardFont(font)) {
throw new TypeError('`font` must be one of type `StandardFonts`');
throw new InvalidOptionPassedError('font', values(StandardFonts), font);
}

const embedder = StandardFontEmbedder.for(font, customName);
Expand Down
25 changes: 22 additions & 3 deletions src/api/PDFFont.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PDFRef,
StandardFontEmbedder,
} from 'src/core';
import { mapFontkitError } from 'src/core/embedders/fontkit-helpers';
import type { SingleLineTextOrGlyphs } from 'src/types/text';
import { assertIs, assertOrUndefined } from 'src/utils';

Expand Down Expand Up @@ -76,7 +77,13 @@ export class PDFFont implements Embeddable {
): PDFHexString {
assertIs(text, 'text', ['string', Array, Uint16Array, Uint32Array]);
this.modified = true;
return this.embedder.encodeText(text, layoutAdvancedParams);
try {
return this.embedder.encodeText(text, layoutAdvancedParams);
} catch (error) {
const mappedError = mapFontkitError(error);
if (mappedError) throw mappedError;
throw error;
}
}

/**
Expand All @@ -93,7 +100,13 @@ export class PDFFont implements Embeddable {
widthOfTextAtSize(text: string, size: number): number {
assertIs(text, 'text', ['string']);
assertIs(size, 'size', ['number']);
return this.embedder.widthOfTextAtSize(text, size);
try {
return this.embedder.widthOfTextAtSize(text, size);
} catch (error) {
const mappedError = mapFontkitError(error);
if (mappedError) throw mappedError;
throw error;
}
}

/**
Expand Down Expand Up @@ -173,7 +186,13 @@ export class PDFFont implements Embeddable {
async embed(): Promise<void> {
// TODO: Cleanup orphan embedded objects if a font is embedded multiple times...
if (this.modified) {
await this.embedder.embedIntoContext(this.doc.context, this.ref);
try {
this.embedder.embedIntoContext(this.doc.context, this.ref);
} catch (error) {
const mappedError = mapFontkitError(error);
if (mappedError) throw mappedError;
throw error;
}
this.modified = false;
}
}
Expand Down
67 changes: 48 additions & 19 deletions src/api/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
setStrokingGrayscaleColor,
setStrokingRgbColor,
} from 'src/api/operators';
import { assertRange, error } from 'src/utils';
import { InvalidColorError } from 'src/api/errors';
import { assertRange } from 'src/utils';

export enum ColorTypes {
Grayscale = 'Grayscale',
Expand Down Expand Up @@ -63,19 +64,41 @@ export const cmyk = (

const { Grayscale, RGB, CMYK } = ColorTypes;

// prettier-ignore
export const setFillingColor = (color: Color) =>
color.type === Grayscale ? setFillingGrayscaleColor(color.gray)
: color.type === RGB ? setFillingRgbColor(color.red, color.green, color.blue)
: color.type === CMYK ? setFillingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
: error(`Invalid color: ${JSON.stringify(color)}`);
export const setFillingColor = (color: Color) => {
switch (color.type) {
case Grayscale:
return setFillingGrayscaleColor(color.gray);
case RGB:
return setFillingRgbColor(color.red, color.green, color.blue);
case CMYK:
return setFillingCmykColor(
color.cyan,
color.magenta,
color.yellow,
color.key,
);
default:
throw new InvalidColorError(color);
}
};

// prettier-ignore
export const setStrokingColor = (color: Color) =>
color.type === Grayscale ? setStrokingGrayscaleColor(color.gray)
: color.type === RGB ? setStrokingRgbColor(color.red, color.green, color.blue)
: color.type === CMYK ? setStrokingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
: error(`Invalid color: ${JSON.stringify(color)}`);
export const setStrokingColor = (color: Color) => {
switch (color.type) {
case Grayscale:
return setStrokingGrayscaleColor(color.gray);
case RGB:
return setStrokingRgbColor(color.red, color.green, color.blue);
case CMYK:
return setStrokingCmykColor(
color.cyan,
color.magenta,
color.yellow,
color.key,
);
default:
throw new InvalidColorError(color);
}
};

// prettier-ignore
export const componentsToColor = (comps?: number[], scale = 1) => (
Expand All @@ -96,9 +119,15 @@ export const componentsToColor = (comps?: number[], scale = 1) => (
: undefined
);

// prettier-ignore
export const colorToComponents = (color: Color) =>
color.type === Grayscale ? [color.gray]
: color.type === RGB ? [color.red, color.green, color.blue]
: color.type === CMYK ? [color.cyan, color.magenta, color.yellow, color.key]
: error(`Invalid color: ${JSON.stringify(color)}`);
export const colorToComponents = (color: Color) => {
switch (color.type) {
case Grayscale:
return [color.gray];
case RGB:
return [color.red, color.green, color.blue];
case CMYK:
return [color.cyan, color.magenta, color.yellow, color.key];
default:
throw new InvalidColorError(color);
}
};
Loading