/** * @license Highcharts JS v9.1.1 (2021-06-04) * * Client side exporting module * * (c) 2015-2021 Torstein Honsi / Oystein Moseng * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/modules/offline-exporting', ['highcharts', 'highcharts/modules/exporting'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); } } _registerModule(_modules, 'Extensions/DownloadURL.js', [_modules['Core/Globals.js']], function (Highcharts) { /* * * * (c) 2015-2021 Oystein Moseng * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Mixin for downloading content in the browser * * */ var isSafari = Highcharts.isSafari; var win = Highcharts.win, doc = win.document, domurl = win.URL || win.webkitURL || win; /** * Convert base64 dataURL to Blob if supported, otherwise returns undefined. * @private * @function Highcharts.dataURLtoBlob * @param {string} dataURL * URL to convert * @return {string|undefined} * Blob */ var dataURLtoBlob = Highcharts.dataURLtoBlob = function (dataURL) { var parts = dataURL .replace(/filename=.*;/, '') .match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/); if (parts && parts.length > 3 && win.atob && win.ArrayBuffer && win.Uint8Array && win.Blob && domurl.createObjectURL) { // Try to convert data URL to Blob var binStr = win.atob(parts[3]), buf = new win.ArrayBuffer(binStr.length), binary = new win.Uint8Array(buf); for (var i = 0; i < binary.length; ++i) { binary[i] = binStr.charCodeAt(i); } var blob = new win.Blob([binary], { 'type': parts[1] }); return domurl.createObjectURL(blob); } }; /** * Download a data URL in the browser. Can also take a blob as first param. * * @private * @function Highcharts.downloadURL * @param {string|global.URL} dataURL * The dataURL/Blob to download * @param {string} filename * The name of the resulting file (w/extension) * @return {void} */ var downloadURL = Highcharts.downloadURL = function (dataURL, filename) { var nav = win.navigator, a = doc.createElement('a'); // IE specific blob implementation // Don't use for normal dataURLs if (typeof dataURL !== 'string' && !(dataURL instanceof String) && nav.msSaveOrOpenBlob) { nav.msSaveOrOpenBlob(dataURL, filename); return; } dataURL = "" + dataURL; // Some browsers have limitations for data URL lengths. Try to convert to // Blob or fall back. Edge always needs that blob. var isOldEdgeBrowser = /Edge\/\d+/.test(nav.userAgent); // Safari on iOS needs Blob in order to download PDF var safariBlob = (isSafari && typeof dataURL === 'string' && dataURL.indexOf('data:application/pdf') === 0); if (safariBlob || isOldEdgeBrowser || dataURL.length > 2000000) { dataURL = dataURLtoBlob(dataURL) || ''; if (!dataURL) { throw new Error('Failed to convert to blob'); } } // Try HTML5 download attr if supported if (typeof a.download !== 'undefined') { a.href = dataURL; a.download = filename; // HTML5 download attribute doc.body.appendChild(a); a.click(); doc.body.removeChild(a); } else { // No download attr, just opening data URI try { var windowRef = win.open(dataURL, 'chart'); if (typeof windowRef === 'undefined' || windowRef === null) { throw new Error('Failed to open window'); } } catch (e) { // window.open failed, trying location.href win.location.href = dataURL; } } }; var exports = { dataURLtoBlob: dataURLtoBlob, downloadURL: downloadURL }; return exports; }); _registerModule(_modules, 'Extensions/OfflineExporting.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js'], _modules['Extensions/DownloadURL.js']], function (Chart, H, D, SVGRenderer, U, DownloadURL) { /* * * * Client side exporting module * * (c) 2015 Torstein Honsi / Oystein Moseng * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var win = H.win, doc = H.doc; var getOptions = D.getOptions; var addEvent = U.addEvent, error = U.error, extend = U.extend, fireEvent = U.fireEvent, merge = U.merge; var downloadURL = DownloadURL.downloadURL; var domurl = win.URL || win.webkitURL || win, // Milliseconds to defer image load event handlers to offset IE bug loadEventDeferDelay = H.isMS ? 150 : 0; // Dummy object so we can reuse our canvas-tools.js without errors H.CanVGRenderer = {}; /* eslint-disable valid-jsdoc */ /** * Downloads a script and executes a callback when done. * * @private * @function getScript * @param {string} scriptLocation * @param {Function} callback * @return {void} */ function getScript(scriptLocation, callback) { var head = doc.getElementsByTagName('head')[0], script = doc.createElement('script'); script.type = 'text/javascript'; script.src = scriptLocation; script.onload = callback; script.onerror = function () { error('Error loading script ' + scriptLocation); }; head.appendChild(script); } /** * Get blob URL from SVG code. Falls back to normal data URI. * * @private * @function Highcharts.svgToDataURL * @param {string} svg * @return {string} */ function svgToDataUrl(svg) { // Webkit and not chrome var userAgent = win.navigator.userAgent; var webKit = (userAgent.indexOf('WebKit') > -1 && userAgent.indexOf('Chrome') < 0); try { // Safari requires data URI since it doesn't allow navigation to blob // URLs. Firefox has an issue with Blobs and internal references, // leading to gradients not working using Blobs (#4550). // foreignObjects also dont work well in Blobs in Chrome (#14780). if (!webKit && !H.isFirefox && svg.indexOf(' width ? 'p' : 'l', // setting orientation to portrait if height exceeds width 'pt', [width, height]); // Workaround for #7090, hidden elements were drawn anyway. It comes // down to https://github.com/yWorks/svg2pdf.js/issues/28. Check this // later. [].forEach.call(svgElement.querySelectorAll('*[visibility="hidden"]'), function (node) { node.parentNode.removeChild(node); }); // Workaround for #13948, multiple stops in linear gradient set to 0 // causing error in Acrobat var gradients = svgElement.querySelectorAll('linearGradient'); for (var index = 0; index < gradients.length; index++) { var gradient = gradients[index]; var stops = gradient.querySelectorAll('stop'); var i = 0; while (i < stops.length && stops[i].getAttribute('offset') === '0' && stops[i + 1].getAttribute('offset') === '0') { stops[i].remove(); i++; } } // Workaround for #15135, zero width spaces, which Highcharts uses to // break lines, are not correctly rendered in PDF. Replace it with a // regular space and offset by some pixels to compensate. [].forEach.call(svgElement.querySelectorAll('tspan'), function (tspan) { if (tspan.textContent === '\u200B') { tspan.textContent = ' '; tspan.setAttribute('dx', -5); } }); win.svg2pdf(svgElement, pdf, { removeInvalid: true }); return pdf.output('datauristring'); } /** * @private * @return {void} */ function downloadPDF() { dummySVGContainer.innerHTML = svg; var textElements = dummySVGContainer.getElementsByTagName('text'), titleElements, svgData, // Copy style property to element from parents if it's not there. // Searches up hierarchy until it finds prop, or hits the chart // container. setStylePropertyFromParents = function (el, propName) { var curParent = el; while (curParent && curParent !== dummySVGContainer) { if (curParent.style[propName]) { el.style[propName] = curParent.style[propName]; break; } curParent = curParent.parentNode; } }; // Workaround for the text styling. Making sure it does pick up settings // for parent elements. [].forEach.call(textElements, function (el) { // Workaround for the text styling. making sure it does pick up the // root element ['font-family', 'font-size'].forEach(function (property) { setStylePropertyFromParents(el, property); }); el.style['font-family'] = (el.style['font-family'] && el.style['font-family'].split(' ').splice(-1)); // Workaround for plotband with width, removing title from text // nodes titleElements = el.getElementsByTagName('title'); [].forEach.call(titleElements, function (titleElement) { el.removeChild(titleElement); }); }); svgData = svgToPdf(dummySVGContainer.firstChild, 0); try { downloadURL(svgData, filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(e); } } /* eslint-enable valid-jsdoc */ // Initiate download depending on file type if (imageType === 'image/svg+xml') { // SVG download. In this case, we want to use Microsoft specific Blob if // available try { if (typeof win.navigator.msSaveOrOpenBlob !== 'undefined') { blob = new MSBlobBuilder(); blob.append(svg); svgurl = blob.getBlob('image/svg+xml'); } else { svgurl = svgToDataUrl(svg); } downloadURL(svgurl, filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(e); } } else if (imageType === 'application/pdf') { if (win.jsPDF && win.svg2pdf) { downloadPDF(); } else { // Must load pdf libraries first. // Don't destroy the object URL // yet since we are doing things asynchronously. A cleaner solution // would be nice, but this will do for now. objectURLRevoke = true; getScript(libURL + 'jspdf.js', function () { getScript(libURL + 'svg2pdf.js', function () { downloadPDF(); }); }); } } else { // PNG/JPEG download - create bitmap from SVG svgurl = svgToDataUrl(svg); finallyHandler = function () { try { domurl.revokeObjectURL(svgurl); } catch (e) { // Ignore } }; // First, try to get PNG by rendering on canvas imageToDataUrl(svgurl, imageType, {}, scale, function (imageURL) { // Success try { downloadURL(imageURL, filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(e); } }, function () { // Failed due to tainted canvas // Create new and untainted canvas var canvas = doc.createElement('canvas'), ctx = canvas.getContext('2d'), imageWidth = svg.match(/^]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, imageHeight = svg.match(/^]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale, downloadWithCanVG = function () { var v = win.canvg.Canvg.fromString(ctx, svg); v.start(); try { downloadURL(win.navigator.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL(imageType), filename); if (successCallback) { successCallback(); } } catch (e) { failCallback(e); } finally { finallyHandler(); } }; canvas.width = imageWidth; canvas.height = imageHeight; if (win.canvg) { // Use preloaded canvg downloadWithCanVG(); } else { // Must load canVG first. // Don't destroy the object URL // yet since we are doing things asynchronously. A cleaner // solution would be nice, but this will do for now. objectURLRevoke = true; getScript(libURL + 'canvg.js', function () { downloadWithCanVG(); }); } }, // No canvas support failCallback, // Failed to load image failCallback, // Finally function () { if (objectURLRevoke) { finallyHandler(); } }); } } /* eslint-disable valid-jsdoc */ /** * Get SVG of chart prepared for client side export. This converts embedded * images in the SVG to data URIs. It requires the regular exporting module. The * options and chartOptions arguments are passed to the getSVGForExport * function. * * @private * @function Highcharts.Chart#getSVGForLocalExport * @param {Highcharts.ExportingOptions} options * @param {Highcharts.Options} chartOptions * @param {Function} failCallback * @param {Function} successCallback * @return {void} */ Chart.prototype.getSVGForLocalExport = function (options, chartOptions, failCallback, successCallback) { var chart = this, images, imagesEmbedded = 0, chartCopyContainer, chartCopyOptions, el, i, l, href, // After grabbing the SVG of the chart's copy container we need to do // sanitation on the SVG sanitize = function (svg) { return chart.sanitizeSVG(svg, chartCopyOptions); }, // When done with last image we have our SVG checkDone = function () { if (imagesEmbedded === images.length) { successCallback(sanitize(chartCopyContainer.innerHTML)); } }, // Success handler, we converted image to base64! embeddedSuccess = function (imageURL, imageType, callbackArgs) { ++imagesEmbedded; // Change image href in chart copy callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL); checkDone(); }; // Hook into getSVG to get a copy of the chart copy's container (#8273) chart.unbindGetSVG = addEvent(chart, 'getSVG', function (e) { chartCopyOptions = e.chartCopy.options; chartCopyContainer = e.chartCopy.container.cloneNode(true); }); // Trigger hook to get chart copy chart.getSVGForExport(options, chartOptions); images = chartCopyContainer.getElementsByTagName('image'); try { // If there are no images to embed, the SVG is okay now. if (!images.length) { // Use SVG of chart copy successCallback(sanitize(chartCopyContainer.innerHTML)); return; } // Go through the images we want to embed for (i = 0, l = images.length; i < l; ++i) { el = images[i]; href = el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'); if (href) { imageToDataUrl(href, 'image/png', { imageElement: el }, options.scale, embeddedSuccess, // Tainted canvas failCallback, // No canvas support failCallback, // Failed to load source failCallback); // Hidden, boosted series have blank href (#10243) } else { ++imagesEmbedded; el.parentNode.removeChild(el); checkDone(); } } } catch (e) { failCallback(e); } // Clean up chart.unbindGetSVG(); }; /* eslint-enable valid-jsdoc */ /** * Exporting and offline-exporting modules required. Export a chart to an image * locally in the user's browser. * * @function Highcharts.Chart#exportChartLocal * * @param {Highcharts.ExportingOptions} [exportingOptions] * Exporting options, the same as in * {@link Highcharts.Chart#exportChart}. * * @param {Highcharts.Options} [chartOptions] * Additional chart options for the exported chart. For example a * different background color can be added here, or `dataLabels` * for export only. * * @return {void} * * @requires modules/exporting */ Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) { var chart = this, options = merge(chart.options.exporting, exportingOptions), fallbackToExportServer = function (err) { if (options.fallbackToExportServer === false) { if (options.error) { options.error(options, err); } else { error(28, true); // Fallback disabled } } else { chart.exportChart(options); } }, svgSuccess = function (svg) { // If SVG contains foreignObjects PDF fails in all browsers and all // exports except SVG will fail in IE, as both CanVG and svg2pdf // choke on this. Gracefully fall back. if (svg.indexOf(' -1 && options.type !== 'image/svg+xml' && (H.isMS || options.type === 'application/pdf')) { fallbackToExportServer('Image type not supported' + 'for charts with embedded HTML'); } else { downloadSVGLocal(svg, extend({ filename: chart.getFilename() }, options), fallbackToExportServer, function () { return fireEvent(chart, 'exportChartLocalSuccess'); }); } }, // Return true if the SVG contains images with external data. With the // boost module there are `image` elements with encoded PNGs, these are // supported by svg2pdf and should pass (#10243). hasExternalImages = function () { return [].some.call(chart.container.getElementsByTagName('image'), function (image) { var href = image.getAttribute('href'); return href !== '' && href.indexOf('data:') !== 0; }); }; // If we are on IE and in styled mode, add a whitelist to the renderer for // inline styles that we want to pass through. There are so many styles by // default in IE that we don't want to blacklist them all. if (H.isMS && chart.styledMode) { SVGRenderer.prototype.inlineWhitelist = [ /^blockSize/, /^border/, /^caretColor/, /^color/, /^columnRule/, /^columnRuleColor/, /^cssFloat/, /^cursor/, /^fill$/, /^fillOpacity/, /^font/, /^inlineSize/, /^length/, /^lineHeight/, /^opacity/, /^outline/, /^parentRule/, /^rx$/, /^ry$/, /^stroke/, /^textAlign/, /^textAnchor/, /^textDecoration/, /^transform/, /^vectorEffect/, /^visibility/, /^x$/, /^y$/ ]; } // Always fall back on: // - MS browsers: Embedded images JPEG/PNG, or any PDF // - Embedded images and PDF if ((H.isMS && (options.type === 'application/pdf' || chart.container.getElementsByTagName('image').length && options.type !== 'image/svg+xml')) || (options.type === 'application/pdf' && hasExternalImages())) { fallbackToExportServer('Image type not supported for this chart/browser.'); return; } chart.getSVGForLocalExport(options, chartOptions || {}, fallbackToExportServer, svgSuccess); }; // Extend the default options to use the local exporter logic merge(true, getOptions().exporting, { libURL: 'https://code.highcharts.com/9.1.1/lib/', // When offline-exporting is loaded, redefine the menu item definitions // related to download. menuItemDefinitions: { downloadPNG: { textKey: 'downloadPNG', onclick: function () { this.exportChartLocal(); } }, downloadJPEG: { textKey: 'downloadJPEG', onclick: function () { this.exportChartLocal({ type: 'image/jpeg' }); } }, downloadSVG: { textKey: 'downloadSVG', onclick: function () { this.exportChartLocal({ type: 'image/svg+xml' }); } }, downloadPDF: { textKey: 'downloadPDF', onclick: function () { this.exportChartLocal({ type: 'application/pdf' }); } } } }); // Compatibility H.downloadSVGLocal = downloadSVGLocal; }); _registerModule(_modules, 'masters/modules/offline-exporting.src.js', [], function () { }); }));