Skip to content

Commit 3b80ce6

Browse files
authored
Allow custom font name on embed (Hopding#543)
* allow custom font name on embed * linting * customFontName -> customName, additional testing around font embedding * address additional comments * remove console log
1 parent 8da936c commit 3b80ce6

8 files changed

+94
-18
lines changed

src/api/PDFDocument.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -792,20 +792,20 @@ export default class PDFDocument {
792792
font: StandardFonts | string | Uint8Array | ArrayBuffer,
793793
options: EmbedFontOptions = {},
794794
): Promise<PDFFont> {
795-
const { subset = false } = options;
795+
const { subset = false, customName } = options;
796796

797797
assertIs(font, 'font', ['string', Uint8Array, ArrayBuffer]);
798798
assertIs(subset, 'subset', ['boolean']);
799799

800800
let embedder: CustomFontEmbedder | StandardFontEmbedder;
801801
if (isStandardFont(font)) {
802-
embedder = StandardFontEmbedder.for(font);
802+
embedder = StandardFontEmbedder.for(font, customName);
803803
} else if (canBeConvertedToUint8Array(font)) {
804804
const bytes = toUint8Array(font);
805805
const fontkit = this.assertFontkit();
806806
embedder = subset
807-
? await CustomFontSubsetEmbedder.for(fontkit, bytes)
808-
: await CustomFontEmbedder.for(fontkit, bytes);
807+
? await CustomFontSubsetEmbedder.for(fontkit, bytes, customName)
808+
: await CustomFontEmbedder.for(fontkit, bytes, customName);
809809
} else {
810810
throw new TypeError(
811811
'`font` must be one of `StandardFonts | string | Uint8Array | ArrayBuffer`',
@@ -827,15 +827,16 @@ export default class PDFDocument {
827827
* const helveticaFont = pdfDoc.embedFont(StandardFonts.Helvetica)
828828
* ```
829829
* @param font The standard font to be embedded.
830+
* @param customName The name to be used when embedding the font.
830831
* @returns The embedded font.
831832
*/
832-
embedStandardFont(font: StandardFonts): PDFFont {
833+
embedStandardFont(font: StandardFonts, customName?: string): PDFFont {
833834
assertIs(font, 'font', ['string']);
834835
if (!isStandardFont(font)) {
835-
throw new TypeError('`font` must be one of type `StandardFontsr`');
836+
throw new TypeError('`font` must be one of type `StandardFonts`');
836837
}
837838

838-
const embedder = StandardFontEmbedder.for(font);
839+
const embedder = StandardFontEmbedder.for(font, customName);
839840

840841
const ref = this.context.nextRef();
841842
const pdfFont = PDFFont.of(ref, this, embedder);

src/api/PDFDocumentOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ export interface CreateOptions {
3333

3434
export interface EmbedFontOptions {
3535
subset?: boolean;
36+
customName?: string;
3637
}

src/core/embedders/CustomFontEmbedder.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,30 @@ import {
2020
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/jpeg.coffee
2121
*/
2222
class CustomFontEmbedder {
23-
static async for(fontkit: Fontkit, fontData: Uint8Array) {
23+
static async for(
24+
fontkit: Fontkit,
25+
fontData: Uint8Array,
26+
customName?: string,
27+
) {
2428
const font = await fontkit.create(fontData);
25-
return new CustomFontEmbedder(font, fontData);
29+
return new CustomFontEmbedder(font, fontData, customName);
2630
}
2731

2832
readonly font: Font;
2933
readonly scale: number;
3034
readonly fontData: Uint8Array;
3135
readonly fontName: string;
36+
readonly customName: string | undefined;
3237

3338
protected baseFontName: string;
3439
protected glyphCache: Cache<Glyph[]>;
3540

36-
protected constructor(font: Font, fontData: Uint8Array) {
41+
protected constructor(font: Font, fontData: Uint8Array, customName?: string) {
3742
this.font = font;
3843
this.scale = 1000 / this.font.unitsPerEm;
3944
this.fontData = fontData;
4045
this.fontName = this.font.postscriptName || 'Font';
46+
this.customName = customName;
4147

4248
this.baseFontName = '';
4349
this.glyphCache = Cache.populatedBy(this.allGlyphsInFontSortedById);
@@ -83,7 +89,7 @@ class CustomFontEmbedder {
8389
}
8490

8591
embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
86-
this.baseFontName = addRandomSuffix(this.fontName);
92+
this.baseFontName = this.customName || addRandomSuffix(this.fontName);
8793
return this.embedFontDict(context, ref);
8894
}
8995

src/core/embedders/CustomFontSubsetEmbedder.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,25 @@ import { Cache, mergeUint8Arrays, toHexStringOfMinLength } from 'src/utils';
1010
* https://github.com/devongovett/pdfkit/blob/e71edab0dd4657b5a767804ba86c94c58d01fbca/lib/image/jpeg.coffee
1111
*/
1212
class CustomFontSubsetEmbedder extends CustomFontEmbedder {
13-
static async for(fontkit: Fontkit, fontData: Uint8Array) {
13+
static async for(
14+
fontkit: Fontkit,
15+
fontData: Uint8Array,
16+
customFontName?: string,
17+
) {
1418
const font = await fontkit.create(fontData);
15-
return new CustomFontSubsetEmbedder(font, fontData);
19+
return new CustomFontSubsetEmbedder(font, fontData, customFontName);
1620
}
1721

1822
private readonly subset: Subset;
1923
private readonly glyphs: Glyph[];
2024
private readonly glyphIdMap: Map<number, number>;
2125

22-
private constructor(font: Font, fontData: Uint8Array) {
23-
super(font, fontData);
26+
private constructor(
27+
font: Font,
28+
fontData: Uint8Array,
29+
customFontName?: string,
30+
) {
31+
super(font, fontData, customFontName);
2432

2533
this.subset = this.font.createSubset();
2634
this.glyphs = [];

src/core/embedders/StandardFontEmbedder.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export interface Glyph {
2121
* https://github.com/foliojs/pdfkit/blob/f91bdd61c164a72ea06be1a43dc0a412afc3925f/lib/font/afm.coffee
2222
*/
2323
class StandardFontEmbedder {
24-
static for = (fontName: FontNames) => new StandardFontEmbedder(fontName);
24+
static for = (fontName: FontNames, customName?: string) =>
25+
new StandardFontEmbedder(fontName, customName);
2526

2627
readonly font: Font;
2728
readonly encoding: Encoding;
2829
readonly fontName: string;
30+
readonly customName: string | undefined;
2931

30-
private constructor(fontName: FontNames) {
32+
private constructor(fontName: FontNames, customName?: string) {
3133
// prettier-ignore
3234
this.encoding = (
3335
fontName === FontNames.ZapfDingbats ? Encodings.ZapfDingbats
@@ -36,6 +38,7 @@ class StandardFontEmbedder {
3638
);
3739
this.font = Font.load(fontName);
3840
this.fontName = this.font.FontName;
41+
this.customName = customName;
3942
}
4043

4144
/**
@@ -85,7 +88,7 @@ class StandardFontEmbedder {
8588
const fontDict = context.obj({
8689
Type: 'Font',
8790
Subtype: 'Type1',
88-
BaseFont: this.font.FontName,
91+
BaseFont: this.customName || this.fontName,
8992

9093
Encoding:
9194
this.encoding === Encodings.WinAnsi ? 'WinAnsiEncoding' : undefined,

tests/api/PDFDocument.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import fontkit from '@pdf-lib/fontkit';
12
import fs from 'fs';
23
import {
34
EncryptedPDFError,
@@ -123,6 +124,43 @@ describe(`PDFDocument`, () => {
123124
});
124125
});
125126

127+
describe(`embedFont() method`, () => {
128+
it(`serializes the same value on every save when using a custom font name`, async () => {
129+
const customFont = fs.readFileSync('assets/fonts/ubuntu/Ubuntu-B.ttf');
130+
const customName = 'Custom-Font-Name';
131+
const pdfDoc1 = await PDFDocument.create({ updateMetadata: false });
132+
const pdfDoc2 = await PDFDocument.create({ updateMetadata: false });
133+
134+
pdfDoc1.registerFontkit(fontkit);
135+
pdfDoc2.registerFontkit(fontkit);
136+
137+
await pdfDoc1.embedFont(customFont, { customName });
138+
await pdfDoc2.embedFont(customFont, { customName });
139+
140+
const savedDoc1 = await pdfDoc1.save();
141+
const savedDoc2 = await pdfDoc2.save();
142+
143+
expect(savedDoc1).toEqual(savedDoc2);
144+
});
145+
146+
it(`does not serialize the same on save when not using a custom font name`, async () => {
147+
const customFont = fs.readFileSync('assets/fonts/ubuntu/Ubuntu-B.ttf');
148+
const pdfDoc1 = await PDFDocument.create();
149+
const pdfDoc2 = await PDFDocument.create();
150+
151+
pdfDoc1.registerFontkit(fontkit);
152+
pdfDoc2.registerFontkit(fontkit);
153+
154+
await pdfDoc1.embedFont(customFont);
155+
await pdfDoc2.embedFont(customFont);
156+
157+
const savedDoc1 = await pdfDoc1.save();
158+
const savedDoc2 = await pdfDoc2.save();
159+
160+
expect(savedDoc1).not.toEqual(savedDoc2);
161+
});
162+
});
163+
126164
describe(`setLanguage() method`, () => {
127165
it(`sets the language of the document`, async () => {
128166
const pdfDoc = await PDFDocument.create();

tests/core/embedders/CustomFontEmbedder.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ describe(`CustomFontEmbedder`, () => {
2525
expect(embedder.fontName).toBe('Ubuntu');
2626
});
2727

28+
it(`can set a custom font name`, async () => {
29+
const customName = 'abc123';
30+
const embedder = await CustomFontEmbedder.for(
31+
fontkit,
32+
new Uint8Array(ubuntuFont),
33+
customName,
34+
);
35+
expect(embedder.customName).toBe(customName);
36+
});
37+
2838
it(`can embed font dictionaries into PDFContexts without a predefined ref`, async () => {
2939
const context = PDFContext.create();
3040
const embedder = await CustomFontEmbedder.for(

tests/core/embedders/StandardFontEmbedder.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ describe(`StandardFontEmbedder`, () => {
1818
expect(embedder.fontName).toBe('Helvetica-Oblique');
1919
});
2020

21+
it(`can use a custom font name`, () => {
22+
const customName = 'Roboto 2';
23+
const embedder = StandardFontEmbedder.for(
24+
FontNames.HelveticaOblique,
25+
customName,
26+
);
27+
expect(embedder.customName).toBe(customName);
28+
});
29+
2130
it(`can embed standard font dictionaries into PDFContexts without a predefined ref`, () => {
2231
const context = PDFContext.create();
2332
const embedder = StandardFontEmbedder.for(FontNames.Courier);

0 commit comments

Comments
 (0)