UNPKG

@amcharts/amcharts4

Version:
1,133 lines (1,132 loc) 171 kB
/** * Export module. * * Parts of Export functionality rely on the following third party libraries: * * [canvg.js](https://github.com/canvg/canvg) * Copyright (c) Gabe Lerner * Licensed under [MIT](https://github.com/canvg/canvg/blob/master/LICENSE) * * [pdfmake](http://pdfmake.org/) * Copyright (c) 2014 bpampuch * Licensed under [MIT](https://github.com/bpampuch/pdfmake/blob/master/LICENSE) * * [SheetJS Community Edition](https://github.com/sheetjs/js-xlsx) * Licensed under [Apache License 2.0](https://github.com/SheetJS/js-xlsx/blob/master/LICENSE) * * [JSZip](http://stuartk.com/jszip) * Copyright (c) Stuart Knightley * Dual licenced under the [MIT license or GPLv3](https://raw.githubusercontent.com/Stuk/jszip/master/LICENSE.markdown). */ import { __awaiter, __extends, __generator } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { ExportMenu } from "./ExportMenu"; import { Adapter } from "../utils/Adapter"; import { Sprite } from "../Sprite"; import { Modal } from "../elements/Modal"; import { List } from "../utils/List"; import { Dictionary } from "../utils/Dictionary"; import { DateFormatter } from "../formatters/DateFormatter"; import { DurationFormatter } from "../formatters/DurationFormatter"; import { NumberFormatter } from "../formatters/NumberFormatter"; import { Language } from "../utils/Language"; import { Validatable } from "../utils/Validatable"; import { color } from "../utils/Color"; import { registry } from "../Registry"; import { options } from "../Options"; import { StyleRule, getComputedStyle } from "../utils/DOM"; import * as $browser from "../utils/Browser"; import * as $object from "../utils/Object"; import * as $net from "../utils/Net"; import * as $dom from "../utils/DOM"; import * as $type from "../utils/Type"; import * as $log from "../utils/Log"; import * as $utils from "../utils/Utils"; import * as $array from "../utils/Array"; import * as $math from "../utils/Math"; import * as $strings from "../utils/Strings"; // This is used to cache the pdfmake loading var pdfmakePromise; /** * Loads pdfmake dynamic module * * This is an asynchronous function. Check the description of `getImage()` * for description and example usage. * * @ignore Exclude from docs * @return Instance of pdfmake * @async */ function _pdfmake() { return __awaiter(this, void 0, void 0, function () { var a, pdfmake, vfs_fonts, global; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Promise.all([ import(/* webpackChunkName: "pdfmake" */ "pdfmake/build/pdfmake.js"), import(/* webpackChunkName: "pdfmake" */ "../../pdfmake/vfs_fonts") ])]; case 1: a = _a.sent(); pdfmake = a[0]; vfs_fonts = a[1]; global = window; global.pdfMake = global.pdfMake || {}; global.pdfMake.vfs = vfs_fonts.default; pdfmake.vfs = vfs_fonts.default; return [2 /*return*/, pdfmake]; } }); }); } // TODO better parsing var fontFamilySrcRegexp = /src: ([^;]+);/; // TODO better checks function supportsBlobUri() { return window.navigator.msSaveOrOpenBlob != null; } // TODO move into utils or something ? function blobToDataUri(blob) { return new Promise(function (resolve, reject) { // TODO handle abort ? var f = new FileReader(); f.onload = function (e) { resolve(f.result); }; f.onerror = function (e) { reject(e); }; f.readAsDataURL(blob); }); } function getCssRules(s) { return __awaiter(this, void 0, void 0, function () { var sheet, e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: sheet = s.sheet; _a.label = 1; case 1: _a.trys.push([1, 2, , 4]); return [2 /*return*/, sheet.cssRules]; case 2: e_1 = _a.sent(); return [4 /*yield*/, new Promise(function (success, error) { s.addEventListener("load", function () { success(sheet.cssRules); }, true); s.addEventListener("error", function (e) { error(e); }, true); setTimeout(function () { error(new Error("Timeout while waiting for <style> to load")); }, 10000); })]; case 3: // Needed because of https://bugzilla.mozilla.org/show_bug.cgi?id=625013 return [2 /*return*/, _a.sent()]; case 4: return [2 /*return*/]; } }); }); } // This loads a stylesheet by URL and then calls the function with it // TODO this should be moved into utils or something function loadStylesheet(doc, url, f) { return __awaiter(this, void 0, void 0, function () { var response, e_2, s, rules; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); return [4 /*yield*/, $net.load(url)]; case 1: response = _a.sent(); return [3 /*break*/, 3]; case 2: e_2 = _a.sent(); console.error("Failed to load stylesheet", url, e_2); return [2 /*return*/]; case 3: s = doc.createElement("style"); s.textContent = response.response; if (options.nonce != "") { s.setAttribute("nonce", options.nonce); } doc.head.appendChild(s); _a.label = 4; case 4: _a.trys.push([4, , 7, 8]); return [4 /*yield*/, getCssRules(s)]; case 5: rules = _a.sent(); return [4 /*yield*/, eachStylesheet(doc, url, rules, f)]; case 6: _a.sent(); return [3 /*break*/, 8]; case 7: doc.head.removeChild(s); return [7 /*endfinally*/]; case 8: return [2 /*return*/]; } }); }); } // This calls a function for each CSSRule inside of a CSSStyleSheet. // If the CSSStyleSheet has any @import, then it will recursively call the function for those CSSRules too. // TODO this should be moved into utils or something function eachStylesheet(doc, topUrl, rules, f) { return __awaiter(this, void 0, void 0, function () { var promises, length, i, rule, url; return __generator(this, function (_a) { switch (_a.label) { case 0: promises = []; length = rules.length; for (i = 0; i < length; i++) { rule = rules[i]; if (rule.type === CSSRule.IMPORT_RULE) { url = rule.href; if (url) { url = $utils.joinUrl(topUrl, url); promises.push(loadStylesheet(doc, url, f)); } } else { f(topUrl, rule); } } if (!promises.length) return [3 /*break*/, 2]; return [4 /*yield*/, Promise.all(promises)]; case 1: _a.sent(); _a.label = 2; case 2: return [2 /*return*/]; } }); }); } // This calls a function for each CSSRule for all of the stylesheets in the page. // If the CSSStyleSheet has any @import, then it will recursively call the function for those CSSRules too. // TODO this should be moved into utils or something function eachStylesheets(f) { return __awaiter(this, void 0, void 0, function () { var iframe, doc_1; return __generator(this, function (_a) { switch (_a.label) { case 0: iframe = document.createElement("iframe"); // This causes it to use the same origin policy as the parent page iframe.src = "about:blank"; // This tries to make it more accessible for screen readers iframe.setAttribute("title", ""); document.head.appendChild(iframe); _a.label = 1; case 1: _a.trys.push([1, , 3, 4]); doc_1 = iframe.contentDocument; // TODO use $dom.getRoot instead of document ? return [4 /*yield*/, Promise.all($array.map(document.styleSheets, function (sheet) { var url = sheet.href; if (url == null) { return eachStylesheet(doc_1, location.href, sheet.cssRules, f); } else { url = $utils.joinUrl(location.href, url); return loadStylesheet(doc_1, url, f); } }))]; case 2: // TODO use $dom.getRoot instead of document ? _a.sent(); return [3 /*break*/, 4]; case 3: document.head.removeChild(iframe); return [7 /*endfinally*/]; case 4: return [2 /*return*/]; } }); }); } /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * [[Export]] allows downloading of current snapshot of the chart as an * image, PDF, or its data in various formats. * * The export functionality is enabled by default in charts and is accessible * via API or optional export menu. * * To enable menu, simply access export's `menu` property. E.g.: * * ```TypeScript * chart.exporting.menu = new am4core.ExportMenu(); * ``` * ```JavaScript * chart.exporting.menu = new am4core.ExportMenu(); * ``` * ```JSON * { * // ... * "exporting": { * "menu": {} * } * } * ``` * * To export via API, use `export()` method: * * ```TypeScript * chart.exporting.export(type, [options]); * ``` * ```JavaScript * chart.exporting.export(type, [options]); * ``` * * E.g.: * * ```TypeScript * chart.exporting.export("png"); * ``` * ```JavaScript * chart.exporting.export("png"); * ``` * * @todo Better loading indicator? * @todo Implement multiplier option * @todo Handling of hanged exports * @important */ var Export = /** @class */ (function (_super) { __extends(Export, _super); /** * Constructor */ function Export(container) { var _this = _super.call(this) || this; /** * Adapter. */ _this.adapter = new Adapter(_this); /** * Holds options for each format. * * @ignore Exclude from docs */ _this._formatOptions = new Dictionary(); /** * Extra [[Sprite]] elements to include in exports. */ _this._extraSprites = []; /** * A list of [[Sprite]] elements that need to be valid before export * commences. */ _this._validateSprites = []; /** * Holds an array of data field names. If set, exported data fields will try * to maintain this order. * * If not set (default), the export will try to maintain the same order as * in source data, or as in `dataFields` (if set). * * @since 4.9.7 */ _this.dataFieldsOrder = []; /** * Indicates whether data fields were generated dynamically (`true`) or * if they were pre-set by the user (`false`). */ _this._dynamicDataFields = true; /** * Holds a list of objects that were temporarily removed from the DOM while * exporting. Those most probably are tainted images, or foreign objects that * would otherwise prevent SVG to be converted to canvas. * * @ignore Exclude from docs */ _this._removedObjects = new List(); /** * Holds references to the objects that were temporarily hidden when export * started, so that we can reveal them back when export ends. */ _this._hiddenObjects = []; /** * Indicates if non-exportable objects are now hidden; */ _this._objectsAlreadyHidden = false; /** * Exported files will be prefixed with whatever it is set here. * * @ignore Exclude from docs */ _this._filePrefix = "amCharts"; /** * If you are using web fonts (such as Google Fonts), your chart might be * using them as well. * * Normally, exporting to image will require to download these fonts so the * are carried over to exported image. * * This setting can be used to disable or enable this functionality. * * @default true */ _this.useWebFonts = true; /** * Many modern displays have use more actual pixels per displayed pixel. This * results in sharper images on screen. Unfortunately, when exported to a * bitmap image of the sam width/height size it will lose those extra pixels, * resulting in somewhat blurry image. * * This is why we are going to export images larger than they are, so that we * don't lose any details. * * If you'd rather export images without change in size, set this to `false`. * * @default true */ _this.useRetina = true; /** * By default Export will try to use built-in method for transforming chart * into an image for download, then fallback to external library (canvg) for * conversion if failed. * * Setting this to `false` will force use of external library for all export * operations. * * It might be useful to turn off simplified export if you are using strict * content security policies, that disallow images with blobs as their * source. * * @default true * @since 4.2.5 */ _this.useSimplifiedExport = true; /** * If export operation takes longer than milliseconds in this second, we will * show a modal saying export operation took longer than expected. */ _this.timeoutDelay = 2000; _this._exportRunning = false; /** * Indicator used by [[Component]]. * * @ignore */ _this._prevHasData = false; _this._container = container; _this.className = "Export"; // Set default options _this._formatOptions.setKey("png", {}); _this._formatOptions.setKey("jpg", { quality: 0.8 }); _this._formatOptions.setKey("gif", {}); _this._formatOptions.setKey("svg", {}); _this._formatOptions.setKey("pdf", { fontSize: 14, imageFormat: "png", align: "left", addURL: true, addColumnNames: true }); _this._formatOptions.setKey("json", { indent: 2, useLocale: true }); _this._formatOptions.setKey("csv", { addColumnNames: true, emptyAs: "", addBOM: true }); _this._formatOptions.setKey("xlsx", { addColumnNames: true, useLocale: true, emptyAs: "" }); _this._formatOptions.setKey("html", { addColumnNames: true, emptyAs: "" }); _this._formatOptions.setKey("pdfdata", { fontSize: 14, imageFormat: "png", addURL: true, addColumnNames: true, emptyAs: "" }); _this._formatOptions.setKey("print", { delay: 500, printMethod: "iframe" }); // Add options adapter _this.adapter.add("options", function (arg) { var formatOptions = _this._formatOptions.getKey(arg.type); if (arg.options) { arg.options = $object.merge(formatOptions, arg.options); } else { arg.options = formatOptions; } return arg; }); _this.applyTheme(); _this.dispatchImmediately("inited"); return _this; } Object.defineProperty(Export.prototype, "menu", { /** * @return ExportMenu instance */ get: function () { return this._menu; }, /** * An instance of [[ExportMenu]]. * * To add an export menu to a chart, set this to a new instance of * [[ExportMenu]]. * * ```TypeScript * chart.exporting.menu = new am4core.ExportMenu(); * ``` * ```JavaScript * chart.exporting.menu = new am4core.ExportMenu(); * ``` * ```JSON * { * // ... * "exporting": { * "menu": {} * } * } * ``` * * @param menu ExportMenu instance */ set: function (menu) { var _this = this; if (this._menu) { this.removeDispose(this._menu); } this._menu = menu; // Set container and language this._menu.container = this.container; this._menu.language = this._language; // Add adapter to check for browser support this._menu.adapter.add("branch", function (arg) { arg.branch.unsupported = !_this.typeSupported(arg.branch.type); return arg; }); // Add click events this._menu.events.on("hit", function (ev) { _this.export(ev.branch.type, ev.branch.options); _this.menu.close(); }); this._menu.events.on("enter", function (ev) { _this.export(ev.branch.type, ev.branch.options); _this.menu.close(); }); this._menu.events.on("over", function (ev) { _this._disablePointers(); }); this._menu.events.on("out", function (ev) { setTimeout(function () { _this._releasePointers(); }, 10); }); // Dispatch event this.dispatchImmediately("menucreated"); // Prefix with Sprite's class name this._menu.adapter.add("classPrefix", function (obj) { obj.classPrefix = options.classNamePrefix + obj.classPrefix; return obj; }); // Add menu to disposers so that it's destroyed when Export is disposed this._disposers.push(this._menu); }, enumerable: true, configurable: true }); /** * Checks if this specific menu item type is supported by current system. * * @param type Menu item type * @return `false` if not supported */ Export.prototype.typeSupported = function (type) { var supported = true; var options = this.getFormatOptions(type); if ($type.hasValue(options) && options.disabled) { supported = false; } else if (type === "pdf") { //supported = this.downloadSupport(); } else if (type === "xlsx") { //supported = (this.downloadSupport() && this._hasData()) ? true : false; supported = this._hasData() ? true : false; } else if (type == "print" && !window.print) { supported = false; } else if (["json", "csv", "html", "pdfdata"].indexOf(type) !== -1 && !this._hasData()) { supported = false; } return this.adapter.apply("supported", { supported: supported, type: type }).supported; }; /** * Checks if data is available. * * @return Has data? */ Export.prototype._hasData = function () { return this.data && this.data.length; }; /** * Get function to handle export for particular format. * * @ignore Exclude from docs */ Export.prototype._getFunction = function (type) { switch (type) { case "png": case "gif": case "jpg": return this.getImage; case "svg": return this.getSVG; case "pdf": case "pdfdata": return this.getPDF; case "xlsx": return this.getExcel; case "csv": return this.getCSV; case "json": return this.getJSON; case "html": return this.getHTML; case "print": return this.getPrint; default: return this.unsupported; } }; /** * Initiates export procedure. * * @param type Export type * @param options Options * @return `true` if export was successful * @async */ Export.prototype.export = function (type, options) { return __awaiter(this, void 0, void 0, function () { var event_1, func, data, event_2, event_3; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: // Check if it's a custom item, and do nothing or execute custom callback if (type == "custom") { this.handleCustom(options); return [2 /*return*/, true]; } // Set export running flag this._exportRunning = true; // Dispatch event if (this.events.isEnabled("exportstarted")) { event_1 = { "type": "exportstarted", "target": this, "format": type, "options": options }; this.events.dispatchImmediately("exportstarted", event_1); } // Schedule a preloader this.showPreloader(); // Schedule a timeout if (this.timeoutDelay) { this.hideTimeout(); this._timeoutTimeout = this.setTimeout(function () { // Dispatch event if (_this.events.isEnabled("exporttimedout")) { var event_4 = { "type": "exporttimedout", "target": _this, "format": type, "options": options }; _this.events.dispatchImmediately("exporttimedout", event_4); } // Show modal _this.showTimeout(); }, this.timeoutDelay); } // Hide items that should not be exported this.hideNonExportableSprites(); func = this._getFunction(type); // Give chance for plugins to override both function and options options = this.adapter.apply("options", { options: options, type: type }).options; func = this.adapter.apply("exportFunction", { func: func, type: type, options: options }).func; return [4 /*yield*/, func.call(this, type, options)]; case 1: data = _a.sent(); // Release pointers this._exportRunning = false; this._releasePointers(); // Restore temporarily hidden elements this.restoreNonExportableSprites(); if (data) { // Dispatch event if (this.events.isEnabled("exportfinished")) { event_2 = { "type": "exportfinished", "target": this, "format": type, "options": options }; this.events.dispatchImmediately("exportfinished", event_2); } // Hide preloader and timeout modals this.hidePreloader(); this.hideTimeout(); if (this.menu) { this.menu.close(); } // Download or print if (type === "print") { return [2 /*return*/, this.print(data, options, this.adapter.apply("title", { title: this.title, options: options }).title)]; } else { if (type == "pdfdata") { return [2 /*return*/, this.download(data, this.filePrefix + ".pdf")]; } return [2 /*return*/, this.download(data, this.filePrefix + "." + type, (options && options.addBOM))]; } } else { // Throw exception? // @todo // Dispatch event if (this.events.isEnabled("error")) { event_3 = { "type": "error", "target": this, "format": type, "options": options }; this.events.dispatchImmediately("error", event_3); } return [2 /*return*/, false]; } return [2 /*return*/]; } }); }); }; /** * A function that should handle unsupported export types. * * @ignore Exclude from docs * @param type Export type * @param options Options * @return Promise * @async */ Export.prototype.unsupported = function (type, options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { // TODO should this return `undefined`? return [2 /*return*/, ""]; }); }); }; /** * Handles click on a "custom" menu item. * * Basically, if it has "callback" enabled, it will be called. Nothing else. * * @ignore Exclude from docs * @param options Options */ Export.prototype.handleCustom = function (options) { if ($type.hasValue(options) && $type.hasValue(options.callback)) { options.callback.call(options.callbackTarget || this, options); } }; /** * Requests a Print of the chart. * * @param type Export type * @param options Options * @return Promise * @async */ Export.prototype.getPrint = function (type, options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.getImage("png", options)]; }); }); }; /** * A function that returns data: URI encoded @font-family, so that way it can be embedded into SVG. * * @ignore Exclude from docs * @return String which can be embedded directly into a <style> element. * @async */ Export.prototype.getFontFamilies = function () { return __awaiter(this, void 0, void 0, function () { var DOMURL, blobs, promises, a; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: DOMURL = this.getDOMURL(); blobs = []; promises = []; return [4 /*yield*/, eachStylesheets(function (topUrl, rule) { if (rule.type === CSSRule.FONT_FACE_RULE) { var cssText_1 = rule.cssText; // TODO this is necessary because Edge doesn't let you access the src using getPropertyValue var src = fontFamilySrcRegexp.exec(cssText_1); if (src !== null) { // TODO make this faster (don't create Promises for non-url stuff) var urls = src[1].split(/ *, */).map(function (url) { return __awaiter(_this, void 0, void 0, function () { var a, after, fullUrl, response, url_1, e_3; return __generator(this, function (_a) { switch (_a.label) { case 0: a = /^url\(["']?([^"'\)]+)["']?\)([^,]*)$/.exec(url); if (!(a === null)) return [3 /*break*/, 1]; return [2 /*return*/, url]; case 1: after = a[2]; fullUrl = $utils.joinUrl(topUrl, a[1]); if (this.webFontFilter && !fullUrl.match(this.webFontFilter)) { return [2 /*return*/, null]; } _a.label = 2; case 2: _a.trys.push([2, 7, , 8]); return [4 /*yield*/, $net.load(fullUrl, undefined, { responseType: "blob" })]; case 3: response = _a.sent(); if (!supportsBlobUri()) return [3 /*break*/, 4]; url_1 = DOMURL.createObjectURL(response.blob); blobs.push(url_1); return [3 /*break*/, 6]; case 4: return [4 /*yield*/, blobToDataUri(response.blob)]; case 5: url_1 = _a.sent(); _a.label = 6; case 6: // TODO should it should escape the URI ? return [2 /*return*/, "url(\"" + url_1 + "\")" + after]; case 7: e_3 = _a.sent(); console.error("Failed to load font", fullUrl, e_3); return [2 /*return*/, null]; case 8: return [2 /*return*/]; } }); }); }); promises.push(Promise.all(urls).then(function (a) { a = a.filter(function (x) { return x != null; }); if (a.length === 0) { return ""; } else { return cssText_1.replace(fontFamilySrcRegexp, "src: " + a.join(", ") + ";"); } })); } } })]; case 1: _a.sent(); return [4 /*yield*/, Promise.all(promises)]; case 2: a = _a.sent(); return [2 /*return*/, { blobs: blobs, cssText: a.filter(function (x) { return !!x; }).join("\n") }]; } }); }); }; /** * Produces image output from the element. * * Converts to a `Canvas` first, then produces an image to download. * * This is an asynchronous function. Rather than returning a result, it * returns a Promise. * * You can use `await` notion from other async functions, or `then()` * anywhere else. * * ```TypeScript * let img; * * // Async * img = await chart.exporting.getImage( "png" ); * * // Sync * chart.exporting.getImage( "png" ).then( ( data ) => { * img = data; * } ); * ``` * ```JavaScript * var img; * chart.exporting.getImage( "png" ).then( ( data ) => { * img = data; * } ); * ``` * * @param type Image format * @param options Options * @param includeExtras Should extra sprites be included if set? * @return Promise */ Export.prototype.getImage = function (type, options, includeExtras) { return __awaiter(this, void 0, void 0, function () { var prehidden, canvas, newCanvas, uri, e_4, data, data; return __generator(this, function (_a) { switch (_a.label) { case 0: prehidden = this._objectsAlreadyHidden; if (!prehidden) { this.hideNonExportableSprites(); } if (!$type.hasValue(options)) { options = this.getFormatOptions(type); } // Wait for required elements to be ready before proceeding return [4 /*yield*/, this.awaitValidSprites()]; case 1: // Wait for required elements to be ready before proceeding _a.sent(); return [4 /*yield*/, this.simplifiedImageExport()]; case 2: if (!_a.sent()) return [3 /*break*/, 10]; canvas = void 0; _a.label = 3; case 3: _a.trys.push([3, 7, , 9]); return [4 /*yield*/, this.getCanvas(options)]; case 4: canvas = _a.sent(); if (!(includeExtras !== false)) return [3 /*break*/, 6]; return [4 /*yield*/, this.addExtras(canvas, options)]; case 5: newCanvas = _a.sent(); this.disposeCanvas(canvas); canvas = newCanvas; _a.label = 6; case 6: uri = canvas.toDataURL(this.getContentType(type), options.quality); // Get rid of the canvas this.disposeCanvas(canvas); if (!prehidden) { this.restoreNonExportableSprites(); } return [2 /*return*/, uri]; case 7: e_4 = _a.sent(); console.error(e_4.message + "\n" + e_4.stack); $log.warn("Simple export failed, falling back to advanced export"); if (canvas) { this.disposeCanvas(canvas); } return [4 /*yield*/, this.getImageAdvanced(type, options, includeExtras)]; case 8: data = _a.sent(); if (!prehidden) { this.restoreNonExportableSprites(); } return [2 /*return*/, data]; case 9: return [3 /*break*/, 12]; case 10: return [4 /*yield*/, this.getImageAdvanced(type, options, includeExtras)]; case 11: data = _a.sent(); if (!prehidden) { this.restoreNonExportableSprites(); } return [2 /*return*/, data]; case 12: return [2 /*return*/]; } }); }); }; /** * Adds extra elements to the canvas. * * @param canvas Original canvas * @param options Options */ Export.prototype.addExtras = function (canvas, options, advanced) { return __awaiter(this, void 0, void 0, function () { var middleLeft_1, middleTop_1, middleWidth_1, middleHeight_1, extraRight_1, extraBottom_1, extras, newCanvas, ctx_1, background, left_1, top_1, right_1, bottom_1; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.extraSprites.length) return [3 /*break*/, 2]; middleLeft_1 = 0; middleTop_1 = 0; middleWidth_1 = canvas.width; middleHeight_1 = canvas.height; extraRight_1 = 0; extraBottom_1 = 0; return [4 /*yield*/, Promise.all($array.map(this.extraSprites, function (extraSprite) { return __awaiter(_this, void 0, void 0, function () { var extra, extraCanvas, extraWidth, extraHeight; return __generator(this, function (_a) { switch (_a.label) { case 0: if (extraSprite instanceof Sprite) { extra = { sprite: extraSprite, position: "bottom" }; } else { extra = extraSprite; } // Set defaults extra.position = extra.position || "bottom"; extra.marginTop = extra.marginTop || 0; extra.marginRight = extra.marginRight || 0; extra.marginBottom = extra.marginBottom || 0; extra.marginLeft = extra.marginLeft || 0; if (!advanced) return [3 /*break*/, 2]; return [4 /*yield*/, extra.sprite.exporting.getCanvasAdvanced(options)]; case 1: extraCanvas = _a.sent(); return [3 /*break*/, 4]; case 2: return [4 /*yield*/, extra.sprite.exporting.getCanvas(options)]; case 3: extraCanvas = _a.sent(); _a.label = 4; case 4: extraWidth = extraCanvas.width + extra.marginLeft + extra.marginRight; extraHeight = extraCanvas.height + extra.marginTop + extra.marginBottom; if (extra.position == "top") { middleWidth_1 = extra.crop ? middleHeight_1 : $math.max(middleWidth_1, extraWidth); middleTop_1 += extraHeight; } else if (extra.position == "right") { middleHeight_1 = extra.crop ? middleHeight_1 : $math.max(middleHeight_1, extraHeight); extraRight_1 += extraWidth; } else if (extra.position == "left") { middleHeight_1 = extra.crop ? middleHeight_1 : $math.max(middleHeight_1, extraHeight); middleLeft_1 += extraWidth; } else if (extra.position === "bottom") { middleWidth_1 = extra.crop ? middleHeight_1 : $math.max(middleWidth_1, extraWidth); extraBottom_1 += extraHeight; } return [2 /*return*/, { canvas: extraCanvas, position: extra.position, left: extra.marginLeft, top: extra.marginTop, width: extraWidth, height: extraHeight }]; } }); }); }))]; case 1: extras = _a.sent(); newCanvas = this.getDisposableCanvas(); newCanvas.width = middleLeft_1 + middleWidth_1 + extraRight_1; newCanvas.height = middleTop_1 + middleHeight_1 + extraBottom_1; ctx_1 = newCanvas.getContext("2d"); background = this.backgroundColor || this.findBackgroundColor(this.sprite.dom); if (background) { ctx_1.fillStyle = background.toString(); ctx_1.fillRect(0, 0, newCanvas.width, newCanvas.height); } left_1 = middleLeft_1; top_1 = middleTop_1; right_1 = left_1 + middleWidth_1; bottom_1 = top_1 + middleHeight_1; // Radiates outwards from center $array.each(extras, function (extra) { if (extra.position == "top") { top_1 -= extra.height; ctx_1.drawImage(extra.canvas, middleLeft_1 + extra.left, top_1 + extra.top); } else if (extra.position == "right") { ctx_1.drawImage(extra.canvas, right_1 + extra.left, middleTop_1 + extra.top); right_1 += extra.width; } else if (extra.position == "left") { left_1 -= extra.width; ctx_1.drawImage(extra.canvas, left_1 + extra.left, middleTop_1 + extra.top); } else if (extra.position === "bottom") { ctx_1.drawImage(extra.canvas, middleLeft_1 + extra.left, bottom_1 + extra.top); bottom_1 += extra.height; } _this.disposeCanvas(extra.canvas); }); ctx_1.drawImage(canvas, middleLeft_1, middleTop_1); return [2 /*return*/, newCanvas]; case 2: return [2 /*return*/, canvas]; } }); }); }; /** * Returns canvas representation of the [[Sprite]]. * * @param options Options * @return Canvas */ Export.prototype.getCanvas = function (options) { return __awaiter(this, void 0, void 0, function () { var background, DOMURL, url, blobs, canvas, width, height, font, fontSize, scale, pixelRatio, ctx, promises, a, data, svg, img; return __generator(this, function (_a) { switch (_a.label) { case 0: // Options are set? if (!$type.hasValue(options)) { options = {}; } background = this.backgroundColor || this.findBackgroundColor(this.sprite.dom); DOMURL = this.getDOMURL(); url = null; blobs = null; _a.label = 1; case 1: _a.trys.push([1, , 4, 5]); width = this.sprite.pixelWidth; height = this.sprite.pixelHeight; font = $dom.findFont(this.sprite.dom); fontSize = $dom.findFontSize(this.sprite.dom);