(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.PagedPolyfill = factory()); }(this, function () { 'use strict'; function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } function getCjsExportFromNamespace (n) { return n && n['default'] || n; } var isImplemented = function () { var assign = Object.assign, obj; if (typeof assign !== "function") return false; obj = { foo: "raz" }; assign(obj, { bar: "dwa" }, { trzy: "trzy" }); return (obj.foo + obj.bar + obj.trzy) === "razdwatrzy"; }; var isImplemented$1 = function () { try { return true; } catch (e) { return false; } }; // eslint-disable-next-line no-empty-function var noop = function () {}; var _undefined = noop(); // Support ES3 engines var isValue = function (val) { return (val !== _undefined) && (val !== null); }; var keys = Object.keys; var shim = function (object) { return keys(isValue(object) ? Object(object) : object); }; var keys$1 = isImplemented$1() ? Object.keys : shim; var validValue = function (value) { if (!isValue(value)) throw new TypeError("Cannot use null or undefined"); return value; }; var max = Math.max; var shim$1 = function (dest, src /*, …srcn*/) { var error, i, length = max(arguments.length, 2), assign; dest = Object(validValue(dest)); assign = function (key) { try { dest[key] = src[key]; } catch (e) { if (!error) error = e; } }; for (i = 1; i < length; ++i) { src = arguments[i]; keys$1(src).forEach(assign); } if (error !== undefined) throw error; return dest; }; var assign = isImplemented() ? Object.assign : shim$1; var forEach = Array.prototype.forEach, create = Object.create; var process = function (src, obj) { var key; for (key in src) obj[key] = src[key]; }; // eslint-disable-next-line no-unused-vars var normalizeOptions = function (opts1 /*, …options*/) { var result = create(null); forEach.call(arguments, function (options) { if (!isValue(options)) return; process(Object(options), result); }); return result; }; // Deprecated var isCallable = function (obj) { return typeof obj === "function"; }; var str = "razdwatrzy"; var isImplemented$2 = function () { if (typeof str.contains !== "function") return false; return (str.contains("dwa") === true) && (str.contains("foo") === false); }; var indexOf = String.prototype.indexOf; var shim$2 = function (searchString/*, position*/) { return indexOf.call(this, searchString, arguments[1]) > -1; }; var contains = isImplemented$2() ? String.prototype.contains : shim$2; var d_1 = createCommonjsModule(function (module) { var d; d = module.exports = function (dscr, value/*, options*/) { var c, e, w, options, desc; if ((arguments.length < 2) || (typeof dscr !== 'string')) { options = value; value = dscr; dscr = null; } else { options = arguments[2]; } if (dscr == null) { c = w = true; e = false; } else { c = contains.call(dscr, 'c'); e = contains.call(dscr, 'e'); w = contains.call(dscr, 'w'); } desc = { value: value, configurable: c, enumerable: e, writable: w }; return !options ? desc : assign(normalizeOptions(options), desc); }; d.gs = function (dscr, get, set/*, options*/) { var c, e, options, desc; if (typeof dscr !== 'string') { options = set; set = get; get = dscr; dscr = null; } else { options = arguments[3]; } if (get == null) { get = undefined; } else if (!isCallable(get)) { options = get; get = set = undefined; } else if (set == null) { set = undefined; } else if (!isCallable(set)) { options = set; set = undefined; } if (dscr == null) { c = true; e = false; } else { c = contains.call(dscr, 'c'); e = contains.call(dscr, 'e'); } desc = { get: get, set: set, configurable: c, enumerable: e }; return !options ? desc : assign(normalizeOptions(options), desc); }; }); var validCallable = function (fn) { if (typeof fn !== "function") throw new TypeError(fn + " is not a function"); return fn; }; var eventEmitter = createCommonjsModule(function (module, exports) { var apply = Function.prototype.apply, call = Function.prototype.call , create = Object.create, defineProperty = Object.defineProperty , defineProperties = Object.defineProperties , hasOwnProperty = Object.prototype.hasOwnProperty , descriptor = { configurable: true, enumerable: false, writable: true } , on, once, off, emit, methods, descriptors, base; on = function (type, listener) { var data; validCallable(listener); if (!hasOwnProperty.call(this, '__ee__')) { data = descriptor.value = create(null); defineProperty(this, '__ee__', descriptor); descriptor.value = null; } else { data = this.__ee__; } if (!data[type]) data[type] = listener; else if (typeof data[type] === 'object') data[type].push(listener); else data[type] = [data[type], listener]; return this; }; once = function (type, listener) { var once, self; validCallable(listener); self = this; on.call(this, type, once = function () { off.call(self, type, once); apply.call(listener, this, arguments); }); once.__eeOnceListener__ = listener; return this; }; off = function (type, listener) { var data, listeners, candidate, i; validCallable(listener); if (!hasOwnProperty.call(this, '__ee__')) return this; data = this.__ee__; if (!data[type]) return this; listeners = data[type]; if (typeof listeners === 'object') { for (i = 0; (candidate = listeners[i]); ++i) { if ((candidate === listener) || (candidate.__eeOnceListener__ === listener)) { if (listeners.length === 2) data[type] = listeners[i ? 0 : 1]; else listeners.splice(i, 1); } } } else { if ((listeners === listener) || (listeners.__eeOnceListener__ === listener)) { delete data[type]; } } return this; }; emit = function (type) { var i, l, listener, listeners, args; if (!hasOwnProperty.call(this, '__ee__')) return; listeners = this.__ee__[type]; if (!listeners) return; if (typeof listeners === 'object') { l = arguments.length; args = new Array(l - 1); for (i = 1; i < l; ++i) args[i - 1] = arguments[i]; listeners = listeners.slice(); for (i = 0; (listener = listeners[i]); ++i) { apply.call(listener, this, args); } } else { switch (arguments.length) { case 1: call.call(listeners, this); break; case 2: call.call(listeners, this, arguments[1]); break; case 3: call.call(listeners, this, arguments[1], arguments[2]); break; default: l = arguments.length; args = new Array(l - 1); for (i = 1; i < l; ++i) { args[i - 1] = arguments[i]; } apply.call(listeners, this, args); } } }; methods = { on: on, once: once, off: off, emit: emit }; descriptors = { on: d_1(on), once: d_1(once), off: d_1(off), emit: d_1(emit) }; base = defineProperties({}, descriptors); module.exports = exports = function (o) { return (o == null) ? create(base) : defineProperties(Object(o), descriptors); }; exports.methods = methods; }); var eventEmitter_1 = eventEmitter.methods; function getBoundingClientRect(element) { if (!element) { return; } let rect; if (typeof element.getBoundingClientRect !== "undefined") { rect = element.getBoundingClientRect(); } else { let range = document.createRange(); range.selectNode(element); rect = range.getBoundingClientRect(); } return rect; } function getClientRects(element) { if (!element) { return; } let rect; if (typeof element.getClientRects !== "undefined") { rect = element.getClientRects(); } else { let range = document.createRange(); range.selectNode(element); rect = range.getClientRects(); } return rect; } /** * Generates a UUID * based on: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript * @returns {string} uuid */ function UUID() { var d = new Date().getTime(); if (typeof performance !== "undefined" && typeof performance.now === "function"){ d += performance.now(); //use high-precision timer if available } return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16); }); } function attr(element, attributes) { for (var i = 0; i < attributes.length; i++) { if(element.hasAttribute(attributes[i])) { return element.getAttribute(attributes[i]); } } } /* Based on by https://mths.be/cssescape v1.5.1 by @mathias | MIT license * Allows # and . */ function querySelectorEscape(value) { if (arguments.length == 0) { throw new TypeError("`CSS.escape` requires an argument."); } var string = String(value); var length = string.length; var index = -1; var codeUnit; var result = ""; var firstCodeUnit = string.charCodeAt(0); while (++index < length) { codeUnit = string.charCodeAt(index); // Note: there’s no need to special-case astral symbols, surrogate // pairs, or lone surrogates. // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER // (U+FFFD). if (codeUnit == 0x0000) { result += "\uFFFD"; continue; } if ( // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is // U+007F, […] (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || // If the character is the first character and is in the range [0-9] // (U+0030 to U+0039), […] (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || // If the character is the second character and is in the range [0-9] // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] ( index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002D ) ) { // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point result += "\\" + codeUnit.toString(16) + " "; continue; } if ( // If the character is the first character and is a `-` (U+002D), and // there is no second character, […] index == 0 && length == 1 && codeUnit == 0x002D ) { result += "\\" + string.charAt(index); continue; } // If the character is not handled by one of the above rules and is // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to // U+005A), or [a-z] (U+0061 to U+007A), […] if ( codeUnit >= 0x0080 || codeUnit == 0x002D || codeUnit == 0x005F || codeUnit == 35 || // Allow # codeUnit == 46 || // Allow . codeUnit >= 0x0030 && codeUnit <= 0x0039 || codeUnit >= 0x0041 && codeUnit <= 0x005A || codeUnit >= 0x0061 && codeUnit <= 0x007A ) { // the character itself result += string.charAt(index); continue; } // Otherwise, the escaped character. // https://drafts.csswg.org/cssom/#escape-a-character result += "\\" + string.charAt(index); } return result; } /** * Creates a new pending promise and provides methods to resolve or reject it. * From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible * @returns {object} defered */ function defer() { this.resolve = null; this.reject = null; this.id = UUID(); this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); Object.freeze(this); } const requestIdleCallback = typeof window !== "undefined" && ("requestIdleCallback" in window ? window.requestIdleCallback : window.requestAnimationFrame); function CSSValueToString(obj) { return obj.value + (obj.unit || ""); } function isElement(node) { return node && node.nodeType === 1; } function isText(node) { return node && node.nodeType === 3; } function *walk(start, limiter) { let node = start; while (node) { yield node; if (node.childNodes.length) { node = node.firstChild; } else if (node.nextSibling) { if (limiter && node === limiter) { node = undefined; break; } node = node.nextSibling; } else { while (node) { node = node.parentNode; if (limiter && node === limiter) { node = undefined; break; } if (node && node.nextSibling) { node = node.nextSibling; break; } } } } } function nodeAfter(node, limiter) { let after = node; if (after.nextSibling) { if (limiter && node === limiter) { return; } after = after.nextSibling; } else { while (after) { after = after.parentNode; if (limiter && after === limiter) { after = undefined; break; } if (after && after.nextSibling) { after = after.nextSibling; break; } } } return after; } function nodeBefore(node, limiter) { let before = node; if (before.previousSibling) { if (limiter && node === limiter) { return; } before = before.previousSibling; } else { while (before) { before = before.parentNode; if (limiter && before === limiter) { before = undefined; break; } if (before && before.previousSibling) { before = before.previousSibling; break; } } } return before; } function elementAfter(node, limiter) { let after = nodeAfter(node); while (after && after.nodeType !== 1) { after = nodeAfter(after); } return after; } function rebuildAncestors(node) { let parent, ancestor; let ancestors = []; let added = []; let fragment = document.createDocumentFragment(); // Gather all ancestors let element = node; while(element.parentNode && element.parentNode.nodeType === 1) { ancestors.unshift(element.parentNode); element = element.parentNode; } for (var i = 0; i < ancestors.length; i++) { ancestor = ancestors[i]; parent = ancestor.cloneNode(false); parent.setAttribute("data-split-from", parent.getAttribute("data-ref")); // ancestor.setAttribute("data-split-to", parent.getAttribute("data-ref")); if (parent.hasAttribute("id")) { let dataID = parent.getAttribute("id"); parent.setAttribute("data-id", dataID); parent.removeAttribute("id"); } // This is handled by css :not, but also tidied up here if (parent.hasAttribute("data-break-before")) { parent.removeAttribute("data-break-before"); } if (parent.hasAttribute("data-previous-break-after")) { parent.removeAttribute("data-previous-break-after"); } if (added.length) { let container = added[added.length-1]; container.appendChild(parent); } else { fragment.appendChild(parent); } added.push(parent); } added = undefined; return fragment; } /* export function split(bound, cutElement, breakAfter) { let needsRemoval = []; let index = indexOf(cutElement); if (!breakAfter && index === 0) { return; } if (breakAfter && index === (cutElement.parentNode.children.length - 1)) { return; } // Create a fragment with rebuilt ancestors let fragment = rebuildAncestors(cutElement); // Clone cut if (!breakAfter) { let clone = cutElement.cloneNode(true); let ref = cutElement.parentNode.getAttribute('data-ref'); let parent = fragment.querySelector("[data-ref='" + ref + "']"); parent.appendChild(clone); needsRemoval.push(cutElement); } // Remove all after cut let next = nodeAfter(cutElement, bound); while (next) { let clone = next.cloneNode(true); let ref = next.parentNode.getAttribute('data-ref'); let parent = fragment.querySelector("[data-ref='" + ref + "']"); parent.appendChild(clone); needsRemoval.push(next); next = nodeAfter(next, bound); } // Remove originals needsRemoval.forEach((node) => { if (node) { node.remove(); } }); // Insert after bounds bound.parentNode.insertBefore(fragment, bound.nextSibling); return [bound, bound.nextSibling]; } */ function needsBreakBefore(node) { if( typeof node !== "undefined" && typeof node.dataset !== "undefined" && typeof node.dataset.breakBefore !== "undefined" && (node.dataset.breakBefore === "always" || node.dataset.breakBefore === "page" || node.dataset.breakBefore === "left" || node.dataset.breakBefore === "right" || node.dataset.breakBefore === "recto" || node.dataset.breakBefore === "verso") ) { return true; } return false; } function needsPreviousBreakAfter(node) { if( typeof node !== "undefined" && typeof node.dataset !== "undefined" && typeof node.dataset.previousBreakAfter !== "undefined" && (node.dataset.previousBreakAfter === "always" || node.dataset.previousBreakAfter === "page" || node.dataset.previousBreakAfter === "left" || node.dataset.previousBreakAfter === "right" || node.dataset.previousBreakAfter === "recto" || node.dataset.previousBreakAfter === "verso") ) { return true; } return false; } function needsPageBreak(node) { if( typeof node !== "undefined" && typeof node.dataset !== "undefined" && (node.dataset.page || node.dataset.afterPage) ) { return true; } return false; } function *words(node) { let currentText = node.nodeValue; let max = currentText.length; let currentOffset = 0; let currentLetter; let range; while(currentOffset < max) { currentLetter = currentText[currentOffset]; if (/^[\S\u202F\u00A0]$/.test(currentLetter)) { if (!range) { range = document.createRange(); range.setStart(node, currentOffset); } } else { if (range) { range.setEnd(node, currentOffset); yield range; range = undefined; } } currentOffset += 1; } if (range) { range.setEnd(node, currentOffset); yield range; range = undefined; } } function *letters(wordRange) { let currentText = wordRange.startContainer; let max = currentText.length; let currentOffset = wordRange.startOffset; // let currentLetter; let range; while(currentOffset < max) { // currentLetter = currentText[currentOffset]; range = document.createRange(); range.setStart(currentText, currentOffset); range.setEnd(currentText, currentOffset+1); yield range; currentOffset += 1; } } function isContainer(node) { let container; if (typeof node.tagName === "undefined") { return true; } if (node.style.display === "none") { return false; } switch (node.tagName) { // Inline case "A": case "ABBR": case "ACRONYM": case "B": case "BDO": case "BIG": case "BR": case "BUTTON": case "CITE": case "CODE": case "DFN": case "EM": case "I": case "IMG": case "INPUT": case "KBD": case "LABEL": case "MAP": case "OBJECT": case "Q": case "SAMP": case "SCRIPT": case "SELECT": case "SMALL": case "SPAN": case "STRONG": case "SUB": case "SUP": case "TEXTAREA": case "TIME": case "TT": case "VAR": case "P": case "H1": case "H2": case "H3": case "H4": case "H5": case "H6": case "FIGCAPTION": case "BLOCKQUOTE": case "PRE": case "LI": case "TR": case "DT": case "DD": case "VIDEO": case "CANVAS": container = false; break; default: container = true; } return container; } function cloneNode(n, deep=false) { return n.cloneNode(deep); } function findElement(node, doc) { const ref = node.getAttribute("data-ref"); return findRef(ref, doc); } function findRef(ref, doc) { return doc.querySelector(`[data-ref='${ref}']`); } function validNode(node) { if (isText(node)) { return true; } if (isElement(node) && node.dataset.ref) { return true; } return false; } function prevValidNode(node) { while (!validNode(node)) { if (node.previousSibling) { node = node.previousSibling; } else { node = node.parentNode; } if (!node) { break; } } return node; } function indexOf$1(node) { let parent = node.parentNode; if (!parent) { return 0; } return Array.prototype.indexOf.call(parent.childNodes, node); } function child(node, index) { return node.childNodes[index]; } function hasContent(node) { if (isElement(node)) { return true; } else if (isText(node) && node.textContent.trim().length) { return true; } return false; } function indexOfTextNode(node, parent) { if (!isText(node)) { return -1; } let nodeTextContent = node.textContent; let child; let index = -1; for (var i = 0; i < parent.childNodes.length; i++) { child = parent.childNodes[i]; if (child.nodeType === 3) { let text = parent.childNodes[i].textContent; if (text.includes(nodeTextContent)) { index = i; break; } } } return index; } /** * Hooks allow for injecting functions that must all complete in order before finishing * They will execute in parallel but all must finish before continuing * Functions may return a promise if they are asycn. * From epubjs/src/utils/hooks * @param {any} context scope of this * @example this.content = new Hook(this); */ class Hook { constructor(context){ this.context = context || this; this.hooks = []; } /** * Adds a function to be run before a hook completes * @example this.content.register(function(){...}); * @return {undefined} void */ register(){ for(var i = 0; i < arguments.length; ++i) { if (typeof arguments[i] === "function") { this.hooks.push(arguments[i]); } else { // unpack array for(var j = 0; j < arguments[i].length; ++j) { this.hooks.push(arguments[i][j]); } } } } /** * Triggers a hook to run all functions * @example this.content.trigger(args).then(function(){...}); * @return {Promise} results */ trigger(){ var args = arguments; var context = this.context; var promises = []; this.hooks.forEach(function(task) { var executing = task.apply(context, args); if(executing && typeof executing["then"] === "function") { // Task is a function that returns a promise promises.push(executing); } // Otherwise Task resolves immediately, add resolved promise with result promises.push(new Promise((resolve, reject) => { resolve(executing); })); }); return Promise.all(promises); } /** * Triggers a hook to run all functions synchronously * @example this.content.trigger(args).then(function(){...}); * @return {Array} results */ triggerSync(){ var args = arguments; var context = this.context; var results = []; this.hooks.forEach(function(task) { var executing = task.apply(context, args); results.push(executing); }); return results; } // Adds a function to be run before a hook completes list(){ return this.hooks; } clear(){ return this.hooks = []; } } const MAX_CHARS_PER_BREAK = 1500; /** * Layout * @class */ class Layout { constructor(element, hooks, maxChars) { this.element = element; this.bounds = this.element.getBoundingClientRect(); if (hooks) { this.hooks = hooks; } else { this.hooks = {}; this.hooks.layout = new Hook(); this.hooks.renderNode = new Hook(); this.hooks.layoutNode = new Hook(); this.hooks.beforeOverflow = new Hook(); this.hooks.onOverflow = new Hook(); this.hooks.onBreakToken = new Hook(); } this.maxChars = maxChars || MAX_CHARS_PER_BREAK; } async renderTo(wrapper, source, breakToken, bounds=this.bounds) { let start = this.getStart(source, breakToken); let walker = walk(start, source); let node; let done; let next; let hasRenderedContent = false; let newBreakToken; let length = 0; while (!done && !newBreakToken) { next = walker.next(); node = next.value; done = next.done; if (!node) { this.hooks && this.hooks.layout.trigger(wrapper, this); let imgs = wrapper.querySelectorAll("img"); if (imgs.length) { await this.waitForImages(imgs); } newBreakToken = this.findBreakToken(wrapper, source, bounds); return newBreakToken; } this.hooks && this.hooks.layoutNode.trigger(node); // Check if the rendered element has a break set if (hasRenderedContent && this.shouldBreak(node)) { this.hooks && this.hooks.layout.trigger(wrapper, this); let imgs = wrapper.querySelectorAll("img"); if (imgs.length) { await this.waitForImages(imgs); } newBreakToken = this.findBreakToken(wrapper, source, bounds); if (!newBreakToken) { newBreakToken = this.breakAt(node); } length = 0; break; } // Should the Node be a shallow or deep clone let shallow = isContainer(node); let rendered = this.append(node, wrapper, breakToken, shallow); length += rendered.textContent.length; // Check if layout has content yet if (!hasRenderedContent) { hasRenderedContent = hasContent(node); } // Skip to the next node if a deep clone was rendered if (!shallow) { walker = walk(nodeAfter(node, source), source); } // Only check x characters if (length >= this.maxChars) { this.hooks && this.hooks.layout.trigger(wrapper, this); let imgs = wrapper.querySelectorAll("img"); if (imgs.length) { await this.waitForImages(imgs); } newBreakToken = this.findBreakToken(wrapper, source, bounds); if (newBreakToken) { length = 0; } } } return newBreakToken; } breakAt(node, offset=0) { return { node, offset }; } shouldBreak(node) { let previousSibling = node.previousSibling; let parentNode = node.parentNode; let parentBreakBefore = needsBreakBefore(node) && parentNode && !previousSibling && needsBreakBefore(parentNode); let doubleBreakBefore; if (parentBreakBefore) { doubleBreakBefore = node.dataset.breakBefore === parentNode.dataset.breakBefore; } return !doubleBreakBefore && needsBreakBefore(node) || needsPreviousBreakAfter(node) || needsPageBreak(node); } getStart(source, breakToken) { let start; let node = breakToken && breakToken.node; if (node) { start = node; } else { start = source.firstChild; } return start; } append(node, dest, breakToken, shallow=true, rebuild=true) { let clone = cloneNode(node, !shallow); if (node.parentNode && isElement(node.parentNode)) { let parent = findElement(node.parentNode, dest); // Rebuild chain if (parent) { parent.appendChild(clone); } else if (rebuild) { let fragment = rebuildAncestors(node); parent = findElement(node.parentNode, fragment); if (!parent) { dest.appendChild(clone); } else if (breakToken && isText(breakToken.node) && breakToken.offset > 0) { clone.textContent = clone.textContent.substring(breakToken.offset); parent.appendChild(clone); } else { parent.appendChild(clone); } dest.appendChild(fragment); } else { dest.appendChild(clone); } } else { dest.appendChild(clone); } let nodeHooks = this.hooks.renderNode.triggerSync(clone, node); nodeHooks.forEach((newNode) => { if (typeof newNode != "undefined") { clone = newNode; } }); return clone; } async waitForImages(imgs) { let results = Array.from(imgs).map(async (img) => { return this.awaitImageLoaded(img); }); await Promise.all(results); } async awaitImageLoaded(image) { return new Promise(resolve => { if (image.complete !== true) { image.onload = function() { let { width, height } = window.getComputedStyle(image); resolve(width, height); }; image.onerror = function(e) { let { width, height } = window.getComputedStyle(image); resolve(width, height, e); }; } else { let { width, height } = window.getComputedStyle(image); resolve(width, height); } }); } avoidBreakInside(node, limiter) { let breakNode; if (node === limiter) { return; } while (node.parentNode) { node = node.parentNode; if (node === limiter) { break; } if(window.getComputedStyle(node)["break-inside"] === "avoid") { breakNode = node; break; } } return breakNode; } createBreakToken(overflow, rendered, source) { let container = overflow.startContainer; let offset = overflow.startOffset; let node, renderedNode, parent, index, temp; if (isElement(container)) { temp = child(container, offset); if (isElement(temp)) { renderedNode = findElement(temp, rendered); if (!renderedNode) { // Find closest element with data-ref renderedNode = findElement(prevValidNode(temp), rendered); return; } node = findElement(renderedNode, source); offset = 0; } else { renderedNode = findElement(container, rendered); if (!renderedNode) { renderedNode = findElement(prevValidNode(container), rendered); } parent = findElement(renderedNode, source); index = indexOfTextNode(temp, parent); node = child(parent, index); offset = 0; } } else { renderedNode = findElement(container.parentNode, rendered); if (!renderedNode) { renderedNode = findElement(prevValidNode(container.parentNode), rendered); } parent = findElement(renderedNode, source); index = indexOfTextNode(container, parent); if (index === -1) { return; } node = child(parent, index); offset += node.textContent.indexOf(container.textContent); } if (!node) { return; } return { node, offset }; } findBreakToken(rendered, source, bounds=this.bounds, extract=true) { let overflow = this.findOverflow(rendered, bounds); let breakToken; let overflowHooks = this.hooks.onOverflow.triggerSync(overflow, rendered, bounds, this); overflowHooks.forEach((newOverflow) => { if (typeof newOverflow != "undefined") { overflow = newOverflow; } }); if (overflow) { breakToken = this.createBreakToken(overflow, rendered, source); let breakHooks = this.hooks.onBreakToken.triggerSync(breakToken, overflow, rendered, this); breakHooks.forEach((newToken) => { if (typeof newToken != "undefined") { breakToken = newToken; } }); if (breakToken && breakToken.node && extract) { this.removeOverflow(overflow); } } return breakToken; } hasOverflow(element, bounds=this.bounds) { let constrainingElement = element && element.parentNode; // this gets the element, instead of the wrapper for the width workaround let { width } = element.getBoundingClientRect(); let scrollWidth = constrainingElement ? constrainingElement.scrollWidth : 0; return Math.max(Math.floor(width), scrollWidth) > Math.round(bounds.width); } findOverflow(rendered, bounds=this.bounds) { if (!this.hasOverflow(rendered, bounds)) return; let start = Math.round(bounds.left); let end = Math.round(bounds.right); let range; let walker = walk(rendered.firstChild, rendered); // Find Start let next, done, node, offset, skip, breakAvoid, prev, br; while (!done) { next = walker.next(); done = next.done; node = next.value; skip = false; breakAvoid = false; prev = undefined; br = undefined; if (node) { let pos = getBoundingClientRect(node); let left = Math.floor(pos.left); let right = Math.floor(pos.right); if (!range && left >= end) { // Check if it is a float let isFloat = false; if (isElement(node) ) { let styles = window.getComputedStyle(node); isFloat = styles.getPropertyValue("float") !== "none"; skip = styles.getPropertyValue("break-inside") === "avoid"; breakAvoid = node.dataset.breakBefore === "avoid" || node.dataset.previousBreakAfter === "avoid"; prev = breakAvoid && nodeBefore(node, rendered); br = node.tagName === "BR" || node.tagName === "WBR"; } if (prev) { range = document.createRange(); range.setStartBefore(prev); break; } if (!br && !isFloat && isElement(node)) { range = document.createRange(); range.setStartBefore(node); break; } if (isText(node) && node.textContent.trim().length) { range = document.createRange(); range.setStartBefore(node); break; } } if (!range && isText(node) && node.textContent.trim().length && window.getComputedStyle(node.parentNode)["break-inside"] !== "avoid") { let rects = getClientRects(node); let rect; left = 0; for (var i = 0; i != rects.length; i++) { rect = rects[i]; if (rect.width > 0 && (!left || rect.left > left)) { left = rect.left; } } if(left >= end) { range = document.createRange(); offset = this.textBreak(node, start, end); if (!offset) { range = undefined; } else { range.setStart(node, offset); } break; } } // Skip children if (skip || right < end) { next = nodeAfter(node, rendered); if (next) { walker = walk(next, rendered); } } } } // Find End if (range) { range.setEndAfter(rendered.lastChild); return range; } } findEndToken(rendered, source, bounds=this.bounds) { if (rendered.childNodes.length === 0) { return; } let lastChild = rendered.lastChild; let lastNodeIndex; while (lastChild && lastChild.lastChild) { if (!validNode(lastChild)) { // Only get elements with refs lastChild = lastChild.previousSibling; } else if(!validNode(lastChild.lastChild)) { // Deal with invalid dom items lastChild = prevValidNode(lastChild.lastChild); break; } else { lastChild = lastChild.lastChild; } } if (isText(lastChild)) { if (lastChild.parentNode.dataset.ref) { lastNodeIndex = indexOf$1(lastChild); lastChild = lastChild.parentNode; } else { lastChild = lastChild.previousSibling; } } let original = findElement(lastChild, source); if (lastNodeIndex) { original = original.childNodes[lastNodeIndex]; } let after = nodeAfter(original); return this.breakAt(after); } textBreak(node, start, end) { let wordwalker = words(node); let left = 0; let right = 0; let word, next, done, pos; let offset; while (!done) { next = wordwalker.next(); word = next.value; done = next.done; if (!word) { break; } pos = getBoundingClientRect(word); left = Math.floor(pos.left); right = Math.floor(pos.right); if (left >= end) { offset = word.startOffset; break; } if (right > end) { let letterwalker = letters(word); let letter, nextLetter, doneLetter; while (!doneLetter) { nextLetter = letterwalker.next(); letter = nextLetter.value; doneLetter = nextLetter.done; if (!letter) { break; } pos = getBoundingClientRect(letter); left = Math.floor(pos.left); if (left >= end) { offset = letter.startOffset; done = true; break; } } } } return offset; } removeOverflow(overflow) { let {startContainer} = overflow; let extracted = overflow.extractContents(); this.hyphenateAtBreak(startContainer); return extracted; } hyphenateAtBreak(startContainer) { if (isText(startContainer)) { let startText = startContainer.textContent; let prevLetter = startText[startText.length-1]; // Add a hyphen if previous character is a letter or soft hyphen if (/^\w|\u00AD$/.test(prevLetter)) { startContainer.parentNode.classList.add("pagedjs_hyphen"); startContainer.textContent += "\u2011"; } } } } eventEmitter(Layout.prototype); /** * Render a page * @class */ class Page { constructor(pagesArea, pageTemplate, blank, hooks) { this.pagesArea = pagesArea; this.pageTemplate = pageTemplate; this.blank = blank; this.width = undefined; this.height = undefined; this.hooks = hooks; // this.element = this.create(this.pageTemplate); } create(template, after) { //let documentFragment = document.createRange().createContextualFragment( TEMPLATE ); //let page = documentFragment.children[0]; let clone = document.importNode(this.pageTemplate.content, true); let page, index; if (after) { this.pagesArea.insertBefore(clone, after.nextElementSibling); index = Array.prototype.indexOf.call(this.pagesArea.children, after.nextElementSibling); page = this.pagesArea.children[index]; } else { this.pagesArea.appendChild(clone); page = this.pagesArea.lastChild; } let pagebox = page.querySelector(".pagedjs_pagebox"); let area = page.querySelector(".pagedjs_page_content"); let size = area.getBoundingClientRect(); area.style.columnWidth = Math.round(size.width) + "px"; area.style.columnGap = "calc(var(--pagedjs-margin-right) + var(--pagedjs-margin-left))"; // area.style.overflow = "scroll"; this.width = Math.round(size.width); this.height = Math.round(size.height); this.element = page; this.pagebox = pagebox; this.area = area; return page; } createWrapper() { let wrapper = document.createElement("div"); this.area.appendChild(wrapper); this.wrapper = wrapper; return wrapper; } index(pgnum) { this.position = pgnum; let page = this.element; // let pagebox = this.pagebox; let index = pgnum+1; let id = `page-${index}`; this.id = id; // page.dataset.pageNumber = index; page.dataset.pageNumber = index; page.setAttribute('id', id); if (this.name) { page.classList.add("pagedjs_" + this.name + "_page"); } if (this.blank) { page.classList.add("pagedjs_blank_page"); } if (pgnum === 0) { page.classList.add("pagedjs_first_page"); } if (pgnum % 2 !== 1) { page.classList.remove("pagedjs_left_page"); page.classList.add("pagedjs_right_page"); } else { page.classList.remove("pagedjs_right_page"); page.classList.add("pagedjs_left_page"); } } /* size(width, height) { if (width === this.width && height === this.height) { return; } this.width = width; this.height = height; this.element.style.width = Math.round(width) + "px"; this.element.style.height = Math.round(height) + "px"; this.element.style.columnWidth = Math.round(width) + "px"; } */ async layout(contents, breakToken, maxChars) { this.clear(); this.startToken = breakToken; this.layoutMethod = new Layout(this.area, this.hooks, maxChars); let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); this.addListeners(contents); this.endToken = newBreakToken; return newBreakToken; } async append(contents, breakToken) { if (!this.layoutMethod) { return this.layout(contents, breakToken); } let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken); this.endToken = newBreakToken; return newBreakToken; } getByParent(ref, entries) { let e; for (var i = 0; i < entries.length; i++) { e = entries[i]; if(e.dataset.ref === ref) { return e; } } } onOverflow(func) { this._onOverflow = func; } onUnderflow(func) { this._onUnderflow = func; } clear() { this.removeListeners(); this.wrapper && this.wrapper.remove(); this.createWrapper(); } addListeners(contents) { if (typeof ResizeObserver !== "undefined") { this.addResizeObserver(contents); } else { this._checkOverflowAfterResize = this.checkOverflowAfterResize.bind(this, contents); this.element.addEventListener("overflow", this._checkOverflowAfterResize, false); this.element.addEventListener("underflow", this._checkOverflowAfterResize, false); } // TODO: fall back to mutation observer? this._onScroll = function() { if(this.listening) { this.element.scrollLeft = 0; } }.bind(this); // Keep scroll left from changing this.element.addEventListener("scroll", this._onScroll); this.listening = true; return true; } removeListeners() { this.listening = false; if (typeof ResizeObserver !== "undefined" && this.ro) { this.ro.disconnect(); } else if (this.element) { this.element.removeEventListener("overflow", this._checkOverflowAfterResize, false); this.element.removeEventListener("underflow", this._checkOverflowAfterResize, false); } this.element &&this.element.removeEventListener("scroll", this._onScroll); } addResizeObserver(contents) { let wrapper = this.wrapper; let prevHeight = wrapper.getBoundingClientRect().height; this.ro = new ResizeObserver( entries => { if (!this.listening) { return; } for (let entry of entries) { const cr = entry.contentRect; if (cr.height > prevHeight) { this.checkOverflowAfterResize(contents); prevHeight = wrapper.getBoundingClientRect().height; } else if (cr.height < prevHeight ) { // TODO: calc line height && (prevHeight - cr.height) >= 22 this.checkUnderflowAfterResize(contents); prevHeight = cr.height; } } }); this.ro.observe(wrapper); } checkOverflowAfterResize(contents) { if (!this.listening || !this.layoutMethod) { return; } let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents); if (newBreakToken) { this.endToken = newBreakToken; this._onOverflow && this._onOverflow(newBreakToken); } } checkUnderflowAfterResize(contents) { if (!this.listening || !this.layoutMethod) { return; } let endToken = this.layoutMethod.findEndToken(this.wrapper, contents); // let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents); if (endToken) { this._onUnderflow && this._onUnderflow(endToken); } } destroy() { this.removeListeners(); this.element.remove(); this.element = undefined; this.wrapper = undefined; } } eventEmitter(Page.prototype); /** * Render a flow of text offscreen * @class */ class ContentParser { constructor(content, cb) { if (content && content.nodeType) { // handle dom this.dom = this.add(content); } else if (typeof content === "string") { this.dom = this.parse(content); } return this.dom; } parse(markup, mime) { let range = document.createRange(); let fragment = range.createContextualFragment(markup); this.addRefs(fragment); this.removeEmpty(fragment); return fragment; } add(contents) { // let fragment = document.createDocumentFragment(); // // let children = [...contents.childNodes]; // for (let child of children) { // let clone = child.cloneNode(true); // fragment.appendChild(clone); // } this.addRefs(contents); this.removeEmpty(contents); return contents; } addRefs(content) { var treeWalker = document.createTreeWalker( content, NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } }, false ); let node = treeWalker.nextNode(); while(node) { if (!node.hasAttribute("data-ref")) { let uuid = UUID(); node.setAttribute("data-ref", uuid); } if (node.id) { node.setAttribute("data-id", node.id); } // node.setAttribute("data-children", node.childNodes.length); // node.setAttribute("data-text", node.textContent.trim().length); node = treeWalker.nextNode(); } } removeEmpty(content) { var treeWalker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { // Only remove more than a single space if (node.textContent.length > 1 && !node.textContent.trim()) { // Don't touch whitespace if text is preformated let parent = node.parentNode; let pre = isElement(parent) && parent.closest("pre"); if (pre) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } else { return NodeFilter.FILTER_REJECT; } } }, false ); let node; let current; node = treeWalker.nextNode(); while(node) { current = node; node = treeWalker.nextNode(); // if (!current.nextSibling || (current.nextSibling && current.nextSibling.nodeType === 1)) { current.parentNode.removeChild(current); // } } } find(ref) { return this.refs[ref]; } // isWrapper(element) { // return wrappersRegex.test(element.nodeName); // } isText(node) { return node.tagName === "TAG"; } isElement(node) { return node.nodeType === 1; } hasChildren(node) { return node.childNodes && node.childNodes.length; } destroy() { this.refs = undefined; this.dom = undefined; } } /** * Queue for handling tasks one at a time * @class * @param {scope} context what this will resolve to in the tasks */ class Queue { constructor(context){ this._q = []; this.context = context; this.tick = requestAnimationFrame; this.running = false; this.paused = false; } /** * Add an item to the queue * @return {Promise} enqueued */ enqueue() { var deferred, promise; var queued; var task = [].shift.call(arguments); var args = arguments; // Handle single args without context // if(args && !Array.isArray(args)) { // args = [args]; // } if(!task) { throw new Error("No Task Provided"); } if(typeof task === "function"){ deferred = new defer(); promise = deferred.promise; queued = { "task" : task, "args" : args, //"context" : context, "deferred" : deferred, "promise" : promise }; } else { // Task is a promise queued = { "promise" : task }; } this._q.push(queued); // Wait to start queue flush if (this.paused == false && !this.running) { this.run(); } return queued.promise; } /** * Run one item * @return {Promise} dequeued */ dequeue(){ var inwait, task, result; if(this._q.length && !this.paused) { inwait = this._q.shift(); task = inwait.task; if(task){ // console.log(task) result = task.apply(this.context, inwait.args); if(result && typeof result["then"] === "function") { // Task is a function that returns a promise return result.then(function(){ inwait.deferred.resolve.apply(this.context, arguments); }.bind(this), function() { inwait.deferred.reject.apply(this.context, arguments); }.bind(this)); } else { // Task resolves immediately inwait.deferred.resolve.apply(this.context, result); return inwait.promise; } } else if(inwait.promise) { // Task is a promise return inwait.promise; } } else { inwait = new defer(); inwait.deferred.resolve(); return inwait.promise; } } // Run All Immediately dump(){ while(this._q.length) { this.dequeue(); } } /** * Run all tasks sequentially, at convince * @return {Promise} all run */ run(){ if(!this.running){ this.running = true; this.defered = new defer(); } this.tick.call(window, () => { if(this._q.length) { this.dequeue() .then(function(){ this.run(); }.bind(this)); } else { this.defered.resolve(); this.running = undefined; } }); // Unpause if(this.paused == true) { this.paused = false; } return this.defered.promise; } /** * Flush all, as quickly as possible * @return {Promise} ran */ flush(){ if(this.running){ return this.running; } if(this._q.length) { this.running = this.dequeue() .then(function(){ this.running = undefined; return this.flush(); }.bind(this)); return this.running; } } /** * Clear all items in wait * @return {void} */ clear(){ this._q = []; } /** * Get the number of tasks in the queue * @return {number} tasks */ length(){ return this._q.length; } /** * Pause a running queue * @return {void} */ pause(){ this.paused = true; } /** * End the queue * @return {void} */ stop(){ this._q = []; this.running = false; this.paused = true; } } const TEMPLATE = `
`; /** * Chop up text into flows * @class */ class Chunker { constructor(content, renderTo) { // this.preview = preview; this.hooks = {}; this.hooks.beforeParsed = new Hook(this); this.hooks.afterParsed = new Hook(this); this.hooks.beforePageLayout = new Hook(this); this.hooks.layout = new Hook(this); this.hooks.renderNode = new Hook(this); this.hooks.layoutNode = new Hook(this); this.hooks.onOverflow = new Hook(this); this.hooks.onBreakToken = new Hook(); this.hooks.afterPageLayout = new Hook(this); this.hooks.afterRendered = new Hook(this); this.pages = []; this._total = 0; this.q = new Queue(this); this.stopped = false; this.rendered = false; this.content = content; this.charsPerBreak = []; this.maxChars; if (content) { this.flow(content, renderTo); } } setup(renderTo) { this.pagesArea = document.createElement("div"); this.pagesArea.classList.add("pagedjs_pages"); if (renderTo) { renderTo.appendChild(this.pagesArea); } else { document.querySelector("body").appendChild(this.pagesArea); } this.pageTemplate = document.createElement("template"); this.pageTemplate.innerHTML = TEMPLATE; } async flow(content, renderTo) { let parsed; await this.hooks.beforeParsed.trigger(content, this); parsed = new ContentParser(content); this.source = parsed; this.breakToken = undefined; if (this.pagesArea && this.pageTemplate) { this.q.clear(); this.removePages(); } else { this.setup(renderTo); } this.emit("rendering", content); await this.hooks.afterParsed.trigger(parsed, this); await this.loadFonts(); let rendered = await this.render(parsed, this.breakToken); while (rendered.canceled) { this.start(); rendered = await this.render(parsed, this.breakToken); } this.rendered = true; await this.hooks.afterRendered.trigger(this.pages, this); this.emit("rendered", this.pages); return this; } // oversetPages() { // let overset = []; // for (let i = 0; i < this.pages.length; i++) { // let page = this.pages[i]; // if (page.overset) { // overset.push(page); // // page.overset = false; // } // } // return overset; // } // // async handleOverset(parsed) { // let overset = this.oversetPages(); // if (overset.length) { // console.log("overset", overset); // let index = this.pages.indexOf(overset[0]) + 1; // console.log("INDEX", index); // // // Remove pages // // this.removePages(index); // // // await this.render(parsed, overset[0].overset); // // // return this.handleOverset(parsed); // } // } async render(parsed, startAt) { let renderer = this.layout(parsed, startAt); let done = false; let result; while (!done) { result = await this.q.enqueue(() => { return this.renderAsync(renderer); }); done = result.done; } return result; } start() { this.rendered = false; this.stopped = false; } stop() { this.stopped = true; // this.q.clear(); } renderOnIdle(renderer) { return new Promise(resolve => { requestIdleCallback(async () => { if (this.stopped) { return resolve({ done: true, canceled: true }); } let result = await renderer.next(); if (this.stopped) { resolve({ done: true, canceled: true }); } else { resolve(result); } }); }); } async renderAsync(renderer) { if (this.stopped) { return { done: true, canceled: true }; } let result = await renderer.next(); if (this.stopped) { return { done: true, canceled: true }; } else { return result; } } async handleBreaks(node) { let currentPage = this.total + 1; let currentPosition = currentPage % 2 === 0 ? "left" : "right"; // TODO: Recto and Verso should reverse for rtl languages let currentSide = currentPage % 2 === 0 ? "verso" : "recto"; let previousBreakAfter; let breakBefore; let page; if (currentPage === 1) { return; } if (node && typeof node.dataset !== "undefined" && typeof node.dataset.previousBreakAfter !== "undefined") { previousBreakAfter = node.dataset.previousBreakAfter; } if (node && typeof node.dataset !== "undefined" && typeof node.dataset.breakBefore !== "undefined") { breakBefore = node.dataset.breakBefore; } if( previousBreakAfter && (previousBreakAfter === "left" || previousBreakAfter === "right") && previousBreakAfter !== currentPosition) { page = this.addPage(true); } else if( previousBreakAfter && (previousBreakAfter === "verso" || previousBreakAfter === "recto") && previousBreakAfter !== currentSide) { page = this.addPage(true); } else if( breakBefore && (breakBefore === "left" || breakBefore === "right") && breakBefore !== currentPosition) { page = this.addPage(true); } else if( breakBefore && (breakBefore === "verso" || breakBefore === "recto") && breakBefore !== currentSide) { page = this.addPage(true); } if (page) { await this.hooks.beforePageLayout.trigger(page, undefined, undefined, this); this.emit("page", page); // await this.hooks.layout.trigger(page.element, page, undefined, this); await this.hooks.afterPageLayout.trigger(page.element, page, undefined, this); this.emit("renderedPage", page); } } async *layout(content, startAt) { let breakToken = startAt || false; while (breakToken !== undefined && (true)) { if (breakToken && breakToken.node) { await this.handleBreaks(breakToken.node); } else { await this.handleBreaks(content.firstChild); } let page = this.addPage(); await this.hooks.beforePageLayout.trigger(page, content, breakToken, this); this.emit("page", page); // Layout content in the page, starting from the breakToken breakToken = await page.layout(content, breakToken, this.maxChars); await this.hooks.afterPageLayout.trigger(page.element, page, breakToken, this); this.emit("renderedPage", page); this.recoredCharLength(page.wrapper.textContent.length); yield breakToken; // Stop if we get undefined, showing we have reached the end of the content } } recoredCharLength(length) { if (length === 0) { return; } this.charsPerBreak.push(length); // Keep the length of the last few breaks if (this.charsPerBreak.length > 4) { this.charsPerBreak.shift(); } this.maxChars = this.charsPerBreak.reduce((a, b) => a + b, 0) / (this.charsPerBreak.length); } removePages(fromIndex=0) { if (fromIndex >= this.pages.length) { return; } // Remove pages for (let i = fromIndex; i < this.pages.length; i++) { this.pages[i].destroy(); } if (fromIndex > 0) { this.pages.splice(fromIndex); } else { this.pages = []; } } addPage(blank) { let lastPage = this.pages[this.pages.length - 1]; // Create a new page from the template let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks); this.pages.push(page); // Create the pages page.create(undefined, lastPage && lastPage.element); page.index(this.total); if (!blank) { // Listen for page overflow page.onOverflow((overflowToken) => { console.warn("overflow on", page.id, overflowToken); // Only reflow while rendering if (this.rendered) { return; } let index = this.pages.indexOf(page) + 1; // Stop the rendering this.stop(); // Set the breakToken to resume at this.breakToken = overflowToken; // Remove pages this.removePages(index); if (this.rendered === true) { this.rendered = false; this.q.enqueue(async () => { this.start(); await this.render(this.source, this.breakToken); this.rendered = true; }); } }); page.onUnderflow((overflowToken) => { // console.log("underflow on", page.id, overflowToken); // page.append(this.source, overflowToken); }); } this.total = this.pages.length; return page; } /* insertPage(index, blank) { let lastPage = this.pages[index]; // Create a new page from the template let page = new Page(this.pagesArea, this.pageTemplate, blank, this.hooks); let total = this.pages.splice(index, 0, page); // Create the pages page.create(undefined, lastPage && lastPage.element); page.index(index + 1); for (let i = index + 2; i < this.pages.length; i++) { this.pages[i].index(i); } if (!blank) { // Listen for page overflow page.onOverflow((overflowToken) => { if (total < this.pages.length) { this.pages[total].layout(this.source, overflowToken); } else { let newPage = this.addPage(); newPage.layout(this.source, overflowToken); } }); page.onUnderflow(() => { // console.log("underflow on", page.id); }); } this.total += 1; return page; } */ get total() { return this._total; } set total(num) { this.pagesArea.style.setProperty("--pagedjs-page-count", num); this._total = num; } loadFonts() { let fontPromises = []; document.fonts.forEach((fontFace) => { if (fontFace.status !== "loaded") { let fontLoaded = fontFace.load().then((r) => { return fontFace.family; }, (r) => { console.warn("Failed to preload font-family:", fontFace.family); return fontFace.family; }); fontPromises.push(fontLoaded); } }); return Promise.all(fontPromises).catch((err) => { console.warn(err); }); } destroy() { this.pagesArea.remove(); this.pageTemplate.remove(); } } eventEmitter(Chunker.prototype); // // item item item item // /------\ /------\ /------\ /------\ // | data | | data | | data | | data | // null <--+-prev |<---+-prev |<---+-prev |<---+-prev | // | next-+--->| next-+--->| next-+--->| next-+--> null // \------/ \------/ \------/ \------/ // ^ ^ // | list | // | /------\ | // \--------------+-head | | // | tail-+--------------/ // \------/ // function createItem(data) { return { prev: null, next: null, data: data }; } function allocateCursor(node, prev, next) { var cursor; if (cursors !== null) { cursor = cursors; cursors = cursors.cursor; cursor.prev = prev; cursor.next = next; cursor.cursor = node.cursor; } else { cursor = { prev: prev, next: next, cursor: node.cursor }; } node.cursor = cursor; return cursor; } function releaseCursor(node) { var cursor = node.cursor; node.cursor = cursor.cursor; cursor.prev = null; cursor.next = null; cursor.cursor = cursors; cursors = cursor; } var cursors = null; var List = function() { this.cursor = null; this.head = null; this.tail = null; }; List.createItem = createItem; List.prototype.createItem = createItem; List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) { var cursor = this.cursor; while (cursor !== null) { if (cursor.prev === prevOld) { cursor.prev = prevNew; } if (cursor.next === nextOld) { cursor.next = nextNew; } cursor = cursor.cursor; } }; List.prototype.getSize = function() { var size = 0; var cursor = this.head; while (cursor) { size++; cursor = cursor.next; } return size; }; List.prototype.fromArray = function(array) { var cursor = null; this.head = null; for (var i = 0; i < array.length; i++) { var item = createItem(array[i]); if (cursor !== null) { cursor.next = item; } else { this.head = item; } item.prev = cursor; cursor = item; } this.tail = cursor; return this; }; List.prototype.toArray = function() { var cursor = this.head; var result = []; while (cursor) { result.push(cursor.data); cursor = cursor.next; } return result; }; List.prototype.toJSON = List.prototype.toArray; List.prototype.isEmpty = function() { return this.head === null; }; List.prototype.first = function() { return this.head && this.head.data; }; List.prototype.last = function() { return this.tail && this.tail.data; }; List.prototype.each = function(fn, context) { var item; if (context === undefined) { context = this; } // push cursor var cursor = allocateCursor(this, null, this.head); while (cursor.next !== null) { item = cursor.next; cursor.next = item.next; fn.call(context, item.data, item, this); } // pop cursor releaseCursor(this); }; List.prototype.forEach = List.prototype.each; List.prototype.eachRight = function(fn, context) { var item; if (context === undefined) { context = this; } // push cursor var cursor = allocateCursor(this, this.tail, null); while (cursor.prev !== null) { item = cursor.prev; cursor.prev = item.prev; fn.call(context, item.data, item, this); } // pop cursor releaseCursor(this); }; List.prototype.forEachRight = List.prototype.eachRight; List.prototype.nextUntil = function(start, fn, context) { if (start === null) { return; } var item; if (context === undefined) { context = this; } // push cursor var cursor = allocateCursor(this, null, start); while (cursor.next !== null) { item = cursor.next; cursor.next = item.next; if (fn.call(context, item.data, item, this)) { break; } } // pop cursor releaseCursor(this); }; List.prototype.prevUntil = function(start, fn, context) { if (start === null) { return; } var item; if (context === undefined) { context = this; } // push cursor var cursor = allocateCursor(this, start, null); while (cursor.prev !== null) { item = cursor.prev; cursor.prev = item.prev; if (fn.call(context, item.data, item, this)) { break; } } // pop cursor releaseCursor(this); }; List.prototype.some = function(fn, context) { var cursor = this.head; if (context === undefined) { context = this; } while (cursor !== null) { if (fn.call(context, cursor.data, cursor, this)) { return true; } cursor = cursor.next; } return false; }; List.prototype.map = function(fn, context) { var result = new List(); var cursor = this.head; if (context === undefined) { context = this; } while (cursor !== null) { result.appendData(fn.call(context, cursor.data, cursor, this)); cursor = cursor.next; } return result; }; List.prototype.filter = function(fn, context) { var result = new List(); var cursor = this.head; if (context === undefined) { context = this; } while (cursor !== null) { if (fn.call(context, cursor.data, cursor, this)) { result.appendData(cursor.data); } cursor = cursor.next; } return result; }; List.prototype.clear = function() { this.head = null; this.tail = null; }; List.prototype.copy = function() { var result = new List(); var cursor = this.head; while (cursor !== null) { result.insert(createItem(cursor.data)); cursor = cursor.next; } return result; }; List.prototype.prepend = function(item) { // head // ^ // item this.updateCursors(null, item, this.head, item); // insert to the beginning of the list if (this.head !== null) { // new item <- first item this.head.prev = item; // new item -> first item item.next = this.head; } else { // if list has no head, then it also has no tail // in this case tail points to the new item this.tail = item; } // head always points to new item this.head = item; return this; }; List.prototype.prependData = function(data) { return this.prepend(createItem(data)); }; List.prototype.append = function(item) { return this.insert(item); }; List.prototype.appendData = function(data) { return this.insert(createItem(data)); }; List.prototype.insert = function(item, before) { if (before !== undefined && before !== null) { // prev before // ^ // item this.updateCursors(before.prev, item, before, item); if (before.prev === null) { // insert to the beginning of list if (this.head !== before) { throw new Error('before doesn\'t belong to list'); } // since head points to before therefore list doesn't empty // no need to check tail this.head = item; before.prev = item; item.next = before; this.updateCursors(null, item); } else { // insert between two items before.prev.next = item; item.prev = before.prev; before.prev = item; item.next = before; } } else { // tail // ^ // item this.updateCursors(this.tail, item, null, item); // insert to the ending of the list if (this.tail !== null) { // last item -> new item this.tail.next = item; // last item <- new item item.prev = this.tail; } else { // if list has no tail, then it also has no head // in this case head points to new item this.head = item; } // tail always points to new item this.tail = item; } return this; }; List.prototype.insertData = function(data, before) { return this.insert(createItem(data), before); }; List.prototype.remove = function(item) { // item // ^ // prev next this.updateCursors(item, item.prev, item, item.next); if (item.prev !== null) { item.prev.next = item.next; } else { if (this.head !== item) { throw new Error('item doesn\'t belong to list'); } this.head = item.next; } if (item.next !== null) { item.next.prev = item.prev; } else { if (this.tail !== item) { throw new Error('item doesn\'t belong to list'); } this.tail = item.prev; } item.prev = null; item.next = null; return item; }; List.prototype.push = function(data) { this.insert(createItem(data)); }; List.prototype.pop = function() { if (this.tail !== null) { return this.remove(this.tail); } }; List.prototype.unshift = function(data) { this.prepend(createItem(data)); }; List.prototype.shift = function() { if (this.head !== null) { return this.remove(this.head); } }; List.prototype.prependList = function(list) { return this.insertList(list, this.head); }; List.prototype.appendList = function(list) { return this.insertList(list); }; List.prototype.insertList = function(list, before) { // ignore empty lists if (list.head === null) { return this; } if (before !== undefined && before !== null) { this.updateCursors(before.prev, list.tail, before, list.head); // insert in the middle of dist list if (before.prev !== null) { // before.prev <-> list.head before.prev.next = list.head; list.head.prev = before.prev; } else { this.head = list.head; } before.prev = list.tail; list.tail.next = before; } else { this.updateCursors(this.tail, list.tail, null, list.head); // insert to end of the list if (this.tail !== null) { // if destination list has a tail, then it also has a head, // but head doesn't change // dest tail -> source head this.tail.next = list.head; // dest tail <- source head list.head.prev = this.tail; } else { // if list has no a tail, then it also has no a head // in this case points head to new item this.head = list.head; } // tail always start point to new item this.tail = list.tail; } list.head = null; list.tail = null; return this; }; List.prototype.replace = function(oldItem, newItemOrList) { if ('head' in newItemOrList) { this.insertList(newItemOrList, oldItem); } else { this.insert(newItemOrList, oldItem); } this.remove(oldItem); }; var list = List; var createCustomError = function createCustomError(name, message) { // use Object.create(), because some VMs prevent setting line/column otherwise // (iOS Safari 10 even throws an exception) var error = Object.create(SyntaxError.prototype); var errorStack = new Error(); error.name = name; error.message = message; Object.defineProperty(error, 'stack', { get: function() { return (errorStack.stack || '').replace(/^(.+\n){1,3}/, name + ': ' + message + '\n'); } }); return error; }; var MAX_LINE_LENGTH = 100; var OFFSET_CORRECTION = 60; var TAB_REPLACEMENT = ' '; function sourceFragment(error, extraLines) { function processLines(start, end) { return lines.slice(start, end).map(function(line, idx) { var num = String(start + idx + 1); while (num.length < maxNumLength) { num = ' ' + num; } return num + ' |' + line; }).join('\n'); } var lines = error.source.split(/\r\n?|\n|\f/); var line = error.line; var column = error.column; var startLine = Math.max(1, line - extraLines) - 1; var endLine = Math.min(line + extraLines, lines.length + 1); var maxNumLength = Math.max(4, String(endLine).length) + 1; var cutLeft = 0; // column correction according to replaced tab before column column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length; if (column > MAX_LINE_LENGTH) { cutLeft = column - OFFSET_CORRECTION + 3; column = OFFSET_CORRECTION - 2; } for (var i = startLine; i <= endLine; i++) { if (i >= 0 && i < lines.length) { lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT); lines[i] = (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') + lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) + (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : ''); } } return [ processLines(startLine, line), new Array(column + maxNumLength + 2).join('-') + '^', processLines(line, endLine) ].filter(Boolean).join('\n'); } var CssSyntaxError = function(message, source, offset, line, column) { var error = createCustomError('CssSyntaxError', message); error.source = source; error.offset = offset; error.line = line; error.column = column; error.sourceFragment = function(extraLines) { return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines); }; Object.defineProperty(error, 'formattedMessage', { get: function() { return ( 'Parse error: ' + error.message + '\n' + sourceFragment(error, 2) ); } }); // for backward capability error.parseError = { offset: offset, line: line, column: column }; return error; }; var error = CssSyntaxError; // token types (note: value shouldn't intersect with used char codes) var WHITESPACE = 1; var IDENTIFIER = 2; var NUMBER = 3; var STRING = 4; var COMMENT = 5; var PUNCTUATOR = 6; var CDO = 7; var CDC = 8; var ATKEYWORD = 14; var FUNCTION = 15; var URL$1 = 16; var RAW = 17; var TAB = 9; var N = 10; var F = 12; var R = 13; var SPACE = 32; var TYPE = { WhiteSpace: WHITESPACE, Identifier: IDENTIFIER, Number: NUMBER, String: STRING, Comment: COMMENT, Punctuator: PUNCTUATOR, CDO: CDO, CDC: CDC, AtKeyword: ATKEYWORD, Function: FUNCTION, Url: URL$1, Raw: RAW, ExclamationMark: 33, // ! QuotationMark: 34, // " NumberSign: 35, // # DollarSign: 36, // $ PercentSign: 37, // % Ampersand: 38, // & Apostrophe: 39, // ' LeftParenthesis: 40, // ( RightParenthesis: 41, // ) Asterisk: 42, // * PlusSign: 43, // + Comma: 44, // , HyphenMinus: 45, // - FullStop: 46, // . Solidus: 47, // / Colon: 58, // : Semicolon: 59, // ; LessThanSign: 60, // < EqualsSign: 61, // = GreaterThanSign: 62, // > QuestionMark: 63, // ? CommercialAt: 64, // @ LeftSquareBracket: 91, // [ Backslash: 92, // \ RightSquareBracket: 93, // ] CircumflexAccent: 94, // ^ LowLine: 95, // _ GraveAccent: 96, // ` LeftCurlyBracket: 123, // { VerticalLine: 124, // | RightCurlyBracket: 125, // } Tilde: 126 // ~ }; var NAME = Object.keys(TYPE).reduce(function(result, key) { result[TYPE[key]] = key; return result; }, {}); // https://drafts.csswg.org/css-syntax/#tokenizer-definitions // > non-ASCII code point // > A code point with a value equal to or greater than U+0080 // > name-start code point // > A letter, a non-ASCII code point, or U+005F LOW LINE (_). // > name code point // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-) // That means only ASCII code points has a special meaning and we a maps for 0..127 codes only var SafeUint32Array = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported var SYMBOL_TYPE = new SafeUint32Array(0x80); var PUNCTUATION = new SafeUint32Array(0x80); var STOP_URL_RAW = new SafeUint32Array(0x80); for (var i = 0; i < SYMBOL_TYPE.length; i++) { SYMBOL_TYPE[i] = IDENTIFIER; } // fill categories [ TYPE.ExclamationMark, // ! TYPE.QuotationMark, // " TYPE.NumberSign, // # TYPE.DollarSign, // $ TYPE.PercentSign, // % TYPE.Ampersand, // & TYPE.Apostrophe, // ' TYPE.LeftParenthesis, // ( TYPE.RightParenthesis, // ) TYPE.Asterisk, // * TYPE.PlusSign, // + TYPE.Comma, // , TYPE.HyphenMinus, // - TYPE.FullStop, // . TYPE.Solidus, // / TYPE.Colon, // : TYPE.Semicolon, // ; TYPE.LessThanSign, // < TYPE.EqualsSign, // = TYPE.GreaterThanSign, // > TYPE.QuestionMark, // ? TYPE.CommercialAt, // @ TYPE.LeftSquareBracket, // [ // TYPE.Backslash, // \ TYPE.RightSquareBracket, // ] TYPE.CircumflexAccent, // ^ // TYPE.LowLine, // _ TYPE.GraveAccent, // ` TYPE.LeftCurlyBracket, // { TYPE.VerticalLine, // | TYPE.RightCurlyBracket, // } TYPE.Tilde // ~ ].forEach(function(key) { SYMBOL_TYPE[Number(key)] = PUNCTUATOR; PUNCTUATION[Number(key)] = PUNCTUATOR; }); for (var i = 48; i <= 57; i++) { SYMBOL_TYPE[i] = NUMBER; } SYMBOL_TYPE[SPACE] = WHITESPACE; SYMBOL_TYPE[TAB] = WHITESPACE; SYMBOL_TYPE[N] = WHITESPACE; SYMBOL_TYPE[R] = WHITESPACE; SYMBOL_TYPE[F] = WHITESPACE; SYMBOL_TYPE[TYPE.Apostrophe] = STRING; SYMBOL_TYPE[TYPE.QuotationMark] = STRING; STOP_URL_RAW[SPACE] = 1; STOP_URL_RAW[TAB] = 1; STOP_URL_RAW[N] = 1; STOP_URL_RAW[R] = 1; STOP_URL_RAW[F] = 1; STOP_URL_RAW[TYPE.Apostrophe] = 1; STOP_URL_RAW[TYPE.QuotationMark] = 1; STOP_URL_RAW[TYPE.LeftParenthesis] = 1; STOP_URL_RAW[TYPE.RightParenthesis] = 1; // whitespace is punctuation ... PUNCTUATION[SPACE] = PUNCTUATOR; PUNCTUATION[TAB] = PUNCTUATOR; PUNCTUATION[N] = PUNCTUATOR; PUNCTUATION[R] = PUNCTUATOR; PUNCTUATION[F] = PUNCTUATOR; // ... hyper minus is not PUNCTUATION[TYPE.HyphenMinus] = 0; var _const = { TYPE: TYPE, NAME: NAME, SYMBOL_TYPE: SYMBOL_TYPE, PUNCTUATION: PUNCTUATION, STOP_URL_RAW: STOP_URL_RAW }; var PUNCTUATION$1 = _const.PUNCTUATION; var STOP_URL_RAW$1 = _const.STOP_URL_RAW; var TYPE$1 = _const.TYPE; var FULLSTOP = TYPE$1.FullStop; var PLUSSIGN = TYPE$1.PlusSign; var HYPHENMINUS = TYPE$1.HyphenMinus; var PUNCTUATOR$1 = TYPE$1.Punctuator; var TAB$1 = 9; var N$1 = 10; var F$1 = 12; var R$1 = 13; var SPACE$1 = 32; var BACK_SLASH = 92; var E = 101; // 'e'.charCodeAt(0) function firstCharOffset(source) { // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) if (source.charCodeAt(0) === 0xFEFF || // UTF-16BE source.charCodeAt(0) === 0xFFFE) { // UTF-16LE return 1; } return 0; } function isHex(code) { return (code >= 48 && code <= 57) || // 0 .. 9 (code >= 65 && code <= 70) || // A .. F (code >= 97 && code <= 102); // a .. f } function isNumber(code) { return code >= 48 && code <= 57; } function isWhiteSpace(code) { return code === SPACE$1 || code === TAB$1 || isNewline(code); } function isNewline(code) { return code === R$1 || code === N$1 || code === F$1; } function getNewlineLength(source, offset, code) { if (isNewline(code)) { if (code === R$1 && offset + 1 < source.length && source.charCodeAt(offset + 1) === N$1) { return 2; } return 1; } return 0; } function cmpChar(testStr, offset, referenceCode) { var code = testStr.charCodeAt(offset); // code.toLowerCase() for A..Z if (code >= 65 && code <= 90) { code = code | 32; } return code === referenceCode; } function cmpStr(testStr, start, end, referenceStr) { if (end - start !== referenceStr.length) { return false; } if (start < 0 || end > testStr.length) { return false; } for (var i = start; i < end; i++) { var testCode = testStr.charCodeAt(i); var refCode = referenceStr.charCodeAt(i - start); // testCode.toLowerCase() for A..Z if (testCode >= 65 && testCode <= 90) { testCode = testCode | 32; } if (testCode !== refCode) { return false; } } return true; } function findWhiteSpaceStart(source, offset) { while (offset >= 0 && isWhiteSpace(source.charCodeAt(offset))) { offset--; } return offset + 1; } function findWhiteSpaceEnd(source, offset) { while (offset < source.length && isWhiteSpace(source.charCodeAt(offset))) { offset++; } return offset; } function findCommentEnd(source, offset) { var commentEnd = source.indexOf('*/', offset); if (commentEnd === -1) { return source.length; } return commentEnd + 2; } function findStringEnd(source, offset, quote) { for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); // TODO: bad string if (code === BACK_SLASH) { offset++; } else if (code === quote) { offset++; break; } } return offset; } function findDecimalNumberEnd(source, offset) { while (offset < source.length && isNumber(source.charCodeAt(offset))) { offset++; } return offset; } function findNumberEnd(source, offset, allowFraction) { var code; offset = findDecimalNumberEnd(source, offset); // fraction: .\d+ if (allowFraction && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) { code = source.charCodeAt(offset + 1); if (isNumber(code)) { offset = findDecimalNumberEnd(source, offset + 1); } } // exponent: e[+-]\d+ if (offset + 1 < source.length) { if ((source.charCodeAt(offset) | 32) === E) { // case insensitive check for `e` code = source.charCodeAt(offset + 1); if (code === PLUSSIGN || code === HYPHENMINUS) { if (offset + 2 < source.length) { code = source.charCodeAt(offset + 2); } } if (isNumber(code)) { offset = findDecimalNumberEnd(source, offset + 2); } } } return offset; } // skip escaped unicode sequence that can ends with space // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? function findEscapeEnd(source, offset) { for (var i = 0; i < 7 && offset + i < source.length; i++) { var code = source.charCodeAt(offset + i); if (i !== 6 && isHex(code)) { continue; } if (i > 0) { offset += i - 1 + getNewlineLength(source, offset + i, code); if (code === SPACE$1 || code === TAB$1) { offset++; } } break; } return offset; } function findIdentifierEnd(source, offset) { for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); if (code === BACK_SLASH) { offset = findEscapeEnd(source, offset + 1); } else if (code < 0x80 && PUNCTUATION$1[code] === PUNCTUATOR$1) { break; } } return offset; } function findUrlRawEnd(source, offset) { for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); if (code === BACK_SLASH) { offset = findEscapeEnd(source, offset + 1); } else if (code < 0x80 && STOP_URL_RAW$1[code] === 1) { break; } } return offset; } var utils = { firstCharOffset: firstCharOffset, isHex: isHex, isNumber: isNumber, isWhiteSpace: isWhiteSpace, isNewline: isNewline, getNewlineLength: getNewlineLength, cmpChar: cmpChar, cmpStr: cmpStr, findWhiteSpaceStart: findWhiteSpaceStart, findWhiteSpaceEnd: findWhiteSpaceEnd, findCommentEnd: findCommentEnd, findStringEnd: findStringEnd, findDecimalNumberEnd: findDecimalNumberEnd, findNumberEnd: findNumberEnd, findEscapeEnd: findEscapeEnd, findIdentifierEnd: findIdentifierEnd, findUrlRawEnd: findUrlRawEnd }; var TYPE$2 = _const.TYPE; var NAME$1 = _const.NAME; var SYMBOL_TYPE$1 = _const.SYMBOL_TYPE; var firstCharOffset$1 = utils.firstCharOffset; var cmpStr$1 = utils.cmpStr; var isNumber$1 = utils.isNumber; var findWhiteSpaceStart$1 = utils.findWhiteSpaceStart; var findWhiteSpaceEnd$1 = utils.findWhiteSpaceEnd; var findCommentEnd$1 = utils.findCommentEnd; var findStringEnd$1 = utils.findStringEnd; var findNumberEnd$1 = utils.findNumberEnd; var findIdentifierEnd$1 = utils.findIdentifierEnd; var findUrlRawEnd$1 = utils.findUrlRawEnd; var NULL = 0; var WHITESPACE$1 = TYPE$2.WhiteSpace; var IDENTIFIER$1 = TYPE$2.Identifier; var NUMBER$1 = TYPE$2.Number; var STRING$1 = TYPE$2.String; var COMMENT$1 = TYPE$2.Comment; var PUNCTUATOR$2 = TYPE$2.Punctuator; var CDO$1 = TYPE$2.CDO; var CDC$1 = TYPE$2.CDC; var ATKEYWORD$1 = TYPE$2.AtKeyword; var FUNCTION$1 = TYPE$2.Function; var URL$2 = TYPE$2.Url; var RAW$1 = TYPE$2.Raw; var N$2 = 10; var F$2 = 12; var R$2 = 13; var STAR = TYPE$2.Asterisk; var SLASH = TYPE$2.Solidus; var FULLSTOP$1 = TYPE$2.FullStop; var PLUSSIGN$1 = TYPE$2.PlusSign; var HYPHENMINUS$1 = TYPE$2.HyphenMinus; var GREATERTHANSIGN = TYPE$2.GreaterThanSign; var LESSTHANSIGN = TYPE$2.LessThanSign; var EXCLAMATIONMARK = TYPE$2.ExclamationMark; var COMMERCIALAT = TYPE$2.CommercialAt; var QUOTATIONMARK = TYPE$2.QuotationMark; var APOSTROPHE = TYPE$2.Apostrophe; var LEFTPARENTHESIS = TYPE$2.LeftParenthesis; var RIGHTPARENTHESIS = TYPE$2.RightParenthesis; var LEFTCURLYBRACKET = TYPE$2.LeftCurlyBracket; var RIGHTCURLYBRACKET = TYPE$2.RightCurlyBracket; var LEFTSQUAREBRACKET = TYPE$2.LeftSquareBracket; var RIGHTSQUAREBRACKET = TYPE$2.RightSquareBracket; var MIN_BUFFER_SIZE = 16 * 1024; var OFFSET_MASK = 0x00FFFFFF; var TYPE_SHIFT = 24; var SafeUint32Array$1 = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported function computeLinesAndColumns(tokenizer, source) { var sourceLength = source.length; var start = firstCharOffset$1(source); var lines = tokenizer.lines; var line = tokenizer.startLine; var columns = tokenizer.columns; var column = tokenizer.startColumn; if (lines === null || lines.length < sourceLength + 1) { lines = new SafeUint32Array$1(Math.max(sourceLength + 1024, MIN_BUFFER_SIZE)); columns = new SafeUint32Array$1(lines.length); } for (var i = start; i < sourceLength; i++) { var code = source.charCodeAt(i); lines[i] = line; columns[i] = column++; if (code === N$2 || code === R$2 || code === F$2) { if (code === R$2 && i + 1 < sourceLength && source.charCodeAt(i + 1) === N$2) { i++; lines[i] = line; columns[i] = column; } line++; column = 1; } } lines[i] = line; columns[i] = column; tokenizer.linesAnsColumnsComputed = true; tokenizer.lines = lines; tokenizer.columns = columns; } function tokenLayout(tokenizer, source, startPos) { var sourceLength = source.length; var offsetAndType = tokenizer.offsetAndType; var balance = tokenizer.balance; var tokenCount = 0; var prevType = 0; var offset = startPos; var anchor = 0; var balanceCloseCode = 0; var balanceStart = 0; var balancePrev = 0; if (offsetAndType === null || offsetAndType.length < sourceLength + 1) { offsetAndType = new SafeUint32Array$1(sourceLength + 1024); balance = new SafeUint32Array$1(sourceLength + 1024); } while (offset < sourceLength) { var code = source.charCodeAt(offset); var type = code < 0x80 ? SYMBOL_TYPE$1[code] : IDENTIFIER$1; balance[tokenCount] = sourceLength; switch (type) { case WHITESPACE$1: offset = findWhiteSpaceEnd$1(source, offset + 1); break; case PUNCTUATOR$2: switch (code) { case balanceCloseCode: balancePrev = balanceStart & OFFSET_MASK; balanceStart = balance[balancePrev]; balanceCloseCode = balanceStart >> TYPE_SHIFT; balance[tokenCount] = balancePrev; balance[balancePrev++] = tokenCount; for (; balancePrev < tokenCount; balancePrev++) { if (balance[balancePrev] === sourceLength) { balance[balancePrev] = tokenCount; } } break; case LEFTSQUAREBRACKET: balance[tokenCount] = balanceStart; balanceCloseCode = RIGHTSQUAREBRACKET; balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount; break; case LEFTCURLYBRACKET: balance[tokenCount] = balanceStart; balanceCloseCode = RIGHTCURLYBRACKET; balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount; break; case LEFTPARENTHESIS: balance[tokenCount] = balanceStart; balanceCloseCode = RIGHTPARENTHESIS; balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount; break; } // /* if (code === STAR && prevType === SLASH) { type = COMMENT$1; offset = findCommentEnd$1(source, offset + 1); tokenCount--; // rewrite prev token break; } // edge case for -.123 and +.123 if (code === FULLSTOP$1 && (prevType === PLUSSIGN$1 || prevType === HYPHENMINUS$1)) { if (offset + 1 < sourceLength && isNumber$1(source.charCodeAt(offset + 1))) { type = NUMBER$1; offset = findNumberEnd$1(source, offset + 2, false); tokenCount--; // rewrite prev token break; } } // if (code === HYPHENMINUS$1 && prevType === HYPHENMINUS$1) { if (offset + 1 < sourceLength && source.charCodeAt(offset + 1) === GREATERTHANSIGN) { type = CDC$1; offset = offset + 2; tokenCount--; // rewrite prev token break; } } // ident( if (code === LEFTPARENTHESIS && prevType === IDENTIFIER$1) { offset = offset + 1; tokenCount--; // rewrite prev token balance[tokenCount] = balance[tokenCount + 1]; balanceStart--; // 4 char length identifier and equal to `url(` (case insensitive) if (offset - anchor === 4 && cmpStr$1(source, anchor, offset, 'url(')) { // special case for url() because it can contain any symbols sequence with few exceptions anchor = findWhiteSpaceEnd$1(source, offset); code = source.charCodeAt(anchor); if (code !== LEFTPARENTHESIS && code !== RIGHTPARENTHESIS && code !== QUOTATIONMARK && code !== APOSTROPHE) { // url( offsetAndType[tokenCount++] = (URL$2 << TYPE_SHIFT) | offset; balance[tokenCount] = sourceLength; // ws* if (anchor !== offset) { offsetAndType[tokenCount++] = (WHITESPACE$1 << TYPE_SHIFT) | anchor; balance[tokenCount] = sourceLength; } // raw type = RAW$1; offset = findUrlRawEnd$1(source, anchor); } else { type = URL$2; } } else { type = FUNCTION$1; } break; } type = code; offset = offset + 1; break; case NUMBER$1: offset = findNumberEnd$1(source, offset + 1, prevType !== FULLSTOP$1); // merge number with a preceding dot, dash or plus if (prevType === FULLSTOP$1 || prevType === HYPHENMINUS$1 || prevType === PLUSSIGN$1) { tokenCount--; // rewrite prev token } break; case STRING$1: offset = findStringEnd$1(source, offset + 1, code); break; default: anchor = offset; offset = findIdentifierEnd$1(source, offset); // merge identifier with a preceding dash if (prevType === HYPHENMINUS$1) { // rewrite prev token tokenCount--; // restore prev prev token type // for case @-prefix-ident prevType = tokenCount === 0 ? 0 : offsetAndType[tokenCount - 1] >> TYPE_SHIFT; } if (prevType === COMMERCIALAT) { // rewrite prev token and change type to tokenCount--; type = ATKEYWORD$1; } } offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset; prevType = type; } // finalize arrays offsetAndType[tokenCount] = offset; balance[tokenCount] = sourceLength; balance[sourceLength] = sourceLength; // prevents false positive balance match with any token while (balanceStart !== 0) { balancePrev = balanceStart & OFFSET_MASK; balanceStart = balance[balancePrev]; balance[balancePrev] = sourceLength; } tokenizer.offsetAndType = offsetAndType; tokenizer.tokenCount = tokenCount; tokenizer.balance = balance; } // // tokenizer // var Tokenizer = function(source, startOffset, startLine, startColumn) { this.offsetAndType = null; this.balance = null; this.lines = null; this.columns = null; this.setSource(source, startOffset, startLine, startColumn); }; Tokenizer.prototype = { setSource: function(source, startOffset, startLine, startColumn) { var safeSource = String(source || ''); var start = firstCharOffset$1(safeSource); this.source = safeSource; this.firstCharOffset = start; this.startOffset = typeof startOffset === 'undefined' ? 0 : startOffset; this.startLine = typeof startLine === 'undefined' ? 1 : startLine; this.startColumn = typeof startColumn === 'undefined' ? 1 : startColumn; this.linesAnsColumnsComputed = false; this.eof = false; this.currentToken = -1; this.tokenType = 0; this.tokenStart = start; this.tokenEnd = start; tokenLayout(this, safeSource, start); this.next(); }, lookupType: function(offset) { offset += this.currentToken; if (offset < this.tokenCount) { return this.offsetAndType[offset] >> TYPE_SHIFT; } return NULL; }, lookupNonWSType: function(offset) { offset += this.currentToken; for (var type; offset < this.tokenCount; offset++) { type = this.offsetAndType[offset] >> TYPE_SHIFT; if (type !== WHITESPACE$1) { return type; } } return NULL; }, lookupValue: function(offset, referenceStr) { offset += this.currentToken; if (offset < this.tokenCount) { return cmpStr$1( this.source, this.offsetAndType[offset - 1] & OFFSET_MASK, this.offsetAndType[offset] & OFFSET_MASK, referenceStr ); } return false; }, getTokenStart: function(tokenNum) { if (tokenNum === this.currentToken) { return this.tokenStart; } if (tokenNum > 0) { return tokenNum < this.tokenCount ? this.offsetAndType[tokenNum - 1] & OFFSET_MASK : this.offsetAndType[this.tokenCount] & OFFSET_MASK; } return this.firstCharOffset; }, getOffsetExcludeWS: function() { if (this.currentToken > 0) { if ((this.offsetAndType[this.currentToken - 1] >> TYPE_SHIFT) === WHITESPACE$1) { return this.currentToken > 1 ? this.offsetAndType[this.currentToken - 2] & OFFSET_MASK : this.firstCharOffset; } } return this.tokenStart; }, getRawLength: function(startToken, endTokenType1, endTokenType2, includeTokenType2) { var cursor = startToken; var balanceEnd; loop: for (; cursor < this.tokenCount; cursor++) { balanceEnd = this.balance[cursor]; // belance end points to offset before start if (balanceEnd < startToken) { break loop; } // check token is stop type switch (this.offsetAndType[cursor] >> TYPE_SHIFT) { case endTokenType1: break loop; case endTokenType2: if (includeTokenType2) { cursor++; } break loop; default: // fast forward to the end of balanced block if (this.balance[balanceEnd] === cursor) { cursor = balanceEnd; } } } return cursor - this.currentToken; }, isBalanceEdge: function(pos) { var balanceStart = this.balance[this.currentToken]; return balanceStart < pos; }, getTokenValue: function() { return this.source.substring(this.tokenStart, this.tokenEnd); }, substrToCursor: function(start) { return this.source.substring(start, this.tokenStart); }, skipWS: function() { for (var i = this.currentToken, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) { if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE$1) { break; } } if (skipTokenCount > 0) { this.skip(skipTokenCount); } }, skipSC: function() { while (this.tokenType === WHITESPACE$1 || this.tokenType === COMMENT$1) { this.next(); } }, skip: function(tokenCount) { var next = this.currentToken + tokenCount; if (next < this.tokenCount) { this.currentToken = next; this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK; next = this.offsetAndType[next]; this.tokenType = next >> TYPE_SHIFT; this.tokenEnd = next & OFFSET_MASK; } else { this.currentToken = this.tokenCount; this.next(); } }, next: function() { var next = this.currentToken + 1; if (next < this.tokenCount) { this.currentToken = next; this.tokenStart = this.tokenEnd; next = this.offsetAndType[next]; this.tokenType = next >> TYPE_SHIFT; this.tokenEnd = next & OFFSET_MASK; } else { this.currentToken = this.tokenCount; this.eof = true; this.tokenType = NULL; this.tokenStart = this.tokenEnd = this.source.length; } }, eat: function(tokenType) { if (this.tokenType !== tokenType) { var offset = this.tokenStart; var message = NAME$1[tokenType] + ' is expected'; // tweak message and offset if (tokenType === IDENTIFIER$1) { // when identifier is expected but there is a function or url if (this.tokenType === FUNCTION$1 || this.tokenType === URL$2) { offset = this.tokenEnd - 1; message += ' but function found'; } } else { // when test type is part of another token show error for current position + 1 // e.g. eat(HYPHENMINUS) will fail on "-foo", but pointing on "-" is odd if (this.source.charCodeAt(this.tokenStart) === tokenType) { offset = offset + 1; } } this.error(message, offset); } this.next(); }, eatNonWS: function(tokenType) { this.skipWS(); this.eat(tokenType); }, consume: function(tokenType) { var value = this.getTokenValue(); this.eat(tokenType); return value; }, consumeFunctionName: function() { var name = this.source.substring(this.tokenStart, this.tokenEnd - 1); this.eat(FUNCTION$1); return name; }, consumeNonWS: function(tokenType) { this.skipWS(); return this.consume(tokenType); }, expectIdentifier: function(name) { if (this.tokenType !== IDENTIFIER$1 || cmpStr$1(this.source, this.tokenStart, this.tokenEnd, name) === false) { this.error('Identifier `' + name + '` is expected'); } this.next(); }, getLocation: function(offset, filename) { if (!this.linesAnsColumnsComputed) { computeLinesAndColumns(this, this.source); } return { source: filename, offset: this.startOffset + offset, line: this.lines[offset], column: this.columns[offset] }; }, getLocationRange: function(start, end, filename) { if (!this.linesAnsColumnsComputed) { computeLinesAndColumns(this, this.source); } return { source: filename, start: { offset: this.startOffset + start, line: this.lines[start], column: this.columns[start] }, end: { offset: this.startOffset + end, line: this.lines[end], column: this.columns[end] } }; }, error: function(message, offset) { var location = typeof offset !== 'undefined' && offset < this.source.length ? this.getLocation(offset) : this.eof ? this.getLocation(findWhiteSpaceStart$1(this.source, this.source.length - 1)) : this.getLocation(this.tokenStart); throw new error( message || 'Unexpected input', this.source, location.offset, location.line, location.column ); }, dump: function() { var offset = 0; return Array.prototype.slice.call(this.offsetAndType, 0, this.tokenCount).map(function(item, idx) { var start = offset; var end = item & OFFSET_MASK; offset = end; return { idx: idx, type: NAME$1[item >> TYPE_SHIFT], chunk: this.source.substring(start, end), balance: this.balance[idx] }; }, this); } }; // extend with error class Tokenizer.CssSyntaxError = error; // extend tokenizer with constants Object.keys(_const).forEach(function(key) { Tokenizer[key] = _const[key]; }); // extend tokenizer with static methods from utils Object.keys(utils).forEach(function(key) { Tokenizer[key] = utils[key]; }); // warm up tokenizer to elimitate code branches that never execute // fix soft deoptimizations (insufficient type feedback) new Tokenizer('\n\r\r\n\f//""\'\'/*\r\n\f*/1a;.\\31\t\+2{url(a);func();+1.2e3 -.4e-5 .6e+7}').getLocation(); var Tokenizer_1 = Tokenizer; var tokenizer = Tokenizer_1; function noop$1(value) { return value; } function generateMultiplier(multiplier) { if (multiplier.min === 0 && multiplier.max === 0) { return '*'; } if (multiplier.min === 0 && multiplier.max === 1) { return '?'; } if (multiplier.min === 1 && multiplier.max === 0) { return multiplier.comma ? '#' : '+'; } if (multiplier.min === 1 && multiplier.max === 1) { return ''; } return ( (multiplier.comma ? '#' : '') + (multiplier.min === multiplier.max ? '{' + multiplier.min + '}' : '{' + multiplier.min + ',' + (multiplier.max !== 0 ? multiplier.max : '') + '}' ) ); } function generateSequence(node, forceBraces, decorate) { var result = node.terms.map(function(term) { return generate(term, forceBraces, decorate); }).join(node.combinator === ' ' ? ' ' : ' ' + node.combinator + ' '); if (node.explicit || forceBraces) { result = (result[0] !== ',' ? '[ ' : '[') + result + ' ]'; } return result; } function generate(node, forceBraces, decorate) { var result; switch (node.type) { case 'Group': result = generateSequence(node, forceBraces, decorate) + (node.disallowEmpty ? '!' : ''); break; case 'Multiplier': // return since node is a composition return ( generate(node.term, forceBraces, decorate) + decorate(generateMultiplier(node), node) ); case 'Type': result = '<' + node.name + '>'; break; case 'Property': result = '<\'' + node.name + '\'>'; break; case 'Keyword': result = node.name; break; case 'AtKeyword': result = '@' + node.name; break; case 'Function': result = node.name + '('; break; case 'String': case 'Token': result = node.value; break; case 'Comma': result = ','; break; default: throw new Error('Unknown node type `' + node.type + '`'); } return decorate(result, node); } var generate_1 = function(node, options) { var decorate = noop$1; var forceBraces = false; if (typeof options === 'function') { decorate = options; } else if (options) { forceBraces = Boolean(options.forceBraces); if (typeof options.decorate === 'function') { decorate = options.decorate; } } return generate(node, forceBraces, decorate); }; function fromMatchResult(matchResult) { var tokens = matchResult.tokens; var longestMatch = matchResult.longestMatch; var node = longestMatch < tokens.length ? tokens[longestMatch].node : null; var mismatchOffset = 0; var entries = 0; var css = ''; for (var i = 0; i < tokens.length; i++) { if (i === longestMatch) { mismatchOffset = css.length; } if (node !== null && tokens[i].node === node) { if (i <= longestMatch) { entries++; } else { entries = 0; } } css += tokens[i].value; } if (node === null) { mismatchOffset = css.length; } return { node: node, css: css, mismatchOffset: mismatchOffset, last: node === null || entries > 1 }; } function getLocation(node, point) { var loc = node && node.loc && node.loc[point]; if (loc) { return { offset: loc.offset, line: loc.line, column: loc.column }; } return null; } var SyntaxReferenceError = function(type, referenceName) { var error = createCustomError( 'SyntaxReferenceError', type + (referenceName ? ' `' + referenceName + '`' : '') ); error.reference = referenceName; return error; }; var MatchError = function(message, lexer, syntax, node, matchResult) { var error = createCustomError('SyntaxMatchError', message); var details = fromMatchResult(matchResult); var mismatchOffset = details.mismatchOffset || 0; var badNode = details.node || node; var end = getLocation(badNode, 'end'); var start = details.last ? end : getLocation(badNode, 'start'); var css = details.css; error.rawMessage = message; error.syntax = syntax ? generate_1(syntax) : ''; error.css = css; error.mismatchOffset = mismatchOffset; error.loc = { source: (badNode && badNode.loc && badNode.loc.source) || '', start: start, end: end }; error.line = start ? start.line : undefined; error.column = start ? start.column : undefined; error.offset = start ? start.offset : undefined; error.message = message + '\n' + ' syntax: ' + error.syntax + '\n' + ' value: ' + (error.css || '') + '\n' + ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^'; return error; }; var error$1 = { SyntaxReferenceError: SyntaxReferenceError, MatchError: MatchError }; var hasOwnProperty = Object.prototype.hasOwnProperty; var keywords = Object.create(null); var properties = Object.create(null); var HYPHENMINUS$2 = 45; // '-'.charCodeAt() function isCustomProperty(str, offset) { offset = offset || 0; return str.length - offset >= 2 && str.charCodeAt(offset) === HYPHENMINUS$2 && str.charCodeAt(offset + 1) === HYPHENMINUS$2; } function getVendorPrefix(str, offset) { offset = offset || 0; // verdor prefix should be at least 3 chars length if (str.length - offset >= 3) { // vendor prefix starts with hyper minus following non-hyper minus if (str.charCodeAt(offset) === HYPHENMINUS$2 && str.charCodeAt(offset + 1) !== HYPHENMINUS$2) { // vendor prefix should contain a hyper minus at the ending var secondDashIndex = str.indexOf('-', offset + 2); if (secondDashIndex !== -1) { return str.substring(offset, secondDashIndex + 1); } } } return ''; } function getKeywordDescriptor(keyword) { if (hasOwnProperty.call(keywords, keyword)) { return keywords[keyword]; } var name = keyword.toLowerCase(); if (hasOwnProperty.call(keywords, name)) { return keywords[keyword] = keywords[name]; } var custom = isCustomProperty(name, 0); var vendor = !custom ? getVendorPrefix(name, 0) : ''; return keywords[keyword] = Object.freeze({ basename: name.substr(vendor.length), name: name, vendor: vendor, prefix: vendor, custom: custom }); } function getPropertyDescriptor(property) { if (hasOwnProperty.call(properties, property)) { return properties[property]; } var name = property; var hack = property[0]; if (hack === '/') { hack = property[1] === '/' ? '//' : '/'; } else if (hack !== '_' && hack !== '*' && hack !== '$' && hack !== '#' && hack !== '+') { hack = ''; } var custom = isCustomProperty(name, hack.length); // re-use result when possible (the same as for lower case) if (!custom) { name = name.toLowerCase(); if (hasOwnProperty.call(properties, name)) { return properties[property] = properties[name]; } } var vendor = !custom ? getVendorPrefix(name, hack.length) : ''; var prefix = name.substr(0, hack.length + vendor.length); return properties[property] = Object.freeze({ basename: name.substr(prefix.length), name: name.substr(hack.length), hack: hack, vendor: vendor, prefix: prefix, custom: custom }); } var names = { keyword: getKeywordDescriptor, property: getPropertyDescriptor, isCustomProperty: isCustomProperty, vendorPrefix: getVendorPrefix }; var findIdentifierEnd$2 = utils.findIdentifierEnd; var findNumberEnd$2 = utils.findNumberEnd; var findDecimalNumberEnd$1 = utils.findDecimalNumberEnd; var isHex$1 = utils.isHex; var SYMBOL_TYPE$2 = _const.SYMBOL_TYPE; var IDENTIFIER$2 = _const.TYPE.Identifier; var PLUSSIGN$2 = _const.TYPE.PlusSign; var HYPHENMINUS$3 = _const.TYPE.HyphenMinus; var NUMBERSIGN = _const.TYPE.NumberSign; var PERCENTAGE = { '%': true }; // https://www.w3.org/TR/css-values-3/#lengths var LENGTH = { // absolute length units 'px': true, 'mm': true, 'cm': true, 'in': true, 'pt': true, 'pc': true, 'q': true, // relative length units 'em': true, 'ex': true, 'ch': true, 'rem': true, // viewport-percentage lengths 'vh': true, 'vw': true, 'vmin': true, 'vmax': true, 'vm': true }; var ANGLE = { 'deg': true, 'grad': true, 'rad': true, 'turn': true }; var TIME = { 's': true, 'ms': true }; var FREQUENCY = { 'hz': true, 'khz': true }; // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution) var RESOLUTION = { 'dpi': true, 'dpcm': true, 'dppx': true, 'x': true // https://github.com/w3c/csswg-drafts/issues/461 }; // https://drafts.csswg.org/css-grid/#fr-unit var FLEX = { 'fr': true }; // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume var DECIBEL = { 'db': true }; // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch var SEMITONES = { 'st': true }; function consumeFunction(token, addTokenToMatch, getNextToken) { var length = 1; var cursor; do { cursor = getNextToken(length++); } while (cursor !== null && cursor.node !== token.node); if (cursor === null) { return false; } while (true) { // consume tokens until cursor if (addTokenToMatch() === cursor) { break; } } return true; } // TODO: implement // can be used wherever , , ,