diff --git a/content/print/_index.md b/content/print/_index.md
new file mode 100644
index 0000000..6437020
--- /dev/null
+++ b/content/print/_index.md
@@ -0,0 +1,4 @@
+---
+title: let's print
+---
+
diff --git a/static/css/styles.css b/static/css/styles.css
index cec06a0..593611f 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -1 +1,18 @@
-/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}a{background-color:transparent}b{font-weight:bolder}img{border-style:none}input{font-family:inherit;font-size:100%;line-height:1.15;margin:0}input{overflow:visible}[type=checkbox]{box-sizing:border-box;padding:0}template{display:none}p{margin:0}ul{list-style:none;margin:0;padding:0}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;line-height:1.5}*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}img{border-style:solid}input::-webkit-input-placeholder{color:#a0aec0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}table{border-collapse:collapse}a{color:inherit;text-decoration:inherit}input{padding:0;line-height:inherit;color:inherit}img,svg{display:block;vertical-align:middle}img{max-width:100%;height:auto}.bg-CoconutCream{background-color:#f2f6d5}.bg-AuChico{background-color:#996561}.border-CoconutCream{border-color:#f2f6d5}.border-b-8{border-bottom-width:8px}.cursor-pointer{cursor:pointer}.block{display:block}.flex{display:-webkit-box;display:flex}.table{display:table}.justify-between{-webkit-box-pack:justify;justify-content:space-between}.font-vg5000{font-family:vg5000-regular,sans}.font-playfair{font-family:playfairdisplay regular,sans}.font-bold{font-weight:700}.h-full{height:100%}.leading-none{line-height:1}.mx-4{margin-left:1rem;margin-right:1rem}.mb-1{margin-bottom:.25rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-12{margin-bottom:3rem}.p-1{padding:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pr-4{padding-right:1rem}.pt-6{padding-top:1.5rem}.pb-8{padding-bottom:2rem}.pt-16{padding-top:4rem}.pt-32{padding-top:8rem}.static{position:static}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.text-CoconutCream{color:#f2f6d5}.text-xs{font-size:.75rem}.text-base{font-size:1rem}.italic{font-style:italic}.z-10{z-index:10}@font-face{font-family:playfairdisplay regular;font-weight:400;src:url(../fonts/PlayfairDisplay-Regular.woff)format('woff')}@font-face{font-family:vg5000-regular;font-weight:400;src:url(../fonts/VG5000-Regular_web.woff)format('woff')}html{font-size:1.2em;background-color:#f2f6d5}img{padding-top:.5rem;padding-bottom:.5rem}h1{font-size:1.875rem}h2{font-size:1.5rem}h3{font-size:1.25rem}blockquote{font-style:italic}p{padding-bottom:.5rem;line-height:1.25}footer{font-family:vg5000-regular,sans;font-size:.75rem;color:#29102f}article ul{position:relative;list-style-type:none;margin-left:0;padding-left:.75rem}article ul li:before{font-family:vg5000-regular,sans;color:#996561;font-size:.75rem;left:0;position:absolute;padding-top:.5rem;padding-bottom:.5rem;content:"β’"}article li{padding-left:.5rem}a{color:#996561}a:hover{text-decoration:underline}.edit-button{border-bottom-width:4px;border-color:#f2f6d5;padding-left:.25rem;padding-right:.25rem;background-color:#996561;margin-bottom:.5rem;font-family:vg5000-regular,sans;color:#f2f6d5}.edit-button:hover{background-color:#f2f6d5;color:#996561;border-bottom-width:2px;border-color:#996561}.title-text{font-family:playfairdisplay regular,sans;font-size:2.25rem;color:#996561}.title-pretext{font-family:vg5000-regular,sans;font-size:1rem;color:#996561}.content-text{font-family:playfairdisplay regular,sans;font-size:1.25rem;color:#29102f}.sidebar-title{font-family:vg5000-regular,sans;font-size:1rem;color:#996561}.sidebar-list{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561}.logo{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561;padding-top:.5rem}.ddmenu .sidebar-title{cursor:pointer}.ddmenu input{display:none}.ddmenu .hiddendiv{padding-bottom:.25rem;display:none}.ddmenu input:not(:checked)~.hiddendiv{display:block;padding-bottom:1rem}#TableOfContents{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561;margin-left:-.5rem}#TableOfContents ul{margin-left:.5rem}#TableOfContents li:before{content:"> "}@media(max-width:767px){.md\:flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}.md\:w-full{width:100%}}@media(min-width:768px){.lg\:flex{display:-webkit-box;display:flex}.lg\:static{position:static}.lg\:sticky{position:-webkit-sticky;position:sticky}.lg\:top-0{top:0}.lg\:w-2\/5{width:40%}.lg\:w-3\/5{width:60%}}
\ No newline at end of file
+/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}a{background-color:transparent}b{font-weight:bolder}img{border-style:none}input{font-family:inherit;font-size:100%;line-height:1.15;margin:0}input{overflow:visible}[type=checkbox]{box-sizing:border-box;padding:0}template{display:none}p{margin:0}ul{list-style:none;margin:0;padding:0}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;line-height:1.5}*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}img{border-style:solid}input::-webkit-input-placeholder{color:#a0aec0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}table{border-collapse:collapse}a{color:inherit;text-decoration:inherit}input{padding:0;line-height:inherit;color:inherit}img,svg{display:block;vertical-align:middle}img{max-width:100%;height:auto}.bg-CoconutCream{background-color:#f2f6d5}.bg-AuChico{background-color:#996561}.border-CoconutCream{border-color:#f2f6d5}.border-b-8{border-bottom-width:8px}.cursor-pointer{cursor:pointer}.block{display:block}.flex{display:-webkit-box;display:flex}.table{display:table}.justify-between{-webkit-box-pack:justify;justify-content:space-between}.font-vg5000{font-family:vg5000-regular,sans}.font-playfair{font-family:playfairdisplay regular,sans}.font-bold{font-weight:700}.h-full{height:100%}.leading-none{line-height:1}.mx-4{margin-left:1rem;margin-right:1rem}.mb-1{margin-bottom:.25rem}.mt-4{margin-top:1rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-12{margin-bottom:3rem}.p-1{padding:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pr-4{padding-right:1rem}.pt-6{padding-top:1.5rem}.pb-8{padding-bottom:2rem}.pt-16{padding-top:4rem}.pt-32{padding-top:8rem}.static{position:static}.sticky{position:-webkit-sticky;position:sticky}.top-0{top:0}.text-CoconutCream{color:#f2f6d5}.text-xs{font-size:.75rem}.text-base{font-size:1rem}.italic{font-style:italic}.z-10{z-index:10}@font-face{font-family:playfairdisplay regular;font-weight:400;src:url(../fonts/PlayfairDisplay-Regular.woff)format('woff')}@font-face{font-family:vg5000-regular;font-weight:400;src:url(../fonts/VG5000-Regular_web.woff)format('woff')}html{font-size:1.2em;background-color:#f2f6d5}img{padding-top:.5rem;padding-bottom:.5rem}h1{font-size:1.875rem}h2{font-size:1.5rem}h3{font-size:1.25rem}blockquote{font-style:italic}p{padding-bottom:.5rem;line-height:1.25}footer{font-family:vg5000-regular,sans;font-size:.75rem;color:#29102f}article ul{position:relative;list-style-type:none;margin-left:0;padding-left:.75rem}article ul li:before{font-family:vg5000-regular,sans;color:#996561;font-size:.75rem;left:0;position:absolute;padding-top:.5rem;padding-bottom:.5rem;content:"β’"}article li{padding-left:.5rem}a{color:#996561}a:hover{text-decoration:underline}.edit-button{border-bottom-width:4px;border-color:#f2f6d5;padding-left:.25rem;padding-right:.25rem;background-color:#996561;margin-bottom:.5rem;font-family:vg5000-regular,sans;color:#f2f6d5}.edit-button:hover{background-color:#f2f6d5;color:#996561;border-bottom-width:2px;border-color:#996561}.title-text{font-family:playfairdisplay regular,sans;font-size:2.25rem;color:#996561}.title-pretext{font-family:vg5000-regular,sans;font-size:1rem;color:#996561}.content-text{font-family:playfairdisplay regular,sans;font-size:1.25rem;color:#29102f}.sidebar-title{font-family:vg5000-regular,sans;font-size:1rem;color:#996561}.sidebar-list{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561}.logo{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561;padding-top:.5rem}.ddmenu .sidebar-title{cursor:pointer}.ddmenu input{display:none}.ddmenu .hiddendiv{padding-bottom:.25rem;display:none}.ddmenu input:not(:checked)~.hiddendiv{display:block;padding-bottom:1rem}#TableOfContents{font-family:vg5000-regular,sans;font-size:1.25rem;color:#996561;margin-left:-.5rem}#TableOfContents ul{margin-left:.5rem}#TableOfContents li:before{content:"> "}@page{size:A4}@page:first{@bottom{content: none;
+ }}@page{margin-bottom:5mm;@top{color: #996561;
+
+ font-size: 1rem;
+
+ font-family: 'VG5000-Regular';
+
+ content: -moz-element(topic);
+
+ content: element(topic);
+ }@bottom{color: #996561;
+
+ font-size: 0.5rem;
+
+ font-family: 'VG5000-Regular';
+
+ content: "ββ π β βββ π βββπ ββββ PAGE: " counter(page) " β ββββ β‘ βπβ β β βββ";
+ }}@media print{body{background-color:#f2f6d5}.runningTopic{position:running(topic)}.topic{-webkit-column-break-before:page;-moz-column-break-before:page;break-before:page}.title-text{font-family:playfairdisplay regular,sans;font-size:4rem;color:#996561;margin-bottom:5rem}.title-pretext{font-family:vg5000-regular,sans;font-size:1.5rem;color:#996561}.topic-text{font-family:playfairdisplay regular,sans;font-size:2.25rem;color:#996561;margin-bottom:5rem}.topic-pretext{font-family:vg5000-regular,sans;font-size:1.5rem;color:#996561}.ddmenu{padding-top:4rem}article ul li:before{content:""}}@media(max-width:767px){.md\:flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}.md\:w-full{width:100%}}@media(min-width:768px){.lg\:flex{display:-webkit-box;display:flex}.lg\:static{position:static}.lg\:sticky{position:-webkit-sticky;position:sticky}.lg\:top-0{top:0}.lg\:w-2\/5{width:40%}.lg\:w-3\/5{width:60%}}
\ No newline at end of file
diff --git a/static/js/paged.polyfill.js b/static/js/paged.polyfill.js
new file mode 100644
index 0000000..bea1e19
--- /dev/null
+++ b/static/js/paged.polyfill.js
@@ -0,0 +1,28367 @@
+(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;
+ }
+
+ // If the character is not handled by one of the above rules and is
+ // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
+ // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
+ // U+005A), or [a-z] (U+0061 to U+007A), [β¦]
+ if (
+ codeUnit >= 0x0080 ||
+ codeUnit == 0x002D ||
+ codeUnit == 0x005F ||
+ codeUnit == 35 || // Allow #
+ codeUnit == 46 || // Allow .
+ codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
+ codeUnit >= 0x0041 && codeUnit <= 0x005A ||
+ codeUnit >= 0x0061 && codeUnit <= 0x007A
+ ) {
+ // the character itself
+ result += string.charAt(index);
+ continue;
+ }
+
+ // Otherwise, the escaped character.
+ // https://drafts.csswg.org/cssom/#escape-a-character
+ result += "\\" + string.charAt(index);
+
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new pending promise and provides methods to resolve or reject it.
+ * From: https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred#backwards_forwards_compatible
+ * @returns {object} defered
+ */
+ function defer() {
+ this.resolve = null;
+
+ this.reject = null;
+
+ this.id = UUID();
+
+ this.promise = new Promise((resolve, reject) => {
+ this.resolve = resolve;
+ this.reject = reject;
+ });
+ Object.freeze(this);
+ }
+
+ const requestIdleCallback = typeof window !== "undefined" && ("requestIdleCallback" in window ? window.requestIdleCallback : window.requestAnimationFrame);
+
+ function CSSValueToString(obj) {
+ return obj.value + (obj.unit || "");
+ }
+
+ function isElement(node) {
+ return node && node.nodeType === 1;
+ }
+
+ function isText(node) {
+ return node && node.nodeType === 3;
+ }
+
+ function *walk(start, limiter) {
+ let node = start;
+
+ while (node) {
+
+ yield node;
+
+ if (node.childNodes.length) {
+ node = node.firstChild;
+ } else if (node.nextSibling) {
+ if (limiter && node === limiter) {
+ node = undefined;
+ break;
+ }
+ node = node.nextSibling;
+ } else {
+ while (node) {
+ node = node.parentNode;
+ if (limiter && node === limiter) {
+ node = undefined;
+ break;
+ }
+ if (node && node.nextSibling) {
+ node = node.nextSibling;
+ break;
+ }
+
+ }
+ }
+ }
+ }
+
+ function nodeAfter(node, limiter) {
+ let after = node;
+
+ if (after.nextSibling) {
+ if (limiter && node === limiter) {
+ return;
+ }
+ after = after.nextSibling;
+ } else {
+ while (after) {
+ after = after.parentNode;
+ if (limiter && after === limiter) {
+ after = undefined;
+ break;
+ }
+ if (after && after.nextSibling) {
+ after = after.nextSibling;
+ break;
+ }
+ }
+ }
+
+ return after;
+ }
+
+ function nodeBefore(node, limiter) {
+ let before = node;
+ if (before.previousSibling) {
+ if (limiter && node === limiter) {
+ return;
+ }
+ before = before.previousSibling;
+ } else {
+ while (before) {
+ before = before.parentNode;
+ if (limiter && before === limiter) {
+ before = undefined;
+ break;
+ }
+ if (before && before.previousSibling) {
+ before = before.previousSibling;
+ break;
+ }
+ }
+ }
+
+ return before;
+ }
+
+ function elementAfter(node, limiter) {
+ let after = nodeAfter(node);
+
+ while (after && after.nodeType !== 1) {
+ after = nodeAfter(after);
+ }
+
+ return after;
+ }
+
+ function rebuildAncestors(node) {
+ let parent, ancestor;
+ let ancestors = [];
+ let added = [];
+
+ let fragment = document.createDocumentFragment();
+
+ // Gather all ancestors
+ let element = node;
+ while(element.parentNode && element.parentNode.nodeType === 1) {
+ ancestors.unshift(element.parentNode);
+ element = element.parentNode;
+ }
+
+ for (var i = 0; i < ancestors.length; i++) {
+ ancestor = ancestors[i];
+ parent = ancestor.cloneNode(false);
+
+ parent.setAttribute("data-split-from", parent.getAttribute("data-ref"));
+ // ancestor.setAttribute("data-split-to", parent.getAttribute("data-ref"));
+
+ if (parent.hasAttribute("id")) {
+ let dataID = parent.getAttribute("id");
+ parent.setAttribute("data-id", dataID);
+ parent.removeAttribute("id");
+ }
+
+ // This is handled by css :not, but also tidied up here
+ if (parent.hasAttribute("data-break-before")) {
+ parent.removeAttribute("data-break-before");
+ }
+
+ if (parent.hasAttribute("data-previous-break-after")) {
+ parent.removeAttribute("data-previous-break-after");
+ }
+
+ if (added.length) {
+ let container = added[added.length-1];
+ container.appendChild(parent);
+ } else {
+ fragment.appendChild(parent);
+ }
+ added.push(parent);
+ }
+
+ added = undefined;
+ return fragment;
+ }
+
+ /*
+ export function split(bound, cutElement, breakAfter) {
+ let needsRemoval = [];
+ let index = indexOf(cutElement);
+
+ if (!breakAfter && index === 0) {
+ return;
+ }
+
+ if (breakAfter && index === (cutElement.parentNode.children.length - 1)) {
+ return;
+ }
+
+ // Create a fragment with rebuilt ancestors
+ let fragment = rebuildAncestors(cutElement);
+
+ // Clone cut
+ if (!breakAfter) {
+ let clone = cutElement.cloneNode(true);
+ let ref = cutElement.parentNode.getAttribute('data-ref');
+ let parent = fragment.querySelector("[data-ref='" + ref + "']");
+ parent.appendChild(clone);
+ needsRemoval.push(cutElement);
+ }
+
+ // Remove all after cut
+ let next = nodeAfter(cutElement, bound);
+ while (next) {
+ let clone = next.cloneNode(true);
+ let ref = next.parentNode.getAttribute('data-ref');
+ let parent = fragment.querySelector("[data-ref='" + ref + "']");
+ parent.appendChild(clone);
+ needsRemoval.push(next);
+ next = nodeAfter(next, bound);
+ }
+
+ // Remove originals
+ needsRemoval.forEach((node) => {
+ if (node) {
+ node.remove();
+ }
+ });
+
+ // Insert after bounds
+ bound.parentNode.insertBefore(fragment, bound.nextSibling);
+ return [bound, bound.nextSibling];
+ }
+ */
+
+ function needsBreakBefore(node) {
+ if( typeof node !== "undefined" &&
+ typeof node.dataset !== "undefined" &&
+ typeof node.dataset.breakBefore !== "undefined" &&
+ (node.dataset.breakBefore === "always" ||
+ node.dataset.breakBefore === "page" ||
+ node.dataset.breakBefore === "left" ||
+ node.dataset.breakBefore === "right" ||
+ node.dataset.breakBefore === "recto" ||
+ node.dataset.breakBefore === "verso")
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function needsPreviousBreakAfter(node) {
+ if( typeof node !== "undefined" &&
+ typeof node.dataset !== "undefined" &&
+ typeof node.dataset.previousBreakAfter !== "undefined" &&
+ (node.dataset.previousBreakAfter === "always" ||
+ node.dataset.previousBreakAfter === "page" ||
+ node.dataset.previousBreakAfter === "left" ||
+ node.dataset.previousBreakAfter === "right" ||
+ node.dataset.previousBreakAfter === "recto" ||
+ node.dataset.previousBreakAfter === "verso")
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function needsPageBreak(node) {
+ if( typeof node !== "undefined" &&
+ typeof node.dataset !== "undefined" &&
+ (node.dataset.page || node.dataset.afterPage)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function *words(node) {
+ let currentText = node.nodeValue;
+ let max = currentText.length;
+ let currentOffset = 0;
+ let currentLetter;
+
+ let range;
+
+ while(currentOffset < max) {
+ currentLetter = currentText[currentOffset];
+ if (/^[\S\u202F\u00A0]$/.test(currentLetter)) {
+ if (!range) {
+ range = document.createRange();
+ range.setStart(node, currentOffset);
+ }
+ } else {
+ if (range) {
+ range.setEnd(node, currentOffset);
+ yield range;
+ range = undefined;
+ }
+ }
+
+ currentOffset += 1;
+ }
+
+ if (range) {
+ range.setEnd(node, currentOffset);
+ yield range;
+ range = undefined;
+ }
+ }
+
+ function *letters(wordRange) {
+ let currentText = wordRange.startContainer;
+ let max = currentText.length;
+ let currentOffset = wordRange.startOffset;
+ // let currentLetter;
+
+ let range;
+
+ while(currentOffset < max) {
+ // currentLetter = currentText[currentOffset];
+ range = document.createRange();
+ range.setStart(currentText, currentOffset);
+ range.setEnd(currentText, currentOffset+1);
+
+ yield range;
+
+ currentOffset += 1;
+ }
+ }
+
+ function isContainer(node) {
+ let container;
+
+ if (typeof node.tagName === "undefined") {
+ return true;
+ }
+
+ if (node.style.display === "none") {
+ return false;
+ }
+
+ switch (node.tagName) {
+ // Inline
+ case "A":
+ case "ABBR":
+ case "ACRONYM":
+ case "B":
+ case "BDO":
+ case "BIG":
+ case "BR":
+ case "BUTTON":
+ case "CITE":
+ case "CODE":
+ case "DFN":
+ case "EM":
+ case "I":
+ case "IMG":
+ case "INPUT":
+ case "KBD":
+ case "LABEL":
+ case "MAP":
+ case "OBJECT":
+ case "Q":
+ case "SAMP":
+ case "SCRIPT":
+ case "SELECT":
+ case "SMALL":
+ case "SPAN":
+ case "STRONG":
+ case "SUB":
+ case "SUP":
+ case "TEXTAREA":
+ case "TIME":
+ case "TT":
+ case "VAR":
+ case "P":
+ case "H1":
+ case "H2":
+ case "H3":
+ case "H4":
+ case "H5":
+ case "H6":
+ case "FIGCAPTION":
+ case "BLOCKQUOTE":
+ case "PRE":
+ case "LI":
+ case "TR":
+ case "DT":
+ case "DD":
+ case "VIDEO":
+ case "CANVAS":
+ container = false;
+ break;
+ default:
+ container = true;
+ }
+
+ return container;
+ }
+
+ function cloneNode(n, deep=false) {
+ return n.cloneNode(deep);
+ }
+
+ function findElement(node, doc) {
+ const ref = node.getAttribute("data-ref");
+ return findRef(ref, doc);
+ }
+
+ function findRef(ref, doc) {
+ return doc.querySelector(`[data-ref='${ref}']`);
+ }
+
+ function validNode(node) {
+ if (isText(node)) {
+ return true;
+ }
+
+ if (isElement(node) && node.dataset.ref) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function prevValidNode(node) {
+ while (!validNode(node)) {
+ if (node.previousSibling) {
+ node = node.previousSibling;
+ } else {
+ node = node.parentNode;
+ }
+
+ if (!node) {
+ break;
+ }
+ }
+
+ return node;
+ }
+
+
+ function indexOf$1(node) {
+ let parent = node.parentNode;
+ if (!parent) {
+ return 0;
+ }
+ return Array.prototype.indexOf.call(parent.childNodes, node);
+ }
+
+ function child(node, index) {
+ return node.childNodes[index];
+ }
+
+ function hasContent(node) {
+ if (isElement(node)) {
+ return true;
+ } else if (isText(node) &&
+ node.textContent.trim().length) {
+ return true;
+ }
+ return false;
+ }
+
+ function indexOfTextNode(node, parent) {
+ if (!isText(node)) {
+ return -1;
+ }
+ let nodeTextContent = node.textContent;
+ let child;
+ let index = -1;
+ for (var i = 0; i < parent.childNodes.length; i++) {
+ child = parent.childNodes[i];
+ if (child.nodeType === 3) {
+ let text = parent.childNodes[i].textContent;
+ if (text.includes(nodeTextContent)) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ return index;
+ }
+
+ 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;
+ }
+
+ async renderTo(wrapper, source, breakToken, bounds=this.bounds) {
+ let start = this.getStart(source, breakToken);
+ let walker = walk(start, source);
+
+ let node;
+ let done;
+ let next;
+
+ let hasRenderedContent = false;
+ let newBreakToken;
+
+ let length = 0;
+
+ while (!done && !newBreakToken) {
+ next = walker.next();
+ node = next.value;
+ done = next.done;
+
+ if (!node) {
+ this.hooks && this.hooks.layout.trigger(wrapper, this);
+
+ let imgs = wrapper.querySelectorAll("img");
+ if (imgs.length) {
+ await this.waitForImages(imgs);
+ }
+
+ newBreakToken = this.findBreakToken(wrapper, source, bounds);
+ return newBreakToken;
+ }
+
+ this.hooks && this.hooks.layoutNode.trigger(node);
+
+ // Check if the rendered element has a break set
+ if (hasRenderedContent && this.shouldBreak(node)) {
+
+ this.hooks && this.hooks.layout.trigger(wrapper, this);
+
+ let imgs = wrapper.querySelectorAll("img");
+ if (imgs.length) {
+ await this.waitForImages(imgs);
+ }
+
+ newBreakToken = this.findBreakToken(wrapper, source, bounds);
+
+ if (!newBreakToken) {
+ newBreakToken = this.breakAt(node);
+ }
+
+ length = 0;
+
+ break;
+ }
+
+ // Should the Node be a shallow or deep clone
+ let shallow = isContainer(node);
+
+ let rendered = this.append(node, wrapper, breakToken, shallow);
+
+ length += rendered.textContent.length;
+
+ // Check if layout has content yet
+ if (!hasRenderedContent) {
+ hasRenderedContent = hasContent(node);
+ }
+
+ // Skip to the next node if a deep clone was rendered
+ if (!shallow) {
+ walker = walk(nodeAfter(node, source), source);
+ }
+
+ // Only check x characters
+ if (length >= this.maxChars) {
+
+ this.hooks && this.hooks.layout.trigger(wrapper, this);
+
+ let imgs = wrapper.querySelectorAll("img");
+ if (imgs.length) {
+ await this.waitForImages(imgs);
+ }
+
+ newBreakToken = this.findBreakToken(wrapper, source, bounds);
+
+ if (newBreakToken) {
+ length = 0;
+ }
+ }
+
+ }
+
+ return newBreakToken;
+ }
+
+ breakAt(node, offset=0) {
+ return {
+ node,
+ offset
+ };
+ }
+
+ shouldBreak(node) {
+ let previousSibling = node.previousSibling;
+ let parentNode = node.parentNode;
+ let parentBreakBefore = needsBreakBefore(node) && parentNode && !previousSibling && needsBreakBefore(parentNode);
+ let doubleBreakBefore;
+
+ if (parentBreakBefore) {
+ doubleBreakBefore = node.dataset.breakBefore === parentNode.dataset.breakBefore;
+ }
+
+ return !doubleBreakBefore && needsBreakBefore(node) || needsPreviousBreakAfter(node) || needsPageBreak(node);
+ }
+
+ getStart(source, breakToken) {
+ let start;
+ let node = breakToken && breakToken.node;
+
+ if (node) {
+ start = node;
+ } else {
+ start = source.firstChild;
+ }
+
+ return start;
+ }
+
+ append(node, dest, breakToken, shallow=true, rebuild=true) {
+
+ let clone = cloneNode(node, !shallow);
+
+ if (node.parentNode && isElement(node.parentNode)) {
+ let parent = findElement(node.parentNode, dest);
+ // Rebuild chain
+ if (parent) {
+ parent.appendChild(clone);
+ } else if (rebuild) {
+ let fragment = rebuildAncestors(node);
+ parent = findElement(node.parentNode, fragment);
+ if (!parent) {
+ dest.appendChild(clone);
+ } else if (breakToken && isText(breakToken.node) && breakToken.offset > 0) {
+ clone.textContent = clone.textContent.substring(breakToken.offset);
+ parent.appendChild(clone);
+ } else {
+ parent.appendChild(clone);
+ }
+
+ dest.appendChild(fragment);
+ } else {
+ dest.appendChild(clone);
+ }
+
+
+ } else {
+ dest.appendChild(clone);
+ }
+
+ let nodeHooks = this.hooks.renderNode.triggerSync(clone, node);
+ nodeHooks.forEach((newNode) => {
+ if (typeof newNode != "undefined") {
+ clone = newNode;
+ }
+ });
+
+ return clone;
+ }
+
+ async waitForImages(imgs) {
+ let results = Array.from(imgs).map(async (img) => {
+ return this.awaitImageLoaded(img);
+ });
+ await Promise.all(results);
+ }
+
+ async awaitImageLoaded(image) {
+ return new Promise(resolve => {
+ if (image.complete !== true) {
+ image.onload = function() {
+ let { width, height } = window.getComputedStyle(image);
+ resolve(width, height);
+ };
+ image.onerror = function(e) {
+ let { width, height } = window.getComputedStyle(image);
+ resolve(width, height, e);
+ };
+ } else {
+ let { width, height } = window.getComputedStyle(image);
+ resolve(width, height);
+ }
+ });
+ }
+
+ avoidBreakInside(node, limiter) {
+ let breakNode;
+
+ if (node === limiter) {
+ return;
+ }
+
+ while (node.parentNode) {
+ node = node.parentNode;
+
+ if (node === limiter) {
+ break;
+ }
+
+ if(window.getComputedStyle(node)["break-inside"] === "avoid") {
+ breakNode = node;
+ break;
+ }
+
+ }
+ return breakNode;
+ }
+
+ createBreakToken(overflow, rendered, source) {
+ let container = overflow.startContainer;
+ let offset = overflow.startOffset;
+ let node, renderedNode, parent, index, temp;
+
+ if (isElement(container)) {
+ temp = child(container, offset);
+
+ if (isElement(temp)) {
+ renderedNode = findElement(temp, rendered);
+
+ if (!renderedNode) {
+ // Find closest element with data-ref
+ renderedNode = findElement(prevValidNode(temp), rendered);
+ return;
+ }
+
+ node = findElement(renderedNode, source);
+ offset = 0;
+ } else {
+ renderedNode = findElement(container, rendered);
+
+ if (!renderedNode) {
+ renderedNode = findElement(prevValidNode(container), rendered);
+ }
+
+ parent = findElement(renderedNode, source);
+ index = indexOfTextNode(temp, parent);
+ node = child(parent, index);
+ offset = 0;
+ }
+ } else {
+ renderedNode = findElement(container.parentNode, rendered);
+
+ if (!renderedNode) {
+ renderedNode = findElement(prevValidNode(container.parentNode), rendered);
+ }
+
+ parent = findElement(renderedNode, source);
+ index = indexOfTextNode(container, parent);
+
+ if (index === -1) {
+ return;
+ }
+
+ node = child(parent, index);
+
+ offset += node.textContent.indexOf(container.textContent);
+ }
+
+ if (!node) {
+ return;
+ }
+
+ return {
+ node,
+ offset
+ };
+
+ }
+
+ findBreakToken(rendered, source, bounds=this.bounds, extract=true) {
+ let overflow = this.findOverflow(rendered, bounds);
+ let breakToken, 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);
+ if (breakToken["node"] && breakToken["offset"] && breakToken["node"].textContent) {
+ breakLetter = breakToken["node"].textContent.charAt(breakToken["offset"]);
+ } else {
+ breakLetter = undefined;
+ }
+
+ let breakHooks = this.hooks.onBreakToken.triggerSync(breakToken, overflow, rendered, this);
+ breakHooks.forEach((newToken) => {
+ if (typeof newToken != "undefined") {
+ breakToken = newToken;
+ }
+ });
+
+
+ if (breakToken && breakToken.node && extract) {
+ this.removeOverflow(overflow, 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.floor(pos.left);
+ let right = Math.floor(pos.right);
+
+ if (!range && left >= end) {
+ // Check if it is a float
+ let isFloat = false;
+
+ if (isElement(node) ) {
+ let styles = window.getComputedStyle(node);
+ isFloat = styles.getPropertyValue("float") !== "none";
+ skip = styles.getPropertyValue("break-inside") === "avoid";
+ breakAvoid = node.dataset.breakBefore === "avoid" || node.dataset.previousBreakAfter === "avoid";
+ prev = breakAvoid && nodeBefore(node, rendered);
+ br = node.tagName === "BR" || node.tagName === "WBR";
+ }
+
+ if (prev) {
+ range = document.createRange();
+ range.setStartBefore(prev);
+ break;
+ }
+
+ if (!br && !isFloat && isElement(node)) {
+ range = document.createRange();
+ range.setStartBefore(node);
+ break;
+ }
+
+ if (isText(node) && node.textContent.trim().length) {
+ range = document.createRange();
+ range.setStartBefore(node);
+ break;
+ }
+
+ }
+
+ if (!range && isText(node) &&
+ node.textContent.trim().length &&
+ window.getComputedStyle(node.parentNode)["break-inside"] !== "avoid") {
+
+ let rects = getClientRects(node);
+ let rect;
+ left = 0;
+ for (var i = 0; i != rects.length; i++) {
+ rect = rects[i];
+ if (rect.width > 0 && (!left || rect.left > left)) {
+ left = rect.left;
+ }
+ }
+
+ if(left >= end) {
+ range = document.createRange();
+ offset = this.textBreak(node, start, end);
+ if (!offset) {
+ range = undefined;
+ } else {
+ range.setStart(node, offset);
+ }
+ break;
+ }
+ }
+
+ // Skip children
+ if (skip || right < end) {
+ next = nodeAfter(node, rendered);
+ if (next) {
+ walker = walk(next, rendered);
+ }
+
+ }
+
+ }
+ }
+
+ // Find End
+ if (range) {
+ range.setEndAfter(rendered.lastChild);
+ return range;
+ }
+
+ }
+
+ findEndToken(rendered, source, bounds=this.bounds) {
+ if (rendered.childNodes.length === 0) {
+ return;
+ }
+
+ let lastChild = rendered.lastChild;
+
+ let lastNodeIndex;
+ while (lastChild && lastChild.lastChild) {
+ if (!validNode(lastChild)) {
+ // Only get elements with refs
+ lastChild = lastChild.previousSibling;
+ } else if(!validNode(lastChild.lastChild)) {
+ // Deal with invalid dom items
+ lastChild = prevValidNode(lastChild.lastChild);
+ break;
+ } else {
+ lastChild = lastChild.lastChild;
+ }
+ }
+
+ if (isText(lastChild)) {
+
+ if (lastChild.parentNode.dataset.ref) {
+ lastNodeIndex = indexOf$1(lastChild);
+ lastChild = lastChild.parentNode;
+ } else {
+ lastChild = lastChild.previousSibling;
+ }
+ }
+
+ let original = findElement(lastChild, source);
+
+ if (lastNodeIndex) {
+ original = original.childNodes[lastNodeIndex];
+ }
+
+ let after = nodeAfter(original);
+
+ return this.breakAt(after);
+ }
+
+ textBreak(node, start, end) {
+ let wordwalker = words(node);
+ let left = 0;
+ let right = 0;
+ let word, next, done, pos;
+ let offset;
+ while (!done) {
+ next = wordwalker.next();
+ word = next.value;
+ done = next.done;
+
+ if (!word) {
+ break;
+ }
+
+ pos = getBoundingClientRect(word);
+
+ left = Math.floor(pos.left);
+ right = Math.floor(pos.right);
+
+ if (left >= end) {
+ offset = word.startOffset;
+ break;
+ }
+
+ if (right > end) {
+ let letterwalker = letters(word);
+ let letter, nextLetter, doneLetter;
+
+ while (!doneLetter) {
+ nextLetter = letterwalker.next();
+ letter = nextLetter.value;
+ doneLetter = nextLetter.done;
+
+ if (!letter) {
+ break;
+ }
+
+ pos = getBoundingClientRect(letter);
+ left = Math.floor(pos.left);
+
+ if (left >= end) {
+ offset = letter.startOffset;
+ done = true;
+
+ break;
+ }
+ }
+ }
+
+ }
+
+ return offset;
+ }
+
+ removeOverflow(overflow, 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";
+ }
+ }
+ }
+ }
+
+ eventEmitter(Layout.prototype);
+
+ /**
+ * Render a page
+ * @class
+ */
+ class Page {
+ constructor(pagesArea, pageTemplate, blank, hooks) {
+ this.pagesArea = pagesArea;
+ this.pageTemplate = pageTemplate;
+ this.blank = blank;
+
+ this.width = undefined;
+ this.height = undefined;
+
+ this.hooks = hooks;
+
+ // this.element = this.create(this.pageTemplate);
+ }
+
+ create(template, after) {
+ //let documentFragment = document.createRange().createContextualFragment( TEMPLATE );
+ //let page = documentFragment.children[0];
+ let clone = document.importNode(this.pageTemplate.content, true);
+
+ let page, index;
+ if (after) {
+ this.pagesArea.insertBefore(clone, after.nextElementSibling);
+ index = Array.prototype.indexOf.call(this.pagesArea.children, after.nextElementSibling);
+ page = this.pagesArea.children[index];
+ } else {
+ this.pagesArea.appendChild(clone);
+ page = this.pagesArea.lastChild;
+ }
+
+ let pagebox = page.querySelector(".pagedjs_pagebox");
+ let area = page.querySelector(".pagedjs_page_content");
+
+
+ let size = area.getBoundingClientRect();
+
+
+ area.style.columnWidth = Math.round(size.width) + "px";
+ area.style.columnGap = "calc(var(--pagedjs-margin-right) + var(--pagedjs-margin-left))";
+ // area.style.overflow = "scroll";
+
+ this.width = Math.round(size.width);
+ this.height = Math.round(size.height);
+
+ this.element = page;
+ this.pagebox = pagebox;
+ this.area = area;
+
+ return page;
+ }
+
+ createWrapper() {
+ let wrapper = document.createElement("div");
+
+ this.area.appendChild(wrapper);
+
+ this.wrapper = wrapper;
+
+ return wrapper;
+ }
+
+ index(pgnum) {
+ this.position = pgnum;
+
+ let page = this.element;
+ // let pagebox = this.pagebox;
+
+ let index = pgnum+1;
+
+ let id = `page-${index}`;
+
+ this.id = id;
+
+ // page.dataset.pageNumber = index;
+
+ page.dataset.pageNumber = index;
+ page.setAttribute('id', id);
+
+ if (this.name) {
+ page.classList.add("pagedjs_" + this.name + "_page");
+ }
+
+ if (this.blank) {
+ page.classList.add("pagedjs_blank_page");
+ }
+
+ if (pgnum === 0) {
+ page.classList.add("pagedjs_first_page");
+ }
+
+ if (pgnum % 2 !== 1) {
+ page.classList.remove("pagedjs_left_page");
+ page.classList.add("pagedjs_right_page");
+ } else {
+ page.classList.remove("pagedjs_right_page");
+ page.classList.add("pagedjs_left_page");
+ }
+ }
+
+ /*
+ size(width, height) {
+ if (width === this.width && height === this.height) {
+ return;
+ }
+ this.width = width;
+ this.height = height;
+
+ this.element.style.width = Math.round(width) + "px";
+ this.element.style.height = Math.round(height) + "px";
+ this.element.style.columnWidth = Math.round(width) + "px";
+ }
+ */
+
+ async layout(contents, breakToken, maxChars) {
+
+ this.clear();
+
+ this.startToken = breakToken;
+
+ this.layoutMethod = new Layout(this.area, this.hooks, maxChars);
+
+ let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
+
+ this.addListeners(contents);
+
+ this.endToken = newBreakToken;
+
+ return newBreakToken;
+ }
+
+ async append(contents, breakToken) {
+
+ if (!this.layoutMethod) {
+ return this.layout(contents, breakToken);
+ }
+
+ let newBreakToken = await this.layoutMethod.renderTo(this.wrapper, contents, breakToken);
+
+ this.endToken = newBreakToken;
+
+ return newBreakToken;
+ }
+
+ getByParent(ref, entries) {
+ let e;
+ for (var i = 0; i < entries.length; i++) {
+ e = entries[i];
+ if(e.dataset.ref === ref) {
+ return e;
+ }
+ }
+ }
+
+ onOverflow(func) {
+ this._onOverflow = func;
+ }
+
+ onUnderflow(func) {
+ this._onUnderflow = func;
+ }
+
+ clear() {
+ this.removeListeners();
+ this.wrapper && this.wrapper.remove();
+ this.createWrapper();
+ }
+
+ addListeners(contents) {
+ if (typeof ResizeObserver !== "undefined") {
+ this.addResizeObserver(contents);
+ } else {
+ this._checkOverflowAfterResize = this.checkOverflowAfterResize.bind(this, contents);
+ this.element.addEventListener("overflow", this._checkOverflowAfterResize, false);
+ this.element.addEventListener("underflow", this._checkOverflowAfterResize, false);
+ }
+ // TODO: fall back to mutation observer?
+
+ this._onScroll = function() {
+ if(this.listening) {
+ this.element.scrollLeft = 0;
+ }
+ }.bind(this);
+
+ // Keep scroll left from changing
+ this.element.addEventListener("scroll", this._onScroll);
+
+ this.listening = true;
+
+ return true;
+ }
+
+ removeListeners() {
+ this.listening = false;
+
+ if (typeof ResizeObserver !== "undefined" && this.ro) {
+ this.ro.disconnect();
+ } else if (this.element) {
+ this.element.removeEventListener("overflow", this._checkOverflowAfterResize, false);
+ this.element.removeEventListener("underflow", this._checkOverflowAfterResize, false);
+ }
+
+ this.element &&this.element.removeEventListener("scroll", this._onScroll);
+
+ }
+
+ addResizeObserver(contents) {
+ let wrapper = this.wrapper;
+ let prevHeight = wrapper.getBoundingClientRect().height;
+ this.ro = new ResizeObserver( entries => {
+
+ if (!this.listening) {
+ return;
+ }
+
+ for (let entry of entries) {
+ const cr = entry.contentRect;
+
+ if (cr.height > prevHeight) {
+ this.checkOverflowAfterResize(contents);
+ prevHeight = wrapper.getBoundingClientRect().height;
+ } else if (cr.height < prevHeight ) { // TODO: calc line height && (prevHeight - cr.height) >= 22
+ this.checkUnderflowAfterResize(contents);
+ prevHeight = cr.height;
+ }
+ }
+ });
+
+ this.ro.observe(wrapper);
+ }
+
+ checkOverflowAfterResize(contents) {
+ if (!this.listening || !this.layoutMethod) {
+ return;
+ }
+
+ let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents);
+
+ if (newBreakToken) {
+ this.endToken = newBreakToken;
+ this._onOverflow && this._onOverflow(newBreakToken);
+ }
+ }
+
+ checkUnderflowAfterResize(contents) {
+ if (!this.listening || !this.layoutMethod) {
+ return;
+ }
+
+ let endToken = this.layoutMethod.findEndToken(this.wrapper, contents);
+
+ // let newBreakToken = this.layoutMethod.findBreakToken(this.wrapper, contents);
+
+ if (endToken) {
+ this._onUnderflow && this._onUnderflow(endToken);
+ }
+ }
+
+
+ destroy() {
+ this.removeListeners();
+
+ this.element.remove();
+
+ this.element = undefined;
+ this.wrapper = undefined;
+ }
+ }
+
+ eventEmitter(Page.prototype);
+
+ /**
+ * Render a flow of text offscreen
+ * @class
+ */
+ class ContentParser {
+
+ constructor(content, cb) {
+ if (content && content.nodeType) {
+ // handle dom
+ this.dom = this.add(content);
+ } else if (typeof content === "string") {
+ this.dom = this.parse(content);
+ }
+
+ return this.dom;
+ }
+
+ parse(markup, mime) {
+ let range = document.createRange();
+ let fragment = range.createContextualFragment(markup);
+
+ this.addRefs(fragment);
+ this.removeEmpty(fragment);
+
+ return fragment;
+ }
+
+ add(contents) {
+ // let fragment = document.createDocumentFragment();
+ //
+ // let children = [...contents.childNodes];
+ // for (let child of children) {
+ // let clone = child.cloneNode(true);
+ // fragment.appendChild(clone);
+ // }
+
+ this.addRefs(contents);
+ this.removeEmpty(contents);
+
+ return contents;
+ }
+
+ addRefs(content) {
+ var treeWalker = document.createTreeWalker(
+ content,
+ NodeFilter.SHOW_ELEMENT,
+ { acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } },
+ false
+ );
+
+ let node = treeWalker.nextNode();
+ while(node) {
+
+ if (!node.hasAttribute("data-ref")) {
+ let uuid = UUID();
+ node.setAttribute("data-ref", uuid);
+ }
+
+ if (node.id) {
+ node.setAttribute("data-id", node.id);
+ }
+
+ // node.setAttribute("data-children", node.childNodes.length);
+
+ // node.setAttribute("data-text", node.textContent.trim().length);
+ node = treeWalker.nextNode();
+ }
+ }
+
+ removeEmpty(content) {
+ var treeWalker = document.createTreeWalker(
+ content,
+ NodeFilter.SHOW_TEXT,
+ { acceptNode: function(node) {
+ // Only remove more than a single space
+ if (node.textContent.length > 1 && !node.textContent.trim()) {
+
+ // Don't touch whitespace if text is preformated
+ let parent = node.parentNode;
+ let pre = isElement(parent) && parent.closest("pre");
+ if (pre) {
+ return NodeFilter.FILTER_REJECT;
+ }
+
+ return NodeFilter.FILTER_ACCEPT;
+ } else {
+ return NodeFilter.FILTER_REJECT;
+ }
+ } },
+ false
+ );
+
+ let node;
+ let current;
+ node = treeWalker.nextNode();
+ while(node) {
+ current = node;
+ node = treeWalker.nextNode();
+ // if (!current.nextSibling || (current.nextSibling && current.nextSibling.nodeType === 1)) {
+ current.parentNode.removeChild(current);
+ // }
+ }
+ }
+
+ find(ref) {
+ return this.refs[ref];
+ }
+
+ // isWrapper(element) {
+ // return wrappersRegex.test(element.nodeName);
+ // }
+
+ isText(node) {
+ return node.tagName === "TAG";
+ }
+
+ isElement(node) {
+ return node.nodeType === 1;
+ }
+
+ hasChildren(node) {
+ return node.childNodes && node.childNodes.length;
+ }
+
+
+ destroy() {
+ this.refs = undefined;
+ this.dom = undefined;
+ }
+ }
+
+ /**
+ * Queue for handling tasks one at a time
+ * @class
+ * @param {scope} context what this will resolve to in the tasks
+ */
+ class Queue {
+ constructor(context){
+ this._q = [];
+ this.context = context;
+ this.tick = requestAnimationFrame;
+ this.running = false;
+ this.paused = false;
+ }
+
+ /**
+ * Add an item to the queue
+ * @return {Promise} enqueued
+ */
+ enqueue() {
+ var deferred, promise;
+ var queued;
+ var task = [].shift.call(arguments);
+ var args = arguments;
+
+ // Handle single args without context
+ // if(args && !Array.isArray(args)) {
+ // args = [args];
+ // }
+ if(!task) {
+ throw new Error("No Task Provided");
+ }
+
+ if(typeof task === "function"){
+
+ deferred = new defer();
+ promise = deferred.promise;
+
+ queued = {
+ "task" : task,
+ "args" : args,
+ //"context" : context,
+ "deferred" : deferred,
+ "promise" : promise
+ };
+
+ } else {
+ // Task is a promise
+ queued = {
+ "promise" : task
+ };
+
+ }
+
+ this._q.push(queued);
+
+ // Wait to start queue flush
+ if (this.paused == false && !this.running) {
+ this.run();
+ }
+
+ return queued.promise;
+ }
+
+ /**
+ * Run one item
+ * @return {Promise} dequeued
+ */
+ dequeue(){
+ var inwait, task, result;
+
+ if(this._q.length && !this.paused) {
+ inwait = this._q.shift();
+ task = inwait.task;
+ if(task){
+ // console.log(task)
+
+ result = task.apply(this.context, inwait.args);
+
+ if(result && typeof result["then"] === "function") {
+ // Task is a function that returns a promise
+ return result.then(function(){
+ inwait.deferred.resolve.apply(this.context, arguments);
+ }.bind(this), function() {
+ inwait.deferred.reject.apply(this.context, arguments);
+ }.bind(this));
+ } else {
+ // Task resolves immediately
+ inwait.deferred.resolve.apply(this.context, result);
+ return inwait.promise;
+ }
+
+
+
+ } else if(inwait.promise) {
+ // Task is a promise
+ return inwait.promise;
+ }
+
+ } else {
+ inwait = new defer();
+ inwait.deferred.resolve();
+ return inwait.promise;
+ }
+
+ }
+
+ // Run All Immediately
+ dump(){
+ while(this._q.length) {
+ this.dequeue();
+ }
+ }
+
+ /**
+ * Run all tasks sequentially, at convince
+ * @return {Promise} all run
+ */
+ run(){
+
+ if(!this.running){
+ this.running = true;
+ this.defered = new defer();
+ }
+
+ this.tick.call(window, () => {
+
+ if(this._q.length) {
+
+ this.dequeue()
+ .then(function(){
+ this.run();
+ }.bind(this));
+
+ } else {
+ this.defered.resolve();
+ this.running = undefined;
+ }
+
+ });
+
+ // Unpause
+ if(this.paused == true) {
+ this.paused = false;
+ }
+
+ return this.defered.promise;
+ }
+
+ /**
+ * Flush all, as quickly as possible
+ * @return {Promise} ran
+ */
+ flush(){
+
+ if(this.running){
+ return this.running;
+ }
+
+ if(this._q.length) {
+ this.running = this.dequeue()
+ .then(function(){
+ this.running = undefined;
+ return this.flush();
+ }.bind(this));
+
+ return this.running;
+ }
+
+ }
+
+ /**
+ * Clear all items in wait
+ * @return {void}
+ */
+ clear(){
+ this._q = [];
+ }
+
+ /**
+ * Get the number of tasks in the queue
+ * @return {number} tasks
+ */
+ length(){
+ return this._q.length;
+ }
+
+ /**
+ * Pause a running queue
+ * @return {void}
+ */
+ pause(){
+ this.paused = true;
+ }
+
+ /**
+ * End the queue
+ * @return {void}
+ */
+ stop(){
+ this._q = [];
+ this.running = false;
+ this.paused = true;
+ }
+ }
+
+ const TEMPLATE = `
+
`;
+
+ /**
+ * Chop up text into flows
+ * @class
+ */
+ class Chunker {
+ constructor(content, renderTo, options) {
+ // this.preview = preview;
+
+ this.settings = options || {};
+
+ this.hooks = {};
+ this.hooks.beforeParsed = new Hook(this);
+ this.hooks.afterParsed = new Hook(this);
+ this.hooks.beforePageLayout = new Hook(this);
+ this.hooks.layout = new Hook(this);
+ this.hooks.renderNode = new Hook(this);
+ this.hooks.layoutNode = new Hook(this);
+ this.hooks.onOverflow = new Hook(this);
+ this.hooks.onBreakToken = new Hook();
+ this.hooks.afterPageLayout = new Hook(this);
+ this.hooks.afterRendered = new Hook(this);
+
+ this.pages = [];
+ this.total = 0;
+
+ this.q = new Queue(this);
+ this.stopped = false;
+ this.rendered = false;
+
+ this.content = content;
+
+ this.charsPerBreak = [];
+ this.maxChars;
+
+ if (content) {
+ this.flow(content, renderTo);
+ }
+ }
+
+ setup(renderTo) {
+ this.pagesArea = document.createElement("div");
+ this.pagesArea.classList.add("pagedjs_pages");
+
+ if (renderTo) {
+ renderTo.appendChild(this.pagesArea);
+ } else {
+ document.querySelector("body").appendChild(this.pagesArea);
+ }
+
+ this.pageTemplate = document.createElement("template");
+ this.pageTemplate.innerHTML = TEMPLATE;
+
+ }
+
+ async flow(content, renderTo) {
+ let parsed;
+
+ await this.hooks.beforeParsed.trigger(content, this);
+
+ parsed = new ContentParser(content);
+
+ this.source = parsed;
+ this.breakToken = undefined;
+
+ if (this.pagesArea && this.pageTemplate) {
+ this.q.clear();
+ this.removePages();
+ } else {
+ this.setup(renderTo);
+ }
+
+ this.emit("rendering", content);
+
+ await this.hooks.afterParsed.trigger(parsed, this);
+
+ await this.loadFonts();
+
+ let rendered = await this.render(parsed, this.breakToken);
+ while (rendered.canceled) {
+ this.start();
+ rendered = await this.render(parsed, this.breakToken);
+ }
+
+ this.rendered = true;
+ 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);
+
+ //
+ // item item item item
+ // /------\ /------\ /------\ /------\
+ // | data | | data | | data | | data |
+ // null <--+-prev |<---+-prev |<---+-prev |<---+-prev |
+ // | next-+--->| next-+--->| next-+--->| next-+--> null
+ // \------/ \------/ \------/ \------/
+ // ^ ^
+ // | list |
+ // | /------\ |
+ // \--------------+-head | |
+ // | tail-+--------------/
+ // \------/
+ //
+
+ function createItem(data) {
+ return {
+ prev: null,
+ next: null,
+ data: data
+ };
+ }
+
+ function allocateCursor(node, prev, next) {
+ var cursor;
+
+ if (cursors !== null) {
+ cursor = cursors;
+ cursors = cursors.cursor;
+ cursor.prev = prev;
+ cursor.next = next;
+ cursor.cursor = node.cursor;
+ } else {
+ cursor = {
+ prev: prev,
+ next: next,
+ cursor: node.cursor
+ };
+ }
+
+ node.cursor = cursor;
+
+ return cursor;
+ }
+
+ function releaseCursor(node) {
+ var cursor = node.cursor;
+
+ node.cursor = cursor.cursor;
+ cursor.prev = null;
+ cursor.next = null;
+ cursor.cursor = cursors;
+ cursors = cursor;
+ }
+
+ var cursors = null;
+ var List = function() {
+ this.cursor = null;
+ this.head = null;
+ this.tail = null;
+ };
+
+ List.createItem = createItem;
+ List.prototype.createItem = createItem;
+
+ List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) {
+ var cursor = this.cursor;
+
+ while (cursor !== null) {
+ if (cursor.prev === prevOld) {
+ cursor.prev = prevNew;
+ }
+
+ if (cursor.next === nextOld) {
+ cursor.next = nextNew;
+ }
+
+ cursor = cursor.cursor;
+ }
+ };
+
+ List.prototype.getSize = function() {
+ var size = 0;
+ var cursor = this.head;
+
+ while (cursor) {
+ size++;
+ cursor = cursor.next;
+ }
+
+ return size;
+ };
+
+ List.prototype.fromArray = function(array) {
+ var cursor = null;
+
+ this.head = null;
+
+ for (var i = 0; i < array.length; i++) {
+ var item = createItem(array[i]);
+
+ if (cursor !== null) {
+ cursor.next = item;
+ } else {
+ this.head = item;
+ }
+
+ item.prev = cursor;
+ cursor = item;
+ }
+
+ this.tail = cursor;
+
+ return this;
+ };
+
+ List.prototype.toArray = function() {
+ var cursor = this.head;
+ var result = [];
+
+ while (cursor) {
+ result.push(cursor.data);
+ cursor = cursor.next;
+ }
+
+ return result;
+ };
+
+ List.prototype.toJSON = List.prototype.toArray;
+
+ List.prototype.isEmpty = function() {
+ return this.head === null;
+ };
+
+ List.prototype.first = function() {
+ return this.head && this.head.data;
+ };
+
+ List.prototype.last = function() {
+ return this.tail && this.tail.data;
+ };
+
+ List.prototype.each = function(fn, context) {
+ var item;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ var cursor = allocateCursor(this, null, this.head);
+
+ while (cursor.next !== null) {
+ item = cursor.next;
+ cursor.next = item.next;
+
+ fn.call(context, item.data, item, this);
+ }
+
+ // pop cursor
+ releaseCursor(this);
+ };
+
+ List.prototype.forEach = List.prototype.each;
+
+ List.prototype.eachRight = function(fn, context) {
+ var item;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ var cursor = allocateCursor(this, this.tail, null);
+
+ while (cursor.prev !== null) {
+ item = cursor.prev;
+ cursor.prev = item.prev;
+
+ fn.call(context, item.data, item, this);
+ }
+
+ // pop cursor
+ releaseCursor(this);
+ };
+
+ List.prototype.forEachRight = List.prototype.eachRight;
+
+ List.prototype.nextUntil = function(start, fn, context) {
+ if (start === null) {
+ return;
+ }
+
+ var item;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ var cursor = allocateCursor(this, null, start);
+
+ while (cursor.next !== null) {
+ item = cursor.next;
+ cursor.next = item.next;
+
+ if (fn.call(context, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ releaseCursor(this);
+ };
+
+ List.prototype.prevUntil = function(start, fn, context) {
+ if (start === null) {
+ return;
+ }
+
+ var item;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ // push cursor
+ var cursor = allocateCursor(this, start, null);
+
+ while (cursor.prev !== null) {
+ item = cursor.prev;
+ cursor.prev = item.prev;
+
+ if (fn.call(context, item.data, item, this)) {
+ break;
+ }
+ }
+
+ // pop cursor
+ releaseCursor(this);
+ };
+
+ List.prototype.some = function(fn, context) {
+ var cursor = this.head;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ while (cursor !== null) {
+ if (fn.call(context, cursor.data, cursor, this)) {
+ return true;
+ }
+
+ cursor = cursor.next;
+ }
+
+ return false;
+ };
+
+ List.prototype.map = function(fn, context) {
+ var result = new List();
+ var cursor = this.head;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ while (cursor !== null) {
+ result.appendData(fn.call(context, cursor.data, cursor, this));
+ cursor = cursor.next;
+ }
+
+ return result;
+ };
+
+ List.prototype.filter = function(fn, context) {
+ var result = new List();
+ var cursor = this.head;
+
+ if (context === undefined) {
+ context = this;
+ }
+
+ while (cursor !== null) {
+ if (fn.call(context, cursor.data, cursor, this)) {
+ result.appendData(cursor.data);
+ }
+ cursor = cursor.next;
+ }
+
+ return result;
+ };
+
+ List.prototype.clear = function() {
+ this.head = null;
+ this.tail = null;
+ };
+
+ List.prototype.copy = function() {
+ var result = new List();
+ var cursor = this.head;
+
+ while (cursor !== null) {
+ result.insert(createItem(cursor.data));
+ cursor = cursor.next;
+ }
+
+ return result;
+ };
+
+ List.prototype.prepend = function(item) {
+ // head
+ // ^
+ // item
+ this.updateCursors(null, item, this.head, item);
+
+ // insert to the beginning of the list
+ if (this.head !== null) {
+ // new item <- first item
+ this.head.prev = item;
+
+ // new item -> first item
+ item.next = this.head;
+ } else {
+ // if list has no head, then it also has no tail
+ // in this case tail points to the new item
+ this.tail = item;
+ }
+
+ // head always points to new item
+ this.head = item;
+
+ return this;
+ };
+
+ List.prototype.prependData = function(data) {
+ return this.prepend(createItem(data));
+ };
+
+ List.prototype.append = function(item) {
+ return this.insert(item);
+ };
+
+ List.prototype.appendData = function(data) {
+ return this.insert(createItem(data));
+ };
+
+ List.prototype.insert = function(item, before) {
+ if (before !== undefined && before !== null) {
+ // prev before
+ // ^
+ // item
+ this.updateCursors(before.prev, item, before, item);
+
+ if (before.prev === null) {
+ // insert to the beginning of list
+ if (this.head !== before) {
+ throw new Error('before doesn\'t belong to list');
+ }
+
+ // since head points to before therefore list doesn't empty
+ // no need to check tail
+ this.head = item;
+ before.prev = item;
+ item.next = before;
+
+ this.updateCursors(null, item);
+ } else {
+
+ // insert between two items
+ before.prev.next = item;
+ item.prev = before.prev;
+
+ before.prev = item;
+ item.next = before;
+ }
+ } else {
+ // tail
+ // ^
+ // item
+ this.updateCursors(this.tail, item, null, item);
+
+ // insert to the ending of the list
+ if (this.tail !== null) {
+ // last item -> new item
+ this.tail.next = item;
+
+ // last item <- new item
+ item.prev = this.tail;
+ } else {
+ // if list has no tail, then it also has no head
+ // in this case head points to new item
+ this.head = item;
+ }
+
+ // tail always points to new item
+ this.tail = item;
+ }
+
+ return this;
+ };
+
+ List.prototype.insertData = function(data, before) {
+ return this.insert(createItem(data), before);
+ };
+
+ List.prototype.remove = function(item) {
+ // item
+ // ^
+ // prev next
+ this.updateCursors(item, item.prev, item, item.next);
+
+ if (item.prev !== null) {
+ item.prev.next = item.next;
+ } else {
+ if (this.head !== item) {
+ throw new Error('item doesn\'t belong to list');
+ }
+
+ this.head = item.next;
+ }
+
+ if (item.next !== null) {
+ item.next.prev = item.prev;
+ } else {
+ if (this.tail !== item) {
+ throw new Error('item doesn\'t belong to list');
+ }
+
+ this.tail = item.prev;
+ }
+
+ item.prev = null;
+ item.next = null;
+
+ return item;
+ };
+
+ List.prototype.push = function(data) {
+ this.insert(createItem(data));
+ };
+
+ List.prototype.pop = function() {
+ if (this.tail !== null) {
+ return this.remove(this.tail);
+ }
+ };
+
+ List.prototype.unshift = function(data) {
+ this.prepend(createItem(data));
+ };
+
+ List.prototype.shift = function() {
+ if (this.head !== null) {
+ return this.remove(this.head);
+ }
+ };
+
+ List.prototype.prependList = function(list) {
+ return this.insertList(list, this.head);
+ };
+
+ List.prototype.appendList = function(list) {
+ return this.insertList(list);
+ };
+
+ List.prototype.insertList = function(list, before) {
+ // ignore empty lists
+ if (list.head === null) {
+ return this;
+ }
+
+ if (before !== undefined && before !== null) {
+ this.updateCursors(before.prev, list.tail, before, list.head);
+
+ // insert in the middle of dist list
+ if (before.prev !== null) {
+ // before.prev <-> list.head
+ before.prev.next = list.head;
+ list.head.prev = before.prev;
+ } else {
+ this.head = list.head;
+ }
+
+ before.prev = list.tail;
+ list.tail.next = before;
+ } else {
+ this.updateCursors(this.tail, list.tail, null, list.head);
+
+ // insert to end of the list
+ if (this.tail !== null) {
+ // if destination list has a tail, then it also has a head,
+ // but head doesn't change
+
+ // dest tail -> source head
+ this.tail.next = list.head;
+
+ // dest tail <- source head
+ list.head.prev = this.tail;
+ } else {
+ // if list has no a tail, then it also has no a head
+ // in this case points head to new item
+ this.head = list.head;
+ }
+
+ // tail always start point to new item
+ this.tail = list.tail;
+ }
+
+ list.head = null;
+ list.tail = null;
+
+ return this;
+ };
+
+ List.prototype.replace = function(oldItem, newItemOrList) {
+ if ('head' in newItemOrList) {
+ this.insertList(newItemOrList, oldItem);
+ } else {
+ this.insert(newItemOrList, oldItem);
+ }
+
+ this.remove(oldItem);
+ };
+
+ var list = List;
+
+ var createCustomError = function createCustomError(name, message) {
+ // use Object.create(), because some VMs prevent setting line/column otherwise
+ // (iOS Safari 10 even throws an exception)
+ var error = Object.create(SyntaxError.prototype);
+ var errorStack = new Error();
+
+ error.name = name;
+ error.message = message;
+
+ Object.defineProperty(error, 'stack', {
+ get: function() {
+ return (errorStack.stack || '').replace(/^(.+\n){1,3}/, name + ': ' + message + '\n');
+ }
+ });
+
+ return error;
+ };
+
+ var MAX_LINE_LENGTH = 100;
+ var OFFSET_CORRECTION = 60;
+ var TAB_REPLACEMENT = ' ';
+
+ function sourceFragment(error, extraLines) {
+ function processLines(start, end) {
+ return lines.slice(start, end).map(function(line, idx) {
+ var num = String(start + idx + 1);
+
+ while (num.length < maxNumLength) {
+ num = ' ' + num;
+ }
+
+ return num + ' |' + line;
+ }).join('\n');
+ }
+
+ var lines = error.source.split(/\r\n?|\n|\f/);
+ var line = error.line;
+ var column = error.column;
+ var startLine = Math.max(1, line - extraLines) - 1;
+ var endLine = Math.min(line + extraLines, lines.length + 1);
+ var maxNumLength = Math.max(4, String(endLine).length) + 1;
+ var cutLeft = 0;
+
+ // column correction according to replaced tab before column
+ column += (TAB_REPLACEMENT.length - 1) * (lines[line - 1].substr(0, column - 1).match(/\t/g) || []).length;
+
+ if (column > MAX_LINE_LENGTH) {
+ cutLeft = column - OFFSET_CORRECTION + 3;
+ column = OFFSET_CORRECTION - 2;
+ }
+
+ for (var i = startLine; i <= endLine; i++) {
+ if (i >= 0 && i < lines.length) {
+ lines[i] = lines[i].replace(/\t/g, TAB_REPLACEMENT);
+ lines[i] =
+ (cutLeft > 0 && lines[i].length > cutLeft ? '\u2026' : '') +
+ lines[i].substr(cutLeft, MAX_LINE_LENGTH - 2) +
+ (lines[i].length > cutLeft + MAX_LINE_LENGTH - 1 ? '\u2026' : '');
+ }
+ }
+
+ return [
+ processLines(startLine, line),
+ new Array(column + maxNumLength + 2).join('-') + '^',
+ processLines(line, endLine)
+ ].filter(Boolean).join('\n');
+ }
+
+ var CssSyntaxError = function(message, source, offset, line, column) {
+ var error = createCustomError('CssSyntaxError', message);
+
+ error.source = source;
+ error.offset = offset;
+ error.line = line;
+ error.column = column;
+
+ error.sourceFragment = function(extraLines) {
+ return sourceFragment(error, isNaN(extraLines) ? 0 : extraLines);
+ };
+ Object.defineProperty(error, 'formattedMessage', {
+ get: function() {
+ return (
+ 'Parse error: ' + error.message + '\n' +
+ sourceFragment(error, 2)
+ );
+ }
+ });
+
+ // for backward capability
+ error.parseError = {
+ offset: offset,
+ line: line,
+ column: column
+ };
+
+ return error;
+ };
+
+ var error = CssSyntaxError;
+
+ // token types (note: value shouldn't intersect with used char codes)
+ var WHITESPACE = 1;
+ var IDENTIFIER = 2;
+ var NUMBER = 3;
+ var STRING = 4;
+ var COMMENT = 5;
+ var PUNCTUATOR = 6;
+ var CDO = 7;
+ var CDC = 8;
+ var ATKEYWORD = 14;
+ var FUNCTION = 15;
+ var URL$1 = 16;
+ var RAW = 17;
+
+ var TAB = 9;
+ var N = 10;
+ var F = 12;
+ var R = 13;
+ var SPACE = 32;
+
+ var TYPE = {
+ WhiteSpace: WHITESPACE,
+ Identifier: IDENTIFIER,
+ Number: NUMBER,
+ String: STRING,
+ Comment: COMMENT,
+ Punctuator: PUNCTUATOR,
+ CDO: CDO,
+ CDC: CDC,
+ AtKeyword: ATKEYWORD,
+ Function: FUNCTION,
+ Url: URL$1,
+ Raw: RAW,
+
+ ExclamationMark: 33, // !
+ QuotationMark: 34, // "
+ NumberSign: 35, // #
+ DollarSign: 36, // $
+ PercentSign: 37, // %
+ Ampersand: 38, // &
+ Apostrophe: 39, // '
+ LeftParenthesis: 40, // (
+ RightParenthesis: 41, // )
+ Asterisk: 42, // *
+ PlusSign: 43, // +
+ Comma: 44, // ,
+ HyphenMinus: 45, // -
+ FullStop: 46, // .
+ Solidus: 47, // /
+ Colon: 58, // :
+ Semicolon: 59, // ;
+ LessThanSign: 60, // <
+ EqualsSign: 61, // =
+ GreaterThanSign: 62, // >
+ QuestionMark: 63, // ?
+ CommercialAt: 64, // @
+ LeftSquareBracket: 91, // [
+ Backslash: 92, // \
+ RightSquareBracket: 93, // ]
+ CircumflexAccent: 94, // ^
+ LowLine: 95, // _
+ GraveAccent: 96, // `
+ LeftCurlyBracket: 123, // {
+ VerticalLine: 124, // |
+ RightCurlyBracket: 125, // }
+ Tilde: 126 // ~
+ };
+
+ var NAME = Object.keys(TYPE).reduce(function(result, key) {
+ result[TYPE[key]] = key;
+ return result;
+ }, {});
+
+ // https://drafts.csswg.org/css-syntax/#tokenizer-definitions
+ // > non-ASCII code point
+ // > A code point with a value equal to or greater than U+0080
+ // > name-start code point
+ // > A letter, a non-ASCII code point, or U+005F LOW LINE (_).
+ // > name code point
+ // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-)
+ // That means only ASCII code points has a special meaning and we a maps for 0..127 codes only
+ var SafeUint32Array = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported
+ var SYMBOL_TYPE = new SafeUint32Array(0x80);
+ var PUNCTUATION = new SafeUint32Array(0x80);
+ var STOP_URL_RAW = new SafeUint32Array(0x80);
+
+ for (var i = 0; i < SYMBOL_TYPE.length; i++) {
+ SYMBOL_TYPE[i] = IDENTIFIER;
+ }
+
+ // fill categories
+ [
+ TYPE.ExclamationMark, // !
+ TYPE.QuotationMark, // "
+ TYPE.NumberSign, // #
+ TYPE.DollarSign, // $
+ TYPE.PercentSign, // %
+ TYPE.Ampersand, // &
+ TYPE.Apostrophe, // '
+ TYPE.LeftParenthesis, // (
+ TYPE.RightParenthesis, // )
+ TYPE.Asterisk, // *
+ TYPE.PlusSign, // +
+ TYPE.Comma, // ,
+ TYPE.HyphenMinus, // -
+ TYPE.FullStop, // .
+ TYPE.Solidus, // /
+ TYPE.Colon, // :
+ TYPE.Semicolon, // ;
+ TYPE.LessThanSign, // <
+ TYPE.EqualsSign, // =
+ TYPE.GreaterThanSign, // >
+ TYPE.QuestionMark, // ?
+ TYPE.CommercialAt, // @
+ TYPE.LeftSquareBracket, // [
+ // TYPE.Backslash, // \
+ TYPE.RightSquareBracket, // ]
+ TYPE.CircumflexAccent, // ^
+ // TYPE.LowLine, // _
+ TYPE.GraveAccent, // `
+ TYPE.LeftCurlyBracket, // {
+ TYPE.VerticalLine, // |
+ TYPE.RightCurlyBracket, // }
+ TYPE.Tilde // ~
+ ].forEach(function(key) {
+ SYMBOL_TYPE[Number(key)] = PUNCTUATOR;
+ PUNCTUATION[Number(key)] = PUNCTUATOR;
+ });
+
+ for (var i = 48; i <= 57; i++) {
+ SYMBOL_TYPE[i] = NUMBER;
+ }
+
+ SYMBOL_TYPE[SPACE] = WHITESPACE;
+ SYMBOL_TYPE[TAB] = WHITESPACE;
+ SYMBOL_TYPE[N] = WHITESPACE;
+ SYMBOL_TYPE[R] = WHITESPACE;
+ SYMBOL_TYPE[F] = WHITESPACE;
+
+ SYMBOL_TYPE[TYPE.Apostrophe] = STRING;
+ SYMBOL_TYPE[TYPE.QuotationMark] = STRING;
+
+ STOP_URL_RAW[SPACE] = 1;
+ STOP_URL_RAW[TAB] = 1;
+ STOP_URL_RAW[N] = 1;
+ STOP_URL_RAW[R] = 1;
+ STOP_URL_RAW[F] = 1;
+ STOP_URL_RAW[TYPE.Apostrophe] = 1;
+ STOP_URL_RAW[TYPE.QuotationMark] = 1;
+ STOP_URL_RAW[TYPE.LeftParenthesis] = 1;
+ STOP_URL_RAW[TYPE.RightParenthesis] = 1;
+
+ // whitespace is punctuation ...
+ PUNCTUATION[SPACE] = PUNCTUATOR;
+ PUNCTUATION[TAB] = PUNCTUATOR;
+ PUNCTUATION[N] = PUNCTUATOR;
+ PUNCTUATION[R] = PUNCTUATOR;
+ PUNCTUATION[F] = PUNCTUATOR;
+ // ... hyper minus is not
+ PUNCTUATION[TYPE.HyphenMinus] = 0;
+
+ var _const = {
+ TYPE: TYPE,
+ NAME: NAME,
+
+ SYMBOL_TYPE: SYMBOL_TYPE,
+ PUNCTUATION: PUNCTUATION,
+ STOP_URL_RAW: STOP_URL_RAW
+ };
+
+ var PUNCTUATION$1 = _const.PUNCTUATION;
+ var STOP_URL_RAW$1 = _const.STOP_URL_RAW;
+ var TYPE$1 = _const.TYPE;
+ var FULLSTOP = TYPE$1.FullStop;
+ var PLUSSIGN = TYPE$1.PlusSign;
+ var HYPHENMINUS = TYPE$1.HyphenMinus;
+ var PUNCTUATOR$1 = TYPE$1.Punctuator;
+ var TAB$1 = 9;
+ var N$1 = 10;
+ var F$1 = 12;
+ var R$1 = 13;
+ var SPACE$1 = 32;
+ var BACK_SLASH = 92;
+ var E = 101; // 'e'.charCodeAt(0)
+
+ function firstCharOffset(source) {
+ // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark)
+ if (source.charCodeAt(0) === 0xFEFF || // UTF-16BE
+ source.charCodeAt(0) === 0xFFFE) { // UTF-16LE
+ return 1;
+ }
+
+ return 0;
+ }
+
+ function isHex(code) {
+ return (code >= 48 && code <= 57) || // 0 .. 9
+ (code >= 65 && code <= 70) || // A .. F
+ (code >= 97 && code <= 102); // a .. f
+ }
+
+ function isNumber(code) {
+ return code >= 48 && code <= 57;
+ }
+
+ function isWhiteSpace(code) {
+ return code === SPACE$1 || code === TAB$1 || isNewline(code);
+ }
+
+ function isNewline(code) {
+ return code === R$1 || code === N$1 || code === F$1;
+ }
+
+ function getNewlineLength(source, offset, code) {
+ if (isNewline(code)) {
+ if (code === R$1 && offset + 1 < source.length && source.charCodeAt(offset + 1) === N$1) {
+ return 2;
+ }
+
+ return 1;
+ }
+
+ return 0;
+ }
+
+ function cmpChar(testStr, offset, referenceCode) {
+ var code = testStr.charCodeAt(offset);
+
+ // code.toLowerCase() for A..Z
+ if (code >= 65 && code <= 90) {
+ code = code | 32;
+ }
+
+ return code === referenceCode;
+ }
+
+ function cmpStr(testStr, start, end, referenceStr) {
+ if (end - start !== referenceStr.length) {
+ return false;
+ }
+
+ if (start < 0 || end > testStr.length) {
+ return false;
+ }
+
+ for (var i = start; i < end; i++) {
+ var testCode = testStr.charCodeAt(i);
+ var refCode = referenceStr.charCodeAt(i - start);
+
+ // testCode.toLowerCase() for A..Z
+ if (testCode >= 65 && testCode <= 90) {
+ testCode = testCode | 32;
+ }
+
+ if (testCode !== refCode) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function findWhiteSpaceStart(source, offset) {
+ while (offset >= 0 && isWhiteSpace(source.charCodeAt(offset))) {
+ offset--;
+ }
+
+ return offset + 1;
+ }
+
+ function findWhiteSpaceEnd(source, offset) {
+ while (offset < source.length && isWhiteSpace(source.charCodeAt(offset))) {
+ offset++;
+ }
+
+ return offset;
+ }
+
+ function findCommentEnd(source, offset) {
+ var commentEnd = source.indexOf('*/', offset);
+
+ if (commentEnd === -1) {
+ return source.length;
+ }
+
+ return commentEnd + 2;
+ }
+
+ function findStringEnd(source, offset, quote) {
+ for (; offset < source.length; offset++) {
+ var code = source.charCodeAt(offset);
+
+ // TODO: bad string
+ if (code === BACK_SLASH) {
+ offset++;
+ } else if (code === quote) {
+ offset++;
+ break;
+ }
+ }
+
+ return offset;
+ }
+
+ function findDecimalNumberEnd(source, offset) {
+ while (offset < source.length && isNumber(source.charCodeAt(offset))) {
+ offset++;
+ }
+
+ return offset;
+ }
+
+ function findNumberEnd(source, offset, allowFraction) {
+ var code;
+
+ offset = findDecimalNumberEnd(source, offset);
+
+ // fraction: .\d+
+ if (allowFraction && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) {
+ code = source.charCodeAt(offset + 1);
+
+ if (isNumber(code)) {
+ offset = findDecimalNumberEnd(source, offset + 1);
+ }
+ }
+
+ // exponent: e[+-]\d+
+ if (offset + 1 < source.length) {
+ if ((source.charCodeAt(offset) | 32) === E) { // case insensitive check for `e`
+ code = source.charCodeAt(offset + 1);
+
+ if (code === PLUSSIGN || code === HYPHENMINUS) {
+ if (offset + 2 < source.length) {
+ code = source.charCodeAt(offset + 2);
+ }
+ }
+
+ if (isNumber(code)) {
+ offset = findDecimalNumberEnd(source, offset + 2);
+ }
+ }
+ }
+
+ return offset;
+ }
+
+ // skip escaped unicode sequence that can ends with space
+ // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
+ function findEscapeEnd(source, offset) {
+ for (var i = 0; i < 7 && offset + i < source.length; i++) {
+ var code = source.charCodeAt(offset + i);
+
+ if (i !== 6 && isHex(code)) {
+ continue;
+ }
+
+ if (i > 0) {
+ offset += i - 1 + getNewlineLength(source, offset + i, code);
+ if (code === SPACE$1 || code === TAB$1) {
+ offset++;
+ }
+ }
+
+ break;
+ }
+
+ return offset;
+ }
+
+ function findIdentifierEnd(source, offset) {
+ for (; offset < source.length; offset++) {
+ var code = source.charCodeAt(offset);
+
+ if (code === BACK_SLASH) {
+ offset = findEscapeEnd(source, offset + 1);
+ } else if (code < 0x80 && PUNCTUATION$1[code] === PUNCTUATOR$1) {
+ break;
+ }
+ }
+
+ return offset;
+ }
+
+ function findUrlRawEnd(source, offset) {
+ for (; offset < source.length; offset++) {
+ var code = source.charCodeAt(offset);
+
+ if (code === BACK_SLASH) {
+ offset = findEscapeEnd(source, offset + 1);
+ } else if (code < 0x80 && STOP_URL_RAW$1[code] === 1) {
+ break;
+ }
+ }
+
+ return offset;
+ }
+
+ var utils = {
+ firstCharOffset: firstCharOffset,
+
+ isHex: isHex,
+ isNumber: isNumber,
+ isWhiteSpace: isWhiteSpace,
+ isNewline: isNewline,
+ getNewlineLength: getNewlineLength,
+
+ cmpChar: cmpChar,
+ cmpStr: cmpStr,
+
+ findWhiteSpaceStart: findWhiteSpaceStart,
+ findWhiteSpaceEnd: findWhiteSpaceEnd,
+ findCommentEnd: findCommentEnd,
+ findStringEnd: findStringEnd,
+ findDecimalNumberEnd: findDecimalNumberEnd,
+ findNumberEnd: findNumberEnd,
+ findEscapeEnd: findEscapeEnd,
+ findIdentifierEnd: findIdentifierEnd,
+ findUrlRawEnd: findUrlRawEnd
+ };
+
+ var TYPE$2 = _const.TYPE;
+ var NAME$1 = _const.NAME;
+ var SYMBOL_TYPE$1 = _const.SYMBOL_TYPE;
+
+
+ var firstCharOffset$1 = utils.firstCharOffset;
+ var cmpStr$1 = utils.cmpStr;
+ var isNumber$1 = utils.isNumber;
+ var findWhiteSpaceStart$1 = utils.findWhiteSpaceStart;
+ var findWhiteSpaceEnd$1 = utils.findWhiteSpaceEnd;
+ var findCommentEnd$1 = utils.findCommentEnd;
+ var findStringEnd$1 = utils.findStringEnd;
+ var findNumberEnd$1 = utils.findNumberEnd;
+ var findIdentifierEnd$1 = utils.findIdentifierEnd;
+ var findUrlRawEnd$1 = utils.findUrlRawEnd;
+
+ var NULL = 0;
+ var WHITESPACE$1 = TYPE$2.WhiteSpace;
+ var IDENTIFIER$1 = TYPE$2.Identifier;
+ var NUMBER$1 = TYPE$2.Number;
+ var STRING$1 = TYPE$2.String;
+ var COMMENT$1 = TYPE$2.Comment;
+ var PUNCTUATOR$2 = TYPE$2.Punctuator;
+ var CDO$1 = TYPE$2.CDO;
+ var CDC$1 = TYPE$2.CDC;
+ var ATKEYWORD$1 = TYPE$2.AtKeyword;
+ var FUNCTION$1 = TYPE$2.Function;
+ var URL$2 = TYPE$2.Url;
+ var RAW$1 = TYPE$2.Raw;
+
+ var N$2 = 10;
+ var F$2 = 12;
+ var R$2 = 13;
+ var STAR = TYPE$2.Asterisk;
+ var SLASH = TYPE$2.Solidus;
+ var FULLSTOP$1 = TYPE$2.FullStop;
+ var PLUSSIGN$1 = TYPE$2.PlusSign;
+ var HYPHENMINUS$1 = TYPE$2.HyphenMinus;
+ var GREATERTHANSIGN = TYPE$2.GreaterThanSign;
+ var LESSTHANSIGN = TYPE$2.LessThanSign;
+ var EXCLAMATIONMARK = TYPE$2.ExclamationMark;
+ var COMMERCIALAT = TYPE$2.CommercialAt;
+ var QUOTATIONMARK = TYPE$2.QuotationMark;
+ var APOSTROPHE = TYPE$2.Apostrophe;
+ var LEFTPARENTHESIS = TYPE$2.LeftParenthesis;
+ var RIGHTPARENTHESIS = TYPE$2.RightParenthesis;
+ var LEFTCURLYBRACKET = TYPE$2.LeftCurlyBracket;
+ var RIGHTCURLYBRACKET = TYPE$2.RightCurlyBracket;
+ var LEFTSQUAREBRACKET = TYPE$2.LeftSquareBracket;
+ var RIGHTSQUAREBRACKET = TYPE$2.RightSquareBracket;
+
+ var MIN_BUFFER_SIZE = 16 * 1024;
+ var OFFSET_MASK = 0x00FFFFFF;
+ var TYPE_SHIFT = 24;
+ var SafeUint32Array$1 = typeof Uint32Array !== 'undefined' ? Uint32Array : Array; // fallback on Array when TypedArray is not supported
+
+ function computeLinesAndColumns(tokenizer, source) {
+ var sourceLength = source.length;
+ var start = firstCharOffset$1(source);
+ var lines = tokenizer.lines;
+ var line = tokenizer.startLine;
+ var columns = tokenizer.columns;
+ var column = tokenizer.startColumn;
+
+ if (lines === null || lines.length < sourceLength + 1) {
+ lines = new SafeUint32Array$1(Math.max(sourceLength + 1024, MIN_BUFFER_SIZE));
+ columns = new SafeUint32Array$1(lines.length);
+ }
+
+ for (var i = start; i < sourceLength; i++) {
+ var code = source.charCodeAt(i);
+
+ lines[i] = line;
+ columns[i] = column++;
+
+ if (code === N$2 || code === R$2 || code === F$2) {
+ if (code === R$2 && i + 1 < sourceLength && source.charCodeAt(i + 1) === N$2) {
+ i++;
+ lines[i] = line;
+ columns[i] = column;
+ }
+
+ line++;
+ column = 1;
+ }
+ }
+
+ lines[i] = line;
+ columns[i] = column;
+
+ tokenizer.linesAnsColumnsComputed = true;
+ tokenizer.lines = lines;
+ tokenizer.columns = columns;
+ }
+
+ function tokenLayout(tokenizer, source, startPos) {
+ var sourceLength = source.length;
+ var offsetAndType = tokenizer.offsetAndType;
+ var balance = tokenizer.balance;
+ var tokenCount = 0;
+ var prevType = 0;
+ var offset = startPos;
+ var anchor = 0;
+ var balanceCloseCode = 0;
+ var balanceStart = 0;
+ var balancePrev = 0;
+
+ if (offsetAndType === null || offsetAndType.length < sourceLength + 1) {
+ offsetAndType = new SafeUint32Array$1(sourceLength + 1024);
+ balance = new SafeUint32Array$1(sourceLength + 1024);
+ }
+
+ while (offset < sourceLength) {
+ var code = source.charCodeAt(offset);
+ var type = code < 0x80 ? SYMBOL_TYPE$1[code] : IDENTIFIER$1;
+
+ balance[tokenCount] = sourceLength;
+
+ switch (type) {
+ case WHITESPACE$1:
+ offset = findWhiteSpaceEnd$1(source, offset + 1);
+ break;
+
+ case PUNCTUATOR$2:
+ switch (code) {
+ case balanceCloseCode:
+ balancePrev = balanceStart & OFFSET_MASK;
+ balanceStart = balance[balancePrev];
+ balanceCloseCode = balanceStart >> TYPE_SHIFT;
+ balance[tokenCount] = balancePrev;
+ balance[balancePrev++] = tokenCount;
+ for (; balancePrev < tokenCount; balancePrev++) {
+ if (balance[balancePrev] === sourceLength) {
+ balance[balancePrev] = tokenCount;
+ }
+ }
+ break;
+
+ case LEFTSQUAREBRACKET:
+ balance[tokenCount] = balanceStart;
+ balanceCloseCode = RIGHTSQUAREBRACKET;
+ balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
+ break;
+
+ case LEFTCURLYBRACKET:
+ balance[tokenCount] = balanceStart;
+ balanceCloseCode = RIGHTCURLYBRACKET;
+ balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
+ break;
+
+ case LEFTPARENTHESIS:
+ balance[tokenCount] = balanceStart;
+ balanceCloseCode = RIGHTPARENTHESIS;
+ balanceStart = (balanceCloseCode << TYPE_SHIFT) | tokenCount;
+ break;
+ }
+
+ // /*
+ if (code === STAR && prevType === SLASH) {
+ type = COMMENT$1;
+ offset = findCommentEnd$1(source, offset + 1);
+ tokenCount--; // rewrite prev token
+ break;
+ }
+
+ // edge case for -.123 and +.123
+ if (code === FULLSTOP$1 && (prevType === PLUSSIGN$1 || prevType === HYPHENMINUS$1)) {
+ if (offset + 1 < sourceLength && isNumber$1(source.charCodeAt(offset + 1))) {
+ type = NUMBER$1;
+ offset = findNumberEnd$1(source, offset + 2, false);
+ tokenCount--; // rewrite prev token
+ break;
+ }
+ }
+
+ //
+ if (code === HYPHENMINUS$1 && prevType === HYPHENMINUS$1) {
+ if (offset + 1 < sourceLength && source.charCodeAt(offset + 1) === GREATERTHANSIGN) {
+ type = CDC$1;
+ offset = offset + 2;
+ tokenCount--; // rewrite prev token
+ break;
+ }
+ }
+
+ // ident(
+ if (code === LEFTPARENTHESIS && prevType === IDENTIFIER$1) {
+ offset = offset + 1;
+ tokenCount--; // rewrite prev token
+ balance[tokenCount] = balance[tokenCount + 1];
+ balanceStart--;
+
+ // 4 char length identifier and equal to `url(` (case insensitive)
+ if (offset - anchor === 4 && cmpStr$1(source, anchor, offset, 'url(')) {
+ // special case for url() because it can contain any symbols sequence with few exceptions
+ anchor = findWhiteSpaceEnd$1(source, offset);
+ code = source.charCodeAt(anchor);
+ if (code !== LEFTPARENTHESIS &&
+ code !== RIGHTPARENTHESIS &&
+ code !== QUOTATIONMARK &&
+ code !== APOSTROPHE) {
+ // url(
+ offsetAndType[tokenCount++] = (URL$2 << TYPE_SHIFT) | offset;
+ balance[tokenCount] = sourceLength;
+
+ // ws*
+ if (anchor !== offset) {
+ offsetAndType[tokenCount++] = (WHITESPACE$1 << TYPE_SHIFT) | anchor;
+ balance[tokenCount] = sourceLength;
+ }
+
+ // raw
+ type = RAW$1;
+ offset = findUrlRawEnd$1(source, anchor);
+ } else {
+ type = URL$2;
+ }
+ } else {
+ type = FUNCTION$1;
+ }
+ break;
+ }
+
+ type = code;
+ offset = offset + 1;
+ break;
+
+ case NUMBER$1:
+ offset = findNumberEnd$1(source, offset + 1, prevType !== FULLSTOP$1);
+
+ // merge number with a preceding dot, dash or plus
+ if (prevType === FULLSTOP$1 ||
+ prevType === HYPHENMINUS$1 ||
+ prevType === PLUSSIGN$1) {
+ tokenCount--; // rewrite prev token
+ }
+
+ break;
+
+ case STRING$1:
+ offset = findStringEnd$1(source, offset + 1, code);
+ break;
+
+ default:
+ anchor = offset;
+ offset = findIdentifierEnd$1(source, offset);
+
+ // merge identifier with a preceding dash
+ if (prevType === HYPHENMINUS$1) {
+ // rewrite prev token
+ tokenCount--;
+ // restore prev prev token type
+ // for case @-prefix-ident
+ prevType = tokenCount === 0 ? 0 : offsetAndType[tokenCount - 1] >> TYPE_SHIFT;
+ }
+
+ if (prevType === COMMERCIALAT) {
+ // rewrite prev token and change type to
+ tokenCount--;
+ type = ATKEYWORD$1;
+ }
+ }
+
+ offsetAndType[tokenCount++] = (type << TYPE_SHIFT) | offset;
+ prevType = type;
+ }
+
+ // finalize arrays
+ offsetAndType[tokenCount] = offset;
+ balance[tokenCount] = sourceLength;
+ balance[sourceLength] = sourceLength; // prevents false positive balance match with any token
+ while (balanceStart !== 0) {
+ balancePrev = balanceStart & OFFSET_MASK;
+ balanceStart = balance[balancePrev];
+ balance[balancePrev] = sourceLength;
+ }
+
+ tokenizer.offsetAndType = offsetAndType;
+ tokenizer.tokenCount = tokenCount;
+ tokenizer.balance = balance;
+ }
+
+ //
+ // tokenizer
+ //
+
+ var Tokenizer = function(source, startOffset, startLine, startColumn) {
+ this.offsetAndType = null;
+ this.balance = null;
+ this.lines = null;
+ this.columns = null;
+
+ this.setSource(source, startOffset, startLine, startColumn);
+ };
+
+ Tokenizer.prototype = {
+ setSource: function(source, startOffset, startLine, startColumn) {
+ var safeSource = String(source || '');
+ var start = firstCharOffset$1(safeSource);
+
+ this.source = safeSource;
+ this.firstCharOffset = start;
+ this.startOffset = typeof startOffset === 'undefined' ? 0 : startOffset;
+ this.startLine = typeof startLine === 'undefined' ? 1 : startLine;
+ this.startColumn = typeof startColumn === 'undefined' ? 1 : startColumn;
+ this.linesAnsColumnsComputed = false;
+
+ this.eof = false;
+ this.currentToken = -1;
+ this.tokenType = 0;
+ this.tokenStart = start;
+ this.tokenEnd = start;
+
+ tokenLayout(this, safeSource, start);
+ this.next();
+ },
+
+ lookupType: function(offset) {
+ offset += this.currentToken;
+
+ if (offset < this.tokenCount) {
+ return this.offsetAndType[offset] >> TYPE_SHIFT;
+ }
+
+ return NULL;
+ },
+ lookupNonWSType: function(offset) {
+ offset += this.currentToken;
+
+ for (var type; offset < this.tokenCount; offset++) {
+ type = this.offsetAndType[offset] >> TYPE_SHIFT;
+
+ if (type !== WHITESPACE$1) {
+ return type;
+ }
+ }
+
+ return NULL;
+ },
+ lookupValue: function(offset, referenceStr) {
+ offset += this.currentToken;
+
+ if (offset < this.tokenCount) {
+ return cmpStr$1(
+ this.source,
+ this.offsetAndType[offset - 1] & OFFSET_MASK,
+ this.offsetAndType[offset] & OFFSET_MASK,
+ referenceStr
+ );
+ }
+
+ return false;
+ },
+ getTokenStart: function(tokenNum) {
+ if (tokenNum === this.currentToken) {
+ return this.tokenStart;
+ }
+
+ if (tokenNum > 0) {
+ return tokenNum < this.tokenCount
+ ? this.offsetAndType[tokenNum - 1] & OFFSET_MASK
+ : this.offsetAndType[this.tokenCount] & OFFSET_MASK;
+ }
+
+ return this.firstCharOffset;
+ },
+ getOffsetExcludeWS: function() {
+ if (this.currentToken > 0) {
+ if ((this.offsetAndType[this.currentToken - 1] >> TYPE_SHIFT) === WHITESPACE$1) {
+ return this.currentToken > 1
+ ? this.offsetAndType[this.currentToken - 2] & OFFSET_MASK
+ : this.firstCharOffset;
+ }
+ }
+ return this.tokenStart;
+ },
+ getRawLength: function(startToken, endTokenType1, endTokenType2, includeTokenType2) {
+ var cursor = startToken;
+ var balanceEnd;
+
+ loop:
+ for (; cursor < this.tokenCount; cursor++) {
+ balanceEnd = this.balance[cursor];
+
+ // belance end points to offset before start
+ if (balanceEnd < startToken) {
+ break loop;
+ }
+
+ // check token is stop type
+ switch (this.offsetAndType[cursor] >> TYPE_SHIFT) {
+ case endTokenType1:
+ break loop;
+
+ case endTokenType2:
+ if (includeTokenType2) {
+ cursor++;
+ }
+ break loop;
+
+ default:
+ // fast forward to the end of balanced block
+ if (this.balance[balanceEnd] === cursor) {
+ cursor = balanceEnd;
+ }
+ }
+
+ }
+
+ return cursor - this.currentToken;
+ },
+ isBalanceEdge: function(pos) {
+ var balanceStart = this.balance[this.currentToken];
+ return balanceStart < pos;
+ },
+
+ getTokenValue: function() {
+ return this.source.substring(this.tokenStart, this.tokenEnd);
+ },
+ substrToCursor: function(start) {
+ return this.source.substring(start, this.tokenStart);
+ },
+
+ skipWS: function() {
+ for (var i = this.currentToken, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) {
+ if ((this.offsetAndType[i] >> TYPE_SHIFT) !== WHITESPACE$1) {
+ break;
+ }
+ }
+
+ if (skipTokenCount > 0) {
+ this.skip(skipTokenCount);
+ }
+ },
+ skipSC: function() {
+ while (this.tokenType === WHITESPACE$1 || this.tokenType === COMMENT$1) {
+ this.next();
+ }
+ },
+ skip: function(tokenCount) {
+ var next = this.currentToken + tokenCount;
+
+ if (next < this.tokenCount) {
+ this.currentToken = next;
+ this.tokenStart = this.offsetAndType[next - 1] & OFFSET_MASK;
+ next = this.offsetAndType[next];
+ this.tokenType = next >> TYPE_SHIFT;
+ this.tokenEnd = next & OFFSET_MASK;
+ } else {
+ this.currentToken = this.tokenCount;
+ this.next();
+ }
+ },
+ next: function() {
+ var next = this.currentToken + 1;
+
+ if (next < this.tokenCount) {
+ this.currentToken = next;
+ this.tokenStart = this.tokenEnd;
+ next = this.offsetAndType[next];
+ this.tokenType = next >> TYPE_SHIFT;
+ this.tokenEnd = next & OFFSET_MASK;
+ } else {
+ this.currentToken = this.tokenCount;
+ this.eof = true;
+ this.tokenType = NULL;
+ this.tokenStart = this.tokenEnd = this.source.length;
+ }
+ },
+
+ eat: function(tokenType) {
+ if (this.tokenType !== tokenType) {
+ var offset = this.tokenStart;
+ var message = NAME$1[tokenType] + ' is expected';
+
+ // tweak message and offset
+ if (tokenType === IDENTIFIER$1) {
+ // when identifier is expected but there is a function or url
+ if (this.tokenType === FUNCTION$1 || this.tokenType === URL$2) {
+ offset = this.tokenEnd - 1;
+ message += ' but function found';
+ }
+ } else {
+ // when test type is part of another token show error for current position + 1
+ // e.g. eat(HYPHENMINUS) will fail on "-foo", but pointing on "-" is odd
+ if (this.source.charCodeAt(this.tokenStart) === tokenType) {
+ offset = offset + 1;
+ }
+ }
+
+ this.error(message, offset);
+ }
+
+ this.next();
+ },
+ eatNonWS: function(tokenType) {
+ this.skipWS();
+ this.eat(tokenType);
+ },
+
+ consume: function(tokenType) {
+ var value = this.getTokenValue();
+
+ this.eat(tokenType);
+
+ return value;
+ },
+ consumeFunctionName: function() {
+ var name = this.source.substring(this.tokenStart, this.tokenEnd - 1);
+
+ this.eat(FUNCTION$1);
+
+ return name;
+ },
+ consumeNonWS: function(tokenType) {
+ this.skipWS();
+
+ return this.consume(tokenType);
+ },
+
+ expectIdentifier: function(name) {
+ if (this.tokenType !== IDENTIFIER$1 || cmpStr$1(this.source, this.tokenStart, this.tokenEnd, name) === false) {
+ this.error('Identifier `' + name + '` is expected');
+ }
+
+ this.next();
+ },
+
+ getLocation: function(offset, filename) {
+ if (!this.linesAnsColumnsComputed) {
+ computeLinesAndColumns(this, this.source);
+ }
+
+ return {
+ source: filename,
+ offset: this.startOffset + offset,
+ line: this.lines[offset],
+ column: this.columns[offset]
+ };
+ },
+
+ getLocationRange: function(start, end, filename) {
+ if (!this.linesAnsColumnsComputed) {
+ computeLinesAndColumns(this, this.source);
+ }
+
+ return {
+ source: filename,
+ start: {
+ offset: this.startOffset + start,
+ line: this.lines[start],
+ column: this.columns[start]
+ },
+ end: {
+ offset: this.startOffset + end,
+ line: this.lines[end],
+ column: this.columns[end]
+ }
+ };
+ },
+
+ error: function(message, offset) {
+ var location = typeof offset !== 'undefined' && offset < this.source.length
+ ? this.getLocation(offset)
+ : this.eof
+ ? this.getLocation(findWhiteSpaceStart$1(this.source, this.source.length - 1))
+ : this.getLocation(this.tokenStart);
+
+ throw new error(
+ message || 'Unexpected input',
+ this.source,
+ location.offset,
+ location.line,
+ location.column
+ );
+ },
+
+ dump: function() {
+ var offset = 0;
+
+ return Array.prototype.slice.call(this.offsetAndType, 0, this.tokenCount).map(function(item, idx) {
+ var start = offset;
+ var end = item & OFFSET_MASK;
+
+ offset = end;
+
+ return {
+ idx: idx,
+ type: NAME$1[item >> TYPE_SHIFT],
+ chunk: this.source.substring(start, end),
+ balance: this.balance[idx]
+ };
+ }, this);
+ }
+ };
+
+ // extend with error class
+ Tokenizer.CssSyntaxError = error;
+
+ // extend tokenizer with constants
+ Object.keys(_const).forEach(function(key) {
+ Tokenizer[key] = _const[key];
+ });
+
+ // extend tokenizer with static methods from utils
+ Object.keys(utils).forEach(function(key) {
+ Tokenizer[key] = utils[key];
+ });
+
+ // warm up tokenizer to elimitate code branches that never execute
+ // fix soft deoptimizations (insufficient type feedback)
+ new Tokenizer('\n\r\r\n\f//""\'\'/*\r\n\f*/1a;.\\31\t\+2{url(a);func();+1.2e3 -.4e-5 .6e+7}').getLocation();
+
+ var Tokenizer_1 = Tokenizer;
+
+ var tokenizer = Tokenizer_1;
+
+ function noop$1(value) {
+ return value;
+ }
+
+ function generateMultiplier(multiplier) {
+ if (multiplier.min === 0 && multiplier.max === 0) {
+ return '*';
+ }
+
+ if (multiplier.min === 0 && multiplier.max === 1) {
+ return '?';
+ }
+
+ if (multiplier.min === 1 && multiplier.max === 0) {
+ return multiplier.comma ? '#' : '+';
+ }
+
+ if (multiplier.min === 1 && multiplier.max === 1) {
+ return '';
+ }
+
+ return (
+ (multiplier.comma ? '#' : '') +
+ (multiplier.min === multiplier.max
+ ? '{' + multiplier.min + '}'
+ : '{' + multiplier.min + ',' + (multiplier.max !== 0 ? multiplier.max : '') + '}'
+ )
+ );
+ }
+
+ function generateSequence(node, forceBraces, decorate) {
+ var result = node.terms.map(function(term) {
+ return generate(term, forceBraces, decorate);
+ }).join(node.combinator === ' ' ? ' ' : ' ' + node.combinator + ' ');
+
+ if (node.explicit || forceBraces) {
+ result = (result[0] !== ',' ? '[ ' : '[') + result + ' ]';
+ }
+
+ return result;
+ }
+
+ function generate(node, forceBraces, decorate) {
+ var result;
+
+ switch (node.type) {
+ case 'Group':
+ result =
+ generateSequence(node, forceBraces, decorate) +
+ (node.disallowEmpty ? '!' : '');
+ break;
+
+ case 'Multiplier':
+ // return since node is a composition
+ return (
+ generate(node.term, forceBraces, decorate) +
+ decorate(generateMultiplier(node), node)
+ );
+
+ case 'Type':
+ result = '<' + node.name + '>';
+ break;
+
+ case 'Property':
+ result = '<\'' + node.name + '\'>';
+ break;
+
+ case 'Keyword':
+ result = node.name;
+ break;
+
+ case 'AtKeyword':
+ result = '@' + node.name;
+ break;
+
+ case 'Function':
+ result = node.name + '(';
+ break;
+
+ case 'String':
+ case 'Token':
+ result = node.value;
+ break;
+
+ case 'Comma':
+ result = ',';
+ break;
+
+ default:
+ throw new Error('Unknown node type `' + node.type + '`');
+ }
+
+ return decorate(result, node);
+ }
+
+ var generate_1 = function(node, options) {
+ var decorate = noop$1;
+ var forceBraces = false;
+
+ if (typeof options === 'function') {
+ decorate = options;
+ } else if (options) {
+ forceBraces = Boolean(options.forceBraces);
+ if (typeof options.decorate === 'function') {
+ decorate = options.decorate;
+ }
+ }
+
+ return generate(node, forceBraces, decorate);
+ };
+
+ function fromMatchResult(matchResult) {
+ var tokens = matchResult.tokens;
+ var longestMatch = matchResult.longestMatch;
+ var node = longestMatch < tokens.length ? tokens[longestMatch].node : null;
+ var mismatchOffset = 0;
+ var entries = 0;
+ var css = '';
+
+ for (var i = 0; i < tokens.length; i++) {
+ if (i === longestMatch) {
+ mismatchOffset = css.length;
+ }
+
+ if (node !== null && tokens[i].node === node) {
+ if (i <= longestMatch) {
+ entries++;
+ } else {
+ entries = 0;
+ }
+ }
+
+ css += tokens[i].value;
+ }
+
+ if (node === null) {
+ mismatchOffset = css.length;
+ }
+
+ return {
+ node: node,
+ css: css,
+ mismatchOffset: mismatchOffset,
+ last: node === null || entries > 1
+ };
+ }
+
+ function getLocation(node, point) {
+ var loc = node && node.loc && node.loc[point];
+
+ if (loc) {
+ return {
+ offset: loc.offset,
+ line: loc.line,
+ column: loc.column
+ };
+ }
+
+ return null;
+ }
+
+ var SyntaxReferenceError = function(type, referenceName) {
+ var error = createCustomError(
+ 'SyntaxReferenceError',
+ type + (referenceName ? ' `' + referenceName + '`' : '')
+ );
+
+ error.reference = referenceName;
+
+ return error;
+ };
+
+ var MatchError = function(message, lexer, syntax, node, matchResult) {
+ var error = createCustomError('SyntaxMatchError', message);
+ var details = fromMatchResult(matchResult);
+ var mismatchOffset = details.mismatchOffset || 0;
+ var badNode = details.node || node;
+ var end = getLocation(badNode, 'end');
+ var start = details.last ? end : getLocation(badNode, 'start');
+ var css = details.css;
+
+ error.rawMessage = message;
+ error.syntax = syntax ? generate_1(syntax) : '';
+ error.css = css;
+ error.mismatchOffset = mismatchOffset;
+ error.loc = {
+ source: (badNode && badNode.loc && badNode.loc.source) || '',
+ start: start,
+ end: end
+ };
+ error.line = start ? start.line : undefined;
+ error.column = start ? start.column : undefined;
+ error.offset = start ? start.offset : undefined;
+ error.message = message + '\n' +
+ ' syntax: ' + error.syntax + '\n' +
+ ' value: ' + (error.css || '') + '\n' +
+ ' --------' + new Array(error.mismatchOffset + 1).join('-') + '^';
+
+ return error;
+ };
+
+ var error$1 = {
+ SyntaxReferenceError: SyntaxReferenceError,
+ MatchError: MatchError
+ };
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+ var keywords = Object.create(null);
+ var properties = Object.create(null);
+ var HYPHENMINUS$2 = 45; // '-'.charCodeAt()
+
+ function isCustomProperty(str, offset) {
+ offset = offset || 0;
+
+ return str.length - offset >= 2 &&
+ str.charCodeAt(offset) === HYPHENMINUS$2 &&
+ str.charCodeAt(offset + 1) === HYPHENMINUS$2;
+ }
+
+ function getVendorPrefix(str, offset) {
+ offset = offset || 0;
+
+ // verdor prefix should be at least 3 chars length
+ if (str.length - offset >= 3) {
+ // vendor prefix starts with hyper minus following non-hyper minus
+ if (str.charCodeAt(offset) === HYPHENMINUS$2 &&
+ str.charCodeAt(offset + 1) !== HYPHENMINUS$2) {
+ // vendor prefix should contain a hyper minus at the ending
+ var secondDashIndex = str.indexOf('-', offset + 2);
+
+ if (secondDashIndex !== -1) {
+ return str.substring(offset, secondDashIndex + 1);
+ }
+ }
+ }
+
+ return '';
+ }
+
+ function getKeywordDescriptor(keyword) {
+ if (hasOwnProperty.call(keywords, keyword)) {
+ return keywords[keyword];
+ }
+
+ var name = keyword.toLowerCase();
+
+ if (hasOwnProperty.call(keywords, name)) {
+ return keywords[keyword] = keywords[name];
+ }
+
+ var custom = isCustomProperty(name, 0);
+ var vendor = !custom ? getVendorPrefix(name, 0) : '';
+
+ return keywords[keyword] = Object.freeze({
+ basename: name.substr(vendor.length),
+ name: name,
+ vendor: vendor,
+ prefix: vendor,
+ custom: custom
+ });
+ }
+
+ function getPropertyDescriptor(property) {
+ if (hasOwnProperty.call(properties, property)) {
+ return properties[property];
+ }
+
+ var name = property;
+ var hack = property[0];
+
+ if (hack === '/') {
+ hack = property[1] === '/' ? '//' : '/';
+ } else if (hack !== '_' &&
+ hack !== '*' &&
+ hack !== '$' &&
+ hack !== '#' &&
+ hack !== '+') {
+ hack = '';
+ }
+
+ var custom = isCustomProperty(name, hack.length);
+
+ // re-use result when possible (the same as for lower case)
+ if (!custom) {
+ name = name.toLowerCase();
+ if (hasOwnProperty.call(properties, name)) {
+ return properties[property] = properties[name];
+ }
+ }
+
+ var vendor = !custom ? getVendorPrefix(name, hack.length) : '';
+ var prefix = name.substr(0, hack.length + vendor.length);
+
+ return properties[property] = Object.freeze({
+ basename: name.substr(prefix.length),
+ name: name.substr(hack.length),
+ hack: hack,
+ vendor: vendor,
+ prefix: prefix,
+ custom: custom
+ });
+ }
+
+ var names = {
+ keyword: getKeywordDescriptor,
+ property: getPropertyDescriptor,
+ isCustomProperty: isCustomProperty,
+ vendorPrefix: getVendorPrefix
+ };
+
+ var findIdentifierEnd$2 = utils.findIdentifierEnd;
+ var findNumberEnd$2 = utils.findNumberEnd;
+ var findDecimalNumberEnd$1 = utils.findDecimalNumberEnd;
+ var isHex$1 = utils.isHex;
+
+ var SYMBOL_TYPE$2 = _const.SYMBOL_TYPE;
+ var IDENTIFIER$2 = _const.TYPE.Identifier;
+ var PLUSSIGN$2 = _const.TYPE.PlusSign;
+ var HYPHENMINUS$3 = _const.TYPE.HyphenMinus;
+ var NUMBERSIGN = _const.TYPE.NumberSign;
+
+ var PERCENTAGE = {
+ '%': true
+ };
+
+ // https://www.w3.org/TR/css-values-3/#lengths
+ var LENGTH = {
+ // absolute length units
+ 'px': true,
+ 'mm': true,
+ 'cm': true,
+ 'in': true,
+ 'pt': true,
+ 'pc': true,
+ 'q': true,
+
+ // relative length units
+ 'em': true,
+ 'ex': true,
+ 'ch': true,
+ 'rem': true,
+
+ // viewport-percentage lengths
+ 'vh': true,
+ 'vw': true,
+ 'vmin': true,
+ 'vmax': true,
+ 'vm': true
+ };
+
+ var ANGLE = {
+ 'deg': true,
+ 'grad': true,
+ 'rad': true,
+ 'turn': true
+ };
+
+ var TIME = {
+ 's': true,
+ 'ms': true
+ };
+
+ var FREQUENCY = {
+ 'hz': true,
+ 'khz': true
+ };
+
+ // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution)
+ var RESOLUTION = {
+ 'dpi': true,
+ 'dpcm': true,
+ 'dppx': true,
+ 'x': true // https://github.com/w3c/csswg-drafts/issues/461
+ };
+
+ // https://drafts.csswg.org/css-grid/#fr-unit
+ var FLEX = {
+ 'fr': true
+ };
+
+ // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume
+ var DECIBEL = {
+ 'db': true
+ };
+
+ // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch
+ var SEMITONES = {
+ 'st': true
+ };
+
+ function consumeFunction(token, addTokenToMatch, getNextToken) {
+ var length = 1;
+ var cursor;
+
+ do {
+ cursor = getNextToken(length++);
+ } while (cursor !== null && cursor.node !== token.node);
+
+ if (cursor === null) {
+ return false;
+ }
+
+ while (true) {
+ // consume tokens until cursor
+ if (addTokenToMatch() === cursor) {
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ // TODO: implement
+ // can be used wherever , , ,