import { select as d3_select } from '../d3.mjs';
import { jsPDF } from './jspdf.mjs';
import { svg2pdf } from './svg2pdf.mjs';
import { isNodeJs, internals, settings } from '../core.mjs';
import { FontHandler, detectPdfFont, kArial, kCourier, kSymbol, kWingdings } from './FontHandler.mjs';
import { approximateLabelWidth, replaceSymbolsInTextNode } from './latex.mjs';
/** @summary Create pdf for existing SVG element
* @return {Promise} with produced PDF file as url string
* @private */
async function makePDF(svg, args) {
const nodejs = isNodeJs();
let need_symbols = false;
const restore_fonts = [], restore_symb = [], restore_wing = [], restore_dominant = [], restore_oblique = [], restore_text = [],
node_transform = svg.node.getAttribute('transform'), custom_fonts = {};
if (svg.reset_tranform)
svg.node.removeAttribute('transform');
d3_select(svg.node).selectAll('g').each(function() {
if (this.hasAttribute('font-family')) {
const name = this.getAttribute('font-family');
if (name === kCourier) {
this.setAttribute('font-family', 'courier');
if (!svg.can_modify) restore_fonts.push(this); // keep to restore it
}
if (name === kSymbol) {
this.setAttribute('font-family', 'symbol');
if (!svg.can_modify) restore_symb.push(this); // keep to restore it
}
if (name === kWingdings) {
this.setAttribute('font-family', 'zapfdingbats');
if (!svg.can_modify) restore_wing.push(this); // keep to restore it
}
if (((name === kArial) || (name === kCourier)) && (this.getAttribute('font-weight') === 'bold') && (this.getAttribute('font-style') === 'oblique')) {
this.setAttribute('font-style', 'italic');
if (!svg.can_modify) restore_oblique.push(this); // keep to restore it
} else if ((name === kCourier) && (this.getAttribute('font-style') === 'oblique')) {
this.setAttribute('font-style', 'italic');
if (!svg.can_modify) restore_oblique.push(this); // keep to restore it
}
}
});
d3_select(svg.node).selectAll('text').each(function() {
if (this.hasAttribute('dominant-baseline')) {
this.setAttribute('dy', '.2em'); // slightly different as in plain text
this.removeAttribute('dominant-baseline');
if (!svg.can_modify) restore_dominant.push(this); // keep to restore it
} else if (svg.can_modify && nodejs && this.getAttribute('dy') === '.4em')
this.setAttribute('dy', '.2em'); // better alignment in PDF
if (replaceSymbolsInTextNode(this)) {
need_symbols = true;
if (!svg.can_modify) restore_text.push(this); // keep to restore it
}
});
let pr = Promise.resolve();
if (nodejs) {
const doc = internals.nodejs_document;
doc.originalCreateElementNS = doc.createElementNS;
globalThis.document = doc;
globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet;
globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule;
doc.createElementNS = function(ns, kind) {
const res = doc.originalCreateElementNS(ns, kind);
res.getBBox = function() {
let width = 50, height = 10;
if (this.tagName === 'text') {
// TODO: use jsDOC fonts for label width estimation
const font = detectPdfFont(this);
width = approximateLabelWidth(this.textContent, font);
height = font.size * 1.2;
}
return { x: 0, y: 0, width, height };
};
return res;
};
pr = import('canvas').then(handle => {
globalThis.Image = handle.Image;
});
}
const orientation = (svg.width < svg.height) ? 'portrait' : 'landscape';
let doc = args?.as_doc ? args.doc : null;
if (doc) {
doc.addPage({
orientation,
unit: 'px',
format: [svg.width + 10, svg.height + 10]
});
} else {
doc = new jsPDF({
orientation,
unit: 'px',
format: [svg.width + 10, svg.height + 10]
});
if (args?.as_doc)
args.doc = doc;
}
// add custom fonts to PDF document, only TTF format supported
d3_select(svg.node).selectAll('style').each(function() {
const fcfg = this.$fontcfg;
if (!fcfg?.n || !fcfg?.base64) return;
const name = fcfg.n;
if ((name === kSymbol) || (name === kWingdings)) return;
if (custom_fonts[name]) return;
custom_fonts[name] = true;
const filename = name.toLowerCase().replace(/\s/g, '') + '.ttf';
doc.addFileToVFS(filename, fcfg.base64);
doc.addFont(filename, fcfg.n, fcfg.s || 'normal');
});
if (need_symbols && !custom_fonts[kSymbol] && settings.LoadSymbolTtf) {
const handler = new FontHandler(122, 10);
pr = pr.then(() => handler.load()).then(() => {
handler.addCustomFontToSvg(d3_select(svg.node));
doc.addFileToVFS(kSymbol + '.ttf', handler.base64);
doc.addFont(kSymbol + '.ttf', kSymbol, 'normal');
});
}
return pr.then(() => svg2pdf(svg.node, doc, { x: 5, y: 5, width: svg.width, height: svg.height })).then(() => {
if (svg.reset_tranform && !svg.can_modify && node_transform)
svg.node.setAttribute('transform', node_transform);
restore_fonts.forEach(node => node.setAttribute('font-family', kCourier));
restore_symb.forEach(node => node.setAttribute('font-family', kSymbol));
restore_wing.forEach(node => node.setAttribute('font-family', kWingdings));
restore_oblique.forEach(node => node.setAttribute('font-style', 'oblique'));
restore_dominant.forEach(node => {
node.setAttribute('dominant-baseline', 'middle');
node.removeAttribute('dy');
});
restore_text.forEach(node => {
node.innerHTML = node.$originalHTML;
if (node.$originalFont)
node.setAttribute('font-family', node.$originalFont);
else
node.removeAttribute('font-family');
});
const res = args?.as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring');
if (nodejs) {
globalThis.document = undefined;
globalThis.CSSStyleSheet = undefined;
globalThis.CSSStyleRule = undefined;
globalThis.Image = undefined;
internals.nodejs_document.createElementNS = internals.nodejs_document.originalCreateElementNS;
if (args?.as_buffer)
return Buffer.from(res);
}
return res;
});
}
export { makePDF };