Skip to content

Commit 8aa6bb6

Browse files
falworks-dyFAL
andauthored
1.17.1-mod.2025.7 : フォント埋め込み時に fontkit の TTFFont インスタンスを受け付けるよう拡張 (#10)
* docs: add AGENTS.md * refactor: separate custom font embedder classes into subset/non-subset implementations * feat: add support for embedding TTFFont instances (subset-only) in PDFDocument * style: code formatting * docs: update internal docs * chore: upgrade devDependencies (types) * docs: update modifications change log * 1.17.1-mod.2025.7 * ci: update Node.js version to v20+ * update comments * change the options argument of `embedTTFFont()` to required * rename CustomFontEmbedder -> AbstractCustomFontEmbedder and CustomFontNonSubsetEmbedder -> CustomFontEmbedder * test: remove unnecessary async/await --------- Co-authored-by: FAL <contact@fal-works.com>
1 parent aa044dd commit 8aa6bb6

18 files changed

+659
-386
lines changed

.github/workflows/npm-publish-github-packages.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- uses: actions/checkout@v4
1515
- uses: actions/setup-node@v4
1616
with:
17-
node-version: 18
17+
node-version: 20
1818
registry-url: https://npm.pkg.github.com/
1919
scope: '@denkiyagi'
2020
cache: yarn
@@ -42,7 +42,7 @@ jobs:
4242
- uses: actions/checkout@v4
4343
- uses: actions/setup-node@v4
4444
with:
45-
node-version: 18
45+
node-version: 20
4646
registry-url: https://npm.pkg.github.com/
4747
scope: '@denkiyagi'
4848
- uses: actions/download-artifact@v4

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515

1616
strategy:
1717
matrix:
18-
node-version: [16.x, 18.x]
18+
node-version: [20.x, 22.x, 24.x]
1919

2020
steps:
2121
- uses: actions/checkout@v4

AGENTS.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# AI Agent Briefing
2+
3+
## Mission & Purpose
4+
- `@denkiyagi/pdf-lib` extends the upstream `pdf-lib` to power PDF generation for `yagisan-reports`, a PDF report generation engine.
5+
- Current focus areas: richer custom font handling via `@denkiyagi/fontkit`, groundwork for improved error handling, adding PDF encryption support, and fixing upstream bugs.
6+
- Keep the fork aligned with upstream quality while preserving `yagisan-reports`-specific behaviors; prefer additive changes and call out intentional divergences in `MODIFICATIONS.md` or `MODIFICATION-DEVELOPMENT.md`.
7+
8+
## Project Orientation
9+
- `src/` – Primary TypeScript source for the library; exports must remain framework-agnostic and compatible with browsers and Node.
10+
- `tests/` – Jest-powered regression coverage.
11+
- `apps/` – Example and manual-test harnesses used to validate real-world document flows.
12+
- `assets/` – Sample PDFs, fonts, and images consumed by docs and tests; treat as fixtures when updating expectations.
13+
- `build/`, `rollup.config.mjs`, `tsconfig.json`, `jest.json` – Tooling scaffolding for bundling, type emission, and tests.
14+
- `docs/` – Markdown/docs site material mirrored from upstream; update only when behavior changes.
15+
- `scratchpad/` – Throwaway experiments; do not rely on contents for production logic.
16+
17+
## Tech Stack
18+
- Node.js 20+ runtime, Yarn package manager.
19+
- TypeScript 4.x across source and typings.
20+
- Critical dependency: `@denkiyagi/fontkit` (our fork) powers font parsing, embedding, and subsetting.
21+
22+
## Common Commands
23+
- `yarn typecheck` – Checks TypeScript types across the codebase.
24+
- `yarn lint` – Runs the lint rules expected by CI; fix warnings locally to avoid pipeline noise.
25+
26+
## Helpful References
27+
- Change log: `MODIFICATIONS.md` (what differs from upstream).
28+
- Dev workflow, release, and scripting notes: `MODIFICATION-DEVELOPMENT.md`.
29+
- Upstream behavior context: `README.md` plus linked docs/examples.

MODIFICATION-DEVELOPMENT.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ After cloning the repository and running `yarn install`, you must run the follow
88
yarn dev:prep
99
```
1010

11+
## Editor setup
12+
13+
If you use VS Code:
14+
15+
`TypeScript: Select TypeScript Version...` -> `Use Workspace Version`
16+
1117
## Before submitting pull request
1218

1319
- Lint and typecheck

MODIFICATIONS.md

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

3+
## [1.17.1-mod.2025.7]
4+
5+
- Add `PDFDocument#embedTTFFont` (subset-only) and `CustomFontSubsetEmbedder.forTTFFont` so pre-created fontkit `TTFFont` instances can be embedded.
6+
- Refactor the internal custom font embedders into separate subset and non-subset implementations by adding the abstract base class `AbstractCustomFontEmbedder`.
7+
38
## [1.17.1-mod.2025.6]
49

510
- Update `@denkiyagi/fontkit` to `2.0.4-mod.2025.2`, which enhances runtime performance for Unicode Variation Sequences (UVS) support.
@@ -12,7 +17,7 @@
1217

1318
- Changed npm dependency `fontkit` to `@denkiyagi/fontkit`
1419
- Improved `options` parameter of `PDFDocument#embedFont`
15-
- Fix `CustomFontEmbedder#embedCIDFontDict` so that it respects glyph metrics when embedding vertical fonts
20+
- Fix `AbstractCustomFontEmbedder#embedCIDFontDict` so that it respects glyph metrics when embedding vertical fonts
1621
- Add methods `PDFFont#getRawStandardFont` and `PDFFont#getRawCustomFont`
1722
- Improve parameters of `PDFPage#drawText`:
1823
- Expand the data type of the `text` parameter so that it also accepts Glyph IDs instead of string

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,7 @@ Check out [MAINTAINERSHIP.md](docs/MAINTAINERSHIP.md) for details on how this re
12091209

12101210
## Prior Art
12111211

1212-
- [`pdfkit`](https://github.com/devongovett/pdfkit) is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creating `pdf-lib`. `pdfkit`'s code for [font embedding](src/core/embedders/CustomFontEmbedder.ts#L17-L21), [PNG embedding](src/core/embedders/PngEmbedder.ts#L7-L11), and [JPG embedding](src/core/embedders/JpegEmbedder.ts#L25-L29) was especially useful.
1212+
- [`pdfkit`](https://github.com/devongovett/pdfkit) is a PDF generation library for Node and the Browser. This library was immensely helpful as a reference and existence proof when creating `pdf-lib`. `pdfkit`'s code for [font embedding](src/core/embedders/AbstractCustomFontEmbedder.ts), [PNG embedding](src/core/embedders/PngEmbedder.ts#L7-L11), and [JPG embedding](src/core/embedders/JpegEmbedder.ts#L25-L29) was especially useful.
12131213
- [`pdf.js`](https://github.com/mozilla/pdf.js) is a PDF rendering library for the Browser. This library was helpful as a reference when writing `pdf-lib`'s parser. Some of the code for stream decoding was [ported directly to TypeScript](src/core/streams) for use in `pdf-lib`.
12141214
- [`pdfbox`](https://pdfbox.apache.org/) is a PDF generation and modification library written in Java. This library was an invaluable reference when implementing form creation and filling APIs for `pdf-lib`.
12151215
- [`jspdf`](https://github.com/MrRio/jsPDF) is a PDF generation library for the browser.

package.json

Lines changed: 4 additions & 4 deletions
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.6",
3+
"version": "1.17.1-mod.2025.7",
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": [
@@ -89,10 +89,10 @@
8989
"@rollup/plugin-json": "^6.0.0",
9090
"@rollup/plugin-node-resolve": "^15.0.1",
9191
"@rollup/plugin-terser": "^0.3.0",
92-
"@types/crypto-js": "^4.0.2",
92+
"@types/crypto-js": "^4.2.2",
9393
"@types/jest": "^26.0.0",
94-
"@types/node-fetch": "^2.5.7",
95-
"@types/pako": "^2.0.0",
94+
"@types/node-fetch": "^2.6.13",
95+
"@types/pako": "^2.0.4",
9696
"downlevel-dts": "^0.5.0",
9797
"flamebearer": "^1.1.3",
9898
"http-server": "^0.12.3",

src/api/PDFDocument.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TTFFont } from '@denkiyagi/fontkit';
12
import type { Embeddable } from 'src/api/Embeddable';
23
import {
34
EncryptedPDFError,
@@ -12,6 +13,7 @@ import { PDFForm } from 'src/api/form/PDFForm';
1213
import { PageSizes } from 'src/api/sizes';
1314
import type { StandardFonts } from 'src/api/StandardFonts';
1415
import {
16+
AbstractCustomFontEmbedder,
1517
CustomFontEmbedder,
1618
CustomFontSubsetEmbedder,
1719
JpegEmbedder,
@@ -913,7 +915,7 @@ export class PDFDocument {
913915
* ```
914916
* @param font The input data for a font.
915917
* @param options The options to be used when embedding the font.
916-
* @returns Resolves with the embedded font.
918+
* @returns The embedded font represented as `PDFFont`.
917919
*/
918920
embedFont(
919921
font: StandardFonts | string | Uint8Array | ArrayBuffer,
@@ -924,14 +926,19 @@ export class PDFDocument {
924926
assertIs(font, 'font', ['string', Uint8Array, ArrayBuffer]);
925927
assertIs(subset, 'subset', ['boolean']);
926928

927-
let embedder: CustomFontEmbedder | StandardFontEmbedder;
929+
let embedder: AbstractCustomFontEmbedder | StandardFontEmbedder;
928930
if (isStandardFont(font)) {
929931
embedder = StandardFontEmbedder.for(font, customName);
930932
} else if (canBeConvertedToUint8Array(font)) {
931933
const bytes = toUint8Array(font);
932934
embedder = subset
933935
? CustomFontSubsetEmbedder.for(bytes, customName, vertical, advanced)
934-
: CustomFontEmbedder.for(bytes, customName, vertical, advanced);
936+
: CustomFontEmbedder.for(
937+
bytes,
938+
customName,
939+
vertical,
940+
advanced,
941+
);
935942
} else {
936943
throw new TypeError(
937944
'`font` must be one of `StandardFonts | string | Uint8Array | ArrayBuffer`',
@@ -945,6 +952,39 @@ export class PDFDocument {
945952
return pdfFont;
946953
}
947954

955+
/**
956+
* Embed a fontkit `TTFFont` instance into this document.
957+
*
958+
* NOTE: `options.subset` must be `true`, because the non-subset font embedder requires
959+
* the raw font bytes which are not available from a `TTFFont` instance.
960+
*
961+
* @param font The `TTFFont` to subset and embed.
962+
* @param options Additional embedding options (the `subset` option must be `true`).
963+
* @returns The embedded font represented as `PDFFont`.
964+
* @throws If `options.subset` is not `true`.
965+
*/
966+
embedTTFFont(font: TTFFont, options: EmbedFontOptions): PDFFont {
967+
const { subset, customName, vertical, advanced } = options;
968+
if (subset !== true) {
969+
throw new TypeError(
970+
'`subset` must explicitly be true when embedding a TTFFont',
971+
);
972+
}
973+
974+
const embedder = CustomFontSubsetEmbedder.forTTFFont(
975+
font,
976+
customName,
977+
vertical,
978+
advanced,
979+
);
980+
981+
const ref = this.context.nextRef();
982+
const pdfFont = PDFFont.of(ref, this, embedder);
983+
this.fonts.push(pdfFont);
984+
985+
return pdfFont;
986+
}
987+
948988
/**
949989
* Embed a standard font into this document.
950990
* For example:

src/api/PDFFont.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import type { Font as RawStandardFont } from '@pdf-lib/standard-fonts';
33
import type { Embeddable } from 'src/api//Embeddable';
44
import { PDFDocument } from 'src/api/PDFDocument';
55
import {
6-
CustomFontEmbedder,
6+
AbstractCustomFontEmbedder,
77
PDFHexString,
88
PDFRef,
99
StandardFontEmbedder,
1010
} from 'src/core';
1111
import type { SingleLineTextOrGlyphs } from 'src/types/text';
1212
import { assertIs, assertOrUndefined } from 'src/utils';
1313

14-
export type FontEmbedder = CustomFontEmbedder | StandardFontEmbedder;
14+
export type FontEmbedder =
15+
| AbstractCustomFontEmbedder
16+
| StandardFontEmbedder;
1517

1618
/**
1719
* Represents a font that has been embedded in a [[PDFDocument]].
@@ -48,7 +50,7 @@ export class PDFFont implements Embeddable {
4850
assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
4951
assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
5052
assertIs(embedder, 'embedder', [
51-
[CustomFontEmbedder, 'CustomFontEmbedder'],
53+
[AbstractCustomFontEmbedder, 'AbstractCustomFontEmbedder'],
5254
[StandardFontEmbedder, 'StandardFontEmbedder'],
5355
]);
5456

@@ -137,9 +139,8 @@ export class PDFFont implements Embeddable {
137139
getCharacterSet(): number[] {
138140
if (this.embedder instanceof StandardFontEmbedder) {
139141
return this.embedder.encoding.supportedCodePoints;
140-
} else {
141-
return this.embedder.font.characterSet;
142142
}
143+
return this.embedder.font.characterSet;
143144
}
144145

145146
/**
@@ -148,20 +149,18 @@ export class PDFFont implements Embeddable {
148149
getRawStandardFont(): RawStandardFont | null {
149150
if (this.embedder instanceof StandardFontEmbedder) {
150151
return this.embedder.font;
151-
} else {
152-
return null;
153152
}
153+
return null;
154154
}
155155

156156
/**
157157
* @returns The raw standard font instance if `this` is a custom font, otherwise `null`.
158158
*/
159159
getRawCustomFont(): TTFFont | null {
160-
if (this.embedder instanceof CustomFontEmbedder) {
160+
if (this.embedder instanceof AbstractCustomFontEmbedder) {
161161
return this.embedder.font;
162-
} else {
163-
return null;
164162
}
163+
return null;
165164
}
166165

167166
/**

0 commit comments

Comments
 (0)