lbx-invoice
Version:
Provides functionality around generating invoices.
171 lines (154 loc) • 6.05 kB
text/typescript
import { readFile, writeFile } from 'fs/promises';
import { AttachmentOptions, PDFDict, PDFDocument, PDFHexString, PDFName, PDFObject, PDFRawStream, PDFRef, PDFString } from 'pdf-lib';
import pdfMake from 'pdfmake/build/pdfmake.min';
import pdfFonts from 'pdfmake/build/vfs_fonts';
import { Column, Content, DynamicContent, ImageDefinition, Size, TDocumentDefinitions, TableCell } from 'pdfmake/interfaces';
pdfMake.vfs = pdfFonts.pdfMake.vfs;
/**
* Definition of an image to be embedded into the document.
*/
export type PdfMakeImageDefinition = ImageDefinition;
/**
* Column used as part of ContentColumns.
*/
export type PdfMakeColumn = Column;
/**
* Common type for all available content elements.
*
* Special content types:
* - A string is rendered like a ContentText
* - An array is rendered like a ContentStack.
*/
export type PdfMakeContent = Content;
/**
* Callback that returns content depending on the current page number,
* the total number of pages, or the size of the current page.
*/
export type PdfMakeDynamicContent = DynamicContent;
/**
* Sizes for the width of stand-alone columns and table columns.
*
* Available options are:
* - A number to define an absolute width in `pt`
* - A percentage string such as `50%` to fill a portion of the available space
* - `auto` to set the width based on the content
* - `*` to fill the remaining available space, distributed equally among
* all star-sized columns.
*/
export type PdfMakeSize = Size;
/**
* A cell of a Table.
* - Can be any valid content. Content objects provide additional properties to control
* the cell's appearance.
* - Use empty objects (`{}`) as placeholders for cells that are covered by other cells
* spanning multiple rows or columns.
*/
export type PdfMakeTableCell = TableCell;
/**
* The definition for an embedded file inside of an pdf.
*/
export interface PdfMakeEmbeddedFile {
/**
* The src of the file.
* Can be either a local file reference, an url or a data uri.
*/
src: string,
/**
* The name of the file with the extension.
*/
name: string,
/**
* A description fo the file.
*/
description?: string
}
/**
* Extended type of the pdfmake document definition.
* Includes the files property of the beta version.
*/
export type PdfMakeDocumentDefinition = TDocumentDefinitions;
/**
* Utility class for handling pdf files.
*/
export abstract class PdfUtilities {
/**
* Creates a pdf from the provided document definition.
* @param documentDefinition - The definition to create the pdf from.
* @returns The finished pdfMake pdf as a base64 string.
*/
static async createPdf(documentDefinition: PdfMakeDocumentDefinition): Promise<string> {
return new Promise<string>((resolve, reject) => {
try {
pdfMake.createPdf(documentDefinition).getBase64(res => resolve(res));
}
catch (error) {
reject(error);
}
});
}
/**
* Attaches a file to the pdf at the given path.
* @param pdfFilePath - The path of the pdf to attach to.
* @param attachment - The raw attachment data.
* @param name - The name of the file to attach.
* @param options - Other options for the attachment, eg. The mime type.
*/
static async attach(
pdfFilePath: string,
attachment: string | Uint8Array | ArrayBuffer,
name: string,
options?: AttachmentOptions
): Promise<void> {
const existingPdfBytes: Buffer = await readFile(pdfFilePath);
const pdfDoc: PDFDocument = await PDFDocument.load(existingPdfBytes);
await pdfDoc.attach(attachment, name, options);
const newPdfBytes: Uint8Array = await pdfDoc.save();
await writeFile(pdfFilePath, newPdfBytes);
}
// TODO: enable
/**
* Sets the XMP Metadata from the given xml string.
* @param metadataXML - The xmp metadata xml as a string.
* @param pdfFilePath - The path of the pdf to set the metadata for.
*/
private static async setXmpMetadata(metadataXML: string, pdfFilePath: string): Promise<void> {
const existingPdfBytes: Buffer = await readFile(pdfFilePath);
const pdfDoc: PDFDocument = await PDFDocument.load(existingPdfBytes);
const metadataStream: PDFRawStream = pdfDoc.context.stream(metadataXML, {
Type: 'Metadata',
Subtype: 'XML',
Length: metadataXML.length
});
const metadataStreamRef: PDFRef = pdfDoc.context.register(metadataStream);
pdfDoc.catalog.set(PDFName.of('Metadata'), metadataStreamRef);
const newPdfBytes: Uint8Array = await pdfDoc.save();
await writeFile(pdfFilePath, newPdfBytes);
}
// TODO: enable
/**
* Gets the XMP Metadata from the pdf under the provided path.
* @param pdfFilePath - The path of the pdf to get the xmp metadata from.
* @returns An PDFObject or undefined, when no metadata was set.
*/
static async getXmpMetadata(pdfFilePath: string): Promise<string | undefined> {
const existingPdfBytes: Buffer = await readFile(pdfFilePath);
const pdfDoc: PDFDocument = await PDFDocument.load(existingPdfBytes);
// eslint-disable-next-line typescript/no-unsafe-call
const infoDict: PDFDict = pdfDoc['getInfoDict']() as PDFDict;
const metadata: PDFObject | undefined = infoDict.lookup(PDFName.of('Metadata'));
if (!metadata) {
return undefined;
}
this.assertIsLiteralOrHexString(metadata);
return metadata.decodeText();
// return (pdfDoc['getInfoDict']() as PDFDict).get(PDFName.of('Metadata'));
}
private static assertIsLiteralOrHexString(pdfObject: PDFObject): asserts pdfObject is PDFHexString | PDFString {
if (
!(pdfObject instanceof PDFHexString)
&& !(pdfObject instanceof PDFString)
) {
throw new Error('values is neither ');
}
}
}