UNPKG

@paprize/core

Version:

Paginate DOM elements for professional, print-ready reports

889 lines (888 loc) 24.1 kB
const re = "paprize", ae = "pz-temp-container", Pe = "pz-current-element", Te = "pz-current-text", ke = "pz-ignored-element", Ae = "pz-ignored-text", M = "pz-page", x = "data-pz-"; let J = !1; const Le = () => { J = !0; }, Fe = () => J; function X(i) { return i.nodeType === Node.ELEMENT_NODE; } function se(i) { return i.nodeType === Node.TEXT_NODE; } function le(i) { const e = i.getBoundingClientRect(), t = getComputedStyle(i), n = parseFloat(t.marginTop) || 0, o = parseFloat(t.marginBottom) || 0; return e.height + n + o; } function Ie(i) { i.style.visibility = "hidden", i.style.position = "absolute", i.style.left = "-9999px", i.style.top = "-9999px"; } class v { name; defaultValue; _reader; constructor(e, t, n) { this.name = e, this._reader = t, this.defaultValue = n; } read(e) { return this._reader(e); } static createStr(e, t) { return new v(e, (n) => n, t); } static createBool(e, t) { return new v( e, (n) => n === "true", t ); } } const A = { hyphen: v.createStr("hyphen", "-"), keepOnSamePage: v.createBool("keep-on-same-page", !1), hyphenationEnabled: v.createBool( "hyphenation-enabled", !0 ) }; function Re(i) { const e = {}; for (const t in i) { const n = i[t]; if (n !== void 0) { const o = A[t]; e[`${x}${o.name}`] = String(n); } } return e; } const ce = Object.fromEntries( Object.entries(A).map(([i, e]) => [ i, e.defaultValue ]) ), z = /* @__PURE__ */ new WeakMap(); function j(i) { if (!i) return {}; if (!(i instanceof Element)) return j(i?.parentNode); const e = {}; for (const o in A) { const r = A[o], s = `${x}${r.name}`, g = i.getAttribute(s); g !== null && (e[o] = r.read(g)); } const n = { ...i.parentNode ? z.get(i.parentNode) : void 0, ...e }; return z.set(i, n), n; } const Y = { id: "default", plugins: [], ...ce }; function de(i, e) { const t = j(i); return { ...Y, ...e, ...t }; } function ue(i) { return i && i.__esModule && Object.prototype.hasOwnProperty.call(i, "default") ? i.default : i; } var k = { exports: {} }, ge = k.exports, U; function pe() { return U || (U = 1, (function(i) { (function(e, t) { i.exports ? i.exports = t() : e.log = t(); })(ge, function() { var e = function() { }, t = "undefined", n = typeof window !== t && typeof window.navigator !== t && /Trident\/|MSIE /.test(window.navigator.userAgent), o = [ "trace", "debug", "info", "warn", "error" ], r = {}, s = null; function g(l, d) { var a = l[d]; if (typeof a.bind == "function") return a.bind(l); try { return Function.prototype.bind.call(a, l); } catch { return function() { return Function.prototype.apply.apply(a, [l, arguments]); }; } } function Z() { console.log && (console.log.apply ? console.log.apply(console, arguments) : Function.prototype.apply.apply(console.log, [console, arguments])), console.trace && console.trace(); } function K(l) { return l === "debug" && (l = "log"), typeof console === t ? !1 : l === "trace" && n ? Z : console[l] !== void 0 ? g(console, l) : console.log !== void 0 ? g(console, "log") : e; } function b() { for (var l = this.getLevel(), d = 0; d < o.length; d++) { var a = o[d]; this[a] = d < l ? e : this.methodFactory(a, l, this.name); } if (this.log = this.debug, typeof console === t && l < this.levels.SILENT) return "No console available for logging"; } function Q(l) { return function() { typeof console !== t && (b.call(this), this[l].apply(this, arguments)); }; } function ee(l, d, a) { return K(l) || Q.apply(this, arguments); } function $(l, d) { var a = this, P, L, m, f = "loglevel"; typeof l == "string" ? f += ":" + l : typeof l == "symbol" && (f = void 0); function ne(c) { var u = (o[c] || "silent").toUpperCase(); if (!(typeof window === t || !f)) { try { window.localStorage[f] = u; return; } catch { } try { window.document.cookie = encodeURIComponent(f) + "=" + u + ";"; } catch { } } } function B() { var c; if (!(typeof window === t || !f)) { try { c = window.localStorage[f]; } catch { } if (typeof c === t) try { var u = window.document.cookie, T = encodeURIComponent(f), D = u.indexOf(T + "="); D !== -1 && (c = /^([^;]+)/.exec( u.slice(D + T.length + 1) )[1]); } catch { } return a.levels[c] === void 0 && (c = void 0), c; } } function ie() { if (!(typeof window === t || !f)) { try { window.localStorage.removeItem(f); } catch { } try { window.document.cookie = encodeURIComponent(f) + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC"; } catch { } } } function y(c) { var u = c; if (typeof u == "string" && a.levels[u.toUpperCase()] !== void 0 && (u = a.levels[u.toUpperCase()]), typeof u == "number" && u >= 0 && u <= a.levels.SILENT) return u; throw new TypeError("log.setLevel() called with invalid level: " + c); } a.name = l, a.levels = { TRACE: 0, DEBUG: 1, INFO: 2, WARN: 3, ERROR: 4, SILENT: 5 }, a.methodFactory = d || ee, a.getLevel = function() { return m ?? L ?? P; }, a.setLevel = function(c, u) { return m = y(c), u !== !1 && ne(m), b.call(a); }, a.setDefaultLevel = function(c) { L = y(c), B() || a.setLevel(c, !1); }, a.resetLevel = function() { m = null, ie(), b.call(a); }, a.enableAll = function(c) { a.setLevel(a.levels.TRACE, c); }, a.disableAll = function(c) { a.setLevel(a.levels.SILENT, c); }, a.rebuild = function() { if (s !== a && (P = y(s.getLevel())), b.call(a), s === a) for (var c in r) r[c].rebuild(); }, P = y( s ? s.getLevel() : "WARN" ); var W = B(); W != null && (m = y(W)), b.call(a); } s = new $(), s.getLogger = function(d) { if (typeof d != "symbol" && typeof d != "string" || d === "") throw new TypeError("You must supply a name when creating a logger."); var a = r[d]; return a || (a = r[d] = new $( d, s.methodFactory )), a; }; var te = typeof window !== t ? window.log : void 0; return s.noConflict = function() { return typeof window !== t && window.log === s && (window.log = te), s; }, s.getLoggers = function() { return r; }, s.default = s, s; }); })(k)), k.exports; } var he = pe(); const _ = /* @__PURE__ */ ue(he), h = _.getLogger(re); h.setDefaultLevel("info"); const F = "\x1B[46mPLUGIN\x1B[0m"; function N(i, e, ...t) { i.sort((n, o) => n.order - o.order).forEach((n) => { const o = n[e]; if (o) { h.debug( F, `executing plugin ${n.name}:${String(e)} ()`, t ); try { o(...t), h.debug( F, `plugin ${n.name}:${String(e)} executed`, t ); } catch (r) { h.debug( F, `plugin ${n.name}:${String(e)} failed`, r ); } } }); } const S = { Element: "element", Text: "text" }; class O { _node; _transaction; type = S.Element; config; constructor(e, t, n) { this._node = e, this._transaction = t, this.config = n; } appendChild(e) { this._transaction.isActive && this._transaction.addRollbackCallback(() => { this._node.removeChild(e.getNode()); }), this._node.appendChild(e.getNode()); } clone(e) { const t = this._node.cloneNode(e), n = new O( t, this._transaction, this.config ); return N( this.config.plugins, "onClone", this.config.id, this._node, n ), n; } getHeight() { return le(this._node); } remove() { this._transaction.addCommitCallback(() => { this._node.remove(); }); } isEmpty() { return this._node.innerHTML === ""; } getChildrenCount() { return this._node.childNodes.length; } getNode() { return this._node; } } class fe { _node; _transaction; type = S.Text; config; constructor(e, t, n) { this._node = e, this._transaction = t, this.config = n; } get textContent() { return this._node.textContent ?? ""; } set textContent(e) { this._node.textContent = e; } remove() { this._transaction.addCommitCallback(() => { this._node.remove(); }); } getNode() { return this._node; } } function I(i, e, t) { if (se(i)) return new fe(i, e, t); if (X(i)) return new O(i, e, t); throw new Error("Unsupported node type"); } const E = "\x1B[106mDOM\x1B[0m"; class me { _transaction; _treeWalker; _config; _completed = !1; _currentNode = null; _previousNode = null; constructor(e, t, n) { this._transaction = t, this._config = n, this._treeWalker = document.createTreeWalker( e, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT ); } get completed() { return this._completed; } get currentNode() { return this._currentNode; } get previousNode() { return this._previousNode; } nextNode() { this._treeWalker.nextNode() || (this._completed = !0), h.debug(E, "moving to next node"), this.setState(); } nextSiblingOrParentSibling() { let e = 0; if (this._treeWalker.nextSibling()) return h.debug(E, "moving to next sibling node"), this.setState(), { parentsTraversed: e }; for (; this._treeWalker.parentNode(); ) if (e++, this._treeWalker.nextSibling()) return h.debug( E, "moving to parent sibling node, traversed:", e ), this.setState(), { parentsTraversed: e }; return this._completed = !0, { parentsTraversed: e }; } firstChildOrNextNode() { return this._treeWalker.firstChild() ? (h.debug(E, "moving to first child node"), this.setState(), { parentsTraversed: 1 }) : (this.nextNode(), { parentsTraversed: 0 }); } setState() { this._previousNode = this._currentNode, this._currentNode = I( this._treeWalker.currentNode, this._transaction, de(this._treeWalker.currentNode, this._config) ), h.debug(E, "moved to node", { currentNode: this.currentNode, previousNode: this.previousNode }); } } class _e { _onRollback; _onCommit; isActive; constructor() { this._onRollback = [], this._onCommit = [], this.isActive = !1; } start = () => { if (this.isActive) throw new Error("Transaction already in progress"); this.isActive = !0, this._onRollback = [], this._onCommit = []; }; addRollbackCallback = (e) => { this._onRollback.push(e); }; addCommitCallback = (e) => { if (!this.isActive) { e(); return; } this._onCommit.push(e); }; rollback = () => { this.isActive && (this.isActive = !1, this._onRollback.forEach((e) => e())); }; commit = () => { this.isActive && (this.isActive = !1, this._onCommit.forEach((e) => e())); }; } const V = "\x1B[102mPAGE\x1B[0m"; class w { currentPage; activeElement; currentElement; parentStack; pageIsFull; pageIndex; pageHeight; constructor(e, t, n, o, r, s, g) { this.currentPage = e, this.activeElement = t, this.currentElement = n, this.parentStack = o, this.pageIsFull = r, this.pageIndex = s, this.pageHeight = g; } static create(e, t, n, o) { return new w( e, null, e, t, !1, n, o ); } clone = () => new w( this.currentPage, this.activeElement, this.currentElement, [...this.parentStack], this.pageIsFull, this.pageIndex, this.pageHeight ); } class H { _pageState; _transaction; _tempContainer; _config; constructor(e, t, n, o) { this._tempContainer = e, this._config = o, this._transaction = n; const r = H.createPageHtmlElement( t.width ), s = this.createNewPage(r); this._pageState = w.create(s, [], 0, t.height), N(this._config.plugins, "onNewPage", o.id, this); } nextPage() { const e = this.createNewPage( this._pageState.currentPage.getNode().cloneNode(!1) ), t = w.create( e, [], this._pageState.pageIndex + 1, this._pageState.pageHeight ); this.cloneParentStackToNewPage(t), this.cleanupEmptyParent(), this._pageState = t, N( this._config.plugins, "onNewPage", this._config.id, this ); } cloneParentStackToNewPage(e) { for (const t of this._pageState.parentStack) { const n = t.clone(!1); e.currentElement.appendChild(n), e.currentElement = n, e.parentStack.push(n); } } cleanupEmptyParent() { const e = [...this._pageState.parentStack], t = () => { for (let n = e.length - 1; n >= 0; n--) { const o = e[n]; o.isEmpty() && o.remove(); } }; this._transaction.addCommitCallback(t); } enterElement() { if (!this._pageState.activeElement || this._pageState.activeElement.type !== S.Element) throw new Error("Invalid state: activeElement is not an Element"); h.debug( V, "entering an element", this._pageState.activeElement ), this._pageState.currentElement = this._pageState.activeElement, this._pageState.parentStack.push(this._pageState.activeElement); } leaveElement() { this._pageState.activeElement = null; const e = this._pageState.parentStack.pop(); h.debug(V, "leaving a parent element", e); const t = this._pageState.parentStack.at(-1); this._pageState.currentElement = t ?? this._pageState.currentPage; } static createPageHtmlElement(e) { const t = document.createElement("div"); return t.style.width = `${e}px`, t.style.maxWidth = `${e}px`, t; } createNewPage(e) { return this._tempContainer.appendChild(e), this._transaction.isActive && this._transaction.addRollbackCallback(() => { this._tempContainer.removeChild(e); }), I(e, this._transaction, this._config); } startTransaction() { this._transaction.start(); const e = this._pageState.clone(); return this._transaction.addRollbackCallback(() => { this._pageState = e; }), this._transaction; } hasEmptySpace(e) { return !this._pageState.pageIsFull && this._pageState.currentPage.getHeight() + (e || 1e-4) <= this._pageState.pageHeight; } isOverFlow() { return this._pageState.currentPage.getHeight() > this._pageState.pageHeight; } markPageAsFull() { this._pageState.pageIsFull = !0; } appendChild(e, t) { const n = e.clone(t); return this._pageState.currentElement.appendChild(n), this._pageState.activeElement = n, n; } addTextNode(e) { if (this._pageState.activeElement?.type === S.Text) return this._pageState.activeElement; const t = document.createTextNode(e), n = I( t, this._transaction, this._config ); return this._pageState.currentElement.appendChild(n), this._pageState.activeElement = n, n; } getPageState() { return this._pageState; } } const p = { None: 0, // The node fits completely on the page, no further splitting required. FullNodePlaced: 1, // The entire node was placed on the page, continue with the next sibling or element. SplitChildren: 2 // The node is too large for the page, and its children must be paginated individually. }; function ve(i, e) { const t = q(i, e); if (t !== p.None) return t; const { rollback: n, commit: o } = e.startTransaction(); e.nextPage(); const r = q(i, e); return r !== p.None ? (o(), r) : (n(), h.debug("Element is too big to fit on a page", i), p.None); } function q(i, e) { if (e.hasEmptySpace(i.getHeight())) { const n = e.appendChild(i, !0); if (e.isOverFlow()) n.remove(); else return p.FullNodePlaced; } if (i.config.keepOnSamePage || i.getChildrenCount() === 0) return p.None; const t = e.appendChild(i, !1); return e.isOverFlow() ? (t.remove(), p.None) : p.SplitChildren; } function Ne(i, e) { let t = p.FullNodePlaced; const n = i.textContent.split(/(\s+)/).filter((s) => s !== ""); let o, r = 0; for (; o || r < n.length; ) { const s = o ?? n[r], g = Se(s, e, i.config); g.completed || (t = p.None), g.pendingToken ? o = g.pendingToken : (o = void 0, r++); } return t; } function Se(i, e, t) { e.hasEmptySpace() || e.nextPage(); let n = e.addTextNode(""); const o = n.textContent; if (n.textContent += i, !e.isOverFlow()) return { completed: !0 }; n.textContent = o; const r = be(i, e, t); return { pendingToken: r.leftovers, completed: r.completed }; } function be(i, e, t) { const { rollback: n, commit: o } = e.startTransaction(); if (e.nextPage(), e.addTextNode(i), !e.isOverFlow()) return o(), { completed: !0 }; if (n(), !t.hyphenationEnabled) return h.warn("Hyphenation disabled, skipping oversized token:", i), { completed: !1 }; const r = ye(i, t.hyphen, e); return { completed: !0, leftovers: r && r.length > 0 ? r : void 0 }; } function ye(i, e, t) { const n = t.addTextNode(""); let o = ""; for (let r = 0; r < i.length; r++) { const s = i[r], g = o + s; if (n.textContent = g + e, !t.hasEmptySpace()) return n.textContent = o ? o + e : "", t.markPageAsFull(), i.slice(r); o = g; } return null; } const C = "\x1B[103mPAGINATOR\x1B[0m"; class R { _domState; _pageManager; _transaction; _tempContainer; _config; constructor(e, t, n) { this._config = { ...Y, ...n }, this._tempContainer = R.createTempContainer(this._config.id), this._transaction = new _e(), this._domState = new me(e, this._transaction, this._config), this._pageManager = new H( this._tempContainer, t, this._transaction, this._config ); } static createTempContainer(e) { const t = document.createElement("div"); return t.style.display = "flex", t.style.flexDirection = "column", t.style.gap = "20px", t.setAttribute(`${x}-section-id`, e), t.classList.add(ae), document.body.appendChild(t), t; } static paginate(e, t, n) { const o = new R(e, t, n); return o.processAllNodes(), Array.from(o._tempContainer.childNodes).filter((s) => X(s)).map((s) => s.innerHTML); } processAllNodes() { this._domState.nextNode(); do { switch (_.debug( C, "paginating node", this._domState.currentNode ), this.processCurrentNode()) { case p.None: this.handleNodeSkipped(); break; case p.FullNodePlaced: this.handleFullNodePlaced(); break; case p.SplitChildren: this.handleChildrenSplit(); break; } N( this._config.plugins, "afterVisitNode", this._config.id, this._domState, this._pageManager ); } while (this._domState.completed === !1); _.debug(C, "pagination completed"); } handleNodeSkipped() { _.debug(C, "node skipped - couldn't paginate"), this._domState.nextNode(); } handleFullNodePlaced() { _.debug(C, "node fully paginated"); const { parentsTraversed: e } = this._domState.nextSiblingOrParentSibling(); for (let t = 0; t < e; t++) this._pageManager.leaveElement(); } handleChildrenSplit() { _.debug( C, "node partially paginated - splitting children" ), this._domState.firstChildOrNextNode().parentsTraversed === 1 && this._domState.previousNode?.type === S.Element && this._pageManager.enterElement(); } processCurrentNode() { if (!this._domState.currentNode) return p.None; if (this._domState.currentNode.type === S.Element) { const e = {}; return N( this._config.plugins, "onVisitElement", this._config.id, this._domState, this._pageManager, e ), e.result !== void 0 ? e.result : ve( this._domState.currentNode, this._pageManager ); } else { const e = {}; return N( this._config.plugins, "onVisitText", this._config.id, this._domState, this._pageManager, e ), e.result !== void 0 ? e.result : Ne( this._domState.currentNode, this._pageManager ); } } } const G = [ "ad", "adipisicing", "aliqua", "aliquip", "amet", "anim", "aute", "cillum", "commodo", "consectetur", "consequat", "culpa", "cupidatat", "deserunt", "do", "dolor", "dolore", "duis", "ea", "eiusmod", "elit", "enim", "esse", "est", "et", "eu", "ex", "excepteur", "exercitation", "fugiat", "id", "in", "incididunt", "ipsum", "irure", "labore", "laboris", "laborum", "Lorem", "magna", "minim", "mollit", "nisi", "non", "nostrud", "nulla", "occaecat", "officia", "pariatur", "proident", "qui", "quis", "reprehenderit", "sint", "sit", "sunt", "tempor", "ullamco", "ut", "velit", "veniam", "voluptate" ]; function Oe(i, e) { if (i <= 0) return ""; const t = [], n = Math.floor(e * 982451653); for (let o = 0; o < i; o++) { const r = (n + o * 2654435761) % Math.pow(2, 32), s = Math.floor( r / Math.pow(2, 32) * G.length ); t.push(G[s]); } return t.length > 0 && (t[0] = t[0].charAt(0).toUpperCase() + t[0].slice(1)), t.join(" ") + "."; } const Ee = "data-pz-page-break", Ce = "pageBreak", xe = { name: Ce, order: 1, onVisitElement: (i, e, t, n) => { e.currentNode.getNode().getAttribute(Ee) === "true" && (t.markPageAsFull(), n.result = p.FullNodePlaced); } }, He = [xe], $e = { name: "debug", order: Number.MAX_SAFE_INTEGER, onNewPage: (i, e) => { const t = e.getPageState().currentPage.getNode(); t.classList.contains(M) || t.classList.add(M), t.setAttribute(`${x}-element`, "page"), t.setAttribute( `${x}-height`, e.getPageState().pageHeight.toString() ); } }, we = "sectionToc"; class Be { state = []; name = we; order = 1; onVisitElement = (e, t, n) => { { const o = t.currentNode.getNode(), r = this.getHeadingLevel(o); if (!r || !o.textContent) return; this.state.push({ sectionId: e, pageNumber: n.getPageState().pageIndex + 1, title: o.textContent, level: r }); } }; getHeadingLevel(e) { const t = e.tagName; return /^H[1-6]$/.test(t) ? parseInt(t.charAt(1), 10) : null; } } const We = "__PAPRIZE_IS_READY", De = "__PAPRIZE_READ_JSON_DATA_FILE"; export { R as Paginator, Be as SectionTocPlugin, p as SplitResult, x as attributePrefix, Re as configToAttributeMap, Oe as createLoremIpsumParagraph, Pe as currentElementClassName, Te as currentTextClassName, $e as debugPlugin, He as defaultPlugins, Le as enableDebugMode, le as getVisibleHeight, ke as ignoredElementClassName, Ae as ignoredTextClassName, Fe as isDebugMode, X as isElement, se as isTextNode, h as logger, re as loggerName, Ie as moveOffscreen, Ee as pageBreakAttributeName, xe as pageBreakPlugin, Ce as pageBreakPluginName, M as pageClassName, We as paprize_isReady, De as paprize_readJsonDataFile, we as sectionTocName, ae as tempContainerClassName }; //# sourceMappingURL=paprize-core.js.map