UNPKG

lbx-invoice

Version:

Provides functionality around generating invoices.

171 lines (154 loc) 6.05 kB
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 '); } } }