UNPKG

pdfmake

Version:

Client/server side PDF printing in pure JavaScript

322 lines (284 loc) 11.3 kB
import PDFDocument from './PDFDocument'; import LayoutBuilder from './LayoutBuilder'; import SVGMeasure from './SVGMeasure'; import { normalizePageSize, normalizePageMargin } from './PageSize'; import { tableLayouts } from './tableLayouts'; import Renderer from './Renderer'; import { isNumber, isValue } from './helpers/variableType'; import { convertToDynamicContent } from './helpers/tools'; /** * Printer which turns document definition into a pdf * * @example * var fontDescriptors = { * Roboto: { * normal: 'fonts/Roboto-Regular.ttf', * bold: 'fonts/Roboto-Medium.ttf', * italics: 'fonts/Roboto-Italic.ttf', * bolditalics: 'fonts/Roboto-MediumItalic.ttf' * } * }; * * var printer = new PdfPrinter(fontDescriptors); */ class PdfPrinter { /** * @param {object} fontDescriptors font definition dictionary * @param {object} virtualfs * @param {object} urlResolver */ constructor(fontDescriptors, virtualfs = null, urlResolver = null) { this.fontDescriptors = fontDescriptors; this.virtualfs = virtualfs; this.urlResolver = urlResolver; } /** * Executes layout engine for the specified document and renders it into a pdfkit document * ready to be saved. * * @param {object} docDefinition * @param {object} options * @returns {Promise<PDFDocument>} resolved promise return a pdfkit document */ createPdfKitDocument(docDefinition, options = {}) { return new Promise((resolve, reject) => { this.resolveUrls(docDefinition).then(() => { try { docDefinition.version = docDefinition.version || '1.3'; docDefinition.subset = docDefinition.subset || undefined; docDefinition.tagged = typeof docDefinition.tagged === 'boolean' ? docDefinition.tagged : false; docDefinition.displayTitle = typeof docDefinition.displayTitle === 'boolean' ? docDefinition.displayTitle : false; docDefinition.compress = typeof docDefinition.compress === 'boolean' ? docDefinition.compress : true; docDefinition.images = docDefinition.images || {}; docDefinition.attachments = docDefinition.attachments || {}; docDefinition.pageMargins = isValue(docDefinition.pageMargins) ? docDefinition.pageMargins : 40; docDefinition.patterns = docDefinition.patterns || {}; if (docDefinition.header && typeof docDefinition.header !== 'function') { docDefinition.header = convertToDynamicContent(docDefinition.header); } if (docDefinition.footer && typeof docDefinition.footer !== 'function') { docDefinition.footer = convertToDynamicContent(docDefinition.footer); } let pageSize = normalizePageSize(docDefinition.pageSize, docDefinition.pageOrientation); let pdfOptions = { size: [pageSize.width, pageSize.height], pdfVersion: docDefinition.version, subset: docDefinition.subset, tagged: docDefinition.tagged, displayTitle: docDefinition.displayTitle, compress: docDefinition.compress, userPassword: docDefinition.userPassword, ownerPassword: docDefinition.ownerPassword, permissions: docDefinition.permissions, lang: docDefinition.language, fontLayoutCache: typeof options.fontLayoutCache === 'boolean' ? options.fontLayoutCache : true, bufferPages: options.bufferPages || false, autoFirstPage: false, info: createMetadata(docDefinition), font: null }; this.pdfKitDoc = new PDFDocument(this.fontDescriptors, docDefinition.images, docDefinition.patterns, docDefinition.attachments, pdfOptions, this.virtualfs); embedFiles(docDefinition, this.pdfKitDoc); const builder = new LayoutBuilder(pageSize, normalizePageMargin(docDefinition.pageMargins), new SVGMeasure()); builder.registerTableLayouts(tableLayouts); if (options.tableLayouts) { builder.registerTableLayouts(options.tableLayouts); } let pages = builder.layoutDocument(docDefinition.content, this.pdfKitDoc, docDefinition.styles || {}, docDefinition.defaultStyle || { fontSize: 12, font: 'Roboto' }, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.watermark, docDefinition.pageBreakBefore); let maxNumberPages = docDefinition.maxPagesNumber || -1; if (isNumber(maxNumberPages) && maxNumberPages > -1) { pages = pages.slice(0, maxNumberPages); } // if pageSize.height is set to Infinity, calculate the actual height of the page that // was laid out using the height of each of the items in the page. if (pageSize.height === Infinity) { let pageHeight = calculatePageHeight(pages, docDefinition.pageMargins); this.pdfKitDoc.options.size = [pageSize.width, pageHeight]; } const renderer = new Renderer(this.pdfKitDoc, options.progressCallback); renderer.renderPages(pages); resolve(this.pdfKitDoc); } catch (e) { reject(e); } }, result => { reject(result); }); }); } /** * @param {object} docDefinition * @returns {Promise} */ resolveUrls(docDefinition) { const getExtendedUrl = url => { if (typeof url === 'object') { return { url: url.url, headers: url.headers }; } return { url: url, headers: {} }; }; return new Promise((resolve, reject) => { if (this.urlResolver === null) { resolve(); } for (let font in this.fontDescriptors) { if (this.fontDescriptors.hasOwnProperty(font)) { if (this.fontDescriptors[font].normal) { if (Array.isArray(this.fontDescriptors[font].normal)) { // TrueType Collection let url = getExtendedUrl(this.fontDescriptors[font].normal[0]); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].normal[0] = url.url; } else { let url = getExtendedUrl(this.fontDescriptors[font].normal); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].normal = url.url; } } if (this.fontDescriptors[font].bold) { if (Array.isArray(this.fontDescriptors[font].bold)) { // TrueType Collection let url = getExtendedUrl(this.fontDescriptors[font].bold[0]); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].bold[0] = url.url; } else { let url = getExtendedUrl(this.fontDescriptors[font].bold); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].bold = url.url; } } if (this.fontDescriptors[font].italics) { if (Array.isArray(this.fontDescriptors[font].italics)) { // TrueType Collection let url = getExtendedUrl(this.fontDescriptors[font].italics[0]); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].italics[0] = url.url; } else { let url = getExtendedUrl(this.fontDescriptors[font].italics); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].italics = url.url; } } if (this.fontDescriptors[font].bolditalics) { if (Array.isArray(this.fontDescriptors[font].bolditalics)) { // TrueType Collection let url = getExtendedUrl(this.fontDescriptors[font].bolditalics[0]); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].bolditalics[0] = url.url; } else { let url = getExtendedUrl(this.fontDescriptors[font].bolditalics); this.urlResolver.resolve(url.url, url.headers); this.fontDescriptors[font].bolditalics = url.url; } } } } if (docDefinition.images) { for (let image in docDefinition.images) { if (docDefinition.images.hasOwnProperty(image)) { let url = getExtendedUrl(docDefinition.images[image]); this.urlResolver.resolve(url.url, url.headers); docDefinition.images[image] = url.url; } } } if (docDefinition.attachments) { for (let attachment in docDefinition.attachments) { if (docDefinition.attachments.hasOwnProperty(attachment) && docDefinition.attachments[attachment].src) { let url = getExtendedUrl(docDefinition.attachments[attachment].src); this.urlResolver.resolve(url.url, url.headers); docDefinition.attachments[attachment].src = url.url; } } } if (docDefinition.files) { for (let file in docDefinition.files) { if (docDefinition.files.hasOwnProperty(file) && docDefinition.files[file].src) { let url = getExtendedUrl(docDefinition.files[file].src); this.urlResolver.resolve(url.url, url.headers); docDefinition.files[file].src = url.url; } } } this.urlResolver.resolved().then(() => { resolve(); }, result => { reject(result); }); }); } } function createMetadata(docDefinition) { // PDF standard has these properties reserved: Title, Author, Subject, Keywords, // Creator, Producer, CreationDate, ModDate, Trapped. // To keep the pdfmake api consistent, the info field are defined lowercase. // Custom properties don't contain a space. function standardizePropertyKey(key) { let standardProperties = ['Title', 'Author', 'Subject', 'Keywords', 'Creator', 'Producer', 'CreationDate', 'ModDate', 'Trapped']; let standardizedKey = key.charAt(0).toUpperCase() + key.slice(1); if (standardProperties.includes(standardizedKey)) { return standardizedKey; } return key.replace(/\s+/g, ''); } let info = { Producer: 'pdfmake', Creator: 'pdfmake' }; if (docDefinition.info) { for (let key in docDefinition.info) { let value = docDefinition.info[key]; if (value) { key = standardizePropertyKey(key); info[key] = value; } } } return info; } function embedFiles(docDefinition, pdfKitDoc) { if (docDefinition.files) { for (const key in docDefinition.files) { const file = docDefinition.files[key]; if (!file.src) return; if (pdfKitDoc.virtualfs && pdfKitDoc.virtualfs.existsSync(file.src)) { file.src = pdfKitDoc.virtualfs.readFileSync(file.src); } file.name = file.name || key; pdfKitDoc.file(file.src, file); } } } function calculatePageHeight(pages, margins) { function getItemHeight(item) { if (typeof item.item.getHeight === 'function') { return item.item.getHeight(); } else if (item.item._height) { return item.item._height; } else if (item.type === 'vector') { if (typeof item.item.y1 !== 'undefined') { return item.item.y1 > item.item.y2 ? item.item.y1 : item.item.y2; } else { return item.item.h; } } else { // TODO: add support for next item types return 0; } } function getBottomPosition(item) { let top = item.item.y || 0; let height = getItemHeight(item); return top + height; } let fixedMargins = normalizePageMargin(margins || 40); let height = fixedMargins.top; pages.forEach(page => { page.items.forEach(item => { let bottomPosition = getBottomPosition(item); if (bottomPosition > height) { height = bottomPosition; } }); }); height += fixedMargins.bottom; return height; } export default PdfPrinter;