dom-to-vector-pdf
Version:
Convert DOM elements to vector PDFs using jsPDF, dom-to-svg and svg2pdf.js
355 lines (335 loc) • 15.7 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var domToSvg = require('dom-to-svg');
var jspdf = require('jspdf');
var svg2pdf_js = require('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 = domToSvg.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.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_js.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();
exports["default"] = instance;
exports.instance = instance;