UNPKG

mouseviewjs

Version:

MouseView.js is a library for presenting mouse-tracking view-window tasks and experiments

1,011 lines (857 loc) 400 kB
// MouseView.js /** * MouseView.js is a library for presenting mouse-tracking view-window tasks and experiments * It overlays a configurable occlusion layer with a viewing window that tracks the mouse position * This allows you to track the temporal and spatial aspects of a web-users attention on a web-page or experiment. * The heatmap function is built on simpleheat.js (Copyright (c) 2015, Vladimir Agafonkin) https://github.com/mourner/simpleheat */ (function(window, undefined) { 'use strict'; // for type safety // do this for node if (typeof module !== 'undefined'){ window = window_holder; } //set up namespace window.mouseview = window.mouseview || {}; mouseview = mouseview || {} // set up name spaces for specific purposes mouseview.datalogger = mouseview.datalogger || {} // for logging data mouseview.params = mouseview.params || {} // for parameters mouseview.animator_raf = mouseview.animator_raf || {} // holder for request animationframe mouseview.timing = mouseview.timing || {} // her we can setup some default settings // parameters for the aperture this can be an int, it will be interpreted as a pixel, or percentage and it will be a percentage of the screen size mouseview.params.apertureSize = '5%'// size of the view window mouseview.params.apertureGauss = 10 // if we are using a gaussian edge, this sets the blurring 0 for no blurr // parameters for the overlay mouseview.params.overlayColour = 'black' //i.e. hex black mouseview.params.overlayAlpha = 0.8 // how transparent the overlay will be mouseview.params.overlayGaussian = 20 // SD in pixels for the gaussian blur filter (experimental -- not consistent on browsers) mouseview.params.overlayGaussianFunc = () => {console.log('overlay generated')} //function that can be set, which will run on completion of blurred overlay being generated mouseview.params.overlayGaussianInterval = 0 // set to zero if updates hapen on resize events, or set to number of ms you want to wait until recapturing canvas // holder for the screenshot canvas mouseview.screen_canvas = {} // options for canvas mouseview.h2canv_opts = { } // holders for overlay width and height mouseview.params.overHeight = document.body.clientHeight mouseview.params.overWidth = document.body.clientWidth //holders for mouse offset due to scrolling mouseview.params.offset = {} mouseview.params.offset.X = 0 mouseview.params.offset.Y = 0 // holders for current mouseposition mouseview.datalogger.X = null mouseview.datalogger.Y = null // holders for recording state mouseview.datalogger.tracking = false // Array to contain objects mouseview.datalogger.data = [] // parameters for timing mouseview.timing.sampleRate = 16.66 // wanted frame time, set as 0 to acheive best, but inconsistent timing mouseview.timing.lastTime = 0.0 // holder for last frame time mouseview.timing.startTime = 0.0 // holder for start time mouseview.timing.finishTime = 0.0 // holder for finish time mouseview.timing.lastOverlayRefresh = 0.0 // holder for last overlay screen refresh // Private functions function init(){ /** * APPEND OVERLAY AND SETUP TRACKER */ // Append overlay with settings mouseview.params.overHeight = window.innerHeight mouseview.params.overWidth = window.innerWidth // make a canvas overlay, so we can use rAF to animate and record accurate times var overlay = document.createElement('canvas') // create canvas element //set settings overlay.id = "overlay"; overlay.width = mouseview.params.overWidth overlay.height = mouseview.params.overHeight overlay.style.zIndex = 99999; overlay.style.position = 'fixed'; overlay.style.display = 'block'; overlay.style.top = '0px' overlay.style.pointerEvents = 'none' overlay.setAttribute('data-html2canvas-ignore','true') //so html2canvas doesn't recapture // set mouse listener to update position on mouse move document.body.addEventListener('mousemove', event => { mouseview.datalogger.x = event.clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.clientY - mouseview.params.offset.Y; }, false); // add event listeners for touch-screen document.body.addEventListener('touchstart', event => { mouseview.datalogger.x = event.touches[0].clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.touches[0].clientY - mouseview.params.offset.Y; }, false); document.body.addEventListener('touchmove', event => { mouseview.datalogger.x = event.touches[0].clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.touches[0].clientY - mouseview.params.offset.Y; }, false); //append to body document.body.appendChild(overlay) // set event listener for resize and scroll window.addEventListener('resize', updateOverlayCanvas); window.addEventListener('scroll', updateOverlayCanvas); window.addEventListener('orientationchange', updateOverlayCanvas); //setup options for html2canvs // get dimensions of full document var body = document.body; var html = document.documentElement; var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); var width = Math.max( body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth ); // setup CSS in the header as a workaround to html2canvas's cropping bug // TODO: test this works in older browsers var css = '.html2canvas-container { width: '+ String(width)+'px !important; height: '+String(height)+'px !important; }', head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); head.appendChild(style); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); mouseview.h2canv_opts = { width: width, height: height, logging: true } window.html2canvas = html2canvas(document.body, mouseview.h2canv_opts) // Use html2canvas to get viewport screen shot and store in global holder window.html2canvas(document.body, mouseview.h2canv_opts).then((canvas) => { // get canvas and draw mouseview.screen_canvas = canvas mouseview.params.overlayGaussianFunc() updateFrame() }) } function removeAll(){ // stop recording if (mouseview.datalogger.tracking === true){ mouseview.stopTracking() } // stop animation var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; cancelAnimationFrame(mouseview.animator_raf) // remove elements var overlay = document.getElementById('overlay'); overlay.parentNode.removeChild(overlay) //remove listeners from body and window window.removeEventListener('resize', updateOverlayCanvas); window.removeEventListener('scroll', updateOverlayCanvas); window.removeEventListener('orientationchange', updateOverlayCanvas); document.removeEventListener('mousemove', event => { mouseview.datalogger.x = event.clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.clientY - mouseview.params.offset.Y; }, false); // add event listeners for touch-screen document.removeEventListener('touchstart', event => { mouseview.datalogger.x = event.touches[0].clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.touches[0].clientY - mouseview.params.offset.Y; }, false); document.removeEventListener('touchmove', event => { mouseview.datalogger.x = event.touches[0].clientX - mouseview.params.offset.X; mouseview.datalogger.y = event.touches[0].clientY - mouseview.params.offset.Y; }, false); } // function to deal with stuff at the animation frame level // this will be called using rAF callback function updateFrame(timestamp){ var overlay = document.getElementById('overlay') var ctx = overlay.getContext('2d'); //clear previous frame ctx.clearRect(0, 0, mouseview.params.overWidth, mouseview.params.overHeight) //draw screenshot with gaussian blur if we have set this to be greater than 0 if(mouseview.params.overlayGaussian > 0){ ctx.filter = 'blur('+mouseview.params.overlayGaussian+'px)'; ctx.globalAlpha = 1; // the screen_canvas collected by html2canvas can be a different size to the rest of the screen, so we need to test its size and then scale appropriately // the style usually represents the actual dimensions var style_height = parseInt(mouseview.screen_canvas.style.height) var style_width = parseInt(mouseview.screen_canvas.style.width) // and the height/width the different dimensions var canv_height = mouseview.screen_canvas.height var canv_width = mouseview.screen_canvas.width //make a float representing how these two relate to each other var h_multi = canv_height/style_height var w_multi = canv_width/style_width // the screen_canvas represents the whole document, so we need to do two things: // - start clipping at X and Y scroll offset // - clip to the size of the inner window // void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); ctx.drawImage(mouseview.screen_canvas, mouseview.params.offset.X*w_multi, mouseview.params.offset.Y*h_multi, // SCROLLING OFFST mouseview.params.overWidth*w_multi, mouseview.params.overHeight*h_multi, //WIDTH AND HEIGHT OF WINDOW 0, 0, // Where to draw on the destination canvas mouseview.params.overWidth, mouseview.params.overHeight); // the width and height of canvas (the same) ctx.filter = 'none'; // reset filter // only draw aperture if we actually have a mouse position if (mouseview.datalogger.x != null || mouseview.datalogger.y != null){ ctx.globalCompositeOperation = 'xor'; ctx.filter = 'blur('+mouseview.params.apertureGauss+'px)'; ctx.beginPath(); var apsize if (typeof(mouseview.params.apertureSize) == "string") { apsize = window.innerHeight * (parseInt(mouseview.params.apertureSize)/100) } else { apsize = mouseview.params.apertureSize } ctx.arc(mouseview.datalogger.x + mouseview.params.offset.X, mouseview.datalogger.y + mouseview.params.offset.Y, apsize, 0, 2 * Math.PI, false); ctx.fill() ctx.filter = 'none'; } } //return to source-over compositing ctx.globalCompositeOperation = 'source-over'; ctx.filter = 'none'; // draw overlay again, based on global options ctx.fillStyle = mouseview.params.overlayColour; ctx.globalAlpha = mouseview.params.overlayAlpha; ctx.fillRect(0, 0, mouseview.params.overWidth, mouseview.params.overHeight); // only draw aperture if we actually have a mouse position if (mouseview.datalogger.x != null || mouseview.datalogger.y != null){ ctx.globalCompositeOperation = 'xor'; ctx.filter = 'blur('+mouseview.params.apertureGauss+'px)'; ctx.beginPath(); var apsize if (typeof(mouseview.params.apertureSize) == "string") { apsize = window.innerHeight * (parseInt(mouseview.params.apertureSize)/100) } else { apsize = mouseview.params.apertureSize } ctx.arc(mouseview.datalogger.x + mouseview.params.offset.X, mouseview.datalogger.y + mouseview.params.offset.Y, apsize, 0, 2 * Math.PI, false); ctx.fill() ctx.filter = 'none' } // if logging log mouse position if (mouseview.datalogger.tracking === true){ // if we have reached desired sampling interval console.log(timestamp, mouseview.timing.lastTime, mouseview.timing.sampleRate) if ( (timestamp - mouseview.timing.lastTime) >= mouseview.timing.sampleRate) { logPosition(mouseview.datalogger.x + mouseview.params.offset.X , mouseview.datalogger.y + mouseview.params.offset.Y, timestamp) //log position mouseview.timing.lastTime = timestamp // update last timestamp } } // if we have a mnual interval for updating overlay! if (mouseview.params.overlayGaussianInterval > 0){ // check if we are due a refresh if ( (timestamp - mouseview.timing.lastOverlayRefresh) >= mouseview.params.overlayGaussianInterval) { updateOverlayCanvas() // do it } } // next frame mouseview.animator_raf = requestAnimationFrame(updateFrame); } // update the overlay canvas itself with new settings // this is at the canvas not ctx level function updateOverlayCanvas(){ //if we are doing a blur then recapture -- but avoid as computationally heavier if (mouseview.params.overlayGaussian > 0){ updateOverlayBlur() } else { // recalculate offset mouseview.params.offset.X = window.pageXOffset mouseview.params.offset.Y = window.pageYOffset var overlay = document.getElementById("overlay") mouseview.params.overWidth = window.innerWidth mouseview.params.overHeight = window.innerHeight // get height and width // set overlay overlay.width = mouseview.params.overWidth overlay.height = mouseview.params.overHeight } mouseview.timing.lastOverlayRefresh = mouseview.timing.lastTime } function updateOverlayBlur(){ //setup options for html2canvs var overlay = document.getElementById("overlay") // recalculate offset mouseview.params.offset.X = window.pageXOffset mouseview.params.offset.Y = window.pageYOffset // get dimensions of full document var body = document.body; var html = document.documentElement; var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight ); var width = Math.max( body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth ); mouseview.h2canv_opts = { scrollY: -mouseview.params.offset.X, scrollX: -mouseview.params.offset.X, width: width, height: height, logging: true } // only update parameters after promise has been done window.html2canvas(document.body, mouseview.h2canv_opts).then((canvas) => { // get canvas and draw mouseview.screen_canvas = canvas mouseview.params.overWidth = window.innerWidth mouseview.params.overHeight = window.innerHeight // get height and width // set overlay overlay.width = mouseview.params.overWidth overlay.height = mouseview.params.overHeight }) } // Start tracking the mouse movements function startTracking(){ mouseview.datalogger.tracking = true mouseview.timing.startTime = window.performance.now() console.log('Started recording data') } // Stop tracking the mouse movements function stopTracking(){ mouseview.datalogger.tracking = false mouseview.timing.finishTimeTime = window.performance.now() console.log('Stopped recording data') console.log(mouseview.datalogger.data) } // logging data function logPosition(xpos, ypos, timestamp){ mouseview.datalogger.data.push({ x: xpos, y: ypos, time: timestamp - mouseview.timing.startTime, event: 'sample' }) } // log a random event function logEvent(event_string){ mouseview.datalogger.data.push({ x: mouseview.datalogger.X, y: mouseview.datalogger.Y, time: mouseview.timing.lastTime - mouseview.timing.startTime, event: event_string }) } //storing data locally (helpful for multiple pages) function storeData(){ mouseview.datalogger.data.unshift(window.location.pathname) // add pathname to beginning of data localStorage.setItem("mouseview_data", JSON.stringify(mouseview.datalogger.data)); } //getting local data (helpful for plotting data) function getData(){ mouseview.datalogger.data = JSON.parse(localStorage.getItem("mouseview_data") || []); mouseview.datalogger.data.push(window.location.pathname) // add pathname to end of data } function clearData(){ mouseview.datalogger.data = [] } // plot a heatmap of the x and y coordinates function plotHeatMap(){ // remove old heatmap if there clearHeatmap() // Append overlay with settings mouseview.params.overWidth = document.body.clientWidth mouseview.params.overHeight = document.body.clientHeight; // get height and width // make a canvas overlay, so we can use rAF to animate and record accurate times var overlay = document.createElement('canvas') // create canvas element //set settings overlay.id = "heatmap"; overlay.width = mouseview.params.overWidth overlay.height = mouseview.params.overHeight overlay.style.zIndex = 999999; overlay.style.position = 'fixed'; overlay.style.display = 'block'; overlay.style.top = '0px' overlay.style.pointerEvents = 'none' document.body.appendChild(overlay)// show the layer // get data into expected format for simplheat (list of lists [[x,y,1],....]) var formattedArray = [] for (var i = 0; i < mouseview.datalogger.data.length; i++) { formattedArray.push([mouseview.datalogger.data[i].x, mouseview.datalogger.data[i].y, 1]) } //pass canvas and data to simpleheat var heat = simpleheat(overlay) heat.data(formattedArray) heat.radius(20,90) heat.draw() } function clearHeatmap(){ var overlay = document.getElementById('heatmap'); if(overlay !== null) {overlay.parentNode.removeChild(overlay)} } // Link specific internal functions to public ones mouseview.init = () => { init() } mouseview.removeAll = () => { removeAll() } mouseview.startTracking = () => { startTracking() } mouseview.stopTracking = () => { stopTracking() } mouseview.storeData = () => { storeData() } mouseview.getData = () => { getData() } mouseview.plotHeatMap = () => { plotHeatMap() } mouseview.clearHeatMap = () => { clearHeatmap() } mouseview.clearData = () => { clearData() } mouseview.logEvent = (event_txt) => { logEvent(event_txt) } mouseview.updateOverlayCanvas = () => { updateOverlayCanvas() } // Setters //bundled dependencies // simple heat function simpleheat(canvas) { if (!(this instanceof simpleheat)) return new simpleheat(canvas); this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; this._ctx = canvas.getContext('2d'); this._width = canvas.width; this._height = canvas.height; this._max = 1; this._data = []; } simpleheat.prototype = { defaultRadius: 25, defaultGradient: { 0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red' }, data: function (data) { this._data = data; return this; }, max: function (max) { this._max = max; return this; }, add: function (point) { this._data.push(point); return this; }, clear: function () { this._data = []; return this; }, radius: function (r, blur) { blur = blur === undefined ? 15 : blur; // create a grayscale blurred circle image that we'll use for drawing points var circle = this._circle = this._createCanvas(), ctx = circle.getContext('2d'), r2 = this._r = r + blur; circle.width = circle.height = r2 * 2; ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2; ctx.shadowBlur = blur; ctx.shadowColor = 'black'; ctx.beginPath(); ctx.arc(-r2, -r2, r, 0, Math.PI * 2, true); ctx.closePath(); ctx.fill(); return this; }, resize: function () { this._width = this._canvas.width; this._height = this._canvas.height; }, gradient: function (grad) { // create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one var canvas = this._createCanvas(), ctx = canvas.getContext('2d'), gradient = ctx.createLinearGradient(0, 0, 0, 256); canvas.width = 1; canvas.height = 256; for (var i in grad) { gradient.addColorStop(+i, grad[i]); } ctx.fillStyle = gradient; ctx.fillRect(0, 0, 1, 256); this._grad = ctx.getImageData(0, 0, 1, 256).data; return this; }, draw: function (minOpacity) { if (!this._circle) this.radius(this.defaultRadius); if (!this._grad) this.gradient(this.defaultGradient); var ctx = this._ctx; ctx.clearRect(0, 0, this._width, this._height); // draw a grayscale heatmap by putting a blurred circle at each data point for (var i = 0, len = this._data.length, p; i < len; i++) { p = this._data[i]; ctx.globalAlpha = Math.min(Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity), 1); ctx.drawImage(this._circle, p[0] - this._r, p[1] - this._r); } // colorize the heatmap, using opacity value of each pixel to get the right color from our gradient var colored = ctx.getImageData(0, 0, this._width, this._height); this._colorize(colored.data, this._grad); ctx.putImageData(colored, 0, 0); return this; }, _colorize: function (pixels, gradient) { for (var i = 0, len = pixels.length, j; i < len; i += 4) { j = pixels[i + 3] * 4; // get gradient color from opacity value if (j) { pixels[i] = gradient[j]; pixels[i + 1] = gradient[j + 1]; pixels[i + 2] = gradient[j + 2]; } } }, _createCanvas: function () { if (typeof document !== 'undefined') { return document.createElement('canvas'); } else { // create a new canvas instance in node.js // the canvas class needs to have a default constructor without any parameter return new this._canvas.constructor(); } } }; //html2canvas function html2canvas() { /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var Bounds = /** @class */ (function () { function Bounds(x, y, w, h) { this.left = x; this.top = y; this.width = w; this.height = h; } Bounds.prototype.add = function (x, y, w, h) { return new Bounds(this.left + x, this.top + y, this.width + w, this.height + h); }; Bounds.fromClientRect = function (clientRect) { return new Bounds(clientRect.left, clientRect.top, clientRect.width, clientRect.height); }; return Bounds; }()); var parseBounds = function (node) { return Bounds.fromClientRect(node.getBoundingClientRect()); }; var parseDocumentSize = function (document) { var body = document.body; var documentElement = document.documentElement; if (!body || !documentElement) { throw new Error("Unable to get document size"); } var width = Math.max(Math.max(body.scrollWidth, documentElement.scrollWidth), Math.max(body.offsetWidth, documentElement.offsetWidth), Math.max(body.clientWidth, documentElement.clientWidth)); var height = Math.max(Math.max(body.scrollHeight, documentElement.scrollHeight), Math.max(body.offsetHeight, documentElement.offsetHeight), Math.max(body.clientHeight, documentElement.clientHeight)); return new Bounds(0, 0, width, height); }; /* * css-line-break 1.1.1 <https://github.com/niklasvh/css-line-break#readme> * Copyright (c) 2019 Niklas von Hertzen <https://hertzen.com> * Released under MIT License */ var toCodePoints = function (str) { var codePoints = []; var i = 0; var length = str.length; while (i < length) { var value = str.charCodeAt(i++); if (value >= 0xd800 && value <= 0xdbff && i < length) { var extra = str.charCodeAt(i++); if ((extra & 0xfc00) === 0xdc00) { codePoints.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000); } else { codePoints.push(value); i--; } } else { codePoints.push(value); } } return codePoints; }; var fromCodePoint = function () { var codePoints = []; for (var _i = 0; _i < arguments.length; _i++) { codePoints[_i] = arguments[_i]; } if (String.fromCodePoint) { return String.fromCodePoint.apply(String, codePoints); } var length = codePoints.length; if (!length) { return ''; } var codeUnits = []; var index = -1; var result = ''; while (++index < length) { var codePoint = codePoints[index]; if (codePoint <= 0xffff) { codeUnits.push(codePoint); } else { codePoint -= 0x10000; codeUnits.push((codePoint >> 10) + 0xd800, codePoint % 0x400 + 0xdc00); } if (index + 1 === length || codeUnits.length > 0x4000) { result += String.fromCharCode.apply(String, codeUnits); codeUnits.length = 0; } } return result; }; var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Use a lookup table to find the index. var lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); for (var i = 0; i < chars.length; i++) { lookup[chars.charCodeAt(i)] = i; } var decode = function (base64) { var bufferLength = base64.length * 0.75, len = base64.length, i, p = 0, encoded1, encoded2, encoded3, encoded4; if (base64[base64.length - 1] === '=') { bufferLength--; if (base64[base64.length - 2] === '=') { bufferLength--; } } var buffer = typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined' && typeof Uint8Array.prototype.slice !== 'undefined' ? new ArrayBuffer(bufferLength) : new Array(bufferLength); var bytes = Array.isArray(buffer) ? buffer : new Uint8Array(buffer); for (i = 0; i < len; i += 4) { encoded1 = lookup[base64.charCodeAt(i)]; encoded2 = lookup[base64.charCodeAt(i + 1)]; encoded3 = lookup[base64.charCodeAt(i + 2)]; encoded4 = lookup[base64.charCodeAt(i + 3)]; bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); } return buffer; }; var polyUint16Array = function (buffer) { var length = buffer.length; var bytes = []; for (var i = 0; i < length; i += 2) { bytes.push((buffer[i + 1] << 8) | buffer[i]); } return bytes; }; var polyUint32Array = function (buffer) { var length = buffer.length; var bytes = []; for (var i = 0; i < length; i += 4) { bytes.push((buffer[i + 3] << 24) | (buffer[i + 2] << 16) | (buffer[i + 1] << 8) | buffer[i]); } return bytes; }; /** Shift size for getting the index-2 table offset. */ var UTRIE2_SHIFT_2 = 5; /** Shift size for getting the index-1 table offset. */ var UTRIE2_SHIFT_1 = 6 + 5; /** * Shift size for shifting left the index array values. * Increases possible data size with 16-bit index values at the cost * of compactability. * This requires data blocks to be aligned by UTRIE2_DATA_GRANULARITY. */ var UTRIE2_INDEX_SHIFT = 2; /** * Difference between the two shift sizes, * for getting an index-1 offset from an index-2 offset. 6=11-5 */ var UTRIE2_SHIFT_1_2 = UTRIE2_SHIFT_1 - UTRIE2_SHIFT_2; /** * The part of the index-2 table for U+D800..U+DBFF stores values for * lead surrogate code _units_ not code _points_. * Values for lead surrogate code _points_ are indexed with this portion of the table. * Length=32=0x20=0x400>>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.) */ var UTRIE2_LSCP_INDEX_2_OFFSET = 0x10000 >> UTRIE2_SHIFT_2; /** Number of entries in a data block. 32=0x20 */ var UTRIE2_DATA_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_2; /** Mask for getting the lower bits for the in-data-block offset. */ var UTRIE2_DATA_MASK = UTRIE2_DATA_BLOCK_LENGTH - 1; var UTRIE2_LSCP_INDEX_2_LENGTH = 0x400 >> UTRIE2_SHIFT_2; /** Count the lengths of both BMP pieces. 2080=0x820 */ var UTRIE2_INDEX_2_BMP_LENGTH = UTRIE2_LSCP_INDEX_2_OFFSET + UTRIE2_LSCP_INDEX_2_LENGTH; /** * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820. * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2. */ var UTRIE2_UTF8_2B_INDEX_2_OFFSET = UTRIE2_INDEX_2_BMP_LENGTH; var UTRIE2_UTF8_2B_INDEX_2_LENGTH = 0x800 >> 6; /* U+0800 is the first code point after 2-byte UTF-8 */ /** * The index-1 table, only used for supplementary code points, at offset 2112=0x840. * Variable length, for code points up to highStart, where the last single-value range starts. * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1. * (For 0x100000 supplementary code points U+10000..U+10ffff.) * * The part of the index-2 table for supplementary code points starts * after this index-1 table. * * Both the index-1 table and the following part of the index-2 table * are omitted completely if there is only BMP data. */ var UTRIE2_INDEX_1_OFFSET = UTRIE2_UTF8_2B_INDEX_2_OFFSET + UTRIE2_UTF8_2B_INDEX_2_LENGTH; /** * Number of index-1 entries for the BMP. 32=0x20 * This part of the index-1 table is omitted from the serialized form. */ var UTRIE2_OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> UTRIE2_SHIFT_1; /** Number of entries in an index-2 block. 64=0x40 */ var UTRIE2_INDEX_2_BLOCK_LENGTH = 1 << UTRIE2_SHIFT_1_2; /** Mask for getting the lower bits for the in-index-2-block offset. */ var UTRIE2_INDEX_2_MASK = UTRIE2_INDEX_2_BLOCK_LENGTH - 1; var slice16 = function (view, start, end) { if (view.slice) { return view.slice(start, end); } return new Uint16Array(Array.prototype.slice.call(view, start, end)); }; var slice32 = function (view, start, end) { if (view.slice) { return view.slice(start, end); } return new Uint32Array(Array.prototype.slice.call(view, start, end)); }; var createTrieFromBase64 = function (base64) { var buffer = decode(base64); var view32 = Array.isArray(buffer) ? polyUint32Array(buffer) : new Uint32Array(buffer); var view16 = Array.isArray(buffer) ? polyUint16Array(buffer) : new Uint16Array(buffer); var headerLength = 24; var index = slice16(view16, headerLength / 2, view32[4] / 2); var data = view32[5] === 2 ? slice16(view16, (headerLength + view32[4]) / 2) : slice32(view32, Math.ceil((headerLength + view32[4]) / 4)); return new Trie(view32[0], view32[1], view32[2], view32[3], index, data); }; var Trie = /** @class */ (function () { function Trie(initialValue, errorValue, highStart, highValueIndex, index, data) { this.initialValue = initialValue; this.errorValue = errorValue; this.highStart = highStart; this.highValueIndex = highValueIndex; this.index = index; this.data = data; } /** * Get the value for a code point as stored in the Trie. * * @param codePoint the code point * @return the value */ Trie.prototype.get = function (codePoint) { var ix; if (codePoint >= 0) { if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) { // Ordinary BMP code point, excluding leading surrogates. // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index. // 16 bit data is stored in the index array itself. ix = this.index[codePoint >> UTRIE2_SHIFT_2]; ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); return this.data[ix]; } if (codePoint <= 0xffff) { // Lead Surrogate Code Point. A Separate index section is stored for // lead surrogate code units and code points. // The main index has the code unit data. // For this function, we need the code point data. // Note: this expression could be refactored for slightly improved efficiency, but // surrogate code points will be so rare in practice that it's not worth it. ix = this.index[UTRIE2_LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2)]; ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); return this.data[ix]; } if (codePoint < this.highStart) { // Supplemental code point, use two-level lookup. ix = UTRIE2_INDEX_1_OFFSET - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH + (codePoint >> UTRIE2_SHIFT_1); ix = this.index[ix]; ix += (codePoint >> UTRIE2_SHIFT_2) & UTRIE2_INDEX_2_MASK; ix = this.index[ix]; ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); return this.data[ix]; } if (codePoint <= 0x10ffff) { return this.data[this.highValueIndex]; } } // Fall through. The code point is outside of the legal range of 0..0x10ffff. return this.errorValue; }; return Trie; }()); var base64 = 'KwAAAAAAAAAACA4AIDoAAPAfAAACAAAAAAAIABAAGABAAEgAUABYAF4AZgBeAGYAYABoAHAAeABeAGYAfACEAIAAiACQAJgAoACoAK0AtQC9AMUAXgBmAF4AZgBeAGYAzQDVAF4AZgDRANkA3gDmAOwA9AD8AAQBDAEUARoBIgGAAIgAJwEvATcBPwFFAU0BTAFUAVwBZAFsAXMBewGDATAAiwGTAZsBogGkAawBtAG8AcIBygHSAdoB4AHoAfAB+AH+AQYCDgIWAv4BHgImAi4CNgI+AkUCTQJTAlsCYwJrAnECeQKBAk0CiQKRApkCoQKoArACuALAAsQCzAIwANQC3ALkAjAA7AL0AvwCAQMJAxADGAMwACADJgMuAzYDPgOAAEYDSgNSA1IDUgNaA1oDYANiA2IDgACAAGoDgAByA3YDfgOAAIQDgACKA5IDmgOAAIAAogOqA4AAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAK8DtwOAAIAAvwPHA88D1wPfAyAD5wPsA/QD/AOAAIAABAQMBBIEgAAWBB4EJgQuBDMEIAM7BEEEXgBJBCADUQRZBGEEaQQwADAAcQQ+AXkEgQSJBJEEgACYBIAAoASoBK8EtwQwAL8ExQSAAIAAgACAAIAAgACgAM0EXgBeAF4AXgBeAF4AXgBeANUEXgDZBOEEXgDpBPEE+QQBBQkFEQUZBSEFKQUxBTUFPQVFBUwFVAVcBV4AYwVeAGsFcwV7BYMFiwWSBV4AmgWgBacFXgBeAF4AXgBeAKsFXgCyBbEFugW7BcIFwgXIBcIFwgXQBdQF3AXkBesF8wX7BQMGCwYTBhsGIwYrBjMGOwZeAD8GRwZNBl4AVAZbBl4AXgBeAF4AXgBeAF4AXgBeAF4AXgBeAGMGXgBqBnEGXgBeAF4AXgBeAF4AXgBeAF4AXgB5BoAG4wSGBo4GkwaAAIADHgR5AF4AXgBeAJsGgABGA4AAowarBrMGswagALsGwwbLBjAA0wbaBtoG3QbaBtoG2gbaBtoG2gblBusG8wb7BgMHCwcTBxsHCwcjBysHMAc1BzUHOgdCB9oGSgdSB1oHYAfaBloHaAfaBlIH2gbaBtoG2gbaBtoG2gbaBjUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHbQdeAF4ANQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQd1B30HNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1B4MH2gaKB68EgACAAIAAgACAAIAAgACAAI8HlwdeAJ8HpweAAIAArwe3B14AXgC/B8UHygcwANAH2AfgB4AA6AfwBz4B+AcACFwBCAgPCBcIogEYAR8IJwiAAC8INwg/CCADRwhPCFcIXwhnCEoDGgSAAIAAgABvCHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIfQh3CHgIeQh6CHsIfAh9CHcIeAh5CHoIewh8CH0Idwh4CHkIegh7CHwIhAiLCI4IMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlggwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAANQc1BzUHNQc1BzUHNQc1BzUHNQc1B54INQc1B6II2gaqCLIIugiAAIAAvgjGCIAAgACAAIAAgACAAIAAgACAAIAAywiHAYAA0wiAANkI3QjlCO0I9Aj8CIAAgACAAAIJCgkSCRoJIgknCTYHLwk3CZYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiWCJYIlgiAAIAAAAFAAXgBeAGAAcABeAHwAQACQAKAArQC9AJ4AXgBeAE0A3gBRAN4A7AD8AMwBGgEAAKcBNwEFAUwBXAF4QkhCmEKnArcCgAHHAsABz4LAAcABwAHAAd+C6ABoAG+C/4LAAcABwAHAAc+DF4MAAcAB54M3gweDV4Nng3eDaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAGgAaABoAEeDqABVg6WDqABoQ6gAaABoAHXDvcONw/3DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DvcO9w73DncPAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcABwAHAAcAB7cPPwlGCU4JMACAAIAAgABWCV4JYQmAAGkJcAl4CXwJgAkwADAAMAAwAIgJgACLCZMJgACZCZ8JowmrCYAAswkwAF4AXgB8AIAAuwkABMMJyQmAAM4JgADVCTAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAqwYWBNkIMAAwADAAMADdCeAJ6AnuCR4E9gkwAP4JBQoNCjAAMACAABUK0wiAAB0KJAosCjQKgAAwADwKQwqAAEsKvQmdCVMKWwowADAAgACAALcEMACAAGMKgABrCjAAMAAwADAAMAAwADAAMAAwADAAMAAeBDAAMAAwADAAMAAwADAAMAAwADAAMAAwAIkEPQFzCnoKiQSCCooKkAqJBJgKoAqkCokEGAGsCrQKvArBCjAAMADJCtEKFQHZCuEK/gHpCvEKMAAwADAAMACAAIwE+QowAIAAPwEBCzAAMAAwADAAMACAAAkLEQswAIAAPwEZCyELgAAOCCkLMAAxCzkLMAAwADAAMAAwADAAXgBeAEELMAAwADAAMAAwADAAMAAwAEkLTQtVC4AAXAtkC4AAiQkwADAAMAAwADAAMAAwADAAbAtxC3kLgAuFC4sLMAAwAJMLlwufCzAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAApwswADAAMACAAIAAgACvC4AAgACAAIAAgACAALcLMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAvwuAAMcLgACAAIAAgACAAIAAyguAAIAAgACAAIAA0QswADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAANkLgACAAIAA4AswADAAMAAwADAAMAAwADAAMAAwADAAMAAwAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACJCR4E6AswADAAhwHwC4AA+AsADAgMEAwwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMAAwADAAMACAAIAAGAwdDCUMMAAwAC0MNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQc1BzUHNQ