New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

html2pdfmake

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

html2pdfmake - npm Package Compare versions

Comparing version
0.0.3
to
0.0.4
+1605
dist/html2pdfmake.mjs
const META = Symbol('__HTML2PDFMAKE');
const NODE = 'NODE';
const UID = 'UID';
const END_WITH_NEWLINE = 'END_WITH_NEWLINE';
const START_WITH_NEWLINE = 'START_WITH_NEW_LINE';
const IS_NEWLINE = 'IS_NEWLINE';
const START_WITH_WHITESPACE = 'START_WITH_WHITESPACE';
const END_WITH_WHITESPACE = 'END_WITH_WHITESPACE';
const IS_WHITESPACE = 'IS_WHITESPACE';
const IS_INPUT = 'IS_INPUT';
const MARGIN = 'MARGIN';
const PADDING = 'PADDING';
const POSITION = 'POSITION';
const HANDLER = 'HANDLER';
const PDFMAKE = 'PDFMAKE';
const ITEMS = 'ITEMS'; // meta items
const STYLE = 'STYLE';
const POS_TOP = 1; // CSS 0
const POS_RIGHT = 2; // CSS 1
const POS_BOTTOM = 3; // CSS 2
const POS_LEFT = 0; // CSS 3
const getPatterns = () => ({
fill: {
boundingBox: [1, 1, 4, 4],
xStep: 1,
yStep: 1,
pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s'
}
});
class Context {
config;
styles;
images = {};
constructor(config, styles) {
this.config = config;
this.styles = styles;
}
}
const globalStyles = () => ({
':root': {
'font-size': '16px'
},
h1: {
'font-size': '32px',
'margin-top': '21.44px',
'margin-bottom': '21.44px',
'font-weight': 'bold'
},
h2: {
'font-size': '24px',
'margin-top': '19.92px',
'margin-bottom': '19.92px',
'font-weight': 'bold'
},
h3: {
'font-size': '18.72px',
'margin-top': '18.72px',
'margin-bottom': '18.72px',
'font-weight': 'bold'
},
h4: {
'font-size': '16px',
'margin-top': '21.28px',
'margin-bottom': '21.28px',
'font-weight': 'bold'
},
h5: {
'font-size': '13.28px',
'margin-top': '22.17px',
'margin-bottom': '22.17px',
'font-weight': 'bold'
},
h6: {
'font-size': '10.72px',
'margin-top': '24.97px',
'margin-bottom': '24.97px',
'font-weight': 'bold'
},
b: {
'font-weight': 'bold'
},
strong: {
'font-weight': 'bold'
},
i: {
'font-style': 'italic',
},
em: {
'font-style': 'italic'
},
s: {
'text-decoration': 'line-through'
},
del: {
'text-decoration': 'line-through'
},
sub: {
'font-size': '22px',
'vertical-align': 'sub'
},
small: {
'font-size': '13px'
},
u: {
'text-decoration': 'underline'
},
ul: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
ol: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
p: {
'margin-top': '16px',
'margin-bottom': '16px'
},
table: {
border: 'none',
padding: '3px'
},
td: {
border: 'none'
},
tr: {
margin: '4px 0'
},
th: {
'font-weight': 'bold',
border: 'none',
'text-align': 'center'
},
a: {
color: '#0000ee',
'text-decoration': 'underline'
},
hr: {
'border-top': '2px solid #9a9a9a',
'border-bottom': '0',
'border-left': '0px solid black',
'border-right': '0',
margin: '8px 0'
}
});
const defaultConfig = () => ({
globalStyles: globalStyles(),
styles: {},
collapseMargin: true,
collapseWhitespace: true,
});
/**
* @description Method to check if an item is an object. Date and Function are considered
* an object, so if you need to exclude those, please update the method accordingly.
* @param item - The item that needs to be checked
* @return {Boolean} Whether or not @item is an object
*/
const isObject = (item) => {
return (item === Object(item) && !Array.isArray(item));
};
/**
* @description Method to perform a deep merge of objects
* @param {Object} target - The targeted object that needs to be merged with the supplied @sources
* @param {Array<Object>} sources - The source(s) that will be used to update the @target object
* @return {Object} The final merged object
*/
const merge = (target, ...sources) => {
// return the target if no sources passed
if (!sources.length) {
return target;
}
const result = target;
if (isObject(result)) {
for (let i = 0; i < sources.length; i += 1) {
if (isObject(sources[i])) {
const elm = sources[i];
Object.keys(elm).forEach(key => {
if (isObject(elm[key])) {
if (!result[key] || !isObject(result[key])) {
result[key] = {};
}
merge(result[key], elm[key]);
}
else {
result[key] = elm[key];
}
});
}
}
}
return result;
};
const isNotText = (item) => typeof item !== 'string';
const isColgroup = (item) => item?.[META]?.[NODE]?.nodeName === 'COLGROUP';
const isImage = (item) => 'image' in item;
const isTable = (item) => 'table' in item;
const isTextArray = (item) => !!item && typeof item !== 'string' && 'text' in item && Array.isArray(item.text);
const isTextSimple = (item) => typeof item !== 'string' && 'text' in item && typeof item.text === 'string';
const isTextOrLeaf = (item) => 'text' in item || typeof item === 'string';
const isList = (item) => 'ul' in item || 'ol' in item;
const isTdOrTh = (item) => item[META]?.[NODE] && (item[META]?.[NODE]?.nodeName === 'TD' || item[META]?.[NODE]?.nodeName === 'TH');
const isHeadline = (item) => item[META]?.[NODE] && (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(item[META]?.[NODE]?.nodeName || ''));
const isElement = (el) => el.nodeType === 1;
const isNode = (el) => el.nodeType === 3 || el.nodeType === 8;
const isCollapsable = (item) => typeof item !== 'undefined' && typeof item !== 'string' && ('stack' in item || 'ul' in item || 'ol' in item) && 'margin' in item;
const getChildItems = (item) => {
if (typeof item === 'string') {
return [];
}
if ('stack' in item) {
return item.stack;
}
if ('text' in item && typeof item.text !== 'string') {
return item.text;
}
if ('table' in item) {
return item.table.body
.flatMap(tr => tr)
.filter(isNotText);
}
if ('ul' in item) {
return item.ul;
}
if ('ol' in item) {
return item.ol;
}
return [];
};
const toUnit = (value, rootPt = 12) => {
// if it's just a number, then return it
if (typeof value === 'number') {
return isFinite(value) ? value : 0;
}
const val = Number(parseFloat(value));
if (isNaN(val)) {
return 0;
}
const match = ('' + value).trim().match(/(pt|px|r?em|cm)$/);
if (!match) {
return val;
}
switch (match[1]) {
case 'em':
case 'rem':
return val * rootPt;
case 'px':
// 1px = 0.75 Point
return Number((val * 0.75).toFixed(2));
case 'cm':
return Number((val * 28.34).toFixed(2));
case 'mm':
return Number((val * 10 * 28.34).toFixed(2));
default:
return val;
}
};
const getUnitOrValue = (value) => typeof value === 'string' && (value.indexOf('%') > -1 || value.indexOf('auto') > -1)
? value
: toUnit(value);
const toUnitOrValue = (value) => getUnitOrValue(value);
const toUnitsOrValues = (value) => value.map(v => getUnitOrValue(v));
const expandValueToUnits = (value) => {
const values = toUnitsOrValues(value.split(' ')
.map(v => v.trim())
.filter(v => v));
if (values === null || !Array.isArray(values)) {
return null;
}
// values[0] = top
// values[1] = right
// values[2] = bottom
// values[3] = left
// pdfmake use left, top, right, bottom, || [horizontal, vertical]
// css use top, right, bottom, left
if (values.length === 1 && values[0] !== null) {
return [values[0], values[0], values[0], values[0]];
}
else if (values.length === 2) {
// topbottom leftright
return [values[1], values[0], values[1], values[0]];
}
else if (values.length === 3) {
// top bottom leftright
return [values[2], values[0], values[2], values[1]];
}
else if (values.length === 4) {
return [values[3], values[0], values[1], values[2]];
}
return null;
};
const handleColumns = (item) => {
const childItems = getChildItems(item);
return {
columns: childItems
.flatMap((subItem) => {
if ('text' in subItem && Array.isArray(subItem.text)) {
return subItem.text
.filter(childItem => !childItem[META]?.[IS_WHITESPACE])
.map(text => {
const width = toUnitOrValue(text[META]?.[STYLE]?.width || 'auto') || 'auto';
return (typeof text === 'string') ? {
text,
width
} : {
...text,
width
};
});
}
return {
stack: [].concat(subItem),
width: toUnitOrValue(subItem[META]?.[STYLE]?.width || 'auto') || 'auto'
};
}),
columnGap: 'columnGap' in item ? item.columnGap : 0
};
};
const handleImg = (image) => {
if (isImage(image) && typeof image.width === 'number' && typeof image.height === 'number') {
image.fit = [image.width, image.height];
}
return image;
};
const handleTable = (item) => {
if (isTable(item)) {
const bodyItem = item.table.body[0]?.[0];
const tableItem = bodyItem && typeof bodyItem !== 'string' && 'table' in bodyItem ? bodyItem : null;
if (!tableItem) {
return item;
}
const innerTable = tableItem.table;
const colgroup = bodyItem[META]?.[ITEMS]?.colgroup;
if (colgroup && Array.isArray(colgroup)) {
innerTable.widths = innerTable.widths || [];
colgroup.forEach((col, i) => {
if (col[META]?.[STYLE]?.width && innerTable.widths) {
innerTable.widths[i] = getUnitOrValue(col[META]?.[STYLE]?.width || 'auto');
}
});
}
const trs = bodyItem[META]?.[ITEMS]?.trs;
if (Array.isArray(trs)) {
trs.forEach((tr, i) => {
if (tr[META]?.[STYLE]?.height && innerTable.heights) {
innerTable.heights[i] = getUnitOrValue(tr[META]?.[STYLE]?.height || 'auto');
}
});
}
const paddingsTopBottom = {};
const paddingsLeftRight = {};
innerTable.body
.forEach((row, trIndex) => {
row.forEach((column, tdIndex) => {
if (typeof column !== 'string') {
if (column[META]?.[PADDING]) {
paddingsTopBottom[trIndex] = paddingsTopBottom[trIndex] || [0, 0];
paddingsLeftRight[tdIndex] = paddingsLeftRight[tdIndex] || [0, 0];
paddingsTopBottom[trIndex] = [
Math.max(paddingsTopBottom[trIndex][0], column[META]?.[PADDING]?.[POS_TOP] || 0),
Math.max(paddingsTopBottom[trIndex][1], column[META]?.[PADDING]?.[POS_BOTTOM] || 0)
];
paddingsLeftRight[tdIndex] = [
Math.max(paddingsLeftRight[tdIndex][0], column[META]?.[PADDING]?.[POS_LEFT] || 0),
Math.max(paddingsLeftRight[tdIndex][1], column[META]?.[PADDING]?.[POS_RIGHT] || 0)
];
}
column.style = column.style || [];
column.style.push(tdIndex % 2 === 0 ? 'td:nth-child(even)' : 'td:nth-child(odd)');
column.style.push(trIndex % 2 === 0 ? 'tr:nth-child(even)' : 'tr:nth-child(odd)');
}
});
});
const tableLayout = {};
const hasPaddingTopBottom = Object.keys(paddingsTopBottom).length > 0;
const hasPaddingLeftRight = Object.keys(paddingsLeftRight).length > 0;
if (hasPaddingTopBottom) {
tableLayout.paddingTop = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][0];
}
return 0;
};
tableLayout.paddingBottom = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][1];
}
return 0;
};
}
if (hasPaddingLeftRight) {
tableLayout.paddingRight = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][1];
}
return 0;
};
tableLayout.paddingLeft = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][0];
}
return 0;
};
}
if (hasPaddingLeftRight || hasPaddingTopBottom) {
tableItem.layout = tableLayout;
}
}
return item;
};
const addTocItem = (item, tocStyle = {}) => {
if ('text' in item && typeof item.text === 'string') {
item.tocItem = true;
merge(item, tocStyle);
}
else if ('stack' in item) {
const text = item.stack.find(s => 'text' in s);
if (text && typeof text !== 'string') {
text.tocItem = true;
merge(text, tocStyle);
}
}
};
const handleHeadlineToc = (item) => {
const tocStyle = {};
if (item[META]?.[NODE]?.nodeName === 'H1') {
Object.assign(tocStyle, {
tocNumberStyle: { bold: true }
});
}
else {
Object.assign(tocStyle, {
tocMargin: [10, 0, 0, 0]
});
}
addTocItem(item, tocStyle);
return item;
};
const handleItem = (item) => {
if (typeof item !== 'string' && item[META]?.[PDFMAKE]) {
merge(item, item[META]?.[PDFMAKE] || {});
}
if (typeof item[META]?.[HANDLER] === 'function') {
return item[META]?.[HANDLER]?.(item) || null;
}
return item;
};
let _uid = 0;
const getUniqueId = (item) => {
const meta = item[META] || {};
const __uid = meta[UID];
const el = item[META]?.[NODE];
if (__uid) {
return __uid;
}
if (!el) {
return '#' + (_uid++);
}
if (isElement(el)) {
const id = el.getAttribute('id');
if (id) {
return id;
}
}
const nodeName = el.nodeName.toLowerCase();
// TODO add parent? Or name something else?
const uid = '#' + nodeName + '-' + (_uid++);
meta[UID] = uid;
item[META] = meta;
return uid;
};
const attrToProps = (item) => {
const el = item[META]?.[NODE];
if (!el || !('getAttribute' in el))
return { [META]: { [STYLE]: {} } };
const cssClass = el.getAttribute('class') || '';
const cssClasses = [...new Set(cssClass.split(' ')
.filter((value) => value)
.map((value) => '.' + value.trim()))];
const nodeName = el.nodeName.toLowerCase();
const parentNodeName = el.parentNode ? el.parentNode.nodeName.toLowerCase() : null;
const styleNames = [
nodeName,
].concat(cssClasses);
if (cssClasses.length > 2) {
styleNames.push(cssClasses.join('')); // .a.b.c
}
if (parentNodeName) {
styleNames.push(parentNodeName + '>' + nodeName);
}
const uniqueId = getUniqueId(item);
styleNames.push(uniqueId); // Should be the last one
const props = {
[META]: {
[STYLE]: item[META]?.[STYLE] || {},
...(item[META] || {})
},
style: [...new Set((item.style || []).concat(styleNames))]
};
for (let i = 0; i < el.attributes.length; i++) {
const name = el.attributes[i].name;
const value = el.getAttribute(name)?.trim() || null;
if (value == null) {
continue;
}
props[META][STYLE][name] = value;
switch (name) {
case 'rowspan':
props.rowSpan = parseInt(value, 10);
break;
case 'colspan':
props.colSpan = parseInt(value, 10);
break;
case 'value':
if (nodeName === 'li') {
props.counter = parseInt(value, 10);
}
break;
case 'start': // ol
if (nodeName === 'ol') {
props.start = parseInt(value, 10);
}
break;
case 'width':
if ('image' in item) {
props.width = toUnit(value);
}
break;
case 'height':
if ('image' in item) {
props.height = toUnit(value);
}
break;
case 'data-fit':
if (value === 'true') {
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width && height) {
props.fit = [toUnit(width), toUnit(height)];
}
}
break;
case 'data-toc-item':
if (value !== 'false') {
let toc = {};
if (value) {
try {
toc = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
props[META][HANDLER] = (item) => {
addTocItem(item, toc);
return item;
};
}
break;
case 'data-pdfmake':
if (value) {
try {
props[META][PDFMAKE] = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
break;
}
}
return props;
};
const inheritStyle = (styles) => {
// TODO what do we want to exclude ?
const pick = {
color: true,
'font-family': true,
'font-size': true,
'font-weight': true,
'font': true,
'line-height': true,
'list-style-type': true,
'list-style': true,
'text-align': true,
// TODO only if parent is text: []
background: true,
'font-style': true,
'background-color': true,
'font-feature-settings': true,
'white-space': true,
'vertical-align': true,
'opacity': true,
'text-decoration': true,
};
return Object.keys(styles).reduce((p, c) => {
if (pick[c] || styles[c] === 'inherit') {
p[c] = styles[c];
}
return p;
}, {});
};
const getBorderStyle = (value) => {
const border = value.split(' ');
const color = border[2] || 'black';
const borderStyle = border[1] || 'solid';
const width = toUnit(border[0]);
return { color, width, borderStyle };
};
const computeBorder = (item, props, directive, value) => {
const { color, width, borderStyle } = getBorderStyle(value);
const tdOrTh = isTdOrTh(item);
const setBorder = (index) => {
props.border = item.border || props.border || [false, false, false, false];
props.borderColor = item.borderColor || props.borderColor || ['black', 'black', 'black', 'black'];
if (value === 'none') {
props.border[index] = false;
}
else {
props.border[index] = true;
props.borderColor[index] = color;
}
};
switch (directive) {
case 'border':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
if (value === 'none') {
props.layout.hLineWidth = () => 0;
props.layout.vLineWidth = () => 0;
break;
}
props.layout.vLineColor = () => color;
props.layout.hLineColor = () => color;
props.layout.hLineWidth = (i, node) => (i === 0 || i === node.table.body.length) ? width : 0;
props.layout.vLineWidth = (i, node) => (i === 0 || i === node.table.widths?.length) ? width : 0;
if (borderStyle === 'dashed') {
props.layout.hLineStyle = () => ({ dash: { length: 2, space: 2 } });
props.layout.vLineStyle = () => ({ dash: { length: 2, space: 2 } });
}
}
else if (tdOrTh) {
setBorder(0);
setBorder(1);
setBorder(2);
setBorder(3);
}
break;
case 'border-bottom':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 0);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === node.table.body.length) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === node.table.body.length) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(3);
}
break;
case 'border-top':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 1);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === 0) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === 0) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(1);
}
break;
case 'border-right':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(2);
}
break;
case 'border-left':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(0);
}
break;
}
};
const computeMargin = (itemProps, item, value, index) => {
const margin = itemProps[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
margin[index] = value;
itemProps[META][MARGIN] = [...margin];
itemProps.margin = margin;
const padding = itemProps[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
const paddingValue = padding[index] || 0;
itemProps.margin[index] = value + paddingValue;
};
const computePadding = (props, item, value, index) => {
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
switch (index) {
case POS_LEFT:
props.layout.paddingLeft = () => toUnit(value);
break;
case POS_TOP:
props.layout.paddingTop = () => toUnit(value);
break;
case POS_RIGHT:
props.layout.paddingRight = () => toUnit(value);
break;
case POS_BOTTOM:
props.layout.paddingBottom = () => toUnit(value);
break;
default:
throw new Error('Unsupported index for padding: ' + index);
}
}
else {
const padding = props[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
padding[index] = value;
props[META][PADDING] = [...padding];
if (!isTdOrTh(item)) {
const margin = props[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
props.margin = margin;
const marginValue = margin[index];
props.margin[index] = value + marginValue;
}
}
};
const styleToProps = (item, styles, parentStyles = {}) => {
const props = {
[META]: {
[STYLE]: {},
...(item[META] || {}),
}
};
const meta = props[META];
const image = isImage(item);
const table = isTable(item);
const text = isTextSimple(item);
const list = isList(item);
const rootFontSize = toUnit(parentStyles['font-size'] || '16px');
if (isHeadline(item)) {
meta[HANDLER] = handleHeadlineToc;
}
Object.keys(styles).forEach((key) => {
const directive = key;
const value = ('' + styles[key]).trim();
props[META][STYLE][directive] = value;
switch (directive) {
case 'padding': {
const paddings = expandValueToUnits(value);
if (table && paddings !== null) {
let layout = props.layout || item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.paddingLeft = () => Number(paddings[POS_LEFT]);
layout.paddingRight = () => Number(paddings[POS_RIGHT]);
layout.paddingTop = (i) => (i === 0) ? Number(paddings[POS_TOP]) : 0;
layout.paddingBottom = (i, node) => (i === node.table.body.length - 1) ? Number(paddings[POS_BOTTOM]) : 0;
props.layout = layout;
}
else if (paddings !== null) {
computePadding(props, item, Number(paddings[POS_TOP]), POS_TOP);
computePadding(props, item, Number(paddings[POS_LEFT]), POS_LEFT);
computePadding(props, item, Number(paddings[POS_RIGHT]), POS_RIGHT);
computePadding(props, item, Number(paddings[POS_BOTTOM]), POS_BOTTOM);
}
break;
}
case 'border':
case 'border-bottom':
case 'border-top':
case 'border-right':
case 'border-left':
computeBorder(item, props, directive, value);
break;
case 'font-size': {
props.fontSize = toUnit(value, rootFontSize);
break;
}
case 'line-height':
props.lineHeight = toUnit(value, rootFontSize);
break;
case 'letter-spacing':
props.characterSpacing = toUnit(value);
break;
case 'text-align':
props.alignment = value;
break;
case 'font-feature-settings': {
const settings = value.split(',').filter(s => s).map(s => s.replace(/['"]/g, ''));
const fontFeatures = item.fontFeatures || props.fontFeatures || [];
fontFeatures.push(...settings);
props.fontFeatures = fontFeatures;
break;
}
case 'font-weight':
switch (value) {
case 'bold':
props.bold = true;
break;
case 'normal':
props.bold = false;
break;
}
break;
case 'text-decoration':
switch (value) {
case 'underline':
props.decoration = 'underline';
break;
case 'line-through':
props.decoration = 'lineThrough';
break;
case 'overline':
props.decoration = 'overline';
break;
}
break;
case 'text-decoration-color':
props.decorationColor = value;
break;
case 'text-decoration-style':
props.decorationStyle = value;
break;
case 'vertical-align':
if (value === 'sub') {
props.sub = true;
}
break;
case 'font-style':
switch (value) {
case 'italic':
props.italics = true;
break;
}
break;
case 'font-family':
props.font = value;
break;
case 'color':
props.color = value;
break;
case 'background':
case 'background-color':
if (table) {
let layout = item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.fillColor = () => value;
props.layout = layout;
}
else if (isTdOrTh(item)) {
props.fillColor = value;
}
else {
props.background = ['fill', value];
}
break;
case 'margin': {
const margin = expandValueToUnits(value)?.map(value => typeof value === 'string' ? 0 : value);
if (margin) {
computeMargin(props, item, margin[POS_TOP], POS_TOP);
computeMargin(props, item, margin[POS_LEFT], POS_LEFT);
computeMargin(props, item, margin[POS_RIGHT], POS_RIGHT);
computeMargin(props, item, margin[POS_BOTTOM], POS_BOTTOM);
}
break;
}
case 'margin-left':
computeMargin(props, item, toUnit(value), POS_LEFT);
break;
case 'margin-top':
computeMargin(props, item, toUnit(value), POS_TOP);
break;
case 'margin-right':
computeMargin(props, item, toUnit(value), POS_RIGHT);
break;
case 'margin-bottom':
computeMargin(props, item, toUnit(value), POS_BOTTOM);
break;
case 'padding-left':
computePadding(props, item, toUnit(value), POS_LEFT);
break;
case 'padding-top':
computePadding(props, item, toUnit(value), POS_TOP);
break;
case 'padding-right':
computePadding(props, item, toUnit(value), POS_RIGHT);
break;
case 'padding-bottom':
computePadding(props, item, toUnit(value), POS_BOTTOM);
break;
case 'page-break-before':
if (value === 'always') {
props.pageBreak = 'before';
}
break;
case 'page-break-after':
if (value === 'always') {
props.pageBreak = 'after';
}
break;
case 'position':
if (value === 'absolute') {
meta[POSITION] = 'absolute';
props.absolutePosition = {};
}
else if (value === 'relative') {
meta[POSITION] = 'relative';
props.relativePosition = {};
}
break;
case 'left':
case 'top':
// TODO can be set before postion:absolute!
if (!props.absolutePosition && !props.relativePosition) {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
if (props.absolutePosition) {
if (directive === 'left') {
props.absolutePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.absolutePosition.y = toUnit(value);
}
}
else if (props.relativePosition) {
if (directive === 'left') {
props.relativePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.relativePosition.y = toUnit(value);
}
}
else {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
break;
case 'white-space':
if (value === 'pre' && meta[NODE]) {
if (text) {
props.text = meta[NODE]?.textContent || '';
}
props.preserveLeadingSpaces = true;
}
break;
case 'display':
if (value === 'flex') {
props[META][HANDLER] = handleColumns;
}
else if (value === 'none') {
props[META][HANDLER] = () => null;
}
break;
case 'opacity':
props.opacity = Number(parseFloat(value));
break;
case 'gap':
props.columnGap = toUnit(value);
break;
case 'list-style-type':
case 'list-style':
if (list) {
props.type = value;
}
else {
props.listType = value;
}
break;
case 'width':
if (table) {
if (value === '100%') {
item.table.widths = ['*'];
}
else {
const width = toUnitOrValue(value);
if (width !== null) {
item.table.widths = [width];
}
}
}
else if (image) {
props.width = toUnit(value);
}
break;
case 'height':
if (image) {
props.height = toUnit(value);
}
break;
case 'max-height':
if (image) {
props.maxHeight = toUnit(value);
}
break;
case 'max-width':
if (image) {
props.maxWidth = toUnit(value);
}
break;
case 'min-height':
if (image) {
props.minHeight = toUnit(value);
}
break;
case 'min-width':
if (image) {
props.minWidth = toUnit(value);
}
break;
case 'object-fit':
if (value === 'contain' && image) {
meta[HANDLER] = handleImg;
}
break;
}
});
return props;
};
/**
* @param el DOM Element
*/
const getInlineStyles = (el) => ('getAttribute' in el ? el.getAttribute('style') || '' : '').split(';')
.map(style => style.trim().toLowerCase().split(':'))
.filter(style => style.length === 2)
.reduce((style, value) => {
style[value[0].trim()] = value[1].trim();
return style;
}, {});
const getDefaultStyles = (el, item, styles) => (item.style || []).concat(el.nodeName.toLowerCase())
.filter((selector) => styles && styles[selector])
.reduce((style, selector) => {
return {
...style,
...styles[selector]
};
}, {});
/**
*
* @param el DOM Element
* @param item
* @param styles additional styles
* @param parentStyles pick styles
*/
const computeProps = (el, item, styles, parentStyles = {}) => {
const defaultStyles = getDefaultStyles(el, item, styles);
const rootStyles = styles[':root'] || globalStyles()[':root'];
const inheritedStyles = inheritStyle(parentStyles);
const cssStyles = Object.assign({}, defaultStyles, inheritedStyles, getInlineStyles(el));
const styleProps = styleToProps(item, cssStyles, Object.assign({}, rootStyles, inheritedStyles));
const attrProps = attrToProps(item);
const props = {
...styleProps,
...attrProps,
[META]: {
...(styleProps[META] || {}),
...(attrProps[META] || {}),
[STYLE]: {
...(styleProps[META][STYLE] || {}),
...(attrProps[META][STYLE] || {}),
}
}
};
return {
cssStyles,
props
};
};
const collapseMargin = (item, prevItem) => {
if (isCollapsable(item) && isCollapsable(prevItem)) {
const prevMargin = prevItem[META]?.[MARGIN] || [0, 0, 0, 0];
prevItem[META] = { ...(prevItem[META] || {}), [MARGIN]: prevMargin };
prevItem.margin[POS_BOTTOM] = prevItem[META]?.[PADDING]?.[POS_BOTTOM] || 0;
const itemMargin = item[META]?.[MARGIN] || [0, 0, 0, 0];
const marginTop = Math.max(itemMargin[POS_TOP], prevMargin[POS_BOTTOM]);
itemMargin[POS_TOP] = marginTop;
prevMargin[POS_BOTTOM] = 0;
item[META] = { ...(item[META] || {}), [MARGIN]: itemMargin };
item.margin[POS_TOP] = marginTop + (item[META]?.[PADDING]?.[POS_TOP] || 0);
}
};
const findLastDeep = (ta) => {
const last = ta.text.at(-1);
if (isTextArray(last)) {
return findLastDeep(last);
}
return last;
};
const findFirstArrayDeep = (ta) => {
const first = ta.text.at(0);
if (isTextArray(first)) {
return findFirstArrayDeep(first);
}
return ta.text;
};
const collapseWhitespace = (item, nextText) => {
const prevLastText = findLastDeep(item);
const nextFirstTextArray = findFirstArrayDeep(nextText);
if (prevLastText && prevLastText[META]?.[IS_WHITESPACE] && nextFirstTextArray[0][META]?.[IS_WHITESPACE]) {
nextFirstTextArray.shift();
}
};
function isBase64(str) {
return /^data:image\/(jpeg|png|jpg);base64,/.test(str);
}
const parseImg = (el, ctx) => {
const src = el.getAttribute('src');
if (!src) {
return null;
}
const name = el.getAttribute('name') || src;
let image;
if (isBase64(src)) {
image = src;
}
else if (ctx.images[name]) {
image = name;
}
else {
ctx.images[src] = name;
image = name;
}
return {
image,
[META]: {}
};
};
const parseSvg = (el) => {
// TODO is this okay?
const svgEl = el.cloneNode(true);
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width) {
svgEl.setAttribute('width', '' + getUnitOrValue(width));
}
if (height) {
svgEl.setAttribute('height', '' + getUnitOrValue(height));
}
return {
svg: svgEl.outerHTML.replace(/\n(\s+)?/g, ''),
};
};
const parseTable = () => {
// TODO table in table?
return {
table: {
body: (items) => {
// tbody -> tr
const colgroup = items.find(isColgroup)?.stack[0] || [];
const tbody = items.filter(item => !isColgroup(item));
const trs = tbody.flatMap((item) => 'stack' in item ? item.stack : []);
const body = trs.map((item) => getChildItems(item));
if (body.length === 0) {
return [];
}
const longestRow = body.reduce((a, b) => a.length <= b.length ? b : a);
const table = {
body,
widths: new Array(longestRow.length).fill('auto'),
heights: new Array(trs.length).fill('auto')
};
return [[{
table,
layout: {
defaultBorder: false
},
[META]: {
[ITEMS]: {
colgroup: 'text' in colgroup && Array.isArray(colgroup.text) ? colgroup.text : [],
trs
},
}
}]];
},
// widths: ['*'],
},
[META]: {
[HANDLER]: handleTable,
},
layout: {}
};
};
const parseText = (el) => {
const text = el.textContent;
if (text === null) {
return null;
}
const keepNewLines = text.replace(/[^\S\r\n]+/, '');
const trimmedText = text.replace(/\n|\t| +/g, ' ')
.replace(/^ +/, '')
.replace(/ +$/, '');
//.trim() removes also &nbsp;
const endWithNL = keepNewLines[keepNewLines.length - 1] === '\n';
const startWithNL = keepNewLines[0] === '\n';
const startWithWhitespace = text[0] === ' ';
const endWithWhitespace = text[text.length - 1] === ' ';
return {
text: trimmedText,
[META]: {
[START_WITH_NEWLINE]: startWithNL,
[END_WITH_NEWLINE]: endWithNL,
[IS_NEWLINE]: startWithNL && endWithNL && trimmedText.length === 0,
[START_WITH_WHITESPACE]: startWithWhitespace,
[END_WITH_WHITESPACE]: endWithWhitespace,
[IS_WHITESPACE]: startWithWhitespace && endWithWhitespace && text.length === 1,
},
};
};
const WHITESPACE = ' ';
const addWhitespace = (type) => ({
text: WHITESPACE,
[META]: {
[IS_WHITESPACE]: type
}
});
const parseAsHTMLCollection = (el) => ['TABLE', 'TBODY', 'TR', 'COLGROUP', 'COL', 'UL', 'OL', 'SELECT'].includes(el.nodeName) && 'children' in el;
const stackRegex = /^(address|blockquote|body|center|colgroup|dir|div|dl|fieldset|form|h[1-6]|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|html)$/i;
const isStackItem = (el) => stackRegex.test(el.nodeName);
const parseChildren = (el, ctx, parentStyles = {}) => {
const items = [];
const children = parseAsHTMLCollection(el) ? el.children : el.childNodes;
for (let i = 0; i < children.length; i++) {
const item = parseByRule(children[i], ctx, parentStyles);
if (item === null) {
continue;
}
const isNewline = !!item[META]?.[IS_NEWLINE];
const prevItem = items[items.length - 1];
if (ctx.config.collapseMargin && prevItem) {
collapseMargin(item, prevItem);
}
if (isNewline && (items.length === 0 || !children[i + 1] || prevItem && 'stack' in prevItem)) {
continue;
}
// Stack item
if (!('text' in item)) {
items.push(item);
continue;
}
const endWithNewLine = !!item[META]?.[END_WITH_NEWLINE];
const startWithNewLine = !!item[META]?.[START_WITH_NEWLINE];
const endWithWhiteSpace = !!item[META]?.[END_WITH_WHITESPACE];
const startWithWhitespace = !!item[META]?.[START_WITH_WHITESPACE];
const isWhitespace = !!item[META]?.[IS_WHITESPACE];
const textItem = Array.isArray(item.text)
? item : { text: [isWhitespace ? addWhitespace('newLine') : item] };
if (!isNewline && !isWhitespace) {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
// add whitespace before
if (startWithNewLine || startWithWhitespace) {
textItem.text.unshift(addWhitespace(startWithNewLine ? 'startWithNewLine' : 'startWithWhitespace'));
}
// add whitespace after
if (endWithNewLine || endWithWhiteSpace) {
textItem.text.push(addWhitespace(endWithNewLine ? 'endWithNewLine' : 'endWithWhiteSpace'));
}
}
// Append text to last text element otherwise a new line is created
if (isTextArray(prevItem)) {
if (ctx.config.collapseWhitespace) {
collapseWhitespace(prevItem, textItem);
}
prevItem.text.push(textItem);
}
else {
// wrap so the next text items will be appended to it
items.push({
text: [textItem]
});
}
}
return items;
};
const getNodeRule = (node) => {
const nodeName = node.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
return () => null;
case '#text':
return parseText;
default:
return () => null;
}
};
const getElementRule = (el) => {
const nodeName = el.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
case 'option': // see <select>
case 'script':
case 'style':
case 'iframe':
case 'object':
return () => null;
case '#text':
return parseText;
case 'a':
return (el) => {
const href = el.getAttribute('href');
if (!href) {
return parseElement(el);
}
const linkify = (item) => {
const children = getChildItems(item);
[].concat(children)
.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
};
return {
text: items => {
items.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
return items;
}
};
};
case 'br':
return () => ({
text: '\n',
[META]: {
[IS_NEWLINE]: true
}
});
case 'qr-code': // CUSTOM
return (el) => {
const content = el.getAttribute('value');
if (!content) {
return null;
}
const sizeAttr = el.getAttribute('data-size');
const size = sizeAttr ? toUnit(sizeAttr) : toUnit('128px');
return {
qr: content,
fit: size,
};
};
case 'toc': // CUSTOM
return (el) => {
const content = el.textContent;
if (!content) {
return null;
}
return {
toc: {
title: {
text: content,
bold: true,
fontSize: toUnit('22px'),
margin: [0, 10, 0, 10]
},
},
};
};
case 'table':
return parseTable;
case 'ul':
return () => {
return {
ul: (items) => items
};
};
case 'ol':
return () => {
return {
ol: (items) => items
};
};
case 'img':
return parseImg;
case 'svg':
return parseSvg;
case 'hr':
// TODO find better <hr> alternative?
return () => {
return {
table: {
widths: ['*'],
body: [
[''],
]
},
style: ['hr'],
};
};
case 'input':
// TODO acro-form
return (el) => {
if (el instanceof HTMLInputElement) {
return {
text: 'value' in el ? el.value : '',
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
case 'select':
// TODO acro-form
return (el) => {
if (el instanceof HTMLSelectElement) {
const value = el.options[el.selectedIndex].value;
return {
text: value,
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
default:
return parseElement;
}
};
const getItemByRule = (el, ctx) => {
if (typeof ctx.config.customRule === 'function') {
const result = ctx.config.customRule(el, ctx);
if (result === null) {
return null;
}
else if (result !== undefined) {
return result;
}
}
if (isElement(el)) { // ELEMENT_NODE
return getElementRule(el)(el, ctx);
}
else if (isNode(el)) { // TEXT_NODE || COMMENT_NODE
return getNodeRule(el)(el, ctx);
}
throw new Error('Unsupported Node Type: ' + el.nodeType);
};
const processItems = (item, ctx, parentStyles = {}) => {
const el = item[META]?.[NODE];
if (typeof item !== 'string' && el) {
const { cssStyles, props } = computeProps(el, item, ctx.styles, parentStyles);
Object.assign(item, props);
if ('stack' in item && typeof item.stack === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.stack = item.stack(children, ctx);
}
else if ('text' in item && typeof item.text === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.text = item.text(children.filter(isTextOrLeaf), ctx);
}
else if ('ul' in item && typeof item.ul === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ul = item.ul(children, ctx);
}
else if ('ol' in item && typeof item.ol === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ol = item.ol(children, ctx);
}
else if ('table' in item && typeof item.table.body === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.table.body = item.table.body(children, ctx);
}
}
return handleItem(item);
};
const parseByRule = (el, ctx, parentStyles = {}) => {
const item = getItemByRule(el, ctx);
if (item === null) {
return null;
}
// Add ref to NODE
const meta = item[META] || {};
meta[NODE] = el;
item[META] = meta;
return processItems(item, ctx, parentStyles);
};
const parseElement = (el) => {
if (isStackItem(el)) {
return {
stack: (items) => items
};
}
return {
text: (items) => {
// Return flat
if (items.length === 1 && 'text' in items[0] && Array.isArray(items[0].text)) {
return items[0].text;
}
return items;
}
};
};
const htmlToDom = (html) => {
if (typeof DOMParser !== 'undefined') {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body;
}
else if (typeof document !== 'undefined' && typeof document.createDocumentFragment === 'function') {
const fragment = document.createDocumentFragment();
const doc = document.createElement('div');
doc.innerHTML = html;
fragment.append(doc);
return fragment.children[0];
}
throw new Error('Could not parse html to DOM. Please use external parser like jsdom.');
};
const parse = (input, _config = defaultConfig()) => {
const config = {
...defaultConfig,
..._config
};
const ctx = new Context(config, Object.assign({}, config.globalStyles, config.styles));
const body = typeof input === 'string' ? htmlToDom(input) : input;
const content = body !== null ? parseChildren(body, ctx) : [];
return {
content,
images: ctx.images,
patterns: getPatterns()
};
};
export { parse };
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.html2pdfmake = {}));
})(this, (function (exports) { 'use strict';
const META = Symbol('__HTML2PDFMAKE');
const NODE = 'NODE';
const UID = 'UID';
const END_WITH_NEWLINE = 'END_WITH_NEWLINE';
const START_WITH_NEWLINE = 'START_WITH_NEW_LINE';
const IS_NEWLINE = 'IS_NEWLINE';
const START_WITH_WHITESPACE = 'START_WITH_WHITESPACE';
const END_WITH_WHITESPACE = 'END_WITH_WHITESPACE';
const IS_WHITESPACE = 'IS_WHITESPACE';
const IS_INPUT = 'IS_INPUT';
const MARGIN = 'MARGIN';
const PADDING = 'PADDING';
const POSITION = 'POSITION';
const HANDLER = 'HANDLER';
const PDFMAKE = 'PDFMAKE';
const ITEMS = 'ITEMS'; // meta items
const STYLE = 'STYLE';
const POS_TOP = 1; // CSS 0
const POS_RIGHT = 2; // CSS 1
const POS_BOTTOM = 3; // CSS 2
const POS_LEFT = 0; // CSS 3
const getPatterns = () => ({
fill: {
boundingBox: [1, 1, 4, 4],
xStep: 1,
yStep: 1,
pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s'
}
});
class Context {
config;
styles;
images = {};
constructor(config, styles) {
this.config = config;
this.styles = styles;
}
}
const globalStyles = () => ({
':root': {
'font-size': '16px'
},
h1: {
'font-size': '32px',
'margin-top': '21.44px',
'margin-bottom': '21.44px',
'font-weight': 'bold'
},
h2: {
'font-size': '24px',
'margin-top': '19.92px',
'margin-bottom': '19.92px',
'font-weight': 'bold'
},
h3: {
'font-size': '18.72px',
'margin-top': '18.72px',
'margin-bottom': '18.72px',
'font-weight': 'bold'
},
h4: {
'font-size': '16px',
'margin-top': '21.28px',
'margin-bottom': '21.28px',
'font-weight': 'bold'
},
h5: {
'font-size': '13.28px',
'margin-top': '22.17px',
'margin-bottom': '22.17px',
'font-weight': 'bold'
},
h6: {
'font-size': '10.72px',
'margin-top': '24.97px',
'margin-bottom': '24.97px',
'font-weight': 'bold'
},
b: {
'font-weight': 'bold'
},
strong: {
'font-weight': 'bold'
},
i: {
'font-style': 'italic',
},
em: {
'font-style': 'italic'
},
s: {
'text-decoration': 'line-through'
},
del: {
'text-decoration': 'line-through'
},
sub: {
'font-size': '22px',
'vertical-align': 'sub'
},
small: {
'font-size': '13px'
},
u: {
'text-decoration': 'underline'
},
ul: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
ol: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
p: {
'margin-top': '16px',
'margin-bottom': '16px'
},
table: {
border: 'none',
padding: '3px'
},
td: {
border: 'none'
},
tr: {
margin: '4px 0'
},
th: {
'font-weight': 'bold',
border: 'none',
'text-align': 'center'
},
a: {
color: '#0000ee',
'text-decoration': 'underline'
},
hr: {
'border-top': '2px solid #9a9a9a',
'border-bottom': '0',
'border-left': '0px solid black',
'border-right': '0',
margin: '8px 0'
}
});
const defaultConfig = () => ({
globalStyles: globalStyles(),
styles: {},
collapseMargin: true,
collapseWhitespace: true,
});
/**
* @description Method to check if an item is an object. Date and Function are considered
* an object, so if you need to exclude those, please update the method accordingly.
* @param item - The item that needs to be checked
* @return {Boolean} Whether or not @item is an object
*/
const isObject = (item) => {
return (item === Object(item) && !Array.isArray(item));
};
/**
* @description Method to perform a deep merge of objects
* @param {Object} target - The targeted object that needs to be merged with the supplied @sources
* @param {Array<Object>} sources - The source(s) that will be used to update the @target object
* @return {Object} The final merged object
*/
const merge = (target, ...sources) => {
// return the target if no sources passed
if (!sources.length) {
return target;
}
const result = target;
if (isObject(result)) {
for (let i = 0; i < sources.length; i += 1) {
if (isObject(sources[i])) {
const elm = sources[i];
Object.keys(elm).forEach(key => {
if (isObject(elm[key])) {
if (!result[key] || !isObject(result[key])) {
result[key] = {};
}
merge(result[key], elm[key]);
}
else {
result[key] = elm[key];
}
});
}
}
}
return result;
};
const isNotText = (item) => typeof item !== 'string';
const isColgroup = (item) => item?.[META]?.[NODE]?.nodeName === 'COLGROUP';
const isImage = (item) => 'image' in item;
const isTable = (item) => 'table' in item;
const isTextArray = (item) => !!item && typeof item !== 'string' && 'text' in item && Array.isArray(item.text);
const isTextSimple = (item) => typeof item !== 'string' && 'text' in item && typeof item.text === 'string';
const isTextOrLeaf = (item) => 'text' in item || typeof item === 'string';
const isList = (item) => 'ul' in item || 'ol' in item;
const isTdOrTh = (item) => item[META]?.[NODE] && (item[META]?.[NODE]?.nodeName === 'TD' || item[META]?.[NODE]?.nodeName === 'TH');
const isHeadline = (item) => item[META]?.[NODE] && (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(item[META]?.[NODE]?.nodeName || ''));
const isElement = (el) => el.nodeType === 1;
const isNode = (el) => el.nodeType === 3 || el.nodeType === 8;
const isCollapsable = (item) => typeof item !== 'undefined' && typeof item !== 'string' && ('stack' in item || 'ul' in item || 'ol' in item) && 'margin' in item;
const getChildItems = (item) => {
if (typeof item === 'string') {
return [];
}
if ('stack' in item) {
return item.stack;
}
if ('text' in item && typeof item.text !== 'string') {
return item.text;
}
if ('table' in item) {
return item.table.body
.flatMap(tr => tr)
.filter(isNotText);
}
if ('ul' in item) {
return item.ul;
}
if ('ol' in item) {
return item.ol;
}
return [];
};
const toUnit = (value, rootPt = 12) => {
// if it's just a number, then return it
if (typeof value === 'number') {
return isFinite(value) ? value : 0;
}
const val = Number(parseFloat(value));
if (isNaN(val)) {
return 0;
}
const match = ('' + value).trim().match(/(pt|px|r?em|cm)$/);
if (!match) {
return val;
}
switch (match[1]) {
case 'em':
case 'rem':
return val * rootPt;
case 'px':
// 1px = 0.75 Point
return Number((val * 0.75).toFixed(2));
case 'cm':
return Number((val * 28.34).toFixed(2));
case 'mm':
return Number((val * 10 * 28.34).toFixed(2));
default:
return val;
}
};
const getUnitOrValue = (value) => typeof value === 'string' && (value.indexOf('%') > -1 || value.indexOf('auto') > -1)
? value
: toUnit(value);
const toUnitOrValue = (value) => getUnitOrValue(value);
const toUnitsOrValues = (value) => value.map(v => getUnitOrValue(v));
const expandValueToUnits = (value) => {
const values = toUnitsOrValues(value.split(' ')
.map(v => v.trim())
.filter(v => v));
if (values === null || !Array.isArray(values)) {
return null;
}
// values[0] = top
// values[1] = right
// values[2] = bottom
// values[3] = left
// pdfmake use left, top, right, bottom, || [horizontal, vertical]
// css use top, right, bottom, left
if (values.length === 1 && values[0] !== null) {
return [values[0], values[0], values[0], values[0]];
}
else if (values.length === 2) {
// topbottom leftright
return [values[1], values[0], values[1], values[0]];
}
else if (values.length === 3) {
// top bottom leftright
return [values[2], values[0], values[2], values[1]];
}
else if (values.length === 4) {
return [values[3], values[0], values[1], values[2]];
}
return null;
};
const handleColumns = (item) => {
const childItems = getChildItems(item);
return {
columns: childItems
.flatMap((subItem) => {
if ('text' in subItem && Array.isArray(subItem.text)) {
return subItem.text
.filter(childItem => !childItem[META]?.[IS_WHITESPACE])
.map(text => {
const width = toUnitOrValue(text[META]?.[STYLE]?.width || 'auto') || 'auto';
return (typeof text === 'string') ? {
text,
width
} : {
...text,
width
};
});
}
return {
stack: [].concat(subItem),
width: toUnitOrValue(subItem[META]?.[STYLE]?.width || 'auto') || 'auto'
};
}),
columnGap: 'columnGap' in item ? item.columnGap : 0
};
};
const handleImg = (image) => {
if (isImage(image) && typeof image.width === 'number' && typeof image.height === 'number') {
image.fit = [image.width, image.height];
}
return image;
};
const handleTable = (item) => {
if (isTable(item)) {
const bodyItem = item.table.body[0]?.[0];
const tableItem = bodyItem && typeof bodyItem !== 'string' && 'table' in bodyItem ? bodyItem : null;
if (!tableItem) {
return item;
}
const innerTable = tableItem.table;
const colgroup = bodyItem[META]?.[ITEMS]?.colgroup;
if (colgroup && Array.isArray(colgroup)) {
innerTable.widths = innerTable.widths || [];
colgroup.forEach((col, i) => {
if (col[META]?.[STYLE]?.width && innerTable.widths) {
innerTable.widths[i] = getUnitOrValue(col[META]?.[STYLE]?.width || 'auto');
}
});
}
const trs = bodyItem[META]?.[ITEMS]?.trs;
if (Array.isArray(trs)) {
trs.forEach((tr, i) => {
if (tr[META]?.[STYLE]?.height && innerTable.heights) {
innerTable.heights[i] = getUnitOrValue(tr[META]?.[STYLE]?.height || 'auto');
}
});
}
const paddingsTopBottom = {};
const paddingsLeftRight = {};
innerTable.body
.forEach((row, trIndex) => {
row.forEach((column, tdIndex) => {
if (typeof column !== 'string') {
if (column[META]?.[PADDING]) {
paddingsTopBottom[trIndex] = paddingsTopBottom[trIndex] || [0, 0];
paddingsLeftRight[tdIndex] = paddingsLeftRight[tdIndex] || [0, 0];
paddingsTopBottom[trIndex] = [
Math.max(paddingsTopBottom[trIndex][0], column[META]?.[PADDING]?.[POS_TOP] || 0),
Math.max(paddingsTopBottom[trIndex][1], column[META]?.[PADDING]?.[POS_BOTTOM] || 0)
];
paddingsLeftRight[tdIndex] = [
Math.max(paddingsLeftRight[tdIndex][0], column[META]?.[PADDING]?.[POS_LEFT] || 0),
Math.max(paddingsLeftRight[tdIndex][1], column[META]?.[PADDING]?.[POS_RIGHT] || 0)
];
}
column.style = column.style || [];
column.style.push(tdIndex % 2 === 0 ? 'td:nth-child(even)' : 'td:nth-child(odd)');
column.style.push(trIndex % 2 === 0 ? 'tr:nth-child(even)' : 'tr:nth-child(odd)');
}
});
});
const tableLayout = {};
const hasPaddingTopBottom = Object.keys(paddingsTopBottom).length > 0;
const hasPaddingLeftRight = Object.keys(paddingsLeftRight).length > 0;
if (hasPaddingTopBottom) {
tableLayout.paddingTop = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][0];
}
return 0;
};
tableLayout.paddingBottom = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][1];
}
return 0;
};
}
if (hasPaddingLeftRight) {
tableLayout.paddingRight = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][1];
}
return 0;
};
tableLayout.paddingLeft = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][0];
}
return 0;
};
}
if (hasPaddingLeftRight || hasPaddingTopBottom) {
tableItem.layout = tableLayout;
}
}
return item;
};
const addTocItem = (item, tocStyle = {}) => {
if ('text' in item && typeof item.text === 'string') {
item.tocItem = true;
merge(item, tocStyle);
}
else if ('stack' in item) {
const text = item.stack.find(s => 'text' in s);
if (text && typeof text !== 'string') {
text.tocItem = true;
merge(text, tocStyle);
}
}
};
const handleHeadlineToc = (item) => {
const tocStyle = {};
if (item[META]?.[NODE]?.nodeName === 'H1') {
Object.assign(tocStyle, {
tocNumberStyle: { bold: true }
});
}
else {
Object.assign(tocStyle, {
tocMargin: [10, 0, 0, 0]
});
}
addTocItem(item, tocStyle);
return item;
};
const handleItem = (item) => {
if (typeof item !== 'string' && item[META]?.[PDFMAKE]) {
merge(item, item[META]?.[PDFMAKE] || {});
}
if (typeof item[META]?.[HANDLER] === 'function') {
return item[META]?.[HANDLER]?.(item) || null;
}
return item;
};
let _uid = 0;
const getUniqueId = (item) => {
const meta = item[META] || {};
const __uid = meta[UID];
const el = item[META]?.[NODE];
if (__uid) {
return __uid;
}
if (!el) {
return '#' + (_uid++);
}
if (isElement(el)) {
const id = el.getAttribute('id');
if (id) {
return id;
}
}
const nodeName = el.nodeName.toLowerCase();
// TODO add parent? Or name something else?
const uid = '#' + nodeName + '-' + (_uid++);
meta[UID] = uid;
item[META] = meta;
return uid;
};
const attrToProps = (item) => {
const el = item[META]?.[NODE];
if (!el || !('getAttribute' in el))
return { [META]: { [STYLE]: {} } };
const cssClass = el.getAttribute('class') || '';
const cssClasses = [...new Set(cssClass.split(' ')
.filter((value) => value)
.map((value) => '.' + value.trim()))];
const nodeName = el.nodeName.toLowerCase();
const parentNodeName = el.parentNode ? el.parentNode.nodeName.toLowerCase() : null;
const styleNames = [
nodeName,
].concat(cssClasses);
if (cssClasses.length > 2) {
styleNames.push(cssClasses.join('')); // .a.b.c
}
if (parentNodeName) {
styleNames.push(parentNodeName + '>' + nodeName);
}
const uniqueId = getUniqueId(item);
styleNames.push(uniqueId); // Should be the last one
const props = {
[META]: {
[STYLE]: item[META]?.[STYLE] || {},
...(item[META] || {})
},
style: [...new Set((item.style || []).concat(styleNames))]
};
for (let i = 0; i < el.attributes.length; i++) {
const name = el.attributes[i].name;
const value = el.getAttribute(name)?.trim() || null;
if (value == null) {
continue;
}
props[META][STYLE][name] = value;
switch (name) {
case 'rowspan':
props.rowSpan = parseInt(value, 10);
break;
case 'colspan':
props.colSpan = parseInt(value, 10);
break;
case 'value':
if (nodeName === 'li') {
props.counter = parseInt(value, 10);
}
break;
case 'start': // ol
if (nodeName === 'ol') {
props.start = parseInt(value, 10);
}
break;
case 'width':
if ('image' in item) {
props.width = toUnit(value);
}
break;
case 'height':
if ('image' in item) {
props.height = toUnit(value);
}
break;
case 'data-fit':
if (value === 'true') {
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width && height) {
props.fit = [toUnit(width), toUnit(height)];
}
}
break;
case 'data-toc-item':
if (value !== 'false') {
let toc = {};
if (value) {
try {
toc = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
props[META][HANDLER] = (item) => {
addTocItem(item, toc);
return item;
};
}
break;
case 'data-pdfmake':
if (value) {
try {
props[META][PDFMAKE] = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
break;
}
}
return props;
};
const inheritStyle = (styles) => {
// TODO what do we want to exclude ?
const pick = {
color: true,
'font-family': true,
'font-size': true,
'font-weight': true,
'font': true,
'line-height': true,
'list-style-type': true,
'list-style': true,
'text-align': true,
// TODO only if parent is text: []
background: true,
'font-style': true,
'background-color': true,
'font-feature-settings': true,
'white-space': true,
'vertical-align': true,
'opacity': true,
'text-decoration': true,
};
return Object.keys(styles).reduce((p, c) => {
if (pick[c] || styles[c] === 'inherit') {
p[c] = styles[c];
}
return p;
}, {});
};
const getBorderStyle = (value) => {
const border = value.split(' ');
const color = border[2] || 'black';
const borderStyle = border[1] || 'solid';
const width = toUnit(border[0]);
return { color, width, borderStyle };
};
const computeBorder = (item, props, directive, value) => {
const { color, width, borderStyle } = getBorderStyle(value);
const tdOrTh = isTdOrTh(item);
const setBorder = (index) => {
props.border = item.border || props.border || [false, false, false, false];
props.borderColor = item.borderColor || props.borderColor || ['black', 'black', 'black', 'black'];
if (value === 'none') {
props.border[index] = false;
}
else {
props.border[index] = true;
props.borderColor[index] = color;
}
};
switch (directive) {
case 'border':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
if (value === 'none') {
props.layout.hLineWidth = () => 0;
props.layout.vLineWidth = () => 0;
break;
}
props.layout.vLineColor = () => color;
props.layout.hLineColor = () => color;
props.layout.hLineWidth = (i, node) => (i === 0 || i === node.table.body.length) ? width : 0;
props.layout.vLineWidth = (i, node) => (i === 0 || i === node.table.widths?.length) ? width : 0;
if (borderStyle === 'dashed') {
props.layout.hLineStyle = () => ({ dash: { length: 2, space: 2 } });
props.layout.vLineStyle = () => ({ dash: { length: 2, space: 2 } });
}
}
else if (tdOrTh) {
setBorder(0);
setBorder(1);
setBorder(2);
setBorder(3);
}
break;
case 'border-bottom':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 0);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === node.table.body.length) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === node.table.body.length) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(3);
}
break;
case 'border-top':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 1);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === 0) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === 0) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(1);
}
break;
case 'border-right':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(2);
}
break;
case 'border-left':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(0);
}
break;
}
};
const computeMargin = (itemProps, item, value, index) => {
const margin = itemProps[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
margin[index] = value;
itemProps[META][MARGIN] = [...margin];
itemProps.margin = margin;
const padding = itemProps[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
const paddingValue = padding[index] || 0;
itemProps.margin[index] = value + paddingValue;
};
const computePadding = (props, item, value, index) => {
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
switch (index) {
case POS_LEFT:
props.layout.paddingLeft = () => toUnit(value);
break;
case POS_TOP:
props.layout.paddingTop = () => toUnit(value);
break;
case POS_RIGHT:
props.layout.paddingRight = () => toUnit(value);
break;
case POS_BOTTOM:
props.layout.paddingBottom = () => toUnit(value);
break;
default:
throw new Error('Unsupported index for padding: ' + index);
}
}
else {
const padding = props[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
padding[index] = value;
props[META][PADDING] = [...padding];
if (!isTdOrTh(item)) {
const margin = props[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
props.margin = margin;
const marginValue = margin[index];
props.margin[index] = value + marginValue;
}
}
};
const styleToProps = (item, styles, parentStyles = {}) => {
const props = {
[META]: {
[STYLE]: {},
...(item[META] || {}),
}
};
const meta = props[META];
const image = isImage(item);
const table = isTable(item);
const text = isTextSimple(item);
const list = isList(item);
const rootFontSize = toUnit(parentStyles['font-size'] || '16px');
if (isHeadline(item)) {
meta[HANDLER] = handleHeadlineToc;
}
Object.keys(styles).forEach((key) => {
const directive = key;
const value = ('' + styles[key]).trim();
props[META][STYLE][directive] = value;
switch (directive) {
case 'padding': {
const paddings = expandValueToUnits(value);
if (table && paddings !== null) {
let layout = props.layout || item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.paddingLeft = () => Number(paddings[POS_LEFT]);
layout.paddingRight = () => Number(paddings[POS_RIGHT]);
layout.paddingTop = (i) => (i === 0) ? Number(paddings[POS_TOP]) : 0;
layout.paddingBottom = (i, node) => (i === node.table.body.length - 1) ? Number(paddings[POS_BOTTOM]) : 0;
props.layout = layout;
}
else if (paddings !== null) {
computePadding(props, item, Number(paddings[POS_TOP]), POS_TOP);
computePadding(props, item, Number(paddings[POS_LEFT]), POS_LEFT);
computePadding(props, item, Number(paddings[POS_RIGHT]), POS_RIGHT);
computePadding(props, item, Number(paddings[POS_BOTTOM]), POS_BOTTOM);
}
break;
}
case 'border':
case 'border-bottom':
case 'border-top':
case 'border-right':
case 'border-left':
computeBorder(item, props, directive, value);
break;
case 'font-size': {
props.fontSize = toUnit(value, rootFontSize);
break;
}
case 'line-height':
props.lineHeight = toUnit(value, rootFontSize);
break;
case 'letter-spacing':
props.characterSpacing = toUnit(value);
break;
case 'text-align':
props.alignment = value;
break;
case 'font-feature-settings': {
const settings = value.split(',').filter(s => s).map(s => s.replace(/['"]/g, ''));
const fontFeatures = item.fontFeatures || props.fontFeatures || [];
fontFeatures.push(...settings);
props.fontFeatures = fontFeatures;
break;
}
case 'font-weight':
switch (value) {
case 'bold':
props.bold = true;
break;
case 'normal':
props.bold = false;
break;
}
break;
case 'text-decoration':
switch (value) {
case 'underline':
props.decoration = 'underline';
break;
case 'line-through':
props.decoration = 'lineThrough';
break;
case 'overline':
props.decoration = 'overline';
break;
}
break;
case 'text-decoration-color':
props.decorationColor = value;
break;
case 'text-decoration-style':
props.decorationStyle = value;
break;
case 'vertical-align':
if (value === 'sub') {
props.sub = true;
}
break;
case 'font-style':
switch (value) {
case 'italic':
props.italics = true;
break;
}
break;
case 'font-family':
props.font = value;
break;
case 'color':
props.color = value;
break;
case 'background':
case 'background-color':
if (table) {
let layout = item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.fillColor = () => value;
props.layout = layout;
}
else if (isTdOrTh(item)) {
props.fillColor = value;
}
else {
props.background = ['fill', value];
}
break;
case 'margin': {
const margin = expandValueToUnits(value)?.map(value => typeof value === 'string' ? 0 : value);
if (margin) {
computeMargin(props, item, margin[POS_TOP], POS_TOP);
computeMargin(props, item, margin[POS_LEFT], POS_LEFT);
computeMargin(props, item, margin[POS_RIGHT], POS_RIGHT);
computeMargin(props, item, margin[POS_BOTTOM], POS_BOTTOM);
}
break;
}
case 'margin-left':
computeMargin(props, item, toUnit(value), POS_LEFT);
break;
case 'margin-top':
computeMargin(props, item, toUnit(value), POS_TOP);
break;
case 'margin-right':
computeMargin(props, item, toUnit(value), POS_RIGHT);
break;
case 'margin-bottom':
computeMargin(props, item, toUnit(value), POS_BOTTOM);
break;
case 'padding-left':
computePadding(props, item, toUnit(value), POS_LEFT);
break;
case 'padding-top':
computePadding(props, item, toUnit(value), POS_TOP);
break;
case 'padding-right':
computePadding(props, item, toUnit(value), POS_RIGHT);
break;
case 'padding-bottom':
computePadding(props, item, toUnit(value), POS_BOTTOM);
break;
case 'page-break-before':
if (value === 'always') {
props.pageBreak = 'before';
}
break;
case 'page-break-after':
if (value === 'always') {
props.pageBreak = 'after';
}
break;
case 'position':
if (value === 'absolute') {
meta[POSITION] = 'absolute';
props.absolutePosition = {};
}
else if (value === 'relative') {
meta[POSITION] = 'relative';
props.relativePosition = {};
}
break;
case 'left':
case 'top':
// TODO can be set before postion:absolute!
if (!props.absolutePosition && !props.relativePosition) {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
if (props.absolutePosition) {
if (directive === 'left') {
props.absolutePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.absolutePosition.y = toUnit(value);
}
}
else if (props.relativePosition) {
if (directive === 'left') {
props.relativePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.relativePosition.y = toUnit(value);
}
}
else {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
break;
case 'white-space':
if (value === 'pre' && meta[NODE]) {
if (text) {
props.text = meta[NODE]?.textContent || '';
}
props.preserveLeadingSpaces = true;
}
break;
case 'display':
if (value === 'flex') {
props[META][HANDLER] = handleColumns;
}
else if (value === 'none') {
props[META][HANDLER] = () => null;
}
break;
case 'opacity':
props.opacity = Number(parseFloat(value));
break;
case 'gap':
props.columnGap = toUnit(value);
break;
case 'list-style-type':
case 'list-style':
if (list) {
props.type = value;
}
else {
props.listType = value;
}
break;
case 'width':
if (table) {
if (value === '100%') {
item.table.widths = ['*'];
}
else {
const width = toUnitOrValue(value);
if (width !== null) {
item.table.widths = [width];
}
}
}
else if (image) {
props.width = toUnit(value);
}
break;
case 'height':
if (image) {
props.height = toUnit(value);
}
break;
case 'max-height':
if (image) {
props.maxHeight = toUnit(value);
}
break;
case 'max-width':
if (image) {
props.maxWidth = toUnit(value);
}
break;
case 'min-height':
if (image) {
props.minHeight = toUnit(value);
}
break;
case 'min-width':
if (image) {
props.minWidth = toUnit(value);
}
break;
case 'object-fit':
if (value === 'contain' && image) {
meta[HANDLER] = handleImg;
}
break;
}
});
return props;
};
/**
* @param el DOM Element
*/
const getInlineStyles = (el) => ('getAttribute' in el ? el.getAttribute('style') || '' : '').split(';')
.map(style => style.trim().toLowerCase().split(':'))
.filter(style => style.length === 2)
.reduce((style, value) => {
style[value[0].trim()] = value[1].trim();
return style;
}, {});
const getDefaultStyles = (el, item, styles) => (item.style || []).concat(el.nodeName.toLowerCase())
.filter((selector) => styles && styles[selector])
.reduce((style, selector) => {
return {
...style,
...styles[selector]
};
}, {});
/**
*
* @param el DOM Element
* @param item
* @param styles additional styles
* @param parentStyles pick styles
*/
const computeProps = (el, item, styles, parentStyles = {}) => {
const defaultStyles = getDefaultStyles(el, item, styles);
const rootStyles = styles[':root'] || globalStyles()[':root'];
const inheritedStyles = inheritStyle(parentStyles);
const cssStyles = Object.assign({}, defaultStyles, inheritedStyles, getInlineStyles(el));
const styleProps = styleToProps(item, cssStyles, Object.assign({}, rootStyles, inheritedStyles));
const attrProps = attrToProps(item);
const props = {
...styleProps,
...attrProps,
[META]: {
...(styleProps[META] || {}),
...(attrProps[META] || {}),
[STYLE]: {
...(styleProps[META][STYLE] || {}),
...(attrProps[META][STYLE] || {}),
}
}
};
return {
cssStyles,
props
};
};
const collapseMargin = (item, prevItem) => {
if (isCollapsable(item) && isCollapsable(prevItem)) {
const prevMargin = prevItem[META]?.[MARGIN] || [0, 0, 0, 0];
prevItem[META] = { ...(prevItem[META] || {}), [MARGIN]: prevMargin };
prevItem.margin[POS_BOTTOM] = prevItem[META]?.[PADDING]?.[POS_BOTTOM] || 0;
const itemMargin = item[META]?.[MARGIN] || [0, 0, 0, 0];
const marginTop = Math.max(itemMargin[POS_TOP], prevMargin[POS_BOTTOM]);
itemMargin[POS_TOP] = marginTop;
prevMargin[POS_BOTTOM] = 0;
item[META] = { ...(item[META] || {}), [MARGIN]: itemMargin };
item.margin[POS_TOP] = marginTop + (item[META]?.[PADDING]?.[POS_TOP] || 0);
}
};
const findLastDeep = (ta) => {
const last = ta.text.at(-1);
if (isTextArray(last)) {
return findLastDeep(last);
}
return last;
};
const findFirstArrayDeep = (ta) => {
const first = ta.text.at(0);
if (isTextArray(first)) {
return findFirstArrayDeep(first);
}
return ta.text;
};
const collapseWhitespace = (item, nextText) => {
const prevLastText = findLastDeep(item);
const nextFirstTextArray = findFirstArrayDeep(nextText);
if (prevLastText && prevLastText[META]?.[IS_WHITESPACE] && nextFirstTextArray[0][META]?.[IS_WHITESPACE]) {
nextFirstTextArray.shift();
}
};
function isBase64(str) {
return /^data:image\/(jpeg|png|jpg);base64,/.test(str);
}
const parseImg = (el, ctx) => {
const src = el.getAttribute('src');
if (!src) {
return null;
}
const name = el.getAttribute('name') || src;
let image;
if (isBase64(src)) {
image = src;
}
else if (ctx.images[name]) {
image = name;
}
else {
ctx.images[src] = name;
image = name;
}
return {
image,
[META]: {}
};
};
const parseSvg = (el) => {
// TODO is this okay?
const svgEl = el.cloneNode(true);
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width) {
svgEl.setAttribute('width', '' + getUnitOrValue(width));
}
if (height) {
svgEl.setAttribute('height', '' + getUnitOrValue(height));
}
return {
svg: svgEl.outerHTML.replace(/\n(\s+)?/g, ''),
};
};
const parseTable = () => {
// TODO table in table?
return {
table: {
body: (items) => {
// tbody -> tr
const colgroup = items.find(isColgroup)?.stack[0] || [];
const tbody = items.filter(item => !isColgroup(item));
const trs = tbody.flatMap((item) => 'stack' in item ? item.stack : []);
const body = trs.map((item) => getChildItems(item));
if (body.length === 0) {
return [];
}
const longestRow = body.reduce((a, b) => a.length <= b.length ? b : a);
const table = {
body,
widths: new Array(longestRow.length).fill('auto'),
heights: new Array(trs.length).fill('auto')
};
return [[{
table,
layout: {
defaultBorder: false
},
[META]: {
[ITEMS]: {
colgroup: 'text' in colgroup && Array.isArray(colgroup.text) ? colgroup.text : [],
trs
},
}
}]];
},
// widths: ['*'],
},
[META]: {
[HANDLER]: handleTable,
},
layout: {}
};
};
const parseText = (el) => {
const text = el.textContent;
if (text === null) {
return null;
}
const keepNewLines = text.replace(/[^\S\r\n]+/, '');
const trimmedText = text.replace(/\n|\t| +/g, ' ')
.replace(/^ +/, '')
.replace(/ +$/, '');
//.trim() removes also &nbsp;
const endWithNL = keepNewLines[keepNewLines.length - 1] === '\n';
const startWithNL = keepNewLines[0] === '\n';
const startWithWhitespace = text[0] === ' ';
const endWithWhitespace = text[text.length - 1] === ' ';
return {
text: trimmedText,
[META]: {
[START_WITH_NEWLINE]: startWithNL,
[END_WITH_NEWLINE]: endWithNL,
[IS_NEWLINE]: startWithNL && endWithNL && trimmedText.length === 0,
[START_WITH_WHITESPACE]: startWithWhitespace,
[END_WITH_WHITESPACE]: endWithWhitespace,
[IS_WHITESPACE]: startWithWhitespace && endWithWhitespace && text.length === 1,
},
};
};
const WHITESPACE = ' ';
const addWhitespace = (type) => ({
text: WHITESPACE,
[META]: {
[IS_WHITESPACE]: type
}
});
const parseAsHTMLCollection = (el) => ['TABLE', 'TBODY', 'TR', 'COLGROUP', 'COL', 'UL', 'OL', 'SELECT'].includes(el.nodeName) && 'children' in el;
const stackRegex = /^(address|blockquote|body|center|colgroup|dir|div|dl|fieldset|form|h[1-6]|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|html)$/i;
const isStackItem = (el) => stackRegex.test(el.nodeName);
const parseChildren = (el, ctx, parentStyles = {}) => {
const items = [];
const children = parseAsHTMLCollection(el) ? el.children : el.childNodes;
for (let i = 0; i < children.length; i++) {
const item = parseByRule(children[i], ctx, parentStyles);
if (item === null) {
continue;
}
const isNewline = !!item[META]?.[IS_NEWLINE];
const prevItem = items[items.length - 1];
if (ctx.config.collapseMargin && prevItem) {
collapseMargin(item, prevItem);
}
if (isNewline && (items.length === 0 || !children[i + 1] || prevItem && 'stack' in prevItem)) {
continue;
}
// Stack item
if (!('text' in item)) {
items.push(item);
continue;
}
const endWithNewLine = !!item[META]?.[END_WITH_NEWLINE];
const startWithNewLine = !!item[META]?.[START_WITH_NEWLINE];
const endWithWhiteSpace = !!item[META]?.[END_WITH_WHITESPACE];
const startWithWhitespace = !!item[META]?.[START_WITH_WHITESPACE];
const isWhitespace = !!item[META]?.[IS_WHITESPACE];
const textItem = Array.isArray(item.text)
? item : { text: [isWhitespace ? addWhitespace('newLine') : item] };
if (!isNewline && !isWhitespace) {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
// add whitespace before
if (startWithNewLine || startWithWhitespace) {
textItem.text.unshift(addWhitespace(startWithNewLine ? 'startWithNewLine' : 'startWithWhitespace'));
}
// add whitespace after
if (endWithNewLine || endWithWhiteSpace) {
textItem.text.push(addWhitespace(endWithNewLine ? 'endWithNewLine' : 'endWithWhiteSpace'));
}
}
// Append text to last text element otherwise a new line is created
if (isTextArray(prevItem)) {
if (ctx.config.collapseWhitespace) {
collapseWhitespace(prevItem, textItem);
}
prevItem.text.push(textItem);
}
else {
// wrap so the next text items will be appended to it
items.push({
text: [textItem]
});
}
}
return items;
};
const getNodeRule = (node) => {
const nodeName = node.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
return () => null;
case '#text':
return parseText;
default:
return () => null;
}
};
const getElementRule = (el) => {
const nodeName = el.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
case 'option': // see <select>
case 'script':
case 'style':
case 'iframe':
case 'object':
return () => null;
case '#text':
return parseText;
case 'a':
return (el) => {
const href = el.getAttribute('href');
if (!href) {
return parseElement(el);
}
const linkify = (item) => {
const children = getChildItems(item);
[].concat(children)
.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
};
return {
text: items => {
items.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
return items;
}
};
};
case 'br':
return () => ({
text: '\n',
[META]: {
[IS_NEWLINE]: true
}
});
case 'qr-code': // CUSTOM
return (el) => {
const content = el.getAttribute('value');
if (!content) {
return null;
}
const sizeAttr = el.getAttribute('data-size');
const size = sizeAttr ? toUnit(sizeAttr) : toUnit('128px');
return {
qr: content,
fit: size,
};
};
case 'toc': // CUSTOM
return (el) => {
const content = el.textContent;
if (!content) {
return null;
}
return {
toc: {
title: {
text: content,
bold: true,
fontSize: toUnit('22px'),
margin: [0, 10, 0, 10]
},
},
};
};
case 'table':
return parseTable;
case 'ul':
return () => {
return {
ul: (items) => items
};
};
case 'ol':
return () => {
return {
ol: (items) => items
};
};
case 'img':
return parseImg;
case 'svg':
return parseSvg;
case 'hr':
// TODO find better <hr> alternative?
return () => {
return {
table: {
widths: ['*'],
body: [
[''],
]
},
style: ['hr'],
};
};
case 'input':
// TODO acro-form
return (el) => {
if (el instanceof HTMLInputElement) {
return {
text: 'value' in el ? el.value : '',
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
case 'select':
// TODO acro-form
return (el) => {
if (el instanceof HTMLSelectElement) {
const value = el.options[el.selectedIndex].value;
return {
text: value,
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
default:
return parseElement;
}
};
const getItemByRule = (el, ctx) => {
if (typeof ctx.config.customRule === 'function') {
const result = ctx.config.customRule(el, ctx);
if (result === null) {
return null;
}
else if (result !== undefined) {
return result;
}
}
if (isElement(el)) { // ELEMENT_NODE
return getElementRule(el)(el, ctx);
}
else if (isNode(el)) { // TEXT_NODE || COMMENT_NODE
return getNodeRule(el)(el, ctx);
}
throw new Error('Unsupported Node Type: ' + el.nodeType);
};
const processItems = (item, ctx, parentStyles = {}) => {
const el = item[META]?.[NODE];
if (typeof item !== 'string' && el) {
const { cssStyles, props } = computeProps(el, item, ctx.styles, parentStyles);
Object.assign(item, props);
if ('stack' in item && typeof item.stack === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.stack = item.stack(children, ctx);
}
else if ('text' in item && typeof item.text === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.text = item.text(children.filter(isTextOrLeaf), ctx);
}
else if ('ul' in item && typeof item.ul === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ul = item.ul(children, ctx);
}
else if ('ol' in item && typeof item.ol === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ol = item.ol(children, ctx);
}
else if ('table' in item && typeof item.table.body === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.table.body = item.table.body(children, ctx);
}
}
return handleItem(item);
};
const parseByRule = (el, ctx, parentStyles = {}) => {
const item = getItemByRule(el, ctx);
if (item === null) {
return null;
}
// Add ref to NODE
const meta = item[META] || {};
meta[NODE] = el;
item[META] = meta;
return processItems(item, ctx, parentStyles);
};
const parseElement = (el) => {
if (isStackItem(el)) {
return {
stack: (items) => items
};
}
return {
text: (items) => {
// Return flat
if (items.length === 1 && 'text' in items[0] && Array.isArray(items[0].text)) {
return items[0].text;
}
return items;
}
};
};
const htmlToDom = (html) => {
if (typeof DOMParser !== 'undefined') {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body;
}
else if (typeof document !== 'undefined' && typeof document.createDocumentFragment === 'function') {
const fragment = document.createDocumentFragment();
const doc = document.createElement('div');
doc.innerHTML = html;
fragment.append(doc);
return fragment.children[0];
}
throw new Error('Could not parse html to DOM. Please use external parser like jsdom.');
};
const parse = (input, _config = defaultConfig()) => {
const config = {
...defaultConfig,
..._config
};
const ctx = new Context(config, Object.assign({}, config.globalStyles, config.styles));
const body = typeof input === 'string' ? htmlToDom(input) : input;
const content = body !== null ? parseChildren(body, ctx) : [];
return {
content,
images: ctx.images,
patterns: getPatterns()
};
};
exports.parse = parse;
Object.defineProperty(exports, '__esModule', { value: true });
}));
+2
-0
# html2pdfmake Changelog
### [0.0.4](https://github.com/dantio/html2pdfmake/compare/v0.0.3...v0.0.4) (2022-05-20)
### [0.0.3](https://github.com/dantio/html2pdfmake/compare/v0.0.2...v0.0.3) (2022-05-20)

@@ -3,0 +5,0 @@

+4
-7
{
"name": "html2pdfmake",
"version": "0.0.3",
"version": "0.0.4",
"description": "HTML/DOM to pdfmake",

@@ -45,9 +45,7 @@ "type": "module",

},
"module": "./dist/index.js",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"module": "./dist/index.js",
"browser": "./lib/html2pdfmake.min.js",
"browser:esm": "./dist/html2pdfmake.min.mjs",
"jsdelivr": "./lib/html2pdfmake.min.js",
"unpkg": "./lib/html2pdfmake.min.js",
"browser": "./lib/html2pdfmake.js",
"browser:esm": "./dist/html2pdfmake.mjs",
"browserslist": "> 0.5%, last 2 versions, not dead",

@@ -90,3 +88,2 @@ "scripts": {

"rollup": "^2.70.2",
"rollup-plugin-terser": "^7.0.2",
"standard-version": "^9.3.2",

@@ -93,0 +90,0 @@ "ts-node": "^10.7.0",

@@ -11,4 +11,6 @@ # html2pdfmake

## Usage
```js
import {parse} from 'html2pdfmake';
```html
<script type="module" src="https://cdn.jsdelivr.net/npm/html2pdfmake/dist/html2pdfmake.min.mjs"></script>
<script>
import {parse} from 'https://cdn.jsdelivr.net/npm/html2pdfmake/dist/html2pdfmake.min.mjs';
const {content, images, patterns} = parse(document.getElementById('template'));

@@ -22,3 +24,3 @@

})
</script>
```

@@ -25,0 +27,0 @@

const META = Symbol('__HTML2PDFMAKE');
const NODE = 'NODE';
const UID = 'UID';
const END_WITH_NEWLINE = 'END_WITH_NEWLINE';
const START_WITH_NEWLINE = 'START_WITH_NEW_LINE';
const IS_NEWLINE = 'IS_NEWLINE';
const START_WITH_WHITESPACE = 'START_WITH_WHITESPACE';
const END_WITH_WHITESPACE = 'END_WITH_WHITESPACE';
const IS_WHITESPACE = 'IS_WHITESPACE';
const IS_INPUT = 'IS_INPUT';
const MARGIN = 'MARGIN';
const PADDING = 'PADDING';
const POSITION = 'POSITION';
const HANDLER = 'HANDLER';
const PDFMAKE = 'PDFMAKE';
const ITEMS = 'ITEMS'; // meta items
const STYLE = 'STYLE';
const POS_TOP = 1; // CSS 0
const POS_RIGHT = 2; // CSS 1
const POS_BOTTOM = 3; // CSS 2
const POS_LEFT = 0; // CSS 3
const getPatterns = () => ({
fill: {
boundingBox: [1, 1, 4, 4],
xStep: 1,
yStep: 1,
pattern: '1 w 0 1 m 4 5 l s 2 0 m 5 3 l s'
}
});
class Context {
config;
styles;
images = {};
constructor(config, styles) {
this.config = config;
this.styles = styles;
}
}
const globalStyles = () => ({
':root': {
'font-size': '16px'
},
h1: {
'font-size': '32px',
'margin-top': '21.44px',
'margin-bottom': '21.44px',
'font-weight': 'bold'
},
h2: {
'font-size': '24px',
'margin-top': '19.92px',
'margin-bottom': '19.92px',
'font-weight': 'bold'
},
h3: {
'font-size': '18.72px',
'margin-top': '18.72px',
'margin-bottom': '18.72px',
'font-weight': 'bold'
},
h4: {
'font-size': '16px',
'margin-top': '21.28px',
'margin-bottom': '21.28px',
'font-weight': 'bold'
},
h5: {
'font-size': '13.28px',
'margin-top': '22.17px',
'margin-bottom': '22.17px',
'font-weight': 'bold'
},
h6: {
'font-size': '10.72px',
'margin-top': '24.97px',
'margin-bottom': '24.97px',
'font-weight': 'bold'
},
b: {
'font-weight': 'bold'
},
strong: {
'font-weight': 'bold'
},
i: {
'font-style': 'italic',
},
em: {
'font-style': 'italic'
},
s: {
'text-decoration': 'line-through'
},
del: {
'text-decoration': 'line-through'
},
sub: {
'font-size': '22px',
'vertical-align': 'sub'
},
small: {
'font-size': '13px'
},
u: {
'text-decoration': 'underline'
},
ul: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
ol: {
'margin-top': '16px',
'margin-bottom': '16px',
'padding-left': '20px'
},
p: {
'margin-top': '16px',
'margin-bottom': '16px'
},
table: {
border: 'none',
padding: '3px'
},
td: {
border: 'none'
},
tr: {
margin: '4px 0'
},
th: {
'font-weight': 'bold',
border: 'none',
'text-align': 'center'
},
a: {
color: '#0000ee',
'text-decoration': 'underline'
},
hr: {
'border-top': '2px solid #9a9a9a',
'border-bottom': '0',
'border-left': '0px solid black',
'border-right': '0',
margin: '8px 0'
}
});
const defaultConfig = () => ({
globalStyles: globalStyles(),
styles: {},
collapseMargin: true,
collapseWhitespace: true,
});
/**
* @description Method to check if an item is an object. Date and Function are considered
* an object, so if you need to exclude those, please update the method accordingly.
* @param item - The item that needs to be checked
* @return {Boolean} Whether or not @item is an object
*/
const isObject = (item) => {
return (item === Object(item) && !Array.isArray(item));
};
/**
* @description Method to perform a deep merge of objects
* @param {Object} target - The targeted object that needs to be merged with the supplied @sources
* @param {Array<Object>} sources - The source(s) that will be used to update the @target object
* @return {Object} The final merged object
*/
const merge = (target, ...sources) => {
// return the target if no sources passed
if (!sources.length) {
return target;
}
const result = target;
if (isObject(result)) {
for (let i = 0; i < sources.length; i += 1) {
if (isObject(sources[i])) {
const elm = sources[i];
Object.keys(elm).forEach(key => {
if (isObject(elm[key])) {
if (!result[key] || !isObject(result[key])) {
result[key] = {};
}
merge(result[key], elm[key]);
}
else {
result[key] = elm[key];
}
});
}
}
}
return result;
};
const isNotText = (item) => typeof item !== 'string';
const isColgroup = (item) => item?.[META]?.[NODE]?.nodeName === 'COLGROUP';
const isImage = (item) => 'image' in item;
const isTable = (item) => 'table' in item;
const isTextArray = (item) => !!item && typeof item !== 'string' && 'text' in item && Array.isArray(item.text);
const isTextSimple = (item) => typeof item !== 'string' && 'text' in item && typeof item.text === 'string';
const isTextOrLeaf = (item) => 'text' in item || typeof item === 'string';
const isList = (item) => 'ul' in item || 'ol' in item;
const isTdOrTh = (item) => item[META]?.[NODE] && (item[META]?.[NODE]?.nodeName === 'TD' || item[META]?.[NODE]?.nodeName === 'TH');
const isHeadline = (item) => item[META]?.[NODE] && (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(item[META]?.[NODE]?.nodeName || ''));
const isElement = (el) => el.nodeType === 1;
const isNode = (el) => el.nodeType === 3 || el.nodeType === 8;
const isCollapsable = (item) => typeof item !== 'undefined' && typeof item !== 'string' && ('stack' in item || 'ul' in item || 'ol' in item) && 'margin' in item;
const getChildItems = (item) => {
if (typeof item === 'string') {
return [];
}
if ('stack' in item) {
return item.stack;
}
if ('text' in item && typeof item.text !== 'string') {
return item.text;
}
if ('table' in item) {
return item.table.body
.flatMap(tr => tr)
.filter(isNotText);
}
if ('ul' in item) {
return item.ul;
}
if ('ol' in item) {
return item.ol;
}
return [];
};
const toUnit = (value, rootPt = 12) => {
// if it's just a number, then return it
if (typeof value === 'number') {
return isFinite(value) ? value : 0;
}
const val = Number(parseFloat(value));
if (isNaN(val)) {
return 0;
}
const match = ('' + value).trim().match(/(pt|px|r?em|cm)$/);
if (!match) {
return val;
}
switch (match[1]) {
case 'em':
case 'rem':
return val * rootPt;
case 'px':
// 1px = 0.75 Point
return Number((val * 0.75).toFixed(2));
case 'cm':
return Number((val * 28.34).toFixed(2));
case 'mm':
return Number((val * 10 * 28.34).toFixed(2));
default:
return val;
}
};
const getUnitOrValue = (value) => typeof value === 'string' && (value.indexOf('%') > -1 || value.indexOf('auto') > -1)
? value
: toUnit(value);
const toUnitOrValue = (value) => getUnitOrValue(value);
const toUnitsOrValues = (value) => value.map(v => getUnitOrValue(v));
const expandValueToUnits = (value) => {
const values = toUnitsOrValues(value.split(' ')
.map(v => v.trim())
.filter(v => v));
if (values === null || !Array.isArray(values)) {
return null;
}
// values[0] = top
// values[1] = right
// values[2] = bottom
// values[3] = left
// pdfmake use left, top, right, bottom, || [horizontal, vertical]
// css use top, right, bottom, left
if (values.length === 1 && values[0] !== null) {
return [values[0], values[0], values[0], values[0]];
}
else if (values.length === 2) {
// topbottom leftright
return [values[1], values[0], values[1], values[0]];
}
else if (values.length === 3) {
// top bottom leftright
return [values[2], values[0], values[2], values[1]];
}
else if (values.length === 4) {
return [values[3], values[0], values[1], values[2]];
}
return null;
};
const handleColumns = (item) => {
const childItems = getChildItems(item);
return {
columns: childItems
.flatMap((subItem) => {
if ('text' in subItem && Array.isArray(subItem.text)) {
return subItem.text
.filter(childItem => !childItem[META]?.[IS_WHITESPACE])
.map(text => {
const width = toUnitOrValue(text[META]?.[STYLE]?.width || 'auto') || 'auto';
return (typeof text === 'string') ? {
text,
width
} : {
...text,
width
};
});
}
return {
stack: [].concat(subItem),
width: toUnitOrValue(subItem[META]?.[STYLE]?.width || 'auto') || 'auto'
};
}),
columnGap: 'columnGap' in item ? item.columnGap : 0
};
};
const handleImg = (image) => {
if (isImage(image) && typeof image.width === 'number' && typeof image.height === 'number') {
image.fit = [image.width, image.height];
}
return image;
};
const handleTable = (item) => {
if (isTable(item)) {
const bodyItem = item.table.body[0]?.[0];
const tableItem = bodyItem && typeof bodyItem !== 'string' && 'table' in bodyItem ? bodyItem : null;
if (!tableItem) {
return item;
}
const innerTable = tableItem.table;
const colgroup = bodyItem[META]?.[ITEMS]?.colgroup;
if (colgroup && Array.isArray(colgroup)) {
innerTable.widths = innerTable.widths || [];
colgroup.forEach((col, i) => {
if (col[META]?.[STYLE]?.width && innerTable.widths) {
innerTable.widths[i] = getUnitOrValue(col[META]?.[STYLE]?.width || 'auto');
}
});
}
const trs = bodyItem[META]?.[ITEMS]?.trs;
if (Array.isArray(trs)) {
trs.forEach((tr, i) => {
if (tr[META]?.[STYLE]?.height && innerTable.heights) {
innerTable.heights[i] = getUnitOrValue(tr[META]?.[STYLE]?.height || 'auto');
}
});
}
const paddingsTopBottom = {};
const paddingsLeftRight = {};
innerTable.body
.forEach((row, trIndex) => {
row.forEach((column, tdIndex) => {
if (typeof column !== 'string') {
if (column[META]?.[PADDING]) {
paddingsTopBottom[trIndex] = paddingsTopBottom[trIndex] || [0, 0];
paddingsLeftRight[tdIndex] = paddingsLeftRight[tdIndex] || [0, 0];
paddingsTopBottom[trIndex] = [
Math.max(paddingsTopBottom[trIndex][0], column[META]?.[PADDING]?.[POS_TOP] || 0),
Math.max(paddingsTopBottom[trIndex][1], column[META]?.[PADDING]?.[POS_BOTTOM] || 0)
];
paddingsLeftRight[tdIndex] = [
Math.max(paddingsLeftRight[tdIndex][0], column[META]?.[PADDING]?.[POS_LEFT] || 0),
Math.max(paddingsLeftRight[tdIndex][1], column[META]?.[PADDING]?.[POS_RIGHT] || 0)
];
}
column.style = column.style || [];
column.style.push(tdIndex % 2 === 0 ? 'td:nth-child(even)' : 'td:nth-child(odd)');
column.style.push(trIndex % 2 === 0 ? 'tr:nth-child(even)' : 'tr:nth-child(odd)');
}
});
});
const tableLayout = {};
const hasPaddingTopBottom = Object.keys(paddingsTopBottom).length > 0;
const hasPaddingLeftRight = Object.keys(paddingsLeftRight).length > 0;
if (hasPaddingTopBottom) {
tableLayout.paddingTop = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][0];
}
return 0;
};
tableLayout.paddingBottom = (i) => {
if (paddingsTopBottom[i]) {
return paddingsTopBottom[i][1];
}
return 0;
};
}
if (hasPaddingLeftRight) {
tableLayout.paddingRight = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][1];
}
return 0;
};
tableLayout.paddingLeft = (i) => {
if (paddingsLeftRight[i]) {
return paddingsLeftRight[i][0];
}
return 0;
};
}
if (hasPaddingLeftRight || hasPaddingTopBottom) {
tableItem.layout = tableLayout;
}
}
return item;
};
const addTocItem = (item, tocStyle = {}) => {
if ('text' in item && typeof item.text === 'string') {
item.tocItem = true;
merge(item, tocStyle);
}
else if ('stack' in item) {
const text = item.stack.find(s => 'text' in s);
if (text && typeof text !== 'string') {
text.tocItem = true;
merge(text, tocStyle);
}
}
};
const handleHeadlineToc = (item) => {
const tocStyle = {};
if (item[META]?.[NODE]?.nodeName === 'H1') {
Object.assign(tocStyle, {
tocNumberStyle: { bold: true }
});
}
else {
Object.assign(tocStyle, {
tocMargin: [10, 0, 0, 0]
});
}
addTocItem(item, tocStyle);
return item;
};
const handleItem = (item) => {
if (typeof item !== 'string' && item[META]?.[PDFMAKE]) {
merge(item, item[META]?.[PDFMAKE] || {});
}
if (typeof item[META]?.[HANDLER] === 'function') {
return item[META]?.[HANDLER]?.(item) || null;
}
return item;
};
let _uid = 0;
const getUniqueId = (item) => {
const meta = item[META] || {};
const __uid = meta[UID];
const el = item[META]?.[NODE];
if (__uid) {
return __uid;
}
if (!el) {
return '#' + (_uid++);
}
if (isElement(el)) {
const id = el.getAttribute('id');
if (id) {
return id;
}
}
const nodeName = el.nodeName.toLowerCase();
// TODO add parent? Or name something else?
const uid = '#' + nodeName + '-' + (_uid++);
meta[UID] = uid;
item[META] = meta;
return uid;
};
const attrToProps = (item) => {
const el = item[META]?.[NODE];
if (!el || !('getAttribute' in el))
return { [META]: { [STYLE]: {} } };
const cssClass = el.getAttribute('class') || '';
const cssClasses = [...new Set(cssClass.split(' ')
.filter((value) => value)
.map((value) => '.' + value.trim()))];
const nodeName = el.nodeName.toLowerCase();
const parentNodeName = el.parentNode ? el.parentNode.nodeName.toLowerCase() : null;
const styleNames = [
nodeName,
].concat(cssClasses);
if (cssClasses.length > 2) {
styleNames.push(cssClasses.join('')); // .a.b.c
}
if (parentNodeName) {
styleNames.push(parentNodeName + '>' + nodeName);
}
const uniqueId = getUniqueId(item);
styleNames.push(uniqueId); // Should be the last one
const props = {
[META]: {
[STYLE]: item[META]?.[STYLE] || {},
...(item[META] || {})
},
style: [...new Set((item.style || []).concat(styleNames))]
};
for (let i = 0; i < el.attributes.length; i++) {
const name = el.attributes[i].name;
const value = el.getAttribute(name)?.trim() || null;
if (value == null) {
continue;
}
props[META][STYLE][name] = value;
switch (name) {
case 'rowspan':
props.rowSpan = parseInt(value, 10);
break;
case 'colspan':
props.colSpan = parseInt(value, 10);
break;
case 'value':
if (nodeName === 'li') {
props.counter = parseInt(value, 10);
}
break;
case 'start': // ol
if (nodeName === 'ol') {
props.start = parseInt(value, 10);
}
break;
case 'width':
if ('image' in item) {
props.width = toUnit(value);
}
break;
case 'height':
if ('image' in item) {
props.height = toUnit(value);
}
break;
case 'data-fit':
if (value === 'true') {
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width && height) {
props.fit = [toUnit(width), toUnit(height)];
}
}
break;
case 'data-toc-item':
if (value !== 'false') {
let toc = {};
if (value) {
try {
toc = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
props[META][HANDLER] = (item) => {
addTocItem(item, toc);
return item;
};
}
break;
case 'data-pdfmake':
if (value) {
try {
props[META][PDFMAKE] = JSON.parse(value);
}
catch (e) {
console.warn('Not valid JSON format.', value);
}
}
break;
}
}
return props;
};
const inheritStyle = (styles) => {
// TODO what do we want to exclude ?
const pick = {
color: true,
'font-family': true,
'font-size': true,
'font-weight': true,
'font': true,
'line-height': true,
'list-style-type': true,
'list-style': true,
'text-align': true,
// TODO only if parent is text: []
background: true,
'font-style': true,
'background-color': true,
'font-feature-settings': true,
'white-space': true,
'vertical-align': true,
'opacity': true,
'text-decoration': true,
};
return Object.keys(styles).reduce((p, c) => {
if (pick[c] || styles[c] === 'inherit') {
p[c] = styles[c];
}
return p;
}, {});
};
const getBorderStyle = (value) => {
const border = value.split(' ');
const color = border[2] || 'black';
const borderStyle = border[1] || 'solid';
const width = toUnit(border[0]);
return { color, width, borderStyle };
};
const computeBorder = (item, props, directive, value) => {
const { color, width, borderStyle } = getBorderStyle(value);
const tdOrTh = isTdOrTh(item);
const setBorder = (index) => {
props.border = item.border || props.border || [false, false, false, false];
props.borderColor = item.borderColor || props.borderColor || ['black', 'black', 'black', 'black'];
if (value === 'none') {
props.border[index] = false;
}
else {
props.border[index] = true;
props.borderColor[index] = color;
}
};
switch (directive) {
case 'border':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
if (value === 'none') {
props.layout.hLineWidth = () => 0;
props.layout.vLineWidth = () => 0;
break;
}
props.layout.vLineColor = () => color;
props.layout.hLineColor = () => color;
props.layout.hLineWidth = (i, node) => (i === 0 || i === node.table.body.length) ? width : 0;
props.layout.vLineWidth = (i, node) => (i === 0 || i === node.table.widths?.length) ? width : 0;
if (borderStyle === 'dashed') {
props.layout.hLineStyle = () => ({ dash: { length: 2, space: 2 } });
props.layout.vLineStyle = () => ({ dash: { length: 2, space: 2 } });
}
}
else if (tdOrTh) {
setBorder(0);
setBorder(1);
setBorder(2);
setBorder(3);
}
break;
case 'border-bottom':
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 0);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === node.table.body.length) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === node.table.body.length) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(3);
}
break;
case 'border-top':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const hLineWidth = props.layout.hLineWidth || (() => 1);
const hLineColor = props.layout.hLineColor || (() => 'black');
props.layout.hLineWidth = (i, node) => (i === 0) ? width : hLineWidth(i, node);
props.layout.hLineColor = (i, node) => (i === 0) ? color : hLineColor(i, node);
}
else if (tdOrTh) {
setBorder(1);
}
break;
case 'border-right':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === node.table.body.length : i % node.table.body.length !== 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(2);
}
break;
case 'border-left':
if (isTable(item)) {
const { color, width } = getBorderStyle(value);
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
const vLineWidth = props.layout.vLineWidth || (() => 1);
const vLineColor = props.layout.vLineColor || (() => 'black');
props.layout.vLineWidth = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? width : vLineWidth(i, node);
props.layout.vLineColor = (i, node) => (node.table.body.length === 1 ? i === 0 : i % node.table.body.length === 0) ? color : vLineColor(i, node);
}
else if (tdOrTh) {
setBorder(0);
}
break;
}
};
const computeMargin = (itemProps, item, value, index) => {
const margin = itemProps[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
margin[index] = value;
itemProps[META][MARGIN] = [...margin];
itemProps.margin = margin;
const padding = itemProps[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
const paddingValue = padding[index] || 0;
itemProps.margin[index] = value + paddingValue;
};
const computePadding = (props, item, value, index) => {
if (isTable(item)) {
props.layout = item.layout || props.layout || {};
if (typeof props.layout === 'string') {
props.layout = {};
}
switch (index) {
case POS_LEFT:
props.layout.paddingLeft = () => toUnit(value);
break;
case POS_TOP:
props.layout.paddingTop = () => toUnit(value);
break;
case POS_RIGHT:
props.layout.paddingRight = () => toUnit(value);
break;
case POS_BOTTOM:
props.layout.paddingBottom = () => toUnit(value);
break;
default:
throw new Error('Unsupported index for padding: ' + index);
}
}
else {
const padding = props[META][PADDING] || item[META]?.[PADDING] || [0, 0, 0, 0];
padding[index] = value;
props[META][PADDING] = [...padding];
if (!isTdOrTh(item)) {
const margin = props[META][MARGIN] || item[META]?.[MARGIN] || [0, 0, 0, 0];
props.margin = margin;
const marginValue = margin[index];
props.margin[index] = value + marginValue;
}
}
};
const styleToProps = (item, styles, parentStyles = {}) => {
const props = {
[META]: {
[STYLE]: {},
...(item[META] || {}),
}
};
const meta = props[META];
const image = isImage(item);
const table = isTable(item);
const text = isTextSimple(item);
const list = isList(item);
const rootFontSize = toUnit(parentStyles['font-size'] || '16px');
if (isHeadline(item)) {
meta[HANDLER] = handleHeadlineToc;
}
Object.keys(styles).forEach((key) => {
const directive = key;
const value = ('' + styles[key]).trim();
props[META][STYLE][directive] = value;
switch (directive) {
case 'padding': {
const paddings = expandValueToUnits(value);
if (table && paddings !== null) {
let layout = props.layout || item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.paddingLeft = () => Number(paddings[POS_LEFT]);
layout.paddingRight = () => Number(paddings[POS_RIGHT]);
layout.paddingTop = (i) => (i === 0) ? Number(paddings[POS_TOP]) : 0;
layout.paddingBottom = (i, node) => (i === node.table.body.length - 1) ? Number(paddings[POS_BOTTOM]) : 0;
props.layout = layout;
}
else if (paddings !== null) {
computePadding(props, item, Number(paddings[POS_TOP]), POS_TOP);
computePadding(props, item, Number(paddings[POS_LEFT]), POS_LEFT);
computePadding(props, item, Number(paddings[POS_RIGHT]), POS_RIGHT);
computePadding(props, item, Number(paddings[POS_BOTTOM]), POS_BOTTOM);
}
break;
}
case 'border':
case 'border-bottom':
case 'border-top':
case 'border-right':
case 'border-left':
computeBorder(item, props, directive, value);
break;
case 'font-size': {
props.fontSize = toUnit(value, rootFontSize);
break;
}
case 'line-height':
props.lineHeight = toUnit(value, rootFontSize);
break;
case 'letter-spacing':
props.characterSpacing = toUnit(value);
break;
case 'text-align':
props.alignment = value;
break;
case 'font-feature-settings': {
const settings = value.split(',').filter(s => s).map(s => s.replace(/['"]/g, ''));
const fontFeatures = item.fontFeatures || props.fontFeatures || [];
fontFeatures.push(...settings);
props.fontFeatures = fontFeatures;
break;
}
case 'font-weight':
switch (value) {
case 'bold':
props.bold = true;
break;
case 'normal':
props.bold = false;
break;
}
break;
case 'text-decoration':
switch (value) {
case 'underline':
props.decoration = 'underline';
break;
case 'line-through':
props.decoration = 'lineThrough';
break;
case 'overline':
props.decoration = 'overline';
break;
}
break;
case 'text-decoration-color':
props.decorationColor = value;
break;
case 'text-decoration-style':
props.decorationStyle = value;
break;
case 'vertical-align':
if (value === 'sub') {
props.sub = true;
}
break;
case 'font-style':
switch (value) {
case 'italic':
props.italics = true;
break;
}
break;
case 'font-family':
props.font = value;
break;
case 'color':
props.color = value;
break;
case 'background':
case 'background-color':
if (table) {
let layout = item.layout || {};
if (typeof layout === 'string') {
layout = {};
}
layout.fillColor = () => value;
props.layout = layout;
}
else if (isTdOrTh(item)) {
props.fillColor = value;
}
else {
props.background = ['fill', value];
}
break;
case 'margin': {
const margin = expandValueToUnits(value)?.map(value => typeof value === 'string' ? 0 : value);
if (margin) {
computeMargin(props, item, margin[POS_TOP], POS_TOP);
computeMargin(props, item, margin[POS_LEFT], POS_LEFT);
computeMargin(props, item, margin[POS_RIGHT], POS_RIGHT);
computeMargin(props, item, margin[POS_BOTTOM], POS_BOTTOM);
}
break;
}
case 'margin-left':
computeMargin(props, item, toUnit(value), POS_LEFT);
break;
case 'margin-top':
computeMargin(props, item, toUnit(value), POS_TOP);
break;
case 'margin-right':
computeMargin(props, item, toUnit(value), POS_RIGHT);
break;
case 'margin-bottom':
computeMargin(props, item, toUnit(value), POS_BOTTOM);
break;
case 'padding-left':
computePadding(props, item, toUnit(value), POS_LEFT);
break;
case 'padding-top':
computePadding(props, item, toUnit(value), POS_TOP);
break;
case 'padding-right':
computePadding(props, item, toUnit(value), POS_RIGHT);
break;
case 'padding-bottom':
computePadding(props, item, toUnit(value), POS_BOTTOM);
break;
case 'page-break-before':
if (value === 'always') {
props.pageBreak = 'before';
}
break;
case 'page-break-after':
if (value === 'always') {
props.pageBreak = 'after';
}
break;
case 'position':
if (value === 'absolute') {
meta[POSITION] = 'absolute';
props.absolutePosition = {};
}
else if (value === 'relative') {
meta[POSITION] = 'relative';
props.relativePosition = {};
}
break;
case 'left':
case 'top':
// TODO can be set before postion:absolute!
if (!props.absolutePosition && !props.relativePosition) {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
if (props.absolutePosition) {
if (directive === 'left') {
props.absolutePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.absolutePosition.y = toUnit(value);
}
}
else if (props.relativePosition) {
if (directive === 'left') {
props.relativePosition.x = toUnit(value);
}
else if (directive === 'top') {
props.relativePosition.y = toUnit(value);
}
}
else {
console.error(directive + ' is set, but no absolute/relative position.');
break;
}
break;
case 'white-space':
if (value === 'pre' && meta[NODE]) {
if (text) {
props.text = meta[NODE]?.textContent || '';
}
props.preserveLeadingSpaces = true;
}
break;
case 'display':
if (value === 'flex') {
props[META][HANDLER] = handleColumns;
}
else if (value === 'none') {
props[META][HANDLER] = () => null;
}
break;
case 'opacity':
props.opacity = Number(parseFloat(value));
break;
case 'gap':
props.columnGap = toUnit(value);
break;
case 'list-style-type':
case 'list-style':
if (list) {
props.type = value;
}
else {
props.listType = value;
}
break;
case 'width':
if (table) {
if (value === '100%') {
item.table.widths = ['*'];
}
else {
const width = toUnitOrValue(value);
if (width !== null) {
item.table.widths = [width];
}
}
}
else if (image) {
props.width = toUnit(value);
}
break;
case 'height':
if (image) {
props.height = toUnit(value);
}
break;
case 'max-height':
if (image) {
props.maxHeight = toUnit(value);
}
break;
case 'max-width':
if (image) {
props.maxWidth = toUnit(value);
}
break;
case 'min-height':
if (image) {
props.minHeight = toUnit(value);
}
break;
case 'min-width':
if (image) {
props.minWidth = toUnit(value);
}
break;
case 'object-fit':
if (value === 'contain' && image) {
meta[HANDLER] = handleImg;
}
break;
}
});
return props;
};
/**
* @param el DOM Element
*/
const getInlineStyles = (el) => ('getAttribute' in el ? el.getAttribute('style') || '' : '').split(';')
.map(style => style.trim().toLowerCase().split(':'))
.filter(style => style.length === 2)
.reduce((style, value) => {
style[value[0].trim()] = value[1].trim();
return style;
}, {});
const getDefaultStyles = (el, item, styles) => (item.style || []).concat(el.nodeName.toLowerCase())
.filter((selector) => styles && styles[selector])
.reduce((style, selector) => {
return {
...style,
...styles[selector]
};
}, {});
/**
*
* @param el DOM Element
* @param item
* @param styles additional styles
* @param parentStyles pick styles
*/
const computeProps = (el, item, styles, parentStyles = {}) => {
const defaultStyles = getDefaultStyles(el, item, styles);
const rootStyles = styles[':root'] || globalStyles()[':root'];
const inheritedStyles = inheritStyle(parentStyles);
const cssStyles = Object.assign({}, defaultStyles, inheritedStyles, getInlineStyles(el));
const styleProps = styleToProps(item, cssStyles, Object.assign({}, rootStyles, inheritedStyles));
const attrProps = attrToProps(item);
const props = {
...styleProps,
...attrProps,
[META]: {
...(styleProps[META] || {}),
...(attrProps[META] || {}),
[STYLE]: {
...(styleProps[META][STYLE] || {}),
...(attrProps[META][STYLE] || {}),
}
}
};
return {
cssStyles,
props
};
};
const collapseMargin = (item, prevItem) => {
if (isCollapsable(item) && isCollapsable(prevItem)) {
const prevMargin = prevItem[META]?.[MARGIN] || [0, 0, 0, 0];
prevItem[META] = { ...(prevItem[META] || {}), [MARGIN]: prevMargin };
prevItem.margin[POS_BOTTOM] = prevItem[META]?.[PADDING]?.[POS_BOTTOM] || 0;
const itemMargin = item[META]?.[MARGIN] || [0, 0, 0, 0];
const marginTop = Math.max(itemMargin[POS_TOP], prevMargin[POS_BOTTOM]);
itemMargin[POS_TOP] = marginTop;
prevMargin[POS_BOTTOM] = 0;
item[META] = { ...(item[META] || {}), [MARGIN]: itemMargin };
item.margin[POS_TOP] = marginTop + (item[META]?.[PADDING]?.[POS_TOP] || 0);
}
};
const findLastDeep = (ta) => {
const last = ta.text.at(-1);
if (isTextArray(last)) {
return findLastDeep(last);
}
return last;
};
const findFirstArrayDeep = (ta) => {
const first = ta.text.at(0);
if (isTextArray(first)) {
return findFirstArrayDeep(first);
}
return ta.text;
};
const collapseWhitespace = (item, nextText) => {
const prevLastText = findLastDeep(item);
const nextFirstTextArray = findFirstArrayDeep(nextText);
if (prevLastText && prevLastText[META]?.[IS_WHITESPACE] && nextFirstTextArray[0][META]?.[IS_WHITESPACE]) {
nextFirstTextArray.shift();
}
};
function isBase64(str) {
return /^data:image\/(jpeg|png|jpg);base64,/.test(str);
}
const parseImg = (el, ctx) => {
const src = el.getAttribute('src');
if (!src) {
return null;
}
const name = el.getAttribute('name') || src;
let image;
if (isBase64(src)) {
image = src;
}
else if (ctx.images[name]) {
image = name;
}
else {
ctx.images[src] = name;
image = name;
}
return {
image,
[META]: {}
};
};
const parseSvg = (el) => {
// TODO is this okay?
const svgEl = el.cloneNode(true);
const width = el.getAttribute('width');
const height = el.getAttribute('height');
if (width) {
svgEl.setAttribute('width', '' + getUnitOrValue(width));
}
if (height) {
svgEl.setAttribute('height', '' + getUnitOrValue(height));
}
return {
svg: svgEl.outerHTML.replace(/\n(\s+)?/g, ''),
};
};
const parseTable = () => {
// TODO table in table?
return {
table: {
body: (items) => {
// tbody -> tr
const colgroup = items.find(isColgroup)?.stack[0] || [];
const tbody = items.filter(item => !isColgroup(item));
const trs = tbody.flatMap((item) => 'stack' in item ? item.stack : []);
const body = trs.map((item) => getChildItems(item));
if (body.length === 0) {
return [];
}
const longestRow = body.reduce((a, b) => a.length <= b.length ? b : a);
const table = {
body,
widths: new Array(longestRow.length).fill('auto'),
heights: new Array(trs.length).fill('auto')
};
return [[{
table,
layout: {
defaultBorder: false
},
[META]: {
[ITEMS]: {
colgroup: 'text' in colgroup && Array.isArray(colgroup.text) ? colgroup.text : [],
trs
},
}
}]];
},
// widths: ['*'],
},
[META]: {
[HANDLER]: handleTable,
},
layout: {}
};
};
const parseText = (el) => {
const text = el.textContent;
if (text === null) {
return null;
}
const keepNewLines = text.replace(/[^\S\r\n]+/, '');
const trimmedText = text.replace(/\n|\t| +/g, ' ')
.replace(/^ +/, '')
.replace(/ +$/, '');
//.trim() removes also &nbsp;
const endWithNL = keepNewLines[keepNewLines.length - 1] === '\n';
const startWithNL = keepNewLines[0] === '\n';
const startWithWhitespace = text[0] === ' ';
const endWithWhitespace = text[text.length - 1] === ' ';
return {
text: trimmedText,
[META]: {
[START_WITH_NEWLINE]: startWithNL,
[END_WITH_NEWLINE]: endWithNL,
[IS_NEWLINE]: startWithNL && endWithNL && trimmedText.length === 0,
[START_WITH_WHITESPACE]: startWithWhitespace,
[END_WITH_WHITESPACE]: endWithWhitespace,
[IS_WHITESPACE]: startWithWhitespace && endWithWhitespace && text.length === 1,
},
};
};
const WHITESPACE = ' ';
const addWhitespace = (type) => ({
text: WHITESPACE,
[META]: {
[IS_WHITESPACE]: type
}
});
const parseAsHTMLCollection = (el) => ['TABLE', 'TBODY', 'TR', 'COLGROUP', 'COL', 'UL', 'OL', 'SELECT'].includes(el.nodeName) && 'children' in el;
const stackRegex = /^(address|blockquote|body|center|colgroup|dir|div|dl|fieldset|form|h[1-6]|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|html)$/i;
const isStackItem = (el) => stackRegex.test(el.nodeName);
const parseChildren = (el, ctx, parentStyles = {}) => {
const items = [];
const children = parseAsHTMLCollection(el) ? el.children : el.childNodes;
for (let i = 0; i < children.length; i++) {
const item = parseByRule(children[i], ctx, parentStyles);
if (item === null) {
continue;
}
const isNewline = !!item[META]?.[IS_NEWLINE];
const prevItem = items[items.length - 1];
if (ctx.config.collapseMargin && prevItem) {
collapseMargin(item, prevItem);
}
if (isNewline && (items.length === 0 || !children[i + 1] || prevItem && 'stack' in prevItem)) {
continue;
}
// Stack item
if (!('text' in item)) {
items.push(item);
continue;
}
const endWithNewLine = !!item[META]?.[END_WITH_NEWLINE];
const startWithNewLine = !!item[META]?.[START_WITH_NEWLINE];
const endWithWhiteSpace = !!item[META]?.[END_WITH_WHITESPACE];
const startWithWhitespace = !!item[META]?.[START_WITH_WHITESPACE];
const isWhitespace = !!item[META]?.[IS_WHITESPACE];
const textItem = Array.isArray(item.text)
? item : { text: [isWhitespace ? addWhitespace('newLine') : item] };
if (!isNewline && !isWhitespace) {
// https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
// add whitespace before
if (startWithNewLine || startWithWhitespace) {
textItem.text.unshift(addWhitespace(startWithNewLine ? 'startWithNewLine' : 'startWithWhitespace'));
}
// add whitespace after
if (endWithNewLine || endWithWhiteSpace) {
textItem.text.push(addWhitespace(endWithNewLine ? 'endWithNewLine' : 'endWithWhiteSpace'));
}
}
// Append text to last text element otherwise a new line is created
if (isTextArray(prevItem)) {
if (ctx.config.collapseWhitespace) {
collapseWhitespace(prevItem, textItem);
}
prevItem.text.push(textItem);
}
else {
// wrap so the next text items will be appended to it
items.push({
text: [textItem]
});
}
}
return items;
};
const getNodeRule = (node) => {
const nodeName = node.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
return () => null;
case '#text':
return parseText;
default:
return () => null;
}
};
const getElementRule = (el) => {
const nodeName = el.nodeName.toLowerCase();
switch (nodeName) {
case '#comment':
case 'option': // see <select>
case 'script':
case 'style':
case 'iframe':
case 'object':
return () => null;
case '#text':
return parseText;
case 'a':
return (el) => {
const href = el.getAttribute('href');
if (!href) {
return parseElement(el);
}
const linkify = (item) => {
const children = getChildItems(item);
[].concat(children)
.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
};
return {
text: items => {
items.forEach((link) => {
if (typeof link !== 'string') {
if (href[0] === '#') {
link.linkToDestination = href.slice(1);
}
else {
link.link = href;
}
}
linkify(link);
});
return items;
}
};
};
case 'br':
return () => ({
text: '\n',
[META]: {
[IS_NEWLINE]: true
}
});
case 'qr-code': // CUSTOM
return (el) => {
const content = el.getAttribute('value');
if (!content) {
return null;
}
const sizeAttr = el.getAttribute('data-size');
const size = sizeAttr ? toUnit(sizeAttr) : toUnit('128px');
return {
qr: content,
fit: size,
};
};
case 'toc': // CUSTOM
return (el) => {
const content = el.textContent;
if (!content) {
return null;
}
return {
toc: {
title: {
text: content,
bold: true,
fontSize: toUnit('22px'),
margin: [0, 10, 0, 10]
},
},
};
};
case 'table':
return parseTable;
case 'ul':
return () => {
return {
ul: (items) => items
};
};
case 'ol':
return () => {
return {
ol: (items) => items
};
};
case 'img':
return parseImg;
case 'svg':
return parseSvg;
case 'hr':
// TODO find better <hr> alternative?
return () => {
return {
table: {
widths: ['*'],
body: [
[''],
]
},
style: ['hr'],
};
};
case 'input':
// TODO acro-form
return (el) => {
if (el instanceof HTMLInputElement) {
return {
text: 'value' in el ? el.value : '',
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
case 'select':
// TODO acro-form
return (el) => {
if (el instanceof HTMLSelectElement) {
const value = el.options[el.selectedIndex].value;
return {
text: value,
[META]: {
[IS_INPUT]: true
}
};
}
return null;
};
default:
return parseElement;
}
};
const getItemByRule = (el, ctx) => {
if (typeof ctx.config.customRule === 'function') {
const result = ctx.config.customRule(el, ctx);
if (result === null) {
return null;
}
else if (result !== undefined) {
return result;
}
}
if (isElement(el)) { // ELEMENT_NODE
return getElementRule(el)(el, ctx);
}
else if (isNode(el)) { // TEXT_NODE || COMMENT_NODE
return getNodeRule(el)(el, ctx);
}
throw new Error('Unsupported Node Type: ' + el.nodeType);
};
const processItems = (item, ctx, parentStyles = {}) => {
const el = item[META]?.[NODE];
if (typeof item !== 'string' && el) {
const { cssStyles, props } = computeProps(el, item, ctx.styles, parentStyles);
Object.assign(item, props);
if ('stack' in item && typeof item.stack === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.stack = item.stack(children, ctx);
}
else if ('text' in item && typeof item.text === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.text = item.text(children.filter(isTextOrLeaf), ctx);
}
else if ('ul' in item && typeof item.ul === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ul = item.ul(children, ctx);
}
else if ('ol' in item && typeof item.ol === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.ol = item.ol(children, ctx);
}
else if ('table' in item && typeof item.table.body === 'function') {
const children = parseChildren(el, ctx, cssStyles);
item.table.body = item.table.body(children, ctx);
}
}
return handleItem(item);
};
const parseByRule = (el, ctx, parentStyles = {}) => {
const item = getItemByRule(el, ctx);
if (item === null) {
return null;
}
// Add ref to NODE
const meta = item[META] || {};
meta[NODE] = el;
item[META] = meta;
return processItems(item, ctx, parentStyles);
};
const parseElement = (el) => {
if (isStackItem(el)) {
return {
stack: (items) => items
};
}
return {
text: (items) => {
// Return flat
if (items.length === 1 && 'text' in items[0] && Array.isArray(items[0].text)) {
return items[0].text;
}
return items;
}
};
};
const htmlToDom = (html) => {
if (typeof DOMParser !== 'undefined') {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
return doc.body;
}
else if (typeof document !== 'undefined' && typeof document.createDocumentFragment === 'function') {
const fragment = document.createDocumentFragment();
const doc = document.createElement('div');
doc.innerHTML = html;
fragment.append(doc);
return fragment.children[0];
}
throw new Error('Could not parse html to DOM. Please use external parser like jsdom.');
};
const parse = (input, _config = defaultConfig()) => {
const config = {
...defaultConfig,
..._config
};
const ctx = new Context(config, Object.assign({}, config.globalStyles, config.styles));
const body = typeof input === 'string' ? htmlToDom(input) : input;
const content = body !== null ? parseChildren(body, ctx) : [];
return {
content,
images: ctx.images,
patterns: getPatterns()
};
};
export { parse };
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).html2pdfmake={})}(this,(function(t){"use strict";const e=Symbol("__HTML2PDFMAKE"),o="END_WITH_NEWLINE",n="START_WITH_NEW_LINE",r="IS_NEWLINE",a="START_WITH_WHITESPACE",i="END_WITH_WHITESPACE",l="IS_WHITESPACE",s="IS_INPUT",c="MARGIN",u="HANDLER",d="ITEMS",p="STYLE";class b{config;styles;images={};constructor(t,e){this.config=t,this.styles=e}}const g=()=>({globalStyles:{":root":{"font-size":"16px"},h1:{"font-size":"32px","margin-top":"21.44px","margin-bottom":"21.44px","font-weight":"bold"},h2:{"font-size":"24px","margin-top":"19.92px","margin-bottom":"19.92px","font-weight":"bold"},h3:{"font-size":"18.72px","margin-top":"18.72px","margin-bottom":"18.72px","font-weight":"bold"},h4:{"font-size":"16px","margin-top":"21.28px","margin-bottom":"21.28px","font-weight":"bold"},h5:{"font-size":"13.28px","margin-top":"22.17px","margin-bottom":"22.17px","font-weight":"bold"},h6:{"font-size":"10.72px","margin-top":"24.97px","margin-bottom":"24.97px","font-weight":"bold"},b:{"font-weight":"bold"},strong:{"font-weight":"bold"},i:{"font-style":"italic"},em:{"font-style":"italic"},s:{"text-decoration":"line-through"},del:{"text-decoration":"line-through"},sub:{"font-size":"22px","vertical-align":"sub"},small:{"font-size":"13px"},u:{"text-decoration":"underline"},ul:{"margin-top":"16px","margin-bottom":"16px","padding-left":"20px"},ol:{"margin-top":"16px","margin-bottom":"16px","padding-left":"20px"},p:{"margin-top":"16px","margin-bottom":"16px"},table:{border:"none",padding:"3px"},td:{border:"none"},tr:{margin:"4px 0"},th:{"font-weight":"bold",border:"none","text-align":"center"},a:{color:"#0000ee","text-decoration":"underline"},hr:{"border-top":"2px solid #9a9a9a","border-bottom":"0","border-left":"0px solid black","border-right":"0",margin:"8px 0"}},styles:{},collapseMargin:!0,collapseWhitespace:!0}),h=t=>t===Object(t)&&!Array.isArray(t),f=(t,...e)=>{if(!e.length)return t;const o=t;if(h(o))for(let t=0;t<e.length;t+=1)if(h(e[t])){const n=e[t];Object.keys(n).forEach((t=>{h(n[t])?(o[t]&&h(o[t])||(o[t]={}),f(o[t],n[t])):o[t]=n[t]}))}return o},y=t=>"string"!=typeof t,m=t=>"COLGROUP"===t?.[e]?.NODE?.nodeName,x=t=>"image"in t,k=t=>"table"in t,N=t=>!!t&&"string"!=typeof t&&"text"in t&&Array.isArray(t.text),w=t=>"text"in t||"string"==typeof t,E=t=>t[e]?.NODE&&("TD"===t[e]?.NODE?.nodeName||"TH"===t[e]?.NODE?.nodeName),A=t=>1===t.nodeType,L=t=>void 0!==t&&"string"!=typeof t&&("stack"in t||"ul"in t||"ol"in t)&&"margin"in t,I=t=>"string"==typeof t?[]:"stack"in t?t.stack:"text"in t&&"string"!=typeof t.text?t.text:"table"in t?t.table.body.flatMap((t=>t)).filter(y):"ul"in t?t.ul:"ol"in t?t.ol:[],S=(t,e=12)=>{if("number"==typeof t)return isFinite(t)?t:0;const o=Number(parseFloat(t));if(isNaN(o))return 0;const n=(""+t).trim().match(/(pt|px|r?em|cm)$/);if(!n)return o;switch(n[1]){case"em":case"rem":return o*e;case"px":return Number((.75*o).toFixed(2));case"cm":return Number((28.34*o).toFixed(2));case"mm":return Number((10*o*28.34).toFixed(2));default:return o}},T=t=>"string"==typeof t&&(t.indexOf("%")>-1||t.indexOf("auto")>-1)?t:S(t),D=t=>T(t),O=t=>{const e=(t=>t.map((t=>T(t))))(t.split(" ").map((t=>t.trim())).filter((t=>t)));return null!==e&&Array.isArray(e)?1===e.length&&null!==e[0]?[e[0],e[0],e[0],e[0]]:2===e.length?[e[1],e[0],e[1],e[0]]:3===e.length?[e[2],e[0],e[2],e[1]]:4===e.length?[e[3],e[0],e[1],e[2]]:null:null},W=t=>({columns:I(t).flatMap((t=>"text"in t&&Array.isArray(t.text)?t.text.filter((t=>!t[e]?.IS_WHITESPACE)).map((t=>{const o=D(t[e]?.STYLE?.width||"auto")||"auto";return"string"==typeof t?{text:t,width:o}:{...t,width:o}})):{stack:[].concat(t),width:D(t[e]?.STYLE?.width||"auto")||"auto"})),columnGap:"columnGap"in t?t.columnGap:0}),v=t=>(x(t)&&"number"==typeof t.width&&"number"==typeof t.height&&(t.fit=[t.width,t.height]),t),P=t=>{if(k(t)){const o=t.table.body[0]?.[0],n=o&&"string"!=typeof o&&"table"in o?o:null;if(!n)return t;const r=n.table,a=o[e]?.ITEMS?.colgroup;a&&Array.isArray(a)&&(r.widths=r.widths||[],a.forEach(((t,o)=>{t[e]?.STYLE?.width&&r.widths&&(r.widths[o]=T(t[e]?.STYLE?.width||"auto"))})));const i=o[e]?.ITEMS?.trs;Array.isArray(i)&&i.forEach(((t,o)=>{t[e]?.STYLE?.height&&r.heights&&(r.heights[o]=T(t[e]?.STYLE?.height||"auto"))}));const l={},s={};r.body.forEach(((t,o)=>{t.forEach(((t,n)=>{"string"!=typeof t&&(t[e]?.PADDING&&(l[o]=l[o]||[0,0],s[n]=s[n]||[0,0],l[o]=[Math.max(l[o][0],t[e]?.PADDING?.[1]||0),Math.max(l[o][1],t[e]?.PADDING?.[3]||0)],s[n]=[Math.max(s[n][0],t[e]?.PADDING?.[0]||0),Math.max(s[n][1],t[e]?.PADDING?.[2]||0)]),t.style=t.style||[],t.style.push(n%2==0?"td:nth-child(even)":"td:nth-child(odd)"),t.style.push(o%2==0?"tr:nth-child(even)":"tr:nth-child(odd)"))}))}));const c={},u=Object.keys(l).length>0,d=Object.keys(s).length>0;u&&(c.paddingTop=t=>l[t]?l[t][0]:0,c.paddingBottom=t=>l[t]?l[t][1]:0),d&&(c.paddingRight=t=>s[t]?s[t][1]:0,c.paddingLeft=t=>s[t]?s[t][0]:0),(d||u)&&(n.layout=c)}return t},C=(t,e={})=>{if("text"in t&&"string"==typeof t.text)t.tocItem=!0,f(t,e);else if("stack"in t){const o=t.stack.find((t=>"text"in t));o&&"string"!=typeof o&&(o.tocItem=!0,f(o,e))}},H=t=>{const o={};return"H1"===t[e]?.NODE?.nodeName?Object.assign(o,{tocNumberStyle:{bold:!0}}):Object.assign(o,{tocMargin:[10,0,0,0]}),C(t,o),t};let M=0;const _=t=>{const o=t[e]?.NODE;if(!o||!("getAttribute"in o))return{[e]:{[p]:{}}};const n=o.getAttribute("class")||"",r=[...new Set(n.split(" ").filter((t=>t)).map((t=>"."+t.trim())))],a=o.nodeName.toLowerCase(),i=o.parentNode?o.parentNode.nodeName.toLowerCase():null,l=[a].concat(r);r.length>2&&l.push(r.join("")),i&&l.push(i+">"+a);const s=(t=>{const o=t[e]||{},n=o.UID,r=t[e]?.NODE;if(n)return n;if(!r)return"#"+M++;if(A(r)){const t=r.getAttribute("id");if(t)return t}const a="#"+r.nodeName.toLowerCase()+"-"+M++;return o.UID=a,t[e]=o,a})(t);l.push(s);const c={[e]:{[p]:t[e]?.STYLE||{},...t[e]||{}},style:[...new Set((t.style||[]).concat(l))]};for(let n=0;n<o.attributes.length;n++){const r=o.attributes[n].name,i=o.getAttribute(r)?.trim()||null;if(null!=i)switch(c[e].STYLE[r]=i,r){case"rowspan":c.rowSpan=parseInt(i,10);break;case"colspan":c.colSpan=parseInt(i,10);break;case"value":"li"===a&&(c.counter=parseInt(i,10));break;case"start":"ol"===a&&(c.start=parseInt(i,10));break;case"width":"image"in t&&(c.width=S(i));break;case"height":"image"in t&&(c.height=S(i));break;case"data-fit":if("true"===i){const t=o.getAttribute("width"),e=o.getAttribute("height");t&&e&&(c.fit=[S(t),S(e)])}break;case"data-toc-item":if("false"!==i){let t={};if(i)try{t=JSON.parse(i)}catch(t){console.warn("Not valid JSON format.",i)}c[e].HANDLER=e=>(C(e,t),e)}break;case"data-pdfmake":if(i)try{c[e].PDFMAKE=JSON.parse(i)}catch(t){console.warn("Not valid JSON format.",i)}}}return c},R=t=>{const e=t.split(" "),o=e[2]||"black",n=e[1]||"solid";return{color:o,width:S(e[0]),borderStyle:n}},G=(t,o,n,r)=>{const a=t[e].MARGIN||o[e]?.MARGIN||[0,0,0,0];a[r]=n,t[e].MARGIN=[...a],t.margin=a;const i=(t[e].PADDING||o[e]?.PADDING||[0,0,0,0])[r]||0;t.margin[r]=n+i},z=(t,o,n,r)=>{if(k(o))switch(t.layout=o.layout||t.layout||{},"string"==typeof t.layout&&(t.layout={}),r){case 0:t.layout.paddingLeft=()=>S(n);break;case 1:t.layout.paddingTop=()=>S(n);break;case 2:t.layout.paddingRight=()=>S(n);break;case 3:t.layout.paddingBottom=()=>S(n);break;default:throw new Error("Unsupported index for padding: "+r)}else{const a=t[e].PADDING||o[e]?.PADDING||[0,0,0,0];if(a[r]=n,t[e].PADDING=[...a],!E(o)){const a=t[e].MARGIN||o[e]?.MARGIN||[0,0,0,0];t.margin=a;const i=a[r];t.margin[r]=n+i}}},j=(t,o,n={})=>{const r={[e]:{[p]:{},...t[e]||{}}},a=r[e],i=x(t),l=k(t),s=(t=>"string"!=typeof t&&"text"in t&&"string"==typeof t.text)(t),c=(t=>"ul"in t||"ol"in t)(t),u=S(n["font-size"]||"16px");return(t=>t[e]?.NODE&&["H1","H2","H3","H4","H5","H6"].includes(t[e]?.NODE?.nodeName||""))(t)&&(a.HANDLER=H),Object.keys(o).forEach((n=>{const d=n,p=(""+o[n]).trim();switch(r[e].STYLE[d]=p,d){case"padding":{const e=O(p);if(l&&null!==e){let o=r.layout||t.layout||{};"string"==typeof o&&(o={}),o.paddingLeft=()=>Number(e[0]),o.paddingRight=()=>Number(e[2]),o.paddingTop=t=>0===t?Number(e[1]):0,o.paddingBottom=(t,o)=>t===o.table.body.length-1?Number(e[3]):0,r.layout=o}else null!==e&&(z(r,t,Number(e[1]),1),z(r,t,Number(e[0]),0),z(r,t,Number(e[2]),2),z(r,t,Number(e[3]),3));break}case"border":case"border-bottom":case"border-top":case"border-right":case"border-left":((t,e,o,n)=>{const{color:r,width:a,borderStyle:i}=R(n),l=E(t),s=o=>{e.border=t.border||e.border||[!1,!1,!1,!1],e.borderColor=t.borderColor||e.borderColor||["black","black","black","black"],"none"===n?e.border[o]=!1:(e.border[o]=!0,e.borderColor[o]=r)};switch(o){case"border":if(k(t)){if(e.layout=t.layout||e.layout||{},"string"==typeof e.layout&&(e.layout={}),"none"===n){e.layout.hLineWidth=()=>0,e.layout.vLineWidth=()=>0;break}e.layout.vLineColor=()=>r,e.layout.hLineColor=()=>r,e.layout.hLineWidth=(t,e)=>0===t||t===e.table.body.length?a:0,e.layout.vLineWidth=(t,e)=>0===t||t===e.table.widths?.length?a:0,"dashed"===i&&(e.layout.hLineStyle=()=>({dash:{length:2,space:2}}),e.layout.vLineStyle=()=>({dash:{length:2,space:2}}))}else l&&(s(0),s(1),s(2),s(3));break;case"border-bottom":if(k(t)){e.layout=t.layout||e.layout||{},"string"==typeof e.layout&&(e.layout={});const o=e.layout.hLineWidth||(()=>0),n=e.layout.hLineColor||(()=>"black");e.layout.hLineWidth=(t,e)=>t===e.table.body.length?a:o(t,e),e.layout.hLineColor=(t,e)=>t===e.table.body.length?r:n(t,e)}else l&&s(3);break;case"border-top":if(k(t)){const{color:o,width:r}=R(n);e.layout=t.layout||e.layout||{},"string"==typeof e.layout&&(e.layout={});const a=e.layout.hLineWidth||(()=>1),i=e.layout.hLineColor||(()=>"black");e.layout.hLineWidth=(t,e)=>0===t?r:a(t,e),e.layout.hLineColor=(t,e)=>0===t?o:i(t,e)}else l&&s(1);break;case"border-right":if(k(t)){const{color:o,width:r}=R(n);e.layout=t.layout||e.layout||{},"string"==typeof e.layout&&(e.layout={});const a=e.layout.vLineWidth||(()=>1),i=e.layout.vLineColor||(()=>"black");e.layout.vLineWidth=(t,e)=>(1===e.table.body.length?t===e.table.body.length:t%e.table.body.length!=0)?r:a(t,e),e.layout.vLineColor=(t,e)=>(1===e.table.body.length?t===e.table.body.length:t%e.table.body.length!=0)?o:i(t,e)}else l&&s(2);break;case"border-left":if(k(t)){const{color:o,width:r}=R(n);e.layout=t.layout||e.layout||{},"string"==typeof e.layout&&(e.layout={});const a=e.layout.vLineWidth||(()=>1),i=e.layout.vLineColor||(()=>"black");e.layout.vLineWidth=(t,e)=>(1===e.table.body.length?0===t:t%e.table.body.length==0)?r:a(t,e),e.layout.vLineColor=(t,e)=>(1===e.table.body.length?0===t:t%e.table.body.length==0)?o:i(t,e)}else l&&s(0)}})(t,r,d,p);break;case"font-size":r.fontSize=S(p,u);break;case"line-height":r.lineHeight=S(p,u);break;case"letter-spacing":r.characterSpacing=S(p);break;case"text-align":r.alignment=p;break;case"font-feature-settings":{const e=p.split(",").filter((t=>t)).map((t=>t.replace(/['"]/g,""))),o=t.fontFeatures||r.fontFeatures||[];o.push(...e),r.fontFeatures=o;break}case"font-weight":switch(p){case"bold":r.bold=!0;break;case"normal":r.bold=!1}break;case"text-decoration":switch(p){case"underline":r.decoration="underline";break;case"line-through":r.decoration="lineThrough";break;case"overline":r.decoration="overline"}break;case"text-decoration-color":r.decorationColor=p;break;case"text-decoration-style":r.decorationStyle=p;break;case"vertical-align":"sub"===p&&(r.sub=!0);break;case"font-style":if("italic"===p)r.italics=!0;break;case"font-family":r.font=p;break;case"color":r.color=p;break;case"background":case"background-color":if(l){let e=t.layout||{};"string"==typeof e&&(e={}),e.fillColor=()=>p,r.layout=e}else E(t)?r.fillColor=p:r.background=["fill",p];break;case"margin":{const e=O(p)?.map((t=>"string"==typeof t?0:t));e&&(G(r,t,e[1],1),G(r,t,e[0],0),G(r,t,e[2],2),G(r,t,e[3],3));break}case"margin-left":G(r,t,S(p),0);break;case"margin-top":G(r,t,S(p),1);break;case"margin-right":G(r,t,S(p),2);break;case"margin-bottom":G(r,t,S(p),3);break;case"padding-left":z(r,t,S(p),0);break;case"padding-top":z(r,t,S(p),1);break;case"padding-right":z(r,t,S(p),2);break;case"padding-bottom":z(r,t,S(p),3);break;case"page-break-before":"always"===p&&(r.pageBreak="before");break;case"page-break-after":"always"===p&&(r.pageBreak="after");break;case"position":"absolute"===p?(a.POSITION="absolute",r.absolutePosition={}):"relative"===p&&(a.POSITION="relative",r.relativePosition={});break;case"left":case"top":if(!r.absolutePosition&&!r.relativePosition){console.error(d+" is set, but no absolute/relative position.");break}if(r.absolutePosition)"left"===d?r.absolutePosition.x=S(p):"top"===d&&(r.absolutePosition.y=S(p));else{if(!r.relativePosition){console.error(d+" is set, but no absolute/relative position.");break}"left"===d?r.relativePosition.x=S(p):"top"===d&&(r.relativePosition.y=S(p))}break;case"white-space":"pre"===p&&a.NODE&&(s&&(r.text=a.NODE?.textContent||""),r.preserveLeadingSpaces=!0);break;case"display":"flex"===p?r[e].HANDLER=W:"none"===p&&(r[e].HANDLER=()=>null);break;case"opacity":r.opacity=Number(parseFloat(p));break;case"gap":r.columnGap=S(p);break;case"list-style-type":case"list-style":c?r.type=p:r.listType=p;break;case"width":if(l)if("100%"===p)t.table.widths=["*"];else{const e=D(p);null!==e&&(t.table.widths=[e])}else i&&(r.width=S(p));break;case"height":i&&(r.height=S(p));break;case"max-height":i&&(r.maxHeight=S(p));break;case"max-width":i&&(r.maxWidth=S(p));break;case"min-height":i&&(r.minHeight=S(p));break;case"min-width":i&&(r.minWidth=S(p));break;case"object-fit":"contain"===p&&i&&(a.HANDLER=v)}})),r},F=(t,o,n,r={})=>{const a=((t,e,o)=>(e.style||[]).concat(t.nodeName.toLowerCase()).filter((t=>o&&o[t])).reduce(((t,e)=>({...t,...o[e]})),{}))(t,o,n),i=n[":root"]||{"font-size":"16px"},l=(t=>{const e={color:!0,"font-family":!0,"font-size":!0,"font-weight":!0,font:!0,"line-height":!0,"list-style-type":!0,"list-style":!0,"text-align":!0,background:!0,"font-style":!0,"background-color":!0,"font-feature-settings":!0,"white-space":!0,"vertical-align":!0,opacity:!0,"text-decoration":!0};return Object.keys(t).reduce(((o,n)=>((e[n]||"inherit"===t[n])&&(o[n]=t[n]),o)),{})})(r),s=Object.assign({},a,l,(t=>("getAttribute"in t&&t.getAttribute("style")||"").split(";").map((t=>t.trim().toLowerCase().split(":"))).filter((t=>2===t.length)).reduce(((t,e)=>(t[e[0].trim()]=e[1].trim(),t)),{}))(t)),c=j(o,s,Object.assign({},i,l)),u=_(o);return{cssStyles:s,props:{...c,...u,[e]:{...c[e]||{},...u[e]||{},[p]:{...c[e].STYLE||{},...u[e].STYLE||{}}}}}},Y=(t,o)=>{if(L(t)&&L(o)){const n=o[e]?.MARGIN||[0,0,0,0];o[e]={...o[e]||{},[c]:n},o.margin[3]=o[e]?.PADDING?.[3]||0;const r=t[e]?.MARGIN||[0,0,0,0],a=Math.max(r[1],n[3]);r[1]=a,n[3]=0,t[e]={...t[e]||{},[c]:r},t.margin[1]=a+(t[e]?.PADDING?.[1]||0)}},B=t=>{const e=t.text.at(-1);return N(e)?B(e):e},U=t=>{const e=t.text.at(0);return N(e)?U(e):t.text},J=(t,o)=>{const n=B(t),r=U(o);n&&n[e]?.IS_WHITESPACE&&r[0][e]?.IS_WHITESPACE&&r.shift()};const K=(t,o)=>{const n=t.getAttribute("src");if(!n)return null;const r=t.getAttribute("name")||n;let a;return/^data:image\/(jpeg|png|jpg);base64,/.test(n)?a=n:(o.images[r]||(o.images[n]=r),a=r),{image:a,[e]:{}}},q=t=>{const e=t.cloneNode(!0),o=t.getAttribute("width"),n=t.getAttribute("height");return o&&e.setAttribute("width",""+T(o)),n&&e.setAttribute("height",""+T(n)),{svg:e.outerHTML.replace(/\n(\s+)?/g,"")}},$=()=>({table:{body:t=>{const o=t.find(m)?.stack[0]||[],n=t.filter((t=>!m(t))).flatMap((t=>"stack"in t?t.stack:[])),r=n.map((t=>I(t)));if(0===r.length)return[];const a=r.reduce(((t,e)=>t.length<=e.length?e:t));return[[{table:{body:r,widths:new Array(a.length).fill("auto"),heights:new Array(n.length).fill("auto")},layout:{defaultBorder:!1},[e]:{[d]:{colgroup:"text"in o&&Array.isArray(o.text)?o.text:[],trs:n}}}]]}},[e]:{[u]:P},layout:{}}),Q=t=>{const s=t.textContent;if(null===s)return null;const c=s.replace(/[^\S\r\n]+/,""),u=s.replace(/\n|\t| +/g," ").replace(/^ +/,"").replace(/ +$/,""),d="\n"===c[c.length-1],p="\n"===c[0],b=" "===s[0],g=" "===s[s.length-1];return{text:u,[e]:{[n]:p,[o]:d,[r]:p&&d&&0===u.length,[a]:b,[i]:g,[l]:b&&g&&1===s.length}}},V=t=>({text:" ",[e]:{[l]:t}}),X=/^(address|blockquote|body|center|colgroup|dir|div|dl|fieldset|form|h[1-6]|hr|isindex|menu|noframes|noscript|ol|p|pre|table|ul|dd|dt|frameset|li|tbody|td|tfoot|th|thead|tr|html)$/i,Z=(t,o,n={})=>{const r=[],a=(t=>["TABLE","TBODY","TR","COLGROUP","COL","UL","OL","SELECT"].includes(t.nodeName)&&"children"in t)(t)?t.children:t.childNodes;for(let t=0;t<a.length;t++){const i=ot(a[t],o,n);if(null===i)continue;const l=!!i[e]?.IS_NEWLINE,s=r[r.length-1];if(o.config.collapseMargin&&s&&Y(i,s),l&&(0===r.length||!a[t+1]||s&&"stack"in s))continue;if(!("text"in i)){r.push(i);continue}const c=!!i[e]?.END_WITH_NEWLINE,u=!!i[e]?.START_WITH_NEW_LINE,d=!!i[e]?.END_WITH_WHITESPACE,p=!!i[e]?.START_WITH_WHITESPACE,b=!!i[e]?.IS_WHITESPACE,g=Array.isArray(i.text)?i:{text:[b?V("newLine"):i]};l||b||((u||p)&&g.text.unshift(V(u?"startWithNewLine":"startWithWhitespace")),(c||d)&&g.text.push(V(c?"endWithNewLine":"endWithWhiteSpace"))),N(s)?(o.config.collapseWhitespace&&J(s,g),s.text.push(g)):r.push({text:[g]})}return r},tt=(t,o)=>{if("function"==typeof o.config.customRule){const e=o.config.customRule(t,o);if(null===e)return null;if(void 0!==e)return e}if(A(t))return(t=>{switch(t.nodeName.toLowerCase()){case"#comment":case"option":case"script":case"style":case"iframe":case"object":return()=>null;case"#text":return Q;case"a":return t=>{const e=t.getAttribute("href");if(!e)return nt(t);const o=t=>{const n=I(t);[].concat(n).forEach((t=>{"string"!=typeof t&&("#"===e[0]?t.linkToDestination=e.slice(1):t.link=e),o(t)}))};return{text:t=>(t.forEach((t=>{"string"!=typeof t&&("#"===e[0]?t.linkToDestination=e.slice(1):t.link=e),o(t)})),t)}};case"br":return()=>({text:"\n",[e]:{[r]:!0}});case"qr-code":return t=>{const e=t.getAttribute("value");if(!e)return null;const o=t.getAttribute("data-size");return{qr:e,fit:S(o||"128px")}};case"toc":return t=>{const e=t.textContent;return e?{toc:{title:{text:e,bold:!0,fontSize:S("22px"),margin:[0,10,0,10]}}}:null};case"table":return $;case"ul":return()=>({ul:t=>t});case"ol":return()=>({ol:t=>t});case"img":return K;case"svg":return q;case"hr":return()=>({table:{widths:["*"],body:[[""]]},style:["hr"]});case"input":return t=>t instanceof HTMLInputElement?{text:"value"in t?t.value:"",[e]:{[s]:!0}}:null;case"select":return t=>t instanceof HTMLSelectElement?{text:t.options[t.selectedIndex].value,[e]:{[s]:!0}}:null;default:return nt}})(t)(t,o);if((t=>3===t.nodeType||8===t.nodeType)(t))return(t=>{switch(t.nodeName.toLowerCase()){case"#comment":default:return()=>null;case"#text":return Q}})(t)(t,o);throw new Error("Unsupported Node Type: "+t.nodeType)},et=(t,o,n={})=>{const r=t[e]?.NODE;if("string"!=typeof t&&r){const{cssStyles:e,props:a}=F(r,t,o.styles,n);if(Object.assign(t,a),"stack"in t&&"function"==typeof t.stack){const n=Z(r,o,e);t.stack=t.stack(n,o)}else if("text"in t&&"function"==typeof t.text){const n=Z(r,o,e);t.text=t.text(n.filter(w),o)}else if("ul"in t&&"function"==typeof t.ul){const n=Z(r,o,e);t.ul=t.ul(n,o)}else if("ol"in t&&"function"==typeof t.ol){const n=Z(r,o,e);t.ol=t.ol(n,o)}else if("table"in t&&"function"==typeof t.table.body){const n=Z(r,o,e);t.table.body=t.table.body(n,o)}}return(t=>("string"!=typeof t&&t[e]?.PDFMAKE&&f(t,t[e]?.PDFMAKE||{}),"function"==typeof t[e]?.HANDLER?t[e]?.HANDLER?.(t)||null:t))(t)},ot=(t,o,n={})=>{const r=tt(t,o);if(null===r)return null;const a=r[e]||{};return a.NODE=t,r[e]=a,et(r,o,n)},nt=t=>(t=>X.test(t.nodeName))(t)?{stack:t=>t}:{text:t=>1===t.length&&"text"in t[0]&&Array.isArray(t[0].text)?t[0].text:t};t.parse=(t,e={globalStyles:{":root":{"font-size":"16px"},h1:{"font-size":"32px","margin-top":"21.44px","margin-bottom":"21.44px","font-weight":"bold"},h2:{"font-size":"24px","margin-top":"19.92px","margin-bottom":"19.92px","font-weight":"bold"},h3:{"font-size":"18.72px","margin-top":"18.72px","margin-bottom":"18.72px","font-weight":"bold"},h4:{"font-size":"16px","margin-top":"21.28px","margin-bottom":"21.28px","font-weight":"bold"},h5:{"font-size":"13.28px","margin-top":"22.17px","margin-bottom":"22.17px","font-weight":"bold"},h6:{"font-size":"10.72px","margin-top":"24.97px","margin-bottom":"24.97px","font-weight":"bold"},b:{"font-weight":"bold"},strong:{"font-weight":"bold"},i:{"font-style":"italic"},em:{"font-style":"italic"},s:{"text-decoration":"line-through"},del:{"text-decoration":"line-through"},sub:{"font-size":"22px","vertical-align":"sub"},small:{"font-size":"13px"},u:{"text-decoration":"underline"},ul:{"margin-top":"16px","margin-bottom":"16px","padding-left":"20px"},ol:{"margin-top":"16px","margin-bottom":"16px","padding-left":"20px"},p:{"margin-top":"16px","margin-bottom":"16px"},table:{border:"none",padding:"3px"},td:{border:"none"},tr:{margin:"4px 0"},th:{"font-weight":"bold",border:"none","text-align":"center"},a:{color:"#0000ee","text-decoration":"underline"},hr:{"border-top":"2px solid #9a9a9a","border-bottom":"0","border-left":"0px solid black","border-right":"0",margin:"8px 0"}},styles:{},collapseMargin:!0,collapseWhitespace:!0})=>{const o={...g,...e},n=new b(o,Object.assign({},o.globalStyles,o.styles)),r="string"==typeof t?(t=>{if("undefined"!=typeof DOMParser)return(new DOMParser).parseFromString(t,"text/html").body;if("undefined"!=typeof document&&"function"==typeof document.createDocumentFragment){const e=document.createDocumentFragment(),o=document.createElement("div");return o.innerHTML=t,e.append(o),e.children[0]}throw new Error("Could not parse html to DOM. Please use external parser like jsdom.")})(t):t;return{content:null!==r?Z(r,n):[],images:n.images,patterns:{fill:{boundingBox:[1,1,4,4],xStep:1,yStep:1,pattern:"1 w 0 1 m 4 5 l s 2 0 m 5 3 l s"}}}},Object.defineProperty(t,"__esModule",{value:!0})}));//# sourceMappingURL=html2pdfmake.min.js.map

Sorry, the diff of this file is too big to display