Skip to content

Commit 43b762c

Browse files
ralfstxclaude
andcommitted
💥 Remove support for fonts in document definition
`FontStore` parsed each font twice: once in `registerFont()` to extract metadata, and again in `_loadFont()` when actually selecting a font. Additionally, a stale TODO comment remained from the pdf-core migration, and the deprecated `fonts` property in the document definition was still supported. Store the `PDFEmbeddedFont` created during `registerFont()` in `FontDef` so that `_loadFont()` can reuse it. Remove the `fonts` property from `DocumentDefinition`, along with `readFonts`, `readFont`, `FontsDefinition`, `FontDefinition`, and the `FontStore` constructor parameter. Update font-store tests to use `registerFont()` with explicit config instead of pre-built `FontDef` arrays. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 394d3c0 commit 43b762c

File tree

8 files changed

+49
-170
lines changed

8 files changed

+49
-170
lines changed

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
referred to by URL instead. The `ImagesDefinition` and
1919
`ImageDefinition` types have been removed.
2020

21+
- The `fonts` property in the document definition. Fonts must now be
22+
registered with `PdfMaker.registerFont()`. The `FontsDefinition` and
23+
`FontDefinition` types have been removed.
24+
2125
### Breaking
2226

2327
- Text height is now based on the OS/2 typographic metrics

‎src/api/PdfMaker.ts‎

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,10 @@ export class PdfMaker {
5555
*/
5656
async makePdf(definition: DocumentDefinition): Promise<Uint8Array> {
5757
const def = readAs(definition, 'definition', readDocumentDefinition);
58-
let fontStore = this.#fontStore;
59-
if (def.fonts) {
60-
fontStore = new FontStore(def.fonts);
61-
console.warn(
62-
'Registering fonts via document definition is deprecated. Use PdfMaker.registerFont() instead.',
63-
);
64-
}
65-
const ctx: MakerCtx = { fontStore, imageLoader: createImageLoader(this.#resourceRoot) };
58+
const ctx: MakerCtx = {
59+
fontStore: this.#fontStore,
60+
imageLoader: createImageLoader(this.#resourceRoot),
61+
};
6662
if (def.dev?.guides != null) ctx.guides = def.dev.guides;
6763
const pages = await layoutPages(def, ctx);
6864
return await renderDocument(def, pages);

‎src/api/document.ts‎

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,6 @@ export type DocumentDefinition = {
5151
*/
5252
margin?: Length | BoxLengths | ((info: PageInfo) => Length | BoxLengths);
5353

54-
/**
55-
* The fonts to use in the document. There is no default. Each font that is used in the document
56-
* must be registered. Not needed for documents that contain only graphics.
57-
*
58-
* @deprecated Register fonts with `PdfMaker` instead.
59-
*/
60-
fonts?: FontsDefinition;
61-
6254
/**
6355
* Metadata to include in the PDF's *document information dictionary*.
6456
*/
@@ -215,38 +207,6 @@ export type CustomInfoAttrs = CustomInfoProps;
215207
*/
216208
export type CustomInfoProps = Record<`XX${string}`, string>;
217209

218-
/**
219-
* An object that defines the fonts to use in the document.
220-
*
221-
* @deprecated Register fonts with `PdfMaker` instead.
222-
*/
223-
export type FontsDefinition = { [name: string]: FontDefinition[] };
224-
225-
/**
226-
* The definition of a single font.
227-
*
228-
* @deprecated Register fonts with `PdfMaker` instead.
229-
*/
230-
export type FontDefinition = {
231-
/**
232-
* The font data as a Uint8Array.
233-
*
234-
* Supports TrueType font files (`.ttf`) and OpenType (`.otf`) font
235-
* files with TrueType outlines.
236-
*/
237-
data: Uint8Array;
238-
239-
/**
240-
* Whether this is a bold font.
241-
*/
242-
bold?: boolean;
243-
244-
/**
245-
* Whether this is an italic font.
246-
*/
247-
italic?: boolean;
248-
};
249-
250210
/**
251211
* Information about the current page, provided to functions that create
252212
* page-specific headers, footers, and margins.

‎src/font-store.test.ts‎

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { PDFEmbeddedFont } from '@ralfstx/pdf-core';
55
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
66

77
import { FontStore } from './font-store.ts';
8-
import type { FontDef } from './fonts.ts';
98
import { mkData } from './test/test-utils.ts';
109

1110
vi.mock('@ralfstx/pdf-core', async (importOriginal) => {
@@ -39,14 +38,6 @@ vi.mock('@ralfstx/pdf-core', async (importOriginal) => {
3938
});
4039

4140
describe('FontStore', () => {
42-
let normalFont: FontDef;
43-
let italicFont: FontDef;
44-
let obliqueFont: FontDef;
45-
let boldFont: FontDef;
46-
let italicBoldFont: FontDef;
47-
let obliqueBoldFont: FontDef;
48-
let otherFont: FontDef;
49-
5041
describe('registerFont', () => {
5142
let robotoRegular: Uint8Array;
5243
let robotoLightItalic: Uint8Array;
@@ -93,23 +84,7 @@ describe('FontStore', () => {
9384
let store: FontStore;
9485

9586
beforeEach(() => {
96-
normalFont = fakeFontDef('Test');
97-
italicFont = fakeFontDef('Test', { style: 'italic' });
98-
obliqueFont = fakeFontDef('Test', { style: 'oblique' });
99-
boldFont = fakeFontDef('Test', { weight: 700 });
100-
italicBoldFont = fakeFontDef('Test', { style: 'italic', weight: 700 });
101-
obliqueBoldFont = fakeFontDef('Test', { style: 'oblique', weight: 700 });
102-
otherFont = fakeFontDef('Other');
103-
104-
store = new FontStore([
105-
normalFont,
106-
italicFont,
107-
obliqueFont,
108-
boldFont,
109-
italicBoldFont,
110-
obliqueBoldFont,
111-
otherFont,
112-
]);
87+
store = createTestStore();
11388
});
11489

11590
afterEach(() => {
@@ -137,7 +112,9 @@ describe('FontStore', () => {
137112
});
138113

139114
it('rejects when no matching font style can be found', async () => {
140-
store = new FontStore([normalFont, boldFont]);
115+
const store = new FontStore();
116+
registerFakeFont(store, 'Test');
117+
registerFakeFont(store, 'Test', { weight: 700 });
141118

142119
await expect(store.selectFont({ fontFamily: 'Test', fontStyle: 'italic' })).rejects.toThrow(
143120
new Error("Could not load font for 'Test', style=italic, weight=normal", {
@@ -181,15 +158,23 @@ describe('FontStore', () => {
181158
});
182159

183160
it('falls back to oblique when no italic font can be found', async () => {
184-
store = new FontStore([normalFont, obliqueFont, boldFont, obliqueBoldFont]);
161+
const store = new FontStore();
162+
registerFakeFont(store, 'Test');
163+
registerFakeFont(store, 'Test', { style: 'oblique' });
164+
registerFakeFont(store, 'Test', { weight: 700 });
165+
registerFakeFont(store, 'Test', { style: 'oblique', weight: 700 });
185166

186167
await expect(store.selectFont({ fontFamily: 'Test', fontStyle: 'italic' })).resolves.toEqual(
187168
expect.objectContaining({ name: 'MockFont:Test:oblique:400' }),
188169
);
189170
});
190171

191172
it('falls back to italic when no oblique font can be found', async () => {
192-
store = new FontStore([normalFont, italicFont, boldFont, italicBoldFont]);
173+
const store = new FontStore();
174+
registerFakeFont(store, 'Test');
175+
registerFakeFont(store, 'Test', { style: 'italic' });
176+
registerFakeFont(store, 'Test', { weight: 700 });
177+
registerFakeFont(store, 'Test', { style: 'italic', weight: 700 });
193178

194179
const font = await store.selectFont({ fontFamily: 'Test', fontStyle: 'italic' });
195180

@@ -228,9 +213,25 @@ describe('FontStore', () => {
228213
});
229214
});
230215

231-
function fakeFontDef(family: string, options?: Partial<FontDef>): FontDef {
216+
function registerFakeFont(
217+
store: FontStore,
218+
family: string,
219+
options?: { style?: string; weight?: number },
220+
) {
232221
const style = options?.style ?? 'normal';
233222
const weight = options?.weight ?? 400;
234-
const data = options?.data ?? mkData([family, style, weight].join(':'));
235-
return { family, style, weight, data };
223+
const data = mkData([family, style, weight].join(':'));
224+
store.registerFont(data, { family, style: style as 'normal', weight });
225+
}
226+
227+
function createTestStore(): FontStore {
228+
const store = new FontStore();
229+
registerFakeFont(store, 'Test');
230+
registerFakeFont(store, 'Test', { style: 'italic' });
231+
registerFakeFont(store, 'Test', { style: 'oblique' });
232+
registerFakeFont(store, 'Test', { weight: 700 });
233+
registerFakeFont(store, 'Test', { style: 'italic', weight: 700 });
234+
registerFakeFont(store, 'Test', { style: 'oblique', weight: 700 });
235+
registerFakeFont(store, 'Other');
236+
return store;
236237
}

‎src/font-store.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ export class FontStore {
1010
readonly #fontDefs: FontDef[];
1111
#fontCache: Record<string, Promise<Font>> = {};
1212

13-
constructor(fontDefs?: FontDef[]) {
14-
this.#fontDefs = fontDefs ?? [];
13+
constructor() {
14+
this.#fontDefs = [];
1515
}
1616

1717
registerFont(data: Uint8Array, config?: FontConfig): void {
1818
const pdfFont = new PDFEmbeddedFont(data);
1919
const family = config?.family ?? pdfFont.familyName;
2020
const style = config?.style ?? pdfFont.style;
2121
const weight = weightToNumber(config?.weight ?? pdfFont.weight);
22-
this.#fontDefs.push({ family, style, weight, data });
22+
this.#fontDefs.push({ family, style, weight, data, pdfFont });
2323
this.#fontCache = {}; // Invalidate cache
2424
}
2525

@@ -40,11 +40,11 @@ export class FontStore {
4040

4141
_loadFont(selector: FontSelector, key: string): Promise<Font> {
4242
const selectedFontDef = selectFontDef(this.#fontDefs, selector);
43-
const pdfFont = new PDFEmbeddedFont(selectedFontDef.data);
43+
const pdfFont = selectedFontDef.pdfFont ?? new PDFEmbeddedFont(selectedFontDef.data);
4444
return Promise.resolve(
4545
pickDefined({
4646
key,
47-
name: pdfFont.fontName ?? selectedFontDef.family, // TODO ?? pdfFont.postscriptName
47+
name: pdfFont.fontName ?? selectedFontDef.family,
4848
style: selector.fontStyle ?? 'normal',
4949
weight: weightToNumber(selector.fontWeight ?? 400),
5050
pdfFont,

‎src/fonts.test.ts‎

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,7 @@
11
import { describe, expect, it } from 'vitest';
22

33
import type { FontWeight } from './api/text.ts';
4-
import { readFonts, weightToNumber } from './fonts.ts';
5-
6-
describe('readFonts', () => {
7-
it('returns fonts array', () => {
8-
const fontsDef = {
9-
Test: [
10-
{ data: mkData('Test_Sans_Normal') },
11-
{ data: mkData('Test_Sans_Italic'), italic: true },
12-
{ data: mkData('Test_Sans_Bold'), bold: true },
13-
{ data: mkData('Test_Sans_BoldItalic'), italic: true, bold: true },
14-
],
15-
Other: [{ data: mkData('Other_Normal') }],
16-
};
17-
18-
const fonts = readFonts(fontsDef);
19-
20-
expect(fonts).toEqual([
21-
{ family: 'Test', style: 'normal', weight: 400, data: mkData('Test_Sans_Normal') },
22-
{ family: 'Test', style: 'italic', weight: 400, data: mkData('Test_Sans_Italic') },
23-
{ family: 'Test', style: 'normal', weight: 700, data: mkData('Test_Sans_Bold') },
24-
{ family: 'Test', style: 'italic', weight: 700, data: mkData('Test_Sans_BoldItalic') },
25-
{ family: 'Other', style: 'normal', weight: 400, data: mkData('Other_Normal') },
26-
]);
27-
});
28-
29-
it('throws on missing input', () => {
30-
expect(() => readFonts(undefined)).toThrow(new TypeError('Expected object, got: undefined'));
31-
});
32-
33-
it('throws on invalid type', () => {
34-
expect(() => readFonts(23)).toThrow(new TypeError('Expected object, got: 23'));
35-
});
36-
37-
it('throws on invalid italic value', () => {
38-
const fn = () => readFonts({ Test: [{ data: 'data', italic: 23 }] });
39-
40-
expect(fn).toThrow(
41-
new TypeError('Invalid value for "Test/0/italic": Expected boolean, got: 23'),
42-
);
43-
});
44-
45-
it('throws on invalid bold value', () => {
46-
const fn = () => readFonts({ Test: [{ data: 'data', bold: 23 }] });
47-
48-
expect(fn).toThrow(new TypeError('Invalid value for "Test/0/bold": Expected boolean, got: 23'));
49-
});
50-
51-
it('throws on missing data', () => {
52-
const fn = () => readFonts({ Test: [{ italic: true }] });
53-
54-
expect(fn).toThrow(new TypeError('Invalid value for "Test/0": Missing value for "data"'));
55-
});
56-
});
4+
import { weightToNumber } from './fonts.ts';
575

586
describe('weightToNumber', () => {
597
it('supports keywords `normal` and `bold`', () => {
@@ -79,7 +27,3 @@ describe('weightToNumber', () => {
7927
expect(() => weightToNumber(0.1)).toThrow(new Error('Invalid font weight: 0.1'));
8028
});
8129
});
82-
83-
function mkData(value: string) {
84-
return new Uint8Array(value.split('').map((c) => c.charCodeAt(0)));
85-
}

‎src/fonts.ts‎

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import type { PDFFont } from '@ralfstx/pdf-core';
1+
import type { PDFEmbeddedFont, PDFFont } from '@ralfstx/pdf-core';
22

33
import type { FontStyle, FontWeight } from './api/text.ts';
4-
import { readBinaryData } from './binary-data.ts';
54
import { printValue } from './print-value.ts';
6-
import { optional, readAs, readBoolean, readObject, required, types } from './types.ts';
75

86
/**
97
* The resolved definition of a font.
@@ -13,6 +11,7 @@ export type FontDef = {
1311
style: FontStyle;
1412
weight: number;
1513
data: Uint8Array;
14+
pdfFont?: PDFEmbeddedFont;
1615
};
1716

1817
export type Font = {
@@ -29,27 +28,6 @@ export type FontSelector = {
2928
fontWeight?: FontWeight;
3029
};
3130

32-
export function readFonts(input: unknown): FontDef[] {
33-
return Object.entries(readObject(input)).flatMap(([name, fontDef]) => {
34-
return readAs(fontDef, name, required(types.array(readFont))).map(
35-
(font) => ({ family: name, ...font }) as FontDef,
36-
);
37-
});
38-
}
39-
40-
export function readFont(input: unknown): Partial<FontDef> {
41-
const obj = readObject(input, {
42-
italic: optional((value) => readBoolean(value) || undefined),
43-
bold: optional((value) => readBoolean(value) || undefined),
44-
data: required(readBinaryData),
45-
});
46-
return {
47-
style: obj.italic ? 'italic' : 'normal',
48-
weight: obj.bold ? 700 : 400,
49-
data: obj.data,
50-
} as FontDef;
51-
}
52-
5331
export function weightToNumber(weight: FontWeight): number {
5432
if (weight === 'normal') {
5533
return 400;

‎src/read-document.ts‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@ import type { PDFDocument } from '@ralfstx/pdf-core';
33
import type { FileRelationShip } from './api/document.ts';
44
import type { BoxEdges, Size } from './box.ts';
55
import { parseEdges } from './box.ts';
6-
import type { FontDef } from './fonts.ts';
7-
import { readFonts } from './fonts.ts';
86
import type { Block, TextAttrs } from './read-block.ts';
97
import { readBlock, readInheritableAttrs } from './read-block.ts';
108
import { parseOrientation, readPageSize } from './read-page-size.ts';
119
import type { Obj } from './types.ts';
1210
import { dynamic, optional, readAs, readObject, required, typeError, types } from './types.ts';
1311

1412
export type DocumentDefinition = {
15-
fonts?: FontDef[];
1613
pageSize?: Size;
1714
pageOrientation?: 'portrait' | 'landscape';
1815
info?: Metadata;
@@ -54,7 +51,6 @@ export type PageInfo = {
5451

5552
export function readDocumentDefinition(input: unknown): DocumentDefinition {
5653
const def1 = readObject(input, {
57-
fonts: optional(readFonts),
5854
pageSize: optional(readPageSize),
5955
pageOrientation: optional(parseOrientation),
6056
info: optional(readInfo),

0 commit comments

Comments
 (0)