/**
* @license Paged.js v0.1.43 | 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;
}
if (node.dataset && node.dataset.undisplayed) {
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;
}
function breakInsideAvoidParentNode(node) {
while ((node = node.parentNode)) {
if (node && node.dataset && node.dataset.breakInside === "avoid") {
return node;
}
}
return null;
}
/**
* Find a parent with a given node name.
* @param {Node} node - initial Node
* @param {string} nodeName - node name (eg. "TD", "TABLE", "STRONG"...)
* @param {Node} limiter - go up to the parent until there's no more parent or the current node is equals to the limiter
* @returns {Node|undefined} - Either:
* 1) The closest parent for a the given node name, or
* 2) undefined if no such node exists.
*/
function parentOf(node, nodeName, limiter) {
if (limiter && node === limiter) {
return;
}
if (node.parentNode) {
while ((node = node.parentNode)) {
if (limiter && node === limiter) {
return;
}
if (node.nodeName === nodeName) {
return node;
}
}
}
}
/**
* 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;
// Check if the node is inside a break-inside: avoid table cell
const insideTableCell = parentOf(node, "TD", rendered);
if (insideTableCell && window.getComputedStyle(insideTableCell)["break-inside"] === "avoid") {
// breaking inside a table cell produces unexpected result, as a workaround, we forcibly avoid break inside in a cell.
prev = insideTableCell;
} else 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 &&
!breakInsideAvoidParentNode(node.parentNode)) {
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 , , ,