@amcharts/amcharts4
Version:
amCharts 4
1,132 lines • 172 kB
JavaScript
/**
* 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].default || a[0];
vfs_fonts = a[1].default || a[1];
global = window;
global.pdfMake = global.pdfMake || {};
global.pdfMake.vfs = vfs_fonts;
pdfmake.vfs = vfs_fonts;
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.s