printmaker
Version:
Generate PDF documents and from JavaScript objects
83 lines (73 loc) • 2.36 kB
text/typescript
import { PDFDocument, PDFFont } from 'pdf-lib';
import { parseBinaryData } from './binary-data.js';
import {
asArray,
asBoolean,
asObject,
check,
getFrom,
Obj,
optional,
pickDefined,
required,
} from './types.js';
export type FontDef = {
name: string;
italic?: boolean;
bold?: boolean;
data: string | Uint8Array | ArrayBuffer;
};
export type Font = {
name: string;
italic?: boolean;
bold?: boolean;
pdfFont: PDFFont;
};
export type FontSelector = {
fontFamily?: string;
italic?: boolean;
bold?: boolean;
};
export function parseFonts(input: unknown): FontDef[] {
const obj = check(input, 'fonts', optional(asObject)) ?? {};
return Object.entries(obj).flatMap(([name, fontDef]) => {
const array = check(fontDef, `fonts['${name}']`, required(asArray));
return array.map((fontDef, idx) => {
const font = check(fontDef, `fonts['${name}'][${idx}]`, required(parseFont));
return { name, ...font } as FontDef;
});
});
}
export function parseFont(def: Obj): Partial<FontDef> {
return pickDefined({
italic: getFrom(def, 'italic', optional(asBoolean)) || undefined,
bold: getFrom(def, 'bold', optional(asBoolean)) || undefined,
data: getFrom(def, 'data', required(parseBinaryData)),
}) as FontDef;
}
export async function embedFonts(fontDefs: FontDef[], doc: PDFDocument): Promise<Font[]> {
return await Promise.all(
fontDefs.map(async (def) => {
const pdfFont = await doc.embedFont(def.data, { subset: true }).catch((error) => {
throw new Error(`Could not embed font "${def.name}": ${error.message ?? error}`);
});
return pickDefined({ name: def.name, italic: def.italic, bold: def.bold, pdfFont }) as Font;
})
);
}
export function selectFont(fonts: Font[], attrs: FontSelector): PDFFont {
const { fontFamily, italic, bold } = attrs;
const font = fonts.find((font) => match(font, { fontFamily, italic, bold }))?.pdfFont;
if (!font) {
const style = italic ? (bold ? 'bold italic' : 'italic') : bold ? 'bold' : 'normal';
throw new Error(`No font found for "${fontFamily} ${style}"`);
}
return font;
}
function match(font: Font, selector: FontSelector): boolean {
return (
(!selector.fontFamily || font.name === selector.fontFamily) &&
!font.italic === !selector.italic &&
!font.bold === !selector.bold
);
}