Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ Minimum requirements bumped to Node 20 and npm 10.
- The functions `text()`, `image()`, `rows()`, and `columns()` to create
blocks with less code and better tool support.

- The `PdfMaker` class to render multiple documents with the same
font configuration.

```ts
const pdfMaker = new PdfMaker(config);
pdfMaker.registerFont(await readFile('path/to/MyFont.ttf'));
pdfMaker.registerFont(await readFile('path/to/MyFont-Bold.ttf'));
const pdf1 = await pdfMaker.makePdf(doc1);
const pdf2 = await pdfMaker.makePdf(doc2);
```

### Deprecated

- `TextAttrs` in favor of `TextProps`.
Expand All @@ -26,6 +37,9 @@ Minimum requirements bumped to Node 20 and npm 10.
- `RectOpts` in favor of `RectProps`.
- `CircleOpts` in favor of `CircleProps`.
- `PathOpts` in favor of `PathProps`.
- The `fonts` property in a document definition.
- The `makePdf` function in favor of the `makePdf` method on the
`PdfMaker` class.

## [0.5.4] - 2024-02-25

Expand Down
56 changes: 56 additions & 0 deletions src/api/PdfMaker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { FontStore } from '../font-store.ts';
import { ImageStore } from '../image-store.ts';
import { layoutPages } from '../layout/layout.ts';
import type { MakerCtx } from '../maker-ctx.ts';
import { readDocumentDefinition } from '../read-document.ts';
import { renderDocument } from '../render/render-document.ts';
import { readAs } from '../types.ts';
import type { DocumentDefinition } from './document.ts';
import type { FontStyle, FontWeight } from './text.ts';

export type FontConfig = {
name?: string;
style?: FontStyle;
weight?: FontWeight;
};

/**
* Generates PDF documents.
*/
export class PdfMaker {
#ctx: MakerCtx;

constructor() {
const fontStore = new FontStore([]);
const imageStore = new ImageStore([]);
this.#ctx = { fontStore, imageStore };
}

/**
* Registers a font to be used in generated PDFs.
*
* @param data The font data. Must be in OpenType (OTF) or TrueType
* (TTF) format.
* @param config Additional configuration of the font, only needed if
* the meta data cannot be extracted from the font.
*/
registerFont(data: Uint8Array, config?: FontConfig): void {
this.#ctx.fontStore.registerFont(data, config);
}

/**
* Generates a PDF from the given document definition.
*
* @param definition The definition of the document to generate.
* @returns The generated PDF document.
*/
async makePdf(definition: DocumentDefinition): Promise<Uint8Array> {
const def = readAs(definition, 'definition', readDocumentDefinition);
const ctx = { ...this.#ctx };
if (def.fonts) ctx.fontStore = new FontStore(def.fonts);
if (def.images) ctx.imageStore = new ImageStore(def.images);
if (def.dev?.guides != null) ctx.guides = def.dev.guides;
const pages = await layoutPages(def, ctx);
return await renderDocument(def, pages);
}
}
6 changes: 6 additions & 0 deletions src/api/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export type DocumentDefinition = {
/**
* The fonts to use in the document. There is no default. Each font that is used in the document
* must be registered. Not needed for documents that contain only graphics.
*
* @deprecated Register fonts with `PdfMaker` instead.
*/
fonts?: FontsDefinition;

Expand Down Expand Up @@ -153,11 +155,15 @@ export type CustomInfoProps = {

/**
* An object that defines the fonts to use in the document.
*
* @deprecated Register fonts with `PdfMaker` instead.
*/
export type FontsDefinition = { [name: string]: FontDefinition[] };

/**
* The definition of a single font.
*
* @deprecated Register fonts with `PdfMaker` instead.
*/
export type FontDefinition = {
/**
Expand Down
3 changes: 3 additions & 0 deletions src/api/make-pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type { DocumentDefinition } from './document.ts';
*
* @param definition The definition of the document to generate.
* @returns The generated PDF document.
*
* @deprecated Create an instance of `PdfMaker` and call `makePdf` on
* that instance.
*/
export async function makePdf(definition: DocumentDefinition): Promise<Uint8Array> {
const def = readAs(definition, 'definition', readDocumentDefinition);
Expand Down
26 changes: 23 additions & 3 deletions src/font-store.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import fontkit from '@pdf-lib/fontkit';
import { toUint8Array } from 'pdf-lib';

import type { FontWeight } from './api/text.ts';
import type { FontConfig } from './api/PdfMaker.ts';
import type { FontStyle, FontWeight } from './api/text.ts';
import type { Font, FontDef, FontSelector } from './fonts.ts';
import { weightToNumber } from './fonts.ts';
import { pickDefined } from './types.ts';

export class FontStore {
readonly #fontDefs: FontDef[];
readonly #fontCache: Record<string, Promise<Font>> = {};
#fontCache: Record<string, Promise<Font>> = {};

constructor(fontDefs: FontDef[]) {
this.#fontDefs = fontDefs;
}

registerFont(data: Uint8Array, config?: FontConfig): void {
const fkFont = fontkit.create(data, config?.name);
const family = config?.name ?? fkFont.familyName ?? 'Unknown';
const style = config?.style ?? extractStyle(fkFont);
const weight = weightToNumber(config?.weight ?? extractWeight(fkFont));
this.#fontDefs.push({ family, style, weight, data, fkFont });
this.#fontCache = {};
}

async selectFont(selector: FontSelector): Promise<Font> {
const cacheKey = [
selector.fontFamily ?? 'any',
Expand All @@ -32,7 +42,7 @@ export class FontStore {
_loadFont(selector: FontSelector): Promise<Font> {
const selectedFont = selectFont(this.#fontDefs, selector);
const data = toUint8Array(selectedFont.data);
const fkFont = fontkit.create(data);
const fkFont = selectedFont.fkFont ?? fontkit.create(data);
return Promise.resolve(
pickDefined({
name: fkFont.fullName ?? fkFont.postscriptName ?? selectedFont.family,
Expand Down Expand Up @@ -109,3 +119,13 @@ function selectFontForWeight(fonts: FontDef[], weight: FontWeight): FontDef | un
}
throw new Error(`Could not find font for weight ${weight}`);
}

function extractStyle(font: fontkit.Font): FontStyle {
if (font.italicAngle === 0) return 'normal';
if ((font.fullName ?? font.postscriptName)?.toLowerCase().includes('oblique')) return 'oblique';
return 'italic';
}

function extractWeight(font: fontkit.Font): number {
return (font['OS/2'] as any)?.usWeightClass ?? 400;
}
1 change: 1 addition & 0 deletions src/fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type FontDef = {
style: FontStyle;
weight: number;
data: string | Uint8Array | ArrayBuffer;
fkFont?: fontkit.Font;
};

export type Font = {
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as document from './api/document.ts';
import * as graphics from './api/graphics.ts';
import * as layout from './api/layout.ts';
import * as makePdf from './api/make-pdf.ts';
import * as pdfMaker from './api/PdfMaker.ts';
import * as sizes from './api/sizes.ts';
import * as text from './api/text.ts';

Expand All @@ -12,6 +13,7 @@ export const pdf = {
...graphics,
...layout,
...makePdf,
...pdfMaker,
...sizes,
...text,
};
Expand All @@ -21,5 +23,6 @@ export * from './api/document.ts';
export * from './api/graphics.ts';
export * from './api/layout.ts';
export * from './api/make-pdf.ts';
export * from './api/PdfMaker.ts';
export * from './api/sizes.ts';
export * from './api/text.ts';
Loading