Skip to content

Commit 4f24c70

Browse files
falworks-dyFAL
andauthored
APIが出しうる例外を pdf-lib 独自のエラークラスに統一 (#12)
* refactor: utils/errors を削除して throw new Error() に置き換え * refactor: エラークラス用のベースクラス(PDFLibError等)を追加 * chore: add property `type: PDFLibErrorType` to `PDFLibError` class * refactor: remove unused error classes * refactor: validatorsモジュールの一部コードを別モジュールに分離 * chore: core系モジュールの生のエラーを独自エラークラスに置き換え * chore: utils系モジュールの生のエラーを独自エラークラスに置き換え * chore: api系モジュールの生のエラーを独自エラークラスに置き換え * test: エラークラスの変更を反映 * chore: fontkitが出すエラーをcatchしてpdf-lib独自エラークラスに変換 * refactor: InvalidPngErrorクラスを汎用化 * chore: UPNGが出すエラーをcatchしてpdf-lib独自エラークラスに変換 * refactor: 役割が重複したエラークラスを統合 * fix: fontkitが出すエラーをより広範囲で捕捉 * docs: update changelog * v1.17.1-mod.2025.8 * test: make makeStubTTFFont slightly more type-safe --------- Co-authored-by: FAL <contact@fal-works.com>
1 parent cc819eb commit 4f24c70

30 files changed

+926
-316
lines changed

MODIFICATIONS.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Modifications
22

3-
## [Unreleased]
3+
## [1.17.1-mod.2025.8]
44

55
### Feature Removals
66

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

14+
### Error Handling
15+
16+
- Introduced `PDFLibError` (exported from `src/core`) with a machine-readable `type: PDFLibErrorType` to classify errors.
17+
- Refactored API/core/utils errors so that all errors extend `PDFLibError` instead of throwing bare `Error`/`TypeError`.
18+
- 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.
19+
1420
### Internal Changes
1521

1622
- Update several devDependencies including `typescript` to their latest versions.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@denkiyagi/pdf-lib",
3-
"version": "1.17.1-mod.2025.7",
3+
"version": "1.17.1-mod.2025.8",
44
"description": "A modified version of pdf-lib. Create and modify PDF files with JavaScript",
55
"author": "DenkiYagi Inc. (modified version author), Andrew Dillon <andrew.dillon.j@gmail.com> (orignal version author)",
66
"contributors": [

src/api/PDFDocument.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Embeddable } from 'src/api/Embeddable';
33
import {
44
EncryptedPDFError,
55
ForeignPageError,
6+
InvalidFontSubsetOptionError,
67
RemovePageFromEmptyDocumentError,
78
} from 'src/api/errors';
89
import { PDFEmbeddedPage } from 'src/api/PDFEmbeddedPage';
@@ -11,7 +12,7 @@ import { PDFImage } from 'src/api/PDFImage';
1112
import { PDFPage } from 'src/api/PDFPage';
1213
import { PDFForm } from 'src/api/form/PDFForm';
1314
import { PageSizes } from 'src/api/sizes';
14-
import type { StandardFonts } from 'src/api/StandardFonts';
15+
import { StandardFonts } from 'src/api/StandardFonts';
1516
import {
1617
AbstractCustomFontEmbedder,
1718
CustomFontEmbedder,
@@ -37,6 +38,7 @@ import {
3738
StandardFontEmbedder,
3839
UnexpectedObjectTypeError,
3940
} from 'src/core';
41+
import { mapFontkitError } from 'src/core/embedders/fontkit-helpers';
4042
import {
4143
ParseSpeeds,
4244
AttachmentOptions,
@@ -50,6 +52,8 @@ import type { PDFObject } from 'src/core/objects/PDFObject';
5052
import type { PDFRef } from 'src/core/objects/PDFRef';
5153
import type { TransformationMatrix } from 'src/types/matrix';
5254
import {
55+
InvalidOptionPassedError,
56+
InvalidTypePassedError,
5357
assertIs,
5458
assertIsOneOfOrUndefined,
5559
assertOrUndefined,
@@ -60,6 +64,7 @@ import {
6064
pluckIndices,
6165
range,
6266
toUint8Array,
67+
values,
6368
} from 'src/utils';
6469
import { FileEmbedder, AFRelationship } from 'src/core/embedders/FileEmbedder';
6570
import { PDFEmbeddedFile } from 'src/api/PDFEmbeddedFile';
@@ -876,12 +881,20 @@ export class PDFDocument {
876881
embedder = StandardFontEmbedder.for(font, customName);
877882
} else if (canBeConvertedToUint8Array(font)) {
878883
const bytes = toUint8Array(font);
879-
embedder = subset
880-
? CustomFontSubsetEmbedder.for(bytes, customName, vertical, advanced)
881-
: CustomFontEmbedder.for(bytes, customName, vertical, advanced);
884+
try {
885+
embedder = subset
886+
? CustomFontSubsetEmbedder.for(bytes, customName, vertical, advanced)
887+
: CustomFontEmbedder.for(bytes, customName, vertical, advanced);
888+
} catch (error) {
889+
const mappedError = mapFontkitError(error);
890+
if (mappedError) throw mappedError;
891+
throw error;
892+
}
882893
} else {
883-
throw new TypeError(
884-
'`font` must be one of `StandardFonts | Uint8Array | ArrayBuffer`',
894+
throw new InvalidTypePassedError(
895+
'font',
896+
['string', Uint8Array, ArrayBuffer],
897+
font,
885898
);
886899
}
887900

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

914-
const embedder = CustomFontSubsetEmbedder.forTTFFont(
915-
font,
916-
customName,
917-
vertical,
918-
advanced,
919-
);
925+
let embedder: AbstractCustomFontEmbedder;
926+
try {
927+
embedder = CustomFontSubsetEmbedder.forTTFFont(
928+
font,
929+
customName,
930+
vertical,
931+
advanced,
932+
);
933+
} catch (error) {
934+
const mappedError = mapFontkitError(error);
935+
if (mappedError) throw mappedError;
936+
throw error;
937+
}
920938

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

945963
const embedder = StandardFontEmbedder.for(font, customName);

src/api/PDFFont.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PDFRef,
99
StandardFontEmbedder,
1010
} from 'src/core';
11+
import { mapFontkitError } from 'src/core/embedders/fontkit-helpers';
1112
import type { SingleLineTextOrGlyphs } from 'src/types/text';
1213
import { assertIs, assertOrUndefined } from 'src/utils';
1314

@@ -76,7 +77,13 @@ export class PDFFont implements Embeddable {
7677
): PDFHexString {
7778
assertIs(text, 'text', ['string', Array, Uint16Array, Uint32Array]);
7879
this.modified = true;
79-
return this.embedder.encodeText(text, layoutAdvancedParams);
80+
try {
81+
return this.embedder.encodeText(text, layoutAdvancedParams);
82+
} catch (error) {
83+
const mappedError = mapFontkitError(error);
84+
if (mappedError) throw mappedError;
85+
throw error;
86+
}
8087
}
8188

8289
/**
@@ -93,7 +100,13 @@ export class PDFFont implements Embeddable {
93100
widthOfTextAtSize(text: string, size: number): number {
94101
assertIs(text, 'text', ['string']);
95102
assertIs(size, 'size', ['number']);
96-
return this.embedder.widthOfTextAtSize(text, size);
103+
try {
104+
return this.embedder.widthOfTextAtSize(text, size);
105+
} catch (error) {
106+
const mappedError = mapFontkitError(error);
107+
if (mappedError) throw mappedError;
108+
throw error;
109+
}
97110
}
98111

99112
/**
@@ -173,7 +186,13 @@ export class PDFFont implements Embeddable {
173186
async embed(): Promise<void> {
174187
// TODO: Cleanup orphan embedded objects if a font is embedded multiple times...
175188
if (this.modified) {
176-
await this.embedder.embedIntoContext(this.doc.context, this.ref);
189+
try {
190+
this.embedder.embedIntoContext(this.doc.context, this.ref);
191+
} catch (error) {
192+
const mappedError = mapFontkitError(error);
193+
if (mappedError) throw mappedError;
194+
throw error;
195+
}
177196
this.modified = false;
178197
}
179198
}

src/api/colors.ts

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
setStrokingGrayscaleColor,
77
setStrokingRgbColor,
88
} from 'src/api/operators';
9-
import { assertRange, error } from 'src/utils';
9+
import { InvalidColorError } from 'src/api/errors';
10+
import { assertRange } from 'src/utils';
1011

1112
export enum ColorTypes {
1213
Grayscale = 'Grayscale',
@@ -63,19 +64,41 @@ export const cmyk = (
6364

6465
const { Grayscale, RGB, CMYK } = ColorTypes;
6566

66-
// prettier-ignore
67-
export const setFillingColor = (color: Color) =>
68-
color.type === Grayscale ? setFillingGrayscaleColor(color.gray)
69-
: color.type === RGB ? setFillingRgbColor(color.red, color.green, color.blue)
70-
: color.type === CMYK ? setFillingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
71-
: error(`Invalid color: ${JSON.stringify(color)}`);
67+
export const setFillingColor = (color: Color) => {
68+
switch (color.type) {
69+
case Grayscale:
70+
return setFillingGrayscaleColor(color.gray);
71+
case RGB:
72+
return setFillingRgbColor(color.red, color.green, color.blue);
73+
case CMYK:
74+
return setFillingCmykColor(
75+
color.cyan,
76+
color.magenta,
77+
color.yellow,
78+
color.key,
79+
);
80+
default:
81+
throw new InvalidColorError(color);
82+
}
83+
};
7284

73-
// prettier-ignore
74-
export const setStrokingColor = (color: Color) =>
75-
color.type === Grayscale ? setStrokingGrayscaleColor(color.gray)
76-
: color.type === RGB ? setStrokingRgbColor(color.red, color.green, color.blue)
77-
: color.type === CMYK ? setStrokingCmykColor(color.cyan, color.magenta, color.yellow, color.key)
78-
: error(`Invalid color: ${JSON.stringify(color)}`);
85+
export const setStrokingColor = (color: Color) => {
86+
switch (color.type) {
87+
case Grayscale:
88+
return setStrokingGrayscaleColor(color.gray);
89+
case RGB:
90+
return setStrokingRgbColor(color.red, color.green, color.blue);
91+
case CMYK:
92+
return setStrokingCmykColor(
93+
color.cyan,
94+
color.magenta,
95+
color.yellow,
96+
color.key,
97+
);
98+
default:
99+
throw new InvalidColorError(color);
100+
}
101+
};
79102

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

99-
// prettier-ignore
100-
export const colorToComponents = (color: Color) =>
101-
color.type === Grayscale ? [color.gray]
102-
: color.type === RGB ? [color.red, color.green, color.blue]
103-
: color.type === CMYK ? [color.cyan, color.magenta, color.yellow, color.key]
104-
: error(`Invalid color: ${JSON.stringify(color)}`);
122+
export const colorToComponents = (color: Color) => {
123+
switch (color.type) {
124+
case Grayscale:
125+
return [color.gray];
126+
case RGB:
127+
return [color.red, color.green, color.blue];
128+
case CMYK:
129+
return [color.cyan, color.magenta, color.yellow, color.key];
130+
default:
131+
throw new InvalidColorError(color);
132+
}
133+
};

0 commit comments

Comments
 (0)