UNPKG

dom-to-vector-pdf

Version:

Convert DOM elements to vector PDFs using jsPDF, dom-to-svg and svg2pdf.js

350 lines (332 loc) 15.5 kB
import { elementToSVG } from 'dom-to-svg'; import { jsPDF } from 'jspdf'; import { svg2pdf } from 'svg2pdf.js'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function normalizeFontWeight(weight) { var weightMap = { normal: '400', bold: '700', }; return weightMap[(weight === null || weight === void 0 ? void 0 : weight.toString()) || 'normal'] || (weight === null || weight === void 0 ? void 0 : weight.toString()) || '400'; } function processSvgFonts(element, fontManager) { if (element.classList.contains('no-print')) { element.remove(); return; } if (element.tagName === 'text' || element.tagName === 'tspan') { var style = element.getAttribute('style'); if (style) { style.split(';').forEach(function (css) { var _a = css.split(':'), key = _a[0], value = _a[1]; if (!key) return; element.setAttribute(key.trim(), value === null || value === void 0 ? void 0 : value.trim()); }); } element.removeAttribute('style'); var fontFamily = element.getAttribute('font-family'); var fontWeight = element.getAttribute('font-weight'); if (fontFamily) { element.setAttribute('font-family', fontManager.getFontId()); element.setAttribute('font-weight', normalizeFontWeight(fontWeight)); } var y = element.getAttribute('y'); if (y) { element.setAttribute('y', String(Number(y) - 3)); } } Array.from(element.children).forEach(function (child) { return processSvgFonts(child, fontManager); }); } var FontManager = /** @class */ (function () { function FontManager() { this.registeredFonts = new Map(); this.callbackList = []; this.fontId = 'PingFang'; } FontManager.prototype.getFontId = function () { return this.fontId; }; FontManager.getInstance = function () { if (!FontManager.instance) { FontManager.instance = new FontManager(); } return FontManager.instance; }; FontManager.prototype.setPdfInstance = function (pdf) { this.pdfInstance = pdf; this.callbackList.forEach(function (callback) { return callback(); }); this.callbackList = []; }; FontManager.prototype.registerFont = function (options) { this.fontId = options.fontId || 'font'; this.addFontToPdf(options); }; FontManager.prototype.registerFonts = function (options) { var _this = this; options.map(function (font) { return _this.registerFont(font); }); }; FontManager.prototype.addFontToPdf = function (options) { var _this = this; if (!this.pdfInstance) { this.callbackList.push(function () { return _this.addFontToPdf(options); }); return; } this.pdfInstance.addFont(options.font, options.fontId || 'font', options.fontStyle || 'normal', normalizeFontWeight(options.fontWeight)); }; return FontManager; }()); function cloneElement(selector) { var originElement = document.querySelector(selector); if (!originElement) { throw new Error("Element with selector \"".concat(selector, "\" not found")); } var parentElement = originElement.parentElement; var element = originElement.cloneNode(true); element.style.cssText = "\n z-index: -999999;\n position: absolute;\n top: 0;\n left: 0;\n "; parentElement === null || parentElement === void 0 ? void 0 : parentElement.appendChild(element); return { element: element, parentElement: parentElement }; } function removeClonedElement(element, parentElement) { parentElement === null || parentElement === void 0 ? void 0 : parentElement.removeChild(element); } function waitForResources(element, resourceTimeout) { return __awaiter(this, void 0, void 0, function () { var queue, timeout, resources; return __generator(this, function (_a) { queue = []; timeout = resourceTimeout !== null && resourceTimeout !== void 0 ? resourceTimeout : 5000; resources = element.querySelectorAll('img'); resources.forEach(function (resource) { queue.push(new Promise(function (resolve) { var done = false; resource.onload = function () { done = true; resolve(resource.src); }; resource.onerror = function () { done = true; resolve(); }; setTimeout(function () { if (!done) { resolve(void 0); } }, timeout); })); }); return [2 /*return*/, Promise.allSettled(queue)]; }); }); } function calculateSymbolScale(symbol) { var viewBox = symbol.getAttribute('viewBox'); if (!viewBox) { return 1; } var _a = viewBox.split(' ').map(Number), width = _a[2]; var expectedSize = 16; // 1em 通常计算的像素值 return expectedSize / width; } function inlineSvgSymbols(element) { var uses = element.querySelectorAll('use'); uses.forEach(function (use) { var href = use.getAttribute('xlink:href') || use.getAttribute('href'); if (!href) { return; } var symbol = document.querySelector(href); if (!symbol) { return; } var g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); Array.from(use.attributes).forEach(function (attr) { if (attr.name !== 'xlink:href' && attr.name !== 'href') { g.setAttribute(attr.name, attr.value); } }); g.innerHTML = "\n <g transform=\"scale(".concat(calculateSymbolScale(symbol), ")\">\n ").concat(symbol.innerHTML, "\n </g>\n "); use.replaceWith(g); }); } function convertToSvg(element) { var svgDocument = elementToSVG(element); var svgElement = svgDocument.documentElement; document.body.appendChild(svgElement); prepareSvgElement(svgElement); return svgElement; } function prepareSvgElement(svgElement) { svgElement.style.cssText = "\n all: unset;\n width: 100%;\n position: absolute;\n top: 0;\n left: 0;\n z-index: -999999;\n "; var utf8Declaration = document.createTextNode('<?xml version="1.0" encoding="utf-8"?>'); svgElement.insertBefore(utf8Declaration, svgElement.firstChild); } function createPdfDocument(svgElement) { var _a = svgElement.getBoundingClientRect(), width = _a.width, height = _a.height; return new jsPDF({ orientation: 'portrait', unit: 'px', format: [width, height], }); } function renderSvgToPdf(svgElement, pdf) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, svg2pdf(svgElement, pdf, { x: 0, y: 0, width: pdf.internal.pageSize.getWidth(), height: pdf.internal.pageSize.getHeight(), })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); } function exportPdf(options, fontManager, hooks) { return __awaiter(this, void 0, void 0, function () { var clonedElement, parentElement, svgElement, cloneResult, pdf, error_1; var _a, _b, _c, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: clonedElement = null; parentElement = null; svgElement = null; _e.label = 1; case 1: _e.trys.push([1, 4, , 5]); cloneResult = cloneElement(options.selector); clonedElement = cloneResult.element; parentElement = cloneResult.parentElement; // 2. Hook: afterDomClone (_a = hooks === null || hooks === void 0 ? void 0 : hooks.afterDomClone) === null || _a === void 0 ? void 0 : _a.call(hooks, clonedElement); // 3. Inline SVG symbols (<use> -> inline content) inlineSvgSymbols(clonedElement); // 4. Wait for resources (images etc.) return [4 /*yield*/, waitForResources(clonedElement, options.resourceTimeout)]; case 2: // 4. Wait for resources (images etc.) _e.sent(); // 5. Convert DOM to SVG svgElement = convertToSvg(clonedElement); removeClonedElement(clonedElement, parentElement); clonedElement = null; // 6. Process SVG font attributes processSvgFonts(svgElement, fontManager); // 7. Hook: beforeSvgConvert (_b = hooks === null || hooks === void 0 ? void 0 : hooks.beforeSvgConvert) === null || _b === void 0 ? void 0 : _b.call(hooks, svgElement); pdf = createPdfDocument(svgElement); fontManager.setPdfInstance(pdf); // 9. Render SVG into PDF return [4 /*yield*/, renderSvgToPdf(svgElement, pdf)]; case 3: // 9. Render SVG into PDF _e.sent(); // 10. Hook: beforePdfSave (_c = hooks === null || hooks === void 0 ? void 0 : hooks.beforePdfGenerate) === null || _c === void 0 ? void 0 : _c.call(hooks, pdf); (_d = hooks === null || hooks === void 0 ? void 0 : hooks.beforePdfSave) === null || _d === void 0 ? void 0 : _d.call(hooks, pdf); // 11. Save PDF pdf.save("".concat(options.filename, ".pdf")); // 12. Cleanup svgElement.remove(); svgElement = null; fontManager.setPdfInstance(null); return [3 /*break*/, 5]; case 4: error_1 = _e.sent(); // Cleanup on error if (clonedElement && parentElement) { removeClonedElement(clonedElement, parentElement); } svgElement === null || svgElement === void 0 ? void 0 : svgElement.remove(); fontManager.setPdfInstance(null); console.error('PDF generation failed:', error_1); throw error_1; case 5: return [2 /*return*/]; } }); }); } var DOMToPDF = /** @class */ (function () { function DOMToPDF() { this.fontManager = FontManager.getInstance(); } DOMToPDF.prototype.exportPDF = function (options, hooks) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, exportPdf(options, this.fontManager, hooks)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; DOMToPDF.prototype.registerFont = function (options) { if (Array.isArray(options)) { this.fontManager.registerFonts(options); } else { this.fontManager.registerFont(options); } }; return DOMToPDF; }()); var instance = new DOMToPDF(); export { instance as default, instance };