Skip to content

Commit a612fb6

Browse files
ralfstxclaude
andcommitted
♻️ Replace Font type with PDFFont from pdf-core
The `Font` type was a thin wrapper around `PDFFont`, duplicating properties (`key`, `style`, `weight`) that already exist on the underlying `PDFFont` class. Consumers always dereferenced `.pdfFont` to access the actual font object. Remove the `Font` type and use `PDFFont` directly throughout the codebase. `FontStore.selectFont()` now returns `PDFFont` instead of `Font`, eliminating the wrapping logic in `_loadFont`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 43b762c commit a612fb6

File tree

12 files changed

+51
-79
lines changed

12 files changed

+51
-79
lines changed

src/font-store.test.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ describe('FontStore', () => {
5959
const selected1 = await store.selectFont({ fontFamily: 'Roboto' });
6060
const selected2 = await store.selectFont({ fontFamily: 'Roboto Light', fontStyle: 'italic' });
6161

62-
expect(selected1.pdfFont.fontName).toBe('Roboto');
63-
expect(selected2.pdfFont.fontName).toBe('Roboto Light Italic');
62+
expect(selected1.fontName).toBe('Roboto');
63+
expect(selected2.fontName).toBe('Roboto Light Italic');
6464
});
6565

6666
it('registers font with custom config', async () => {
@@ -75,8 +75,8 @@ describe('FontStore', () => {
7575
const selected1 = await store.selectFont({ fontFamily: 'Custom Name' });
7676
const selected2 = await store.selectFont({ fontFamily: 'Custom Name', fontWeight: 'bold' });
7777

78-
expect(selected1.pdfFont.fontName).toBe('Roboto Light Italic');
79-
expect(selected2.pdfFont.fontName).toBe('Roboto');
78+
expect(selected1.fontName).toBe('Roboto Light Italic');
79+
expect(selected2.fontName).toBe('Roboto');
8080
});
8181
});
8282

@@ -131,29 +131,29 @@ describe('FontStore', () => {
131131
const font3 = await store.selectFont({ fontFamily, fontStyle: 'italic' });
132132
const font4 = await store.selectFont({ fontFamily, fontStyle: 'italic', fontWeight: 'bold' });
133133

134-
expect(font1.pdfFont.fontName).toBe('MockFont:Test:normal:400');
135-
expect(font2.pdfFont.fontName).toBe('MockFont:Test:normal:700');
136-
expect(font3.pdfFont.fontName).toBe('MockFont:Test:italic:400');
137-
expect(font4.pdfFont.fontName).toBe('MockFont:Test:italic:700');
134+
expect(font1.fontName).toBe('MockFont:Test:normal:400');
135+
expect(font2.fontName).toBe('MockFont:Test:normal:700');
136+
expect(font3.fontName).toBe('MockFont:Test:italic:400');
137+
expect(font4.fontName).toBe('MockFont:Test:italic:700');
138138
});
139139

140140
it('selects first matching font if no family specified', async () => {
141141
const font1 = await store.selectFont({});
142-
expect(font1.pdfFont.fontName).toBe('MockFont:Test:normal:400');
142+
expect(font1.fontName).toBe('MockFont:Test:normal:400');
143143

144144
const font2 = await store.selectFont({ fontWeight: 'bold' });
145-
expect(font2.pdfFont.fontName).toBe('MockFont:Test:normal:700');
145+
expect(font2.fontName).toBe('MockFont:Test:normal:700');
146146

147147
const font3 = await store.selectFont({ fontStyle: 'italic' });
148-
expect(font3.pdfFont.fontName).toBe('MockFont:Test:italic:400');
148+
expect(font3.fontName).toBe('MockFont:Test:italic:400');
149149

150150
const font4 = await store.selectFont({ fontStyle: 'italic', fontWeight: 'bold' });
151-
expect(font4.pdfFont.fontName).toBe('MockFont:Test:italic:700');
151+
expect(font4.fontName).toBe('MockFont:Test:italic:700');
152152
});
153153

154154
it('selects font with matching font family', async () => {
155155
await expect(store.selectFont({ fontFamily: 'Other' })).resolves.toEqual(
156-
expect.objectContaining({ name: 'MockFont:Other:normal:400' }),
156+
expect.objectContaining({ fontName: 'MockFont:Other:normal:400' }),
157157
);
158158
});
159159

@@ -165,7 +165,7 @@ describe('FontStore', () => {
165165
registerFakeFont(store, 'Test', { style: 'oblique', weight: 700 });
166166

167167
await expect(store.selectFont({ fontFamily: 'Test', fontStyle: 'italic' })).resolves.toEqual(
168-
expect.objectContaining({ name: 'MockFont:Test:oblique:400' }),
168+
expect.objectContaining({ fontName: 'MockFont:Test:oblique:400' }),
169169
);
170170
});
171171

@@ -178,17 +178,15 @@ describe('FontStore', () => {
178178

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

181-
expect(font.pdfFont).toEqual(
182-
expect.objectContaining({ fontName: 'MockFont:Test:italic:400' }),
183-
);
181+
expect(font).toEqual(expect.objectContaining({ fontName: 'MockFont:Test:italic:400' }));
184182
});
185183

186184
it('falls back when no matching font weight can be found', async () => {
187185
await expect(store.selectFont({ fontFamily: 'Other', fontWeight: 'bold' })).resolves.toEqual(
188-
expect.objectContaining({ name: 'MockFont:Other:normal:400' }),
186+
expect.objectContaining({ fontName: 'MockFont:Other:normal:400' }),
189187
);
190188
await expect(store.selectFont({ fontFamily: 'Other', fontWeight: 200 })).resolves.toEqual(
191-
expect.objectContaining({ name: 'MockFont:Other:normal:400' }),
189+
expect.objectContaining({ fontName: 'MockFont:Other:normal:400' }),
192190
);
193191
});
194192

src/font-store.ts

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

33
import type { FontConfig } from './api/PdfMaker.ts';
44
import type { FontWeight } from './api/text.ts';
5-
import type { Font, FontDef, FontSelector } from './fonts.ts';
5+
import type { FontDef, FontSelector } from './fonts.ts';
66
import { weightToNumber } from './fonts.ts';
7-
import { pickDefined } from './types.ts';
87

98
export class FontStore {
109
readonly #fontDefs: FontDef[];
11-
#fontCache: Record<string, Promise<Font>> = {};
10+
#fontCache: Record<string, Promise<PDFFont>> = {};
1211

1312
constructor() {
1413
this.#fontDefs = [];
@@ -23,33 +22,24 @@ export class FontStore {
2322
this.#fontCache = {}; // Invalidate cache
2423
}
2524

26-
async selectFont(selector: FontSelector): Promise<Font> {
25+
async selectFont(selector: FontSelector): Promise<PDFFont> {
2726
const cacheKey = [
2827
selector.fontFamily ?? 'any',
2928
selector.fontStyle ?? 'normal',
3029
selector.fontWeight ?? 'normal',
3130
].join(':');
3231
try {
33-
return await (this.#fontCache[cacheKey] ??= this._loadFont(selector, cacheKey));
32+
return await (this.#fontCache[cacheKey] ??= this._loadFont(selector));
3433
} catch (error) {
3534
const { fontFamily: family, fontStyle: style, fontWeight: weight } = selector;
3635
const selectorStr = `'${family}', style=${style ?? 'normal'}, weight=${weight ?? 'normal'}`;
3736
throw new Error(`Could not load font for ${selectorStr}`, { cause: error });
3837
}
3938
}
4039

41-
_loadFont(selector: FontSelector, key: string): Promise<Font> {
40+
_loadFont(selector: FontSelector): Promise<PDFFont> {
4241
const selectedFontDef = selectFontDef(this.#fontDefs, selector);
43-
const pdfFont = selectedFontDef.pdfFont ?? new PDFEmbeddedFont(selectedFontDef.data);
44-
return Promise.resolve(
45-
pickDefined({
46-
key,
47-
name: pdfFont.fontName ?? selectedFontDef.family,
48-
style: selector.fontStyle ?? 'normal',
49-
weight: weightToNumber(selector.fontWeight ?? 400),
50-
pdfFont,
51-
}),
52-
);
42+
return Promise.resolve(selectedFontDef.pdfFont ?? new PDFEmbeddedFont(selectedFontDef.data));
5343
}
5444
}
5545

src/fonts.ts

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

33
import type { FontStyle, FontWeight } from './api/text.ts';
44
import { printValue } from './print-value.ts';
@@ -14,14 +14,6 @@ export type FontDef = {
1414
pdfFont?: PDFEmbeddedFont;
1515
};
1616

17-
export type Font = {
18-
key: string;
19-
name: string;
20-
style: FontStyle;
21-
weight: number;
22-
pdfFont: PDFFont;
23-
};
24-
2517
export type FontSelector = {
2618
fontFamily?: string;
2719
fontStyle?: FontStyle;

src/frame.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { PDFImage } from '@ralfstx/pdf-core';
1+
import type { PDFFont, PDFImage } from '@ralfstx/pdf-core';
22

3-
import type { Font } from './fonts.ts';
43
import type { Color } from './read-color.ts';
54
import type { PathCommand } from './svg-paths.ts';
65

@@ -37,7 +36,7 @@ export type TextRowObject = {
3736

3837
export type TextSegmentObject = {
3938
text: string;
40-
font: Font;
39+
font: PDFFont;
4140
fontSize: number;
4241
color?: Color;
4342
rise?: number;

src/layout/layout-text.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
import type { PDFFont } from '@ralfstx/pdf-core';
12
import { beforeEach, describe, expect, it } from 'vitest';
23

34
import type { Box } from '../box.ts';
45
import { rgb } from '../colors.ts';
56
import { FontStore } from '../font-store.ts';
6-
import type { Font, FontSelector } from '../fonts.ts';
7+
import type { FontSelector } from '../fonts.ts';
78
import type { MakerCtx } from '../maker-ctx.ts';
89
import { extractTextRows, fakeFont, range, span } from '../test/test-utils.ts';
910
import { layoutTextContent } from './layout-text.ts';
1011

1112
describe('layout-text', () => {
12-
let defaultFont: Font;
13+
let defaultFont: PDFFont;
1314
let box: Box;
1415
let ctx: MakerCtx;
1516

src/layout/layout-text.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import type { PDFFont } from '@ralfstx/pdf-core';
2+
13
import type { Box, Size } from '../box.ts';
2-
import type { Font } from '../fonts.ts';
34
import type { LinkObject, RenderObject, TextRowObject, TextSegmentObject } from '../frame.ts';
45
import { createRowGuides } from '../guides.ts';
56
import type { MakerCtx } from '../maker-ctx.ts';
@@ -136,8 +137,8 @@ function layoutTextRow(segments: TextSegment[], box: Box) {
136137
return { row, objects, remainder };
137138
}
138139

139-
function getDescent(font: Font, fontSize: number) {
140-
return Math.abs((font.pdfFont.descent * fontSize) / 1000);
140+
function getDescent(font: PDFFont, fontSize: number) {
141+
return Math.abs((font.descent * fontSize) / 1000);
141142
}
142143

143144
/**

src/render/render-page.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import type { PDFDocument } from '@ralfstx/pdf-core';
1+
import type { PDFDocument, PDFFont } from '@ralfstx/pdf-core';
22
import { PDFPage } from '@ralfstx/pdf-core';
33
import { beforeEach, describe, expect, it, vi } from 'vitest';
44

55
import type { Size } from '../box.ts';
6-
import type { Font } from '../fonts.ts';
76
import type { Frame } from '../frame.ts';
87
import type { Page } from '../page.ts';
98
import { fakeFont, getContentStream } from '../test/test-utils.ts';
@@ -79,7 +78,7 @@ describe('render-page', () => {
7978
describe('renderFrame', () => {
8079
let page: Page;
8180
let size: Size;
82-
let font: Font;
81+
let font: PDFFont;
8382

8483
beforeEach(() => {
8584
size = { width: 500, height: 800 };

src/render/render-text.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import type { PDFFont } from '@ralfstx/pdf-core';
12
import { PDFPage } from '@ralfstx/pdf-core';
23
import { beforeEach, describe, expect, it } from 'vitest';
34

45
import type { Size } from '../box.ts';
5-
import type { Font } from '../fonts.ts';
66
import type { TextObject } from '../frame.ts';
77
import type { Page } from '../page.ts';
88
import { fakeFont, getContentStream } from '../test/test-utils.ts';
@@ -11,7 +11,7 @@ import { renderText } from './render-text.ts';
1111
describe('render-text', () => {
1212
let page: Page;
1313
let size: Size;
14-
let font: Font;
14+
let font: PDFFont;
1515

1616
beforeEach(() => {
1717
size = { width: 500, height: 800 };

src/render/render-text.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ export function renderText(object: TextObject, page: Page, base: Pos) {
1818
object.rows?.forEach((row) => {
1919
cs.setTextMatrix(1, 0, 0, 1, x + row.x, y - row.y - row.baseline);
2020
row.segments?.forEach((seg) => {
21-
const pdfFont = seg.font.pdfFont;
22-
if (!pdfFont) throw new Error('PDF font not initialized');
23-
const glyphRun = pdfFont.shapeText(seg.text, { defaultFeatures: false });
21+
const glyphRun = seg.font.shapeText(seg.text, { defaultFeatures: false });
2422

2523
setTextColorOp(cs, state, seg.color);
26-
setTextFontAndSizeOp(cs, state, pdfFont, seg.fontSize);
24+
setTextFontAndSizeOp(cs, state, seg.font, seg.fontSize);
2725
setTextRiseOp(cs, state, seg.rise);
2826
setLetterSpacingOp(cs, state, seg.letterSpacing);
2927
cs.showPositionedText(glyphRun);

src/test/test-utils.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import type { PDFFont } from '@ralfstx/pdf-core';
22
import { PDFImage, PDFRef } from '@ralfstx/pdf-core';
33

4-
import type { Font } from '../fonts.ts';
4+
import type { FontWeight } from '../api/text.ts';
55
import { weightToNumber } from '../fonts.ts';
66
import type { Frame } from '../frame.ts';
77
import type { Page } from '../page.ts';
88
import type { TextAttrs, TextSpan } from '../read-block.ts';
99

10-
export function fakeFont(name: string, opts?: Partial<Omit<Font, 'name'>>): Font {
11-
const key = `${name}-${opts?.style ?? 'normal'}-${opts?.weight ?? 400}`;
12-
const font: Font = {
13-
key,
14-
name,
15-
style: opts?.style ?? 'normal',
16-
weight: weightToNumber(opts?.weight ?? 'normal'),
17-
pdfFont: fakePdfFont(key),
18-
};
19-
return font;
10+
export function fakeFont(name: string, opts?: { style?: string; weight?: FontWeight }): PDFFont {
11+
const key = `${name}-${opts?.style ?? 'normal'}-${weightToNumber(opts?.weight ?? 'normal')}`;
12+
return fakePdfFont(key);
2013
}
2114

2215
export function fakeImage(width: number, height: number): PDFImage {

0 commit comments

Comments
 (0)