package.es-modules.Extensions.OfflineExporting.OfflineExporting.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of highcharts Show documentation
Show all versions of highcharts Show documentation
JavaScript charting framework
The newest version!
/* *
*
* Client side exporting module
*
* (c) 2015 Torstein Honsi / Oystein Moseng
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import AST from '../../Core/Renderer/HTML/AST.js';
import Chart from '../../Core/Chart/Chart.js';
import D from '../../Core/Defaults.js';
const { defaultOptions } = D;
import DownloadURL from '../DownloadURL.js';
const { downloadURL } = DownloadURL;
import Exporting from '../Exporting/Exporting.js';
import H from '../../Core/Globals.js';
const { doc, win } = H;
import HU from '../../Core/HttpUtilities.js';
const { ajax } = HU;
import OfflineExportingDefaults from './OfflineExportingDefaults.js';
import U from '../../Core/Utilities.js';
const { addEvent, error, extend, fireEvent, merge } = U;
AST.allowedAttributes.push('data-z-index', 'fill-opacity', 'filter', 'rx', 'ry', 'stroke-dasharray', 'stroke-linejoin', 'stroke-opacity', 'text-anchor', 'transform', 'version', 'viewBox', 'visibility', 'xmlns', 'xmlns:xlink');
AST.allowedTags.push('desc', 'clippath', 'g');
/* *
*
* Composition
*
* */
var OfflineExporting;
(function (OfflineExporting) {
/* *
*
* Declarations
*
* */
/* *
*
* Constants
*
* */
// Dummy object so we can reuse our canvas-tools.js without errors
OfflineExporting.CanVGRenderer = {}, OfflineExporting.domurl = win.URL || win.webkitURL || win,
// Milliseconds to defer image load event handlers to offset IE bug
OfflineExporting.loadEventDeferDelay = H.isMS ? 150 : 0;
/* *
*
* Functions
*
* */
/* eslint-disable valid-jsdoc */
/**
* Extends OfflineExporting with Chart.
* @private
*/
function compose(ChartClass) {
const chartProto = ChartClass.prototype;
if (!chartProto.exportChartLocal) {
chartProto.getSVGForLocalExport = getSVGForLocalExport;
chartProto.exportChartLocal = exportChartLocal;
// Extend the default options to use the local exporter logic
merge(true, defaultOptions.exporting, OfflineExportingDefaults);
}
return ChartClass;
}
OfflineExporting.compose = compose;
/**
* Get data URL to an image of an SVG and call download on it options
* object:
* - **filename:** Name of resulting downloaded file without extension.
* Default is `chart`.
*
* - **type:** File type of resulting download. Default is `image/png`.
*
* - **scale:** Scaling factor of downloaded image compared to source.
* Default is `1`.
*
* - **libURL:** URL pointing to location of dependency scripts to download
* on demand. Default is the exporting.libURL option of the global
* Highcharts options pointing to our server.
*
* @function Highcharts.downloadSVGLocal
*
* @param {string} svg
* The generated SVG
*
* @param {Highcharts.ExportingOptions} options
* The exporting options
*
* @param {Function} failCallback
* The callback function in case of errors
*
* @param {Function} [successCallback]
* The callback function in case of success
*
*/
function downloadSVGLocal(svg, options, failCallback, successCallback) {
const dummySVGContainer = doc.createElement('div'), imageType = options.type || 'image/png', filename = ((options.filename || 'chart') +
'.' +
(imageType === 'image/svg+xml' ?
'svg' : imageType.split('/')[1])), scale = options.scale || 1;
let svgurl, blob, finallyHandler, libURL = (options.libURL || defaultOptions.exporting.libURL), objectURLRevoke = true, pdfFont = options.pdfFont;
// Allow libURL to end with or without fordward slash
libURL = libURL.slice(-1) !== '/' ? libURL + '/' : libURL;
/*
* Detect if we need to load TTF fonts for the PDF, then load them and
* proceed.
*
* @private
*/
const loadPdfFonts = (svgElement, callback) => {
const hasNonASCII = (s) => (
// eslint-disable-next-line no-control-regex
/[^\u0000-\u007F\u200B]+/.test(s));
// Register an event in order to add the font once jsPDF is
// initialized
const addFont = (variant, base64) => {
win.jspdf.jsPDF.API.events.push([
'initialized',
function () {
this.addFileToVFS(variant, base64);
this.addFont(variant, 'HighchartsFont', variant);
if (!this.getFontList().HighchartsFont) {
this.setFont('HighchartsFont');
}
}
]);
};
// If there are no non-ASCII characters in the SVG, do not use
// bother downloading the font files
if (pdfFont && !hasNonASCII(svgElement.textContent || '')) {
pdfFont = void 0;
}
// Add new font if the URL is declared, #6417.
const variants = ['normal', 'italic', 'bold', 'bolditalic'];
// Shift the first element off the variants and add as a font.
// Then asynchronously trigger the next variant until calling the
// callback when the variants are empty.
let normalBase64;
const shiftAndLoadVariant = () => {
const variant = variants.shift();
// All variants shifted and possibly loaded, proceed
if (!variant) {
return callback();
}
const url = pdfFont && pdfFont[variant];
if (url) {
ajax({
url,
responseType: 'blob',
success: (data, xhr) => {
const reader = new FileReader();
reader.onloadend = function () {
if (typeof this.result === 'string') {
const base64 = this.result.split(',')[1];
addFont(variant, base64);
if (variant === 'normal') {
normalBase64 = base64;
}
}
shiftAndLoadVariant();
};
reader.readAsDataURL(xhr.response);
},
error: shiftAndLoadVariant
});
}
else {
// For other variants, fall back to normal text weight/style
if (normalBase64) {
addFont(variant, normalBase64);
}
shiftAndLoadVariant();
}
};
shiftAndLoadVariant();
};
/*
* @private
*/
const downloadPDF = () => {
AST.setElementHTML(dummySVGContainer, svg);
const textElements = dummySVGContainer.getElementsByTagName('text'),
// 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) {
let curParent = el;
while (curParent && curParent !== dummySVGContainer) {
if (curParent.style[propName]) {
let value = curParent.style[propName];
if (propName === 'fontSize' && /em$/.test(value)) {
value = Math.round(parseFloat(value) * 16) + 'px';
}
el.style[propName] = value;
break;
}
curParent = curParent.parentNode;
}
};
let titleElements, outlineElements;
// 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
['fontFamily', 'fontSize']
.forEach((property) => {
setStylePropertyFromParents(el, property);
});
el.style.fontFamily = pdfFont && pdfFont.normal ?
// Custom PDF font
'HighchartsFont' :
// Generic font (serif, sans-serif etc)
String(el.style.fontFamily &&
el.style.fontFamily.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);
});
// Remove all .highcharts-text-outline elements, #17170
outlineElements =
el.getElementsByClassName('highcharts-text-outline');
while (outlineElements.length > 0) {
const outline = outlineElements[0];
if (outline.parentNode) {
outline.parentNode.removeChild(outline);
}
}
});
const svgNode = dummySVGContainer.querySelector('svg');
if (svgNode) {
loadPdfFonts(svgNode, () => {
svgToPdf(svgNode, 0, scale, (pdfData) => {
try {
downloadURL(pdfData, filename);
if (successCallback) {
successCallback();
}
}
catch (e) {
failCallback(e);
}
});
});
}
};
// 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.MSBlobBuilder !== 'undefined') {
blob = new win.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.jspdf.jsPDF) {
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', downloadPDF);
});
}
}
else {
// PNG/JPEG download - create bitmap from SVG
svgurl = svgToDataUrl(svg);
finallyHandler = function () {
try {
OfflineExporting.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 () {
if (svg.length > 100000000 /* RegexLimits.svgLimit */) {
throw new Error('Input too long');
}
// Failed due to tainted canvas
// Create new and untainted canvas
const canvas = doc.createElement('canvas'), ctx = canvas.getContext('2d'), matchedImageWidth = svg.match(
// eslint-disable-next-line max-len
/^