/** * @license Paged.js v0.1.42 | MIT | https://gitlab.pagedmedia.org/tools/pagedjs */ (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 { Object.keys("primitive"); 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; /** * 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 = []; } } 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; } // support for period character in id if (codeUnit == 0x002E) { if (string.charAt(0) == "#") { result += "\\."; 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) { if (limiter && node === limiter) { return; } let significantNode = nextSignificantNode(node); if (significantNode) { return significantNode; } if (node.parentNode) { while ((node = node.parentNode)) { if (limiter && node === limiter) { return; } significantNode = nextSignificantNode(node); if (significantNode) { return significantNode; } } } } function nodeBefore(node, limiter) { if (limiter && node === limiter) { return; } let significantNode = previousSignificantNode(node); if (significantNode) { return significantNode; } if (node.parentNode) { while ((node = node.parentNode)) { if (limiter && node === limiter) { return; } significantNode = previousSignificantNode(node); if (significantNode) { return significantNode; } } } } function elementAfter(node, limiter) { let after = nodeAfter(node, limiter); while (after && after.nodeType !== 1) { after = nodeAfter(after, limiter); } return after; } function elementBefore(node, limiter) { let before = nodeBefore(node, limiter); while (before && before.nodeType !== 1) { before = nodeBefore(before, limiter); } return before; } function displayedElementAfter(node, limiter) { let after = elementAfter(node, limiter); while (after && after.dataset.undisplayed) { after = elementAfter(after); } return after; } function displayedElementBefore(node, limiter) { let before = elementBefore(node, limiter); while (before && before.dataset.undisplayed) { before = elementBefore(before); } return before; } 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, previousSignificantNode) { if (typeof node === "undefined" || !previousSignificantNode || isIgnorable(node)) { return false; } const previousSignificantNodePage = previousSignificantNode.dataset ? previousSignificantNode.dataset.page : undefined; const currentNodePage = node.dataset ? node.dataset.page : undefined; return currentNodePage !== previousSignificantNodePage; } 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 && 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; } /** * Throughout, whitespace is defined as one of the characters * "\t" TAB \u0009 * "\n" LF \u000A * "\r" CR \u000D * " " SPC \u0020 * * This does not use Javascript's "\s" because that includes non-breaking * spaces (and also some other characters). */ /** * Determine if a node should be ignored by the iterator functions. * taken from https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace#Whitespace_helper_functions * * @param {Node} node An object implementing the DOM1 |Node| interface. * @return {boolean} true if the node is: * 1) A |Text| node that is all whitespace * 2) A |Comment| node * and otherwise false. */ function isIgnorable(node) { return (node.nodeType === 8) || // A comment node ((node.nodeType === 3) && isAllWhitespace(node)); // a text node, all whitespace } /** * Determine whether a node's text content is entirely whitespace. * * @param {Node} node A node implementing the |CharacterData| interface (i.e., a |Text|, |Comment|, or |CDATASection| node * @return {boolean} true if all of the text content of |nod| is whitespace, otherwise false. */ function isAllWhitespace(node) { return !(/[^\t\n\r ]/.test(node.textContent)); } /** * Version of |previousSibling| that skips nodes that are entirely * whitespace or comments. (Normally |previousSibling| is a property * of all DOM nodes that gives the sibling node, the node that is * a child of the same parent, that occurs immediately before the * reference node.) * * @param {ChildNode} sib The reference node. * @return {Node|null} Either: * 1) The closest previous sibling to |sib| that is not ignorable according to |is_ignorable|, or * 2) null if no such node exists. */ function previousSignificantNode(sib) { while ((sib = sib.previousSibling)) { if (!isIgnorable(sib)) return sib; } return null; } /** * Version of |nextSibling| that skips nodes that are entirely * whitespace or comments. * * @param {ChildNode} sib The reference node. * @return {Node|null} Either: * 1) The closest next sibling to |sib| that is not ignorable according to |is_ignorable|, or * 2) null if no such node exists. */ function nextSignificantNode(sib) { while ((sib = sib.nextSibling)) { if (!isIgnorable(sib)) return sib; } return null; } function filterTree(content, func, what) { const treeWalker = document.createTreeWalker( content || this.dom, what || NodeFilter.SHOW_ALL, func ? { acceptNode: func } : null, false ); let node; let current; node = treeWalker.nextNode(); while(node) { current = node; node = treeWalker.nextNode(); current.parentNode.removeChild(current); } } /** * Layout * @class */ class BreakToken { constructor(node, offset) { this.node = node; this.offset = offset; } equals(otherBreakToken) { if (!otherBreakToken) { return false; } if (this["node"] && otherBreakToken["node"] && this["node"] !== otherBreakToken["node"]) { return false; } if (this["offset"] && otherBreakToken["offset"] && this["offset"] !== otherBreakToken["offset"]) { return false; } return true; } } const MAX_CHARS_PER_BREAK = 1500; /** * Layout * @class */ class Layout { constructor(element, hooks, options) { 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.settings = options || {}; this.maxChars = this.settings.maxChars || MAX_CHARS_PER_BREAK; this.forceRenderBreak = false; } async renderTo(wrapper, source, breakToken, bounds = this.bounds) { let start = this.getStart(source, breakToken); let walker = walk(start, source); let node; let prevNode; let done; let next; let hasRenderedContent = false; let newBreakToken; let length = 0; let prevBreakToken = breakToken || new BreakToken(start); while (!done && !newBreakToken) { next = walker.next(); prevNode = node; 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, prevBreakToken); if (newBreakToken && newBreakToken.equals(prevBreakToken)) { console.warn("Unable to layout item: ", prevNode); return undefined; } 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, prevBreakToken); if (!newBreakToken) { newBreakToken = this.breakAt(node); } if (newBreakToken && newBreakToken.equals(prevBreakToken)) { console.warn("Unable to layout item: ", node); return undefined; } 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); } if (this.forceRenderBreak) { this.hooks && this.hooks.layout.trigger(wrapper, this); newBreakToken = this.findBreakToken(wrapper, source, bounds, prevBreakToken); if (!newBreakToken) { newBreakToken = this.breakAt(node); } length = 0; this.forceRenderBreak = false; break; } // 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, prevBreakToken); if (newBreakToken && newBreakToken.equals(prevBreakToken)) { console.warn("Unable to layout item: ", node); return undefined; } if (newBreakToken) { length = 0; } } } return newBreakToken; } breakAt(node, offset = 0) { let newBreakToken = new BreakToken( node, offset ); let breakHooks = this.hooks.onBreakToken.triggerSync(newBreakToken, undefined, node, this); breakHooks.forEach((newToken) => { if (typeof newToken != "undefined") { newBreakToken = newToken; } }); return newBreakToken; } shouldBreak(node) { let previousSibling = previousSignificantNode(node); 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, previousSibling); } forceBreak() { this.forceRenderBreak = true; } 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, this); 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 let prevNode = prevValidNode(temp); if (!isElement(prevNode)) { prevNode = prevNode.parentElement; } renderedNode = findElement(prevNode, rendered); // Check if temp is the last rendered node at its level. if (!temp.nextSibling) { // We need to ensure that the previous sibling of temp is fully rendered. const renderedNodeFromSource = findElement(renderedNode, source); const walker = document.createTreeWalker(renderedNodeFromSource, NodeFilter.SHOW_ELEMENT); const lastChildOfRenderedNodeFromSource = walker.lastChild(); const lastChildOfRenderedNodeMatchingFromRendered = findElement(lastChildOfRenderedNodeFromSource, rendered); // Check if we found that the last child in source if (!lastChildOfRenderedNodeMatchingFromRendered) { // Pending content to be rendered before virtual break token return; } // Otherwise we will return a break token as per below } // renderedNode is actually the last unbroken box that does not overflow. // Break Token is therefore the next sibling of renderedNode within source node. node = findElement(renderedNode, source).nextSibling; offset = 0; } else { 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); // No seperatation for the first textNode of an element if(index === 0) { node = parent; offset = 0; } else { 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 new BreakToken( node, offset ); } findBreakToken(rendered, source, bounds = this.bounds, prevBreakToken, extract = true) { let overflow = this.findOverflow(rendered, bounds); let breakToken, breakLetter; 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); // breakToken is nullable let breakHooks = this.hooks.onBreakToken.triggerSync(breakToken, overflow, rendered, this); breakHooks.forEach((newToken) => { if (typeof newToken != "undefined") { breakToken = newToken; } }); // Stop removal if we are in a loop if (breakToken && breakToken.equals(prevBreakToken)) { return breakToken; } if (breakToken && breakToken["node"] && breakToken["offset"] && breakToken["node"].textContent) { breakLetter = breakToken["node"].textContent.charAt(breakToken["offset"]); } else { breakLetter = undefined; } if (breakToken && breakToken.node && extract) { this.removeOverflow(overflow, breakLetter); } } 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.round(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.selectNode(prev); break; } if (!br && !isFloat && isElement(node)) { range = document.createRange(); range.selectNode(node); break; } if (isText(node) && node.textContent.trim().length) { range = document.createRange(); range.selectNode(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, breakLetter) { let {startContainer} = overflow; let extracted = overflow.extractContents(); this.hyphenateAtBreak(startContainer, breakLetter); return extracted; } hyphenateAtBreak(startContainer, breakLetter) { 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 ( (breakLetter && /^\w|\u00AD$/.test(prevLetter) && /^\w|\u00AD$/.test(breakLetter)) || (!breakLetter && /^\w|\u00AD$/.test(prevLetter)) ) { startContainer.parentNode.classList.add("pagedjs_hyphen"); startContainer.textContent += this.settings.hyphenGlyph || "\u2011"; } } } equalTokens(a, b) { if (!a || !b) { return false; } if (a["node"] && b["node"] && a["node"] !== b["node"]) { return false; } if (a["offset"] && b["offset"] && a["offset"] !== b["offset"]) { return false; } return true; } } 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; } requestAnimationFrame(() => { 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, this.startToken); 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); 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); 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); return contents; } addRefs(content) { var treeWalker = document.createTreeWalker( content, NodeFilter.SHOW_ELEMENT, null, 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(); } } find(ref) { return this.refs[ref]; } 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, options) { // this.preview = preview; this.settings = options || {}; this.hooks = {}; this.hooks.beforeParsed = new Hook(this); this.hooks.filter = 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.hooks.filter.triggerSync(parsed); this.source = parsed; this.breakToken = undefined; if (this.pagesArea && this.pageTemplate) { this.q.clear(); this.removePages(); } else { this.setup(renderTo); } this.emit("rendering", parsed); 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; this.pagesArea.style.setProperty("--pagedjs-page-count", this.total); 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, this.settings); 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 = []; } this.total = this.pages.length; } 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; } */ 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); // // list // ┌──────┐ // ┌──────────────┼─head │ // │ │ tail─┼──────────────┐ // │ └──────┘ │ // ▼ ▼ // item item item item // ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ // null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ // │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null // ├──────┤ ├──────┤ ├──────┤ ├──────┤ // │ data │ │ data │ │ data │ │ data │ // └──────┘ └──────┘ └──────┘ └──────┘ // 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_1 = 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 SyntaxError$1 = function(message, source, offset, line, column) { var error = createCustomError('SyntaxError', 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 _SyntaxError = SyntaxError$1; // CSS Syntax Module Level 3 // https://www.w3.org/TR/css-syntax-3/ var TYPE = { EOF: 0, // Ident: 1, // Function: 2, // AtKeyword: 3, // Hash: 4, // String: 5, // BadString: 6, // Url: 7, // BadUrl: 8, // Delim: 9, // Number: 10, // Percentage: 11, // Dimension: 12, // WhiteSpace: 13, // CDO: 14, // CDC: 15, // Colon: 16, // : Semicolon: 17, // ; Comma: 18, // , LeftSquareBracket: 19, // <[-token> RightSquareBracket: 20, // <]-token> LeftParenthesis: 21, // <(-token> RightParenthesis: 22, // <)-token> LeftCurlyBracket: 23, // <{-token> RightCurlyBracket: 24, // <}-token> Comment: 25 }; var NAME = Object.keys(TYPE).reduce(function(result, key) { result[TYPE[key]] = key; return result; }, {}); var _const = { TYPE: TYPE, NAME: NAME }; var EOF = 0; // https://drafts.csswg.org/css-syntax-3/ // § 4.2. Definitions // digit // A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9). function isDigit(code) { return code >= 0x0030 && code <= 0x0039; } // hex digit // A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F), // or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f). function isHexDigit(code) { return ( isDigit(code) || // 0 .. 9 (code >= 0x0041 && code <= 0x0046) || // A .. F (code >= 0x0061 && code <= 0x0066) // a .. f ); } // uppercase letter // A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z). function isUppercaseLetter(code) { return code >= 0x0041 && code <= 0x005A; } // lowercase letter // A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z). function isLowercaseLetter(code) { return code >= 0x0061 && code <= 0x007A; } // letter // An uppercase letter or a lowercase letter. function isLetter(code) { return isUppercaseLetter(code) || isLowercaseLetter(code); } // non-ASCII code point // A code point with a value equal to or greater than U+0080 . function isNonAscii(code) { return code >= 0x0080; } // name-start code point // A letter, a non-ASCII code point, or U+005F LOW LINE (_). function isNameStart(code) { return isLetter(code) || isNonAscii(code) || code === 0x005F; } // name code point // A name-start code point, a digit, or U+002D HYPHEN-MINUS (-). function isName(code) { return isNameStart(code) || isDigit(code) || code === 0x002D; } // non-printable code point // A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION, // or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE. function isNonPrintable(code) { return ( (code >= 0x0000 && code <= 0x0008) || (code === 0x000B) || (code >= 0x000E && code <= 0x001F) || (code === 0x007F) ); } // newline // U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, // as they are converted to U+000A LINE FEED during preprocessing. // TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED function isNewline(code) { return code === 0x000A || code === 0x000D || code === 0x000C; } // whitespace // A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE. function isWhiteSpace(code) { return isNewline(code) || code === 0x0020 || code === 0x0009; } // § 4.3.8. Check if two code points are a valid escape function isValidEscape(first, second) { // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. if (first !== 0x005C) { return false; } // Otherwise, if the second code point is a newline or EOF, return false. if (isNewline(second) || second === EOF) { return false; } // Otherwise, return true. return true; } // § 4.3.9. Check if three code points would start an identifier function isIdentifierStart(first, second, third) { // Look at the first code point: // U+002D HYPHEN-MINUS if (first === 0x002D) { // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS, // or the second and third code points are a valid escape, return true. Otherwise, return false. return ( isNameStart(second) || second === 0x002D || isValidEscape(second, third) ); } // name-start code point if (isNameStart(first)) { // Return true. return true; } // U+005C REVERSE SOLIDUS (\) if (first === 0x005C) { // If the first and second code points are a valid escape, return true. Otherwise, return false. return isValidEscape(first, second); } // anything else // Return false. return false; } // § 4.3.10. Check if three code points would start a number function isNumberStart(first, second, third) { // Look at the first code point: // U+002B PLUS SIGN (+) // U+002D HYPHEN-MINUS (-) if (first === 0x002B || first === 0x002D) { // If the second code point is a digit, return true. if (isDigit(second)) { return 2; } // Otherwise, if the second code point is a U+002E FULL STOP (.) // and the third code point is a digit, return true. // Otherwise, return false. return second === 0x002E && isDigit(third) ? 3 : 0; } // U+002E FULL STOP (.) if (first === 0x002E) { // If the second code point is a digit, return true. Otherwise, return false. return isDigit(second) ? 2 : 0; } // digit if (isDigit(first)) { // Return true. return 1; } // anything else // Return false. return 0; } // // Misc // // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) function isBOM(code) { // UTF-16BE if (code === 0xFEFF) { return 1; } // UTF-16LE if (code === 0xFFFE) { return 1; } return 0; } // Fast code category // // 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 define a maps for 0..127 codes only var CATEGORY = new Array(0x80); charCodeCategory.Eof = 0x80; charCodeCategory.WhiteSpace = 0x82; charCodeCategory.Digit = 0x83; charCodeCategory.NameStart = 0x84; charCodeCategory.NonPrintable = 0x85; for (var i = 0; i < CATEGORY.length; i++) { switch (true) { case isWhiteSpace(i): CATEGORY[i] = charCodeCategory.WhiteSpace; break; case isDigit(i): CATEGORY[i] = charCodeCategory.Digit; break; case isNameStart(i): CATEGORY[i] = charCodeCategory.NameStart; break; case isNonPrintable(i): CATEGORY[i] = charCodeCategory.NonPrintable; break; default: CATEGORY[i] = i || charCodeCategory.Eof; } } function charCodeCategory(code) { return code < 0x80 ? CATEGORY[code] : charCodeCategory.NameStart; } var charCodeDefinitions = { isDigit: isDigit, isHexDigit: isHexDigit, isUppercaseLetter: isUppercaseLetter, isLowercaseLetter: isLowercaseLetter, isLetter: isLetter, isNonAscii: isNonAscii, isNameStart: isNameStart, isName: isName, isNonPrintable: isNonPrintable, isNewline: isNewline, isWhiteSpace: isWhiteSpace, isValidEscape: isValidEscape, isIdentifierStart: isIdentifierStart, isNumberStart: isNumberStart, isBOM: isBOM, charCodeCategory: charCodeCategory }; var isDigit$1 = charCodeDefinitions.isDigit; var isHexDigit$1 = charCodeDefinitions.isHexDigit; var isUppercaseLetter$1 = charCodeDefinitions.isUppercaseLetter; var isName$1 = charCodeDefinitions.isName; var isWhiteSpace$1 = charCodeDefinitions.isWhiteSpace; var isValidEscape$1 = charCodeDefinitions.isValidEscape; function getCharCode(source, offset) { return offset < source.length ? source.charCodeAt(offset) : 0; } function getNewlineLength(source, offset, code) { if (code === 13 /* \r */ && getCharCode(source, offset + 1) === 10 /* \n */) { return 2; } return 1; } function cmpChar(testStr, offset, referenceCode) { var code = testStr.charCodeAt(offset); // code.toLowerCase() for A..Z if (isUppercaseLetter$1(code)) { 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 referenceCode = referenceStr.charCodeAt(i - start); // testCode.toLowerCase() for A..Z if (isUppercaseLetter$1(testCode)) { testCode = testCode | 32; } if (testCode !== referenceCode) { return false; } } return true; } function findWhiteSpaceStart(source, offset) { for (; offset >= 0; offset--) { if (!isWhiteSpace$1(source.charCodeAt(offset))) { break; } } return offset + 1; } function findWhiteSpaceEnd(source, offset) { for (; offset < source.length; offset++) { if (!isWhiteSpace$1(source.charCodeAt(offset))) { break; } } return offset; } function findDecimalNumberEnd(source, offset) { for (; offset < source.length; offset++) { if (!isDigit$1(source.charCodeAt(offset))) { break; } } return offset; } // § 4.3.7. Consume an escaped code point function consumeEscaped(source, offset) { // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and // that the next input code point has already been verified to be part of a valid escape. offset += 2; // hex digit if (isHexDigit$1(getCharCode(source, offset - 1))) { // Consume as many hex digits as possible, but no more than 5. // Note that this means 1-6 hex digits have been consumed in total. for (var maxOffset = Math.min(source.length, offset + 5); offset < maxOffset; offset++) { if (!isHexDigit$1(getCharCode(source, offset))) { break; } } // If the next input code point is whitespace, consume it as well. var code = getCharCode(source, offset); if (isWhiteSpace$1(code)) { offset += getNewlineLength(source, offset, code); } } return offset; } // §4.3.11. Consume a name // Note: This algorithm does not do the verification of the first few code points that are necessary // to ensure the returned code points would constitute an . If that is the intended use, // ensure that the stream starts with an identifier before calling this algorithm. function consumeName(source, offset) { // Let result initially be an empty string. // Repeatedly consume the next input code point from the stream: for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); // name code point if (isName$1(code)) { // Append the code point to result. continue; } // the stream starts with a valid escape if (isValidEscape$1(code, getCharCode(source, offset + 1))) { // Consume an escaped code point. Append the returned code point to result. offset = consumeEscaped(source, offset) - 1; continue; } // anything else // Reconsume the current input code point. Return result. break; } return offset; } // §4.3.12. Consume a number function consumeNumber(source, offset) { var code = source.charCodeAt(offset); // 2. If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), // consume it and append it to repr. if (code === 0x002B || code === 0x002D) { code = source.charCodeAt(offset += 1); } // 3. While the next input code point is a digit, consume it and append it to repr. if (isDigit$1(code)) { offset = findDecimalNumberEnd(source, offset + 1); code = source.charCodeAt(offset); } // 4. If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then: if (code === 0x002E && isDigit$1(source.charCodeAt(offset + 1))) { // 4.1 Consume them. // 4.2 Append them to repr. code = source.charCodeAt(offset += 2); // 4.3 Set type to "number". // TODO // 4.4 While the next input code point is a digit, consume it and append it to repr. offset = findDecimalNumberEnd(source, offset); } // 5. If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) // or U+0065 LATIN SMALL LETTER E (e), ... , followed by a digit, then: if (cmpChar(source, offset, 101 /* e */)) { var sign = 0; code = source.charCodeAt(offset + 1); // ... optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+) ... if (code === 0x002D || code === 0x002B) { sign = 1; code = source.charCodeAt(offset + 2); } // ... followed by a digit if (isDigit$1(code)) { // 5.1 Consume them. // 5.2 Append them to repr. // 5.3 Set type to "number". // TODO // 5.4 While the next input code point is a digit, consume it and append it to repr. offset = findDecimalNumberEnd(source, offset + 1 + sign + 1); } } return offset; } // § 4.3.14. Consume the remnants of a bad url // ... its sole use is to consume enough of the input stream to reach a recovery point // where normal tokenizing can resume. function consumeBadUrlRemnants(source, offset) { // Repeatedly consume the next input code point from the stream: for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); // U+0029 RIGHT PARENTHESIS ()) // EOF if (code === 0x0029) { // Return. offset++; break; } if (isValidEscape$1(code, getCharCode(source, offset + 1))) { // Consume an escaped code point. // Note: This allows an escaped right parenthesis ("\)") to be encountered // without ending the . This is otherwise identical to // the "anything else" clause. offset = consumeEscaped(source, offset); } } return offset; } var utils = { consumeEscaped: consumeEscaped, consumeName: consumeName, consumeNumber: consumeNumber, consumeBadUrlRemnants: consumeBadUrlRemnants, cmpChar: cmpChar, cmpStr: cmpStr, getNewlineLength: getNewlineLength, findWhiteSpaceStart: findWhiteSpaceStart, findWhiteSpaceEnd: findWhiteSpaceEnd }; var TYPE$1 = _const.TYPE; var NAME$1 = _const.NAME; var cmpStr$1 = utils.cmpStr; var EOF$1 = TYPE$1.EOF; var WHITESPACE = TYPE$1.WhiteSpace; var COMMENT = TYPE$1.Comment; var OFFSET_MASK = 0x00FFFFFF; var TYPE_SHIFT = 24; var TokenStream = function() { this.offsetAndType = null; this.balance = null; this.reset(); }; TokenStream.prototype = { reset: function() { this.eof = false; this.tokenIndex = -1; this.tokenType = 0; this.tokenStart = this.firstCharOffset; this.tokenEnd = this.firstCharOffset; }, lookupType: function(offset) { offset += this.tokenIndex; if (offset < this.tokenCount) { return this.offsetAndType[offset] >> TYPE_SHIFT; } return EOF$1; }, lookupOffset: function(offset) { offset += this.tokenIndex; if (offset < this.tokenCount) { return this.offsetAndType[offset - 1] & OFFSET_MASK; } return this.source.length; }, lookupValue: function(offset, referenceStr) { offset += this.tokenIndex; 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(tokenIndex) { if (tokenIndex === this.tokenIndex) { return this.tokenStart; } if (tokenIndex > 0) { return tokenIndex < this.tokenCount ? this.offsetAndType[tokenIndex - 1] & OFFSET_MASK : this.offsetAndType[this.tokenCount] & OFFSET_MASK; } return this.firstCharOffset; }, // TODO: -> skipUntilBalanced getRawLength: function(startToken, mode) { var cursor = startToken; var balanceEnd; var offset = this.offsetAndType[Math.max(cursor - 1, 0)] & OFFSET_MASK; var type; loop: for (; cursor < this.tokenCount; cursor++) { balanceEnd = this.balance[cursor]; // stop scanning on balance edge that points to offset before start token if (balanceEnd < startToken) { break loop; } type = this.offsetAndType[cursor] >> TYPE_SHIFT; // check token is stop type switch (mode(type, this.source, offset)) { case 1: break loop; case 2: cursor++; break loop; default: offset = this.offsetAndType[cursor] & OFFSET_MASK; // fast forward to the end of balanced block if (this.balance[balanceEnd] === cursor) { cursor = balanceEnd; } } } return cursor - this.tokenIndex; }, isBalanceEdge: function(pos) { return this.balance[this.tokenIndex] < pos; }, isDelim: function(code, offset) { if (offset) { return ( this.lookupType(offset) === TYPE$1.Delim && this.source.charCodeAt(this.lookupOffset(offset)) === code ); } return ( this.tokenType === TYPE$1.Delim && this.source.charCodeAt(this.tokenStart) === code ); }, getTokenValue: function() { return this.source.substring(this.tokenStart, this.tokenEnd); }, getTokenLength: function() { return this.tokenEnd - this.tokenStart; }, substrToCursor: function(start) { return this.source.substring(start, this.tokenStart); }, skipWS: function() { for (var i = this.tokenIndex, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) { if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE) { break; } } if (skipTokenCount > 0) { this.skip(skipTokenCount); } }, skipSC: function() { while (this.tokenType === WHITESPACE || this.tokenType === COMMENT) { this.next(); } }, skip: function(tokenCount) { var next = this.tokenIndex + tokenCount; if (next < this.tokenCount) { this.tokenIndex = 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.tokenIndex = this.tokenCount; this.next(); } }, next: function() { var next = this.tokenIndex + 1; if (next < this.tokenCount) { this.tokenIndex = next; this.tokenStart = this.tokenEnd; next = this.offsetAndType[next]; this.tokenType = next >> TYPE_SHIFT; this.tokenEnd = next & OFFSET_MASK; } else { this.tokenIndex = this.tokenCount; this.eof = true; this.tokenType = EOF$1; this.tokenStart = this.tokenEnd = this.source.length; } }, dump: function() { var offset = this.firstCharOffset; 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); } }; var TokenStream_1 = TokenStream; 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 generateTypeOpts(node) { switch (node.type) { case 'Range': return ( ' [' + (node.min === null ? '-∞' : node.min) + ',' + (node.max === null ? '∞' : node.max) + ']' ); default: throw new Error('Unknown node type `' + node.type + '`'); } } function generateSequence(node, decorate, forceBraces, compact) { var combinator = node.combinator === ' ' || compact ? node.combinator : ' ' + node.combinator + ' '; var result = node.terms.map(function(term) { return generate(term, decorate, forceBraces, compact); }).join(combinator); if (node.explicit || forceBraces) { result = (compact || result[0] === ',' ? '[' : '[ ') + result + (compact ? ']' : ' ]'); } return result; } function generate(node, decorate, forceBraces, compact) { var result; switch (node.type) { case 'Group': result = generateSequence(node, decorate, forceBraces, compact) + (node.disallowEmpty ? '!' : ''); break; case 'Multiplier': // return since node is a composition return ( generate(node.term, decorate, forceBraces, compact) + decorate(generateMultiplier(node), node) ); case 'Type': result = '<' + node.name + (node.opts ? decorate(generateTypeOpts(node.opts), node.opts) : '') + '>'; 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; var compact = false; if (typeof options === 'function') { decorate = options; } else if (options) { forceBraces = Boolean(options.forceBraces); compact = Boolean(options.compact); if (typeof options.decorate === 'function') { decorate = options.decorate; } } return generate(node, decorate, forceBraces, compact); }; function fromMatchResult(matchResult) { var tokens = matchResult.tokens; var longestMatch = matchResult.longestMatch; var node = longestMatch < tokens.length ? tokens[longestMatch].node : null; var mismatchOffset = -1; 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; } return { node: node, css: css, mismatchOffset: mismatchOffset === -1 ? css.length : 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, 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 = { SyntaxReferenceError: SyntaxReferenceError, MatchError: MatchError }; var hasOwnProperty = Object.prototype.hasOwnProperty; var keywords = Object.create(null); var properties = Object.create(null); var HYPHENMINUS = 45; // '-'.charCodeAt() function isCustomProperty(str, offset) { offset = offset || 0; return str.length - offset >= 2 && str.charCodeAt(offset) === HYPHENMINUS && str.charCodeAt(offset + 1) === HYPHENMINUS; } 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 && str.charCodeAt(offset + 1) !== HYPHENMINUS) { // 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 !== '&') { 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 MIN_SIZE = 16 * 1024; var SafeUint32Array = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported var adoptBuffer = function adoptBuffer(buffer, size) { if (buffer === null || buffer.length < size) { return new SafeUint32Array(Math.max(size + 1024, MIN_SIZE)); } return buffer; }; var TYPE$2 = _const.TYPE; var isNewline$1 = charCodeDefinitions.isNewline; var isName$2 = charCodeDefinitions.isName; var isValidEscape$2 = charCodeDefinitions.isValidEscape; var isNumberStart$1 = charCodeDefinitions.isNumberStart; var isIdentifierStart$1 = charCodeDefinitions.isIdentifierStart; var charCodeCategory$1 = charCodeDefinitions.charCodeCategory; var isBOM$1 = charCodeDefinitions.isBOM; var cmpStr$2 = utils.cmpStr; var getNewlineLength$1 = utils.getNewlineLength; var findWhiteSpaceEnd$1 = utils.findWhiteSpaceEnd; var consumeEscaped$1 = utils.consumeEscaped; var consumeName$1 = utils.consumeName; var consumeNumber$1 = utils.consumeNumber; var consumeBadUrlRemnants$1 = utils.consumeBadUrlRemnants; var OFFSET_MASK$1 = 0x00FFFFFF; var TYPE_SHIFT$1 = 24; function tokenize(source, stream) { function getCharCode(offset) { return offset < sourceLength ? source.charCodeAt(offset) : 0; } // § 4.3.3. Consume a numeric token function consumeNumericToken() { // Consume a number and let number be the result. offset = consumeNumber$1(source, offset); // If the next 3 input code points would start an identifier, then: if (isIdentifierStart$1(getCharCode(offset), getCharCode(offset + 1), getCharCode(offset + 2))) { // Create a with the same value and type flag as number, and a unit set initially to the empty string. // Consume a name. Set the ’s unit to the returned value. // Return the . type = TYPE$2.Dimension; offset = consumeName$1(source, offset); return; } // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. if (getCharCode(offset) === 0x0025) { // Create a with the same value as number, and return it. type = TYPE$2.Percentage; offset++; return; } // Otherwise, create a with the same value and type flag as number, and return it. type = TYPE$2.Number; } // § 4.3.4. Consume an ident-like token function consumeIdentLikeToken() { const nameStartOffset = offset; // Consume a name, and let string be the result. offset = consumeName$1(source, offset); // If string’s value is an ASCII case-insensitive match for "url", // and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. if (cmpStr$2(source, nameStartOffset, offset, 'url') && getCharCode(offset) === 0x0028) { // While the next two input code points are whitespace, consume the next input code point. offset = findWhiteSpaceEnd$1(source, offset + 1); // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), // or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), // then create a with its value set to string and return it. if (getCharCode(offset) === 0x0022 || getCharCode(offset) === 0x0027) { type = TYPE$2.Function; offset = nameStartOffset + 4; return; } // Otherwise, consume a url token, and return it. consumeUrlToken(); return; } // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it. // Create a with its value set to string and return it. if (getCharCode(offset) === 0x0028) { type = TYPE$2.Function; offset++; return; } // Otherwise, create an with its value set to string and return it. type = TYPE$2.Ident; } // § 4.3.5. Consume a string token function consumeStringToken(endingCodePoint) { // This algorithm may be called with an ending code point, which denotes the code point // that ends the string. If an ending code point is not specified, // the current input code point is used. if (!endingCodePoint) { endingCodePoint = getCharCode(offset++); } // Initially create a with its value set to the empty string. type = TYPE$2.String; // Repeatedly consume the next input code point from the stream: for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); switch (charCodeCategory$1(code)) { // ending code point case endingCodePoint: // Return the . offset++; return; // EOF case charCodeCategory$1.Eof: // This is a parse error. Return the . return; // newline case charCodeCategory$1.WhiteSpace: if (isNewline$1(code)) { // This is a parse error. Reconsume the current input code point, // create a , and return it. offset += getNewlineLength$1(source, offset, code); type = TYPE$2.BadString; return; } break; // U+005C REVERSE SOLIDUS (\) case 0x005C: // If the next input code point is EOF, do nothing. if (offset === source.length - 1) { break; } var nextCode = getCharCode(offset + 1); // Otherwise, if the next input code point is a newline, consume it. if (isNewline$1(nextCode)) { offset += getNewlineLength$1(source, offset + 1, nextCode); } else if (isValidEscape$2(code, nextCode)) { // Otherwise, (the stream starts with a valid escape) consume // an escaped code point and append the returned code point to // the ’s value. offset = consumeEscaped$1(source, offset) - 1; } break; // anything else // Append the current input code point to the ’s value. } } } // § 4.3.6. Consume a url token // Note: This algorithm assumes that the initial "url(" has already been consumed. // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo). // A quoted value, like url("foo"), is parsed as a . Consume an ident-like token // automatically handles this distinction; this algorithm shouldn’t be called directly otherwise. function consumeUrlToken() { // Initially create a with its value set to the empty string. type = TYPE$2.Url; // Consume as much whitespace as possible. offset = findWhiteSpaceEnd$1(source, offset); // Repeatedly consume the next input code point from the stream: for (; offset < source.length; offset++) { var code = source.charCodeAt(offset); switch (charCodeCategory$1(code)) { // U+0029 RIGHT PARENTHESIS ()) case 0x0029: // Return the . offset++; return; // EOF case charCodeCategory$1.Eof: // This is a parse error. Return the . return; // whitespace case charCodeCategory$1.WhiteSpace: // Consume as much whitespace as possible. offset = findWhiteSpaceEnd$1(source, offset); // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, // consume it and return the // (if EOF was encountered, this is a parse error); if (getCharCode(offset) === 0x0029 || offset >= source.length) { if (offset < source.length) { offset++; } return; } // otherwise, consume the remnants of a bad url, create a , // and return it. offset = consumeBadUrlRemnants$1(source, offset); type = TYPE$2.BadUrl; return; // U+0022 QUOTATION MARK (") // U+0027 APOSTROPHE (') // U+0028 LEFT PARENTHESIS (() // non-printable code point case 0x0022: case 0x0027: case 0x0028: case charCodeCategory$1.NonPrintable: // This is a parse error. Consume the remnants of a bad url, // create a , and return it. offset = consumeBadUrlRemnants$1(source, offset); type = TYPE$2.BadUrl; return; // U+005C REVERSE SOLIDUS (\) case 0x005C: // If the stream starts with a valid escape, consume an escaped code point and // append the returned code point to the ’s value. if (isValidEscape$2(code, getCharCode(offset + 1))) { offset = consumeEscaped$1(source, offset) - 1; break; } // Otherwise, this is a parse error. Consume the remnants of a bad url, // create a , and return it. offset = consumeBadUrlRemnants$1(source, offset); type = TYPE$2.BadUrl; return; // anything else // Append the current input code point to the ’s value. } } } if (!stream) { stream = new TokenStream_1(); } // ensure source is a string source = String(source || ''); var sourceLength = source.length; var offsetAndType = adoptBuffer(stream.offsetAndType, sourceLength + 1); // +1 because of eof-token var balance = adoptBuffer(stream.balance, sourceLength + 1); var tokenCount = 0; var start = isBOM$1(getCharCode(0)); var offset = start; var balanceCloseType = 0; var balanceStart = 0; var balancePrev = 0; // https://drafts.csswg.org/css-syntax-3/#consume-token // § 4.3.1. Consume a token while (offset < sourceLength) { var code = source.charCodeAt(offset); var type = 0; balance[tokenCount] = sourceLength; switch (charCodeCategory$1(code)) { // whitespace case charCodeCategory$1.WhiteSpace: // Consume as much whitespace as possible. Return a . type = TYPE$2.WhiteSpace; offset = findWhiteSpaceEnd$1(source, offset + 1); break; // U+0022 QUOTATION MARK (") case 0x0022: // Consume a string token and return it. consumeStringToken(); break; // U+0023 NUMBER SIGN (#) case 0x0023: // If the next input code point is a name code point or the next two input code points are a valid escape, then: if (isName$2(getCharCode(offset + 1)) || isValidEscape$2(getCharCode(offset + 1), getCharCode(offset + 2))) { // Create a . type = TYPE$2.Hash; // If the next 3 input code points would start an identifier, set the ’s type flag to "id". // if (isIdentifierStart(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { // // TODO: set id flag // } // Consume a name, and set the ’s value to the returned string. offset = consumeName$1(source, offset + 1); // Return the . } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+0027 APOSTROPHE (') case 0x0027: // Consume a string token and return it. consumeStringToken(); break; // U+0028 LEFT PARENTHESIS (() case 0x0028: // Return a <(-token>. type = TYPE$2.LeftParenthesis; offset++; break; // U+0029 RIGHT PARENTHESIS ()) case 0x0029: // Return a <)-token>. type = TYPE$2.RightParenthesis; offset++; break; // U+002B PLUS SIGN (+) case 0x002B: // If the input stream starts with a number, ... if (isNumberStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { // ... reconsume the current input code point, consume a numeric token, and return it. consumeNumericToken(); } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+002C COMMA (,) case 0x002C: // Return a . type = TYPE$2.Comma; offset++; break; // U+002D HYPHEN-MINUS (-) case 0x002D: // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. if (isNumberStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { consumeNumericToken(); } else { // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a . if (getCharCode(offset + 1) === 0x002D && getCharCode(offset + 2) === 0x003E) { type = TYPE$2.CDC; offset = offset + 3; } else { // Otherwise, if the input stream starts with an identifier, ... if (isIdentifierStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { // ... reconsume the current input code point, consume an ident-like token, and return it. consumeIdentLikeToken(); } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } } } break; // U+002E FULL STOP (.) case 0x002E: // If the input stream starts with a number, ... if (isNumberStart$1(code, getCharCode(offset + 1), getCharCode(offset + 2))) { // ... reconsume the current input code point, consume a numeric token, and return it. consumeNumericToken(); } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+002F SOLIDUS (/) case 0x002F: // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*), if (getCharCode(offset + 1) === 0x002A) { // ... consume them and all following code points up to and including the first U+002A ASTERISK (*) // followed by a U+002F SOLIDUS (/), or up to an EOF code point. type = TYPE$2.Comment; offset = source.indexOf('*/', offset + 2) + 2; if (offset === 1) { offset = source.length; } } else { type = TYPE$2.Delim; offset++; } break; // U+003A COLON (:) case 0x003A: // Return a . type = TYPE$2.Colon; offset++; break; // U+003B SEMICOLON (;) case 0x003B: // Return a . type = TYPE$2.Semicolon; offset++; break; // U+003C LESS-THAN SIGN (<) case 0x003C: // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), ... if (getCharCode(offset + 1) === 0x0021 && getCharCode(offset + 2) === 0x002D && getCharCode(offset + 3) === 0x002D) { // ... consume them and return a . type = TYPE$2.CDO; offset = offset + 4; } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+0040 COMMERCIAL AT (@) case 0x0040: // If the next 3 input code points would start an identifier, ... if (isIdentifierStart$1(getCharCode(offset + 1), getCharCode(offset + 2), getCharCode(offset + 3))) { // ... consume a name, create an with its value set to the returned value, and return it. type = TYPE$2.AtKeyword; offset = consumeName$1(source, offset + 1); } else { // Otherwise, return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+005B LEFT SQUARE BRACKET ([) case 0x005B: // Return a <[-token>. type = TYPE$2.LeftSquareBracket; offset++; break; // U+005C REVERSE SOLIDUS (\) case 0x005C: // If the input stream starts with a valid escape, ... if (isValidEscape$2(code, getCharCode(offset + 1))) { // ... reconsume the current input code point, consume an ident-like token, and return it. consumeIdentLikeToken(); } else { // Otherwise, this is a parse error. Return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } break; // U+005D RIGHT SQUARE BRACKET (]) case 0x005D: // Return a <]-token>. type = TYPE$2.RightSquareBracket; offset++; break; // U+007B LEFT CURLY BRACKET ({) case 0x007B: // Return a <{-token>. type = TYPE$2.LeftCurlyBracket; offset++; break; // U+007D RIGHT CURLY BRACKET (}) case 0x007D: // Return a <}-token>. type = TYPE$2.RightCurlyBracket; offset++; break; // digit case charCodeCategory$1.Digit: // Reconsume the current input code point, consume a numeric token, and return it. consumeNumericToken(); break; // name-start code point case charCodeCategory$1.NameStart: // Reconsume the current input code point, consume an ident-like token, and return it. consumeIdentLikeToken(); break; // EOF case charCodeCategory$1.Eof: // Return an . break; // anything else default: // Return a with its value set to the current input code point. type = TYPE$2.Delim; offset++; } switch (type) { case balanceCloseType: balancePrev = balanceStart & OFFSET_MASK$1; balanceStart = balance[balancePrev]; balanceCloseType = balanceStart >> TYPE_SHIFT$1; balance[tokenCount] = balancePrev; balance[balancePrev++] = tokenCount; for (; balancePrev < tokenCount; balancePrev++) { if (balance[balancePrev] === sourceLength) { balance[balancePrev] = tokenCount; } } break; case TYPE$2.LeftParenthesis: case TYPE$2.Function: balance[tokenCount] = balanceStart; balanceCloseType = TYPE$2.RightParenthesis; balanceStart = (balanceCloseType << TYPE_SHIFT$1) | tokenCount; break; case TYPE$2.LeftSquareBracket: balance[tokenCount] = balanceStart; balanceCloseType = TYPE$2.RightSquareBracket; balanceStart = (balanceCloseType << TYPE_SHIFT$1) | tokenCount; break; case TYPE$2.LeftCurlyBracket: balance[tokenCount] = balanceStart; balanceCloseType = TYPE$2.RightCurlyBracket; balanceStart = (balanceCloseType << TYPE_SHIFT$1) | tokenCount; break; } offsetAndType[tokenCount++] = (type << TYPE_SHIFT$1) | offset; } // finalize buffers offsetAndType[tokenCount] = (TYPE$2.EOF << TYPE_SHIFT$1) | offset; // balance[tokenCount] = sourceLength; balance[sourceLength] = sourceLength; // prevents false positive balance match with any token while (balanceStart !== 0) { balancePrev = balanceStart & OFFSET_MASK$1; balanceStart = balance[balancePrev]; balance[balancePrev] = sourceLength; } // update stream stream.source = source; stream.firstCharOffset = start; stream.offsetAndType = offsetAndType; stream.tokenCount = tokenCount; stream.balance = balance; stream.reset(); stream.next(); return stream; } // extend tokenizer with constants Object.keys(_const).forEach(function(key) { tokenize[key] = _const[key]; }); // extend tokenizer with static methods from utils Object.keys(charCodeDefinitions).forEach(function(key) { tokenize[key] = charCodeDefinitions[key]; }); Object.keys(utils).forEach(function(key) { tokenize[key] = utils[key]; }); var tokenizer = tokenize; var isDigit$2 = tokenizer.isDigit; var cmpChar$1 = tokenizer.cmpChar; var TYPE$3 = tokenizer.TYPE; var DELIM = TYPE$3.Delim; var WHITESPACE$1 = TYPE$3.WhiteSpace; var COMMENT$1 = TYPE$3.Comment; var IDENT = TYPE$3.Ident; var NUMBER = TYPE$3.Number; var DIMENSION = TYPE$3.Dimension; var PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+) var HYPHENMINUS$1 = 0x002D; // U+002D HYPHEN-MINUS (-) var N = 0x006E; // U+006E LATIN SMALL LETTER N (n) var DISALLOW_SIGN = true; var ALLOW_SIGN = false; function isDelim(token, code) { return token !== null && token.type === DELIM && token.value.charCodeAt(0) === code; } function skipSC(token, offset, getNextToken) { while (token !== null && (token.type === WHITESPACE$1 || token.type === COMMENT$1)) { token = getNextToken(++offset); } return offset; } function checkInteger(token, valueOffset, disallowSign, offset) { if (!token) { return 0; } var code = token.value.charCodeAt(valueOffset); if (code === PLUSSIGN || code === HYPHENMINUS$1) { if (disallowSign) { // Number sign is not allowed return 0; } valueOffset++; } for (; valueOffset < token.value.length; valueOffset++) { if (!isDigit$2(token.value.charCodeAt(valueOffset))) { // Integer is expected return 0; } } return offset + 1; } // ... // ... ['+' | '-'] function consumeB(token, offset_, getNextToken) { var sign = false; var offset = skipSC(token, offset_, getNextToken); token = getNextToken(offset); if (token === null) { return offset_; } if (token.type !== NUMBER) { if (isDelim(token, PLUSSIGN) || isDelim(token, HYPHENMINUS$1)) { sign = true; offset = skipSC(getNextToken(++offset), offset, getNextToken); token = getNextToken(offset); if (token === null && token.type !== NUMBER) { return 0; } } else { return offset_; } } if (!sign) { var code = token.value.charCodeAt(0); if (code !== PLUSSIGN && code !== HYPHENMINUS$1) { // Number sign is expected return 0; } } return checkInteger(token, sign ? 0 : 1, sign, offset); } // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb var genericAnPlusB = function anPlusB(token, getNextToken) { /* eslint-disable brace-style*/ var offset = 0; if (!token) { return 0; } // if (token.type === NUMBER) { return checkInteger(token, 0, ALLOW_SIGN, offset); // b } // -n // -n // -n ['+' | '-'] // -n- // else if (token.type === IDENT && token.value.charCodeAt(0) === HYPHENMINUS$1) { // expect 1st char is N if (!cmpChar$1(token.value, 1, N)) { return 0; } switch (token.value.length) { // -n // -n // -n ['+' | '-'] case 2: return consumeB(getNextToken(++offset), offset, getNextToken); // -n- case 3: if (token.value.charCodeAt(2) !== HYPHENMINUS$1) { return 0; } offset = skipSC(getNextToken(++offset), offset, getNextToken); token = getNextToken(offset); return checkInteger(token, 0, DISALLOW_SIGN, offset); // default: if (token.value.charCodeAt(2) !== HYPHENMINUS$1) { return 0; } return checkInteger(token, 3, DISALLOW_SIGN, offset); } } // '+'? n // '+'? n // '+'? n ['+' | '-'] // '+'? n- // '+'? else if (token.type === IDENT || (isDelim(token, PLUSSIGN) && getNextToken(offset + 1).type === IDENT)) { // just ignore a plus if (token.type !== IDENT) { token = getNextToken(++offset); } if (token === null || !cmpChar$1(token.value, 0, N)) { return 0; } switch (token.value.length) { // '+'? n // '+'? n // '+'? n ['+' | '-'] case 1: return consumeB(getNextToken(++offset), offset, getNextToken); // '+'? n- case 2: if (token.value.charCodeAt(1) !== HYPHENMINUS$1) { return 0; } offset = skipSC(getNextToken(++offset), offset, getNextToken); token = getNextToken(offset); return checkInteger(token, 0, DISALLOW_SIGN, offset); // '+'? default: if (token.value.charCodeAt(1) !== HYPHENMINUS$1) { return 0; } return checkInteger(token, 2, DISALLOW_SIGN, offset); } } // // // // // ['+' | '-'] else if (token.type === DIMENSION) { var code = token.value.charCodeAt(0); var sign = code === PLUSSIGN || code === HYPHENMINUS$1 ? 1 : 0; for (var i = sign; i < token.value.length; i++) { if (!isDigit$2(token.value.charCodeAt(i))) { break; } } if (i === sign) { // Integer is expected return 0; } if (!cmpChar$1(token.value, i, N)) { return 0; } // // // ['+' | '-'] if (i + 1 === token.value.length) { return consumeB(getNextToken(++offset), offset, getNextToken); } else { if (token.value.charCodeAt(i + 1) !== HYPHENMINUS$1) { return 0; } // if (i + 2 === token.value.length) { offset = skipSC(getNextToken(++offset), offset, getNextToken); token = getNextToken(offset); return checkInteger(token, 0, DISALLOW_SIGN, offset); } // else { return checkInteger(token, i + 2, DISALLOW_SIGN, offset); } } } return 0; }; var isHexDigit$2 = tokenizer.isHexDigit; var cmpChar$2 = tokenizer.cmpChar; var TYPE$4 = tokenizer.TYPE; var IDENT$1 = TYPE$4.Ident; var DELIM$1 = TYPE$4.Delim; var NUMBER$1 = TYPE$4.Number; var DIMENSION$1 = TYPE$4.Dimension; var PLUSSIGN$1 = 0x002B; // U+002B PLUS SIGN (+) var HYPHENMINUS$2 = 0x002D; // U+002D HYPHEN-MINUS (-) var QUESTIONMARK = 0x003F; // U+003F QUESTION MARK (?) var U = 0x0075; // U+0075 LATIN SMALL LETTER U (u) function isDelim$1(token, code) { return token !== null && token.type === DELIM$1 && token.value.charCodeAt(0) === code; } function startsWith(token, code) { return token.value.charCodeAt(0) === code; } function hexSequence(token, offset, allowDash) { for (var pos = offset, hexlen = 0; pos < token.value.length; pos++) { var code = token.value.charCodeAt(pos); if (code === HYPHENMINUS$2 && allowDash && hexlen !== 0) { if (hexSequence(token, offset + hexlen + 1, false) > 0) { return 6; // dissallow following question marks } return 0; // dash at the ending of a hex sequence is not allowed } if (!isHexDigit$2(code)) { return 0; // not a hex digit } if (++hexlen > 6) { return 0; // too many hex digits } } return hexlen; } function withQuestionMarkSequence(consumed, length, getNextToken) { if (!consumed) { return 0; // nothing consumed } while (isDelim$1(getNextToken(length), QUESTIONMARK)) { if (++consumed > 6) { return 0; // too many question marks } length++; } return length; } // https://drafts.csswg.org/css-syntax/#urange // Informally, the production has three forms: // U+0001 // Defines a range consisting of a single code point, in this case the code point "1". // U+0001-00ff // Defines a range of codepoints between the first and the second value, in this case // the range between "1" and "ff" (255 in decimal) inclusive. // U+00?? // Defines a range of codepoints where the "?" characters range over all hex digits, // in this case defining the same as the value U+0000-00ff. // In each form, a maximum of 6 digits is allowed for each hexadecimal number (if you treat "?" as a hexadecimal digit). // // = // u '+' '?'* | // u '?'* | // u '?'* | // u | // u | // u '+' '?'+ var genericUrange = function urange(token, getNextToken) { var length = 0; // should start with `u` or `U` if (token === null || token.type !== IDENT$1 || !cmpChar$2(token.value, 0, U)) { return 0; } token = getNextToken(++length); if (token === null) { return 0; } // u '+' '?'* // u '+' '?'+ if (isDelim$1(token, PLUSSIGN$1)) { token = getNextToken(++length); if (token === null) { return 0; } if (token.type === IDENT$1) { // u '+' '?'* return withQuestionMarkSequence(hexSequence(token, 0, true), ++length, getNextToken); } if (isDelim$1(token, QUESTIONMARK)) { // u '+' '?'+ return withQuestionMarkSequence(1, ++length, getNextToken); } // Hex digit or question mark is expected return 0; } // u '?'* // u // u if (token.type === NUMBER$1) { if (!startsWith(token, PLUSSIGN$1)) { return 0; } var consumedHexLength = hexSequence(token, 1, true); if (consumedHexLength === 0) { return 0; } token = getNextToken(++length); if (token === null) { // u return length; } if (token.type === DIMENSION$1 || token.type === NUMBER$1) { // u // u if (!startsWith(token, HYPHENMINUS$2) || !hexSequence(token, 1, false)) { return 0; } return length + 1; } // u '?'* return withQuestionMarkSequence(consumedHexLength, length, getNextToken); } // u '?'* if (token.type === DIMENSION$1) { if (!startsWith(token, PLUSSIGN$1)) { return 0; } return withQuestionMarkSequence(hexSequence(token, 1, true), ++length, getNextToken); } return 0; }; var isIdentifierStart$2 = tokenizer.isIdentifierStart; var isHexDigit$3 = tokenizer.isHexDigit; var isDigit$3 = tokenizer.isDigit; var cmpStr$3 = tokenizer.cmpStr; var consumeNumber$2 = tokenizer.consumeNumber; var TYPE$5 = tokenizer.TYPE; var cssWideKeywords = ['unset', 'initial', 'inherit']; var calcFunctionNames = ['calc(', '-moz-calc(', '-webkit-calc(']; // 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 }; // safe char code getter function charCode(str, index) { return index < str.length ? str.charCodeAt(index) : 0; } function eqStr(actual, expected) { return cmpStr$3(actual, 0, actual.length, expected); } function eqStrAny(actual, expected) { for (var i = 0; i < expected.length; i++) { if (eqStr(actual, expected[i])) { return true; } } return false; } // IE postfix hack, i.e. 123\0 or 123px\9 function isPostfixIeHack(str, offset) { if (offset !== str.length - 2) { return false; } return ( str.charCodeAt(offset) === 0x005C && // U+005C REVERSE SOLIDUS (\) isDigit$3(str.charCodeAt(offset + 1)) ); } function outOfRange(opts, value, numEnd) { if (opts && opts.type === 'Range') { var num = Number( numEnd !== undefined && numEnd !== value.length ? value.substr(0, numEnd) : value ); if (isNaN(num)) { return true; } if (opts.min !== null && num < opts.min) { return true; } if (opts.max !== null && num > opts.max) { return true; } } return false; } function consumeFunction(token, getNextToken) { var startIdx = token.index; var length = 0; // balanced token consuming do { length++; if (token.balance <= startIdx) { break; } } while (token = getNextToken(length)); return length; } // TODO: implement // can be used wherever , , ,