import paper from 'paper'; import { isElement, traverse } from '../../../Utils/DOM'; export default class HTML2Paper { position = { x: 0, y: 0, }; lineGroup = false; previousLineGroup = false; createLineGroup(parentItem) { this.finishLine(); const group = new paper.Group({ applyMatrix: false, }); parentItem.addChild(group); this.lineGroup = group; return group; } finishImport() { this.finishLine(); } finishLine() { const { lineGroup, position, previousLineGroup } = this; if (lineGroup) { const { height } = lineGroup.bounds; if (height === 0 && previousLineGroup) { position.y += previousLineGroup.bounds.height; lineGroup.remove(); } else { this.previousLineGroup = lineGroup; lineGroup.position.y = position.y + lineGroup.bounds.height / 2; position.y = lineGroup.bounds.bottomLeft.y; } } position.x = 0; } importHTMLRecursive(el, parentItem, props = {}) { const { position } = this; const postProcess = []; for (const node of el.childNodes) { if (node.nodeType === Node.TEXT_NODE) { const text = new paper.PointText({ content: node.nodeValue, ...props, }); this.lineGroup.addChild(text); text.position.x = position.x + text.bounds.width / 2; text.position.y = text.bounds.height / 2; position.x = text.bounds.bottomRight.x; } else if (node.nodeType === Node.ELEMENT_NODE) { switch (node.nodeName) { case 'BR': { // skip BR since it is wrapped in a DIV which already sets the position break; } case 'SPAN': { const addProps = this.collectFontStyleProps(node); // call the recursion to add children this.importHTMLRecursive(node, parentItem, { ...props, ...addProps }); break; } case 'DIV': case 'P': { this.createLineGroup(parentItem); const group = this.lineGroup; const addProps = this.collectFontStyleProps(node); // call the recursion to add children this.importHTMLRecursive(node, parentItem, { ...props, ...addProps }); postProcess.push(() => { const containerBounds = parentItem.internalBounds; switch (props.textAlign) { case 'center': group.position.x = containerBounds.width / 2; break; case 'right': group.position.x = containerBounds.width - group.bounds.width / 2; break; case 'left': default: group.position.x = group.bounds.width / 2; break; } }); break; } default: { // skip tag from processing } } } } for (const process of postProcess) { process(); } } collectFontStyleProps(node) { const styleProps = {}; const { color, fontStyle, fontWeight, fontSize, fontFamily } = node.style; if (color) { styleProps.fillColor = color; } if (fontWeight || fontStyle) { styleProps.fontWeight = [fontStyle, fontWeight].filter(i => !!i).join(' '); } if (fontSize) { // ignore fontSize for now, since it's global for the text object // styleProps.fontSize = fontSize; } if (fontFamily) { styleProps.fontFamily = fontFamily; } return styleProps; } importHTML( strOrDomElement, targetGroup = null, keepContent = false, addBaseRect = true, initialProps = {} ) { let domElement; if (isElement(strOrDomElement)) { domElement = strOrDomElement; } else if (typeof strOrDomElement === 'string') { domElement = document.createElement('div'); domElement.innerHTML = strOrDomElement; } else { throw new Error(` HTML2Paper: wrong input type: ${typeof strOrDomElement}. It should be an instance of HTMLElement or string. `); } domElement = prepareInitialStyling(domElement, initialProps); if (targetGroup && !keepContent) { targetGroup.removeChildren(); } let root = targetGroup; if (!root) { root = new paper.Group({ applyMatrix: false, }); } const textGroup = new paper.Group({ applyMatrix: false, }); this.createLineGroup(textGroup); this.importHTMLRecursive(domElement, textGroup, initialProps); this.finishImport(); if (addBaseRect) { const rect = new paper.Path.Rectangle(textGroup.bounds); rect.data = { placeholder: true, }; root.addChild(rect); } root.addChild(textGroup); // the selection is not visualized after the group content has changed // so it has to be reapplied programmatically root.setSelected(root.isSelected()); return root; } } /** * * @param {HTMLElement} domElement * @param {object} initialProps */ function prepareInitialStyling(domElement: HTMLElement, initialProps: any) { const node = domElement.cloneNode(true); const { fontSize } = initialProps; if (node && node.firstChild && fontSize) { node.firstChild.style.fontSize = `${fontSize}px`; } return node; } export function removePlaceholders(xmlDoc) { // remove placeholders traverse(xmlDoc, node => { try { const data = node.dataset && node.dataset.paperData && JSON.parse(node.dataset.paperData); if (data && data.placeholder) { node.parentNode.removeChild(node); } } catch (e) { // no handling needed } }); }