--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.composeImages.js Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,330 @@
+/** ## jquery.flot.composeImages.js
+
+This plugin is used to expose a function used to overlap several canvases and
+SVGs, for the purpose of creating a snaphot out of them.
+
+### When composeImages is used:
+When multiple canvases and SVGs have to be overlapped into a single image
+and their offset on the page, must be preserved.
+
+### Where can be used:
+In creating a downloadable snapshot of the plots, axes, cursors etc of a graph.
+
+### How it works:
+The entry point is composeImages function. It expects an array of objects,
+which should be either canvases or SVGs (or a mix). It does a prevalidation
+of them, by verifying if they will be usable or not, later in the flow.
+After selecting only usable sources, it passes them to getGenerateTempImg
+function, which generates temporary images out of them. This function
+expects that some of the passed sources (canvas or SVG) may still have
+problems being converted to an image and makes sure the promises system,
+used by composeImages function, moves forward. As an example, SVGs with
+missing information from header or with unsupported content, may lead to
+failure in generating the temporary image. Temporary images are required
+mostly on extracting content from SVGs, but this is also where the x/y
+offsets are extracted for each image which will be added. For SVGs in
+particular, their CSS rules have to be applied.
+After all temporary images are generated, they are overlapped using
+getExecuteImgComposition function. This is where the destination canvas
+is set to the proper dimensions. It is then output by composeImages.
+This function returns a promise, which can be used to wait for the whole
+composition process. It requires to be asynchronous, because this is how
+temporary images load their data.
+*/
+
+(function($) {
+ "use strict";
+ const GENERALFAILURECALLBACKERROR = -100; //simply a negative number
+ const SUCCESSFULIMAGEPREPARATION = 0;
+ const EMPTYARRAYOFIMAGESOURCES = -1;
+ const NEGATIVEIMAGESIZE = -2;
+ var pixelRatio = 1;
+ var browser = $.plot.browser;
+ var getPixelRatio = browser.getPixelRatio;
+
+ function composeImages(canvasOrSvgSources, destinationCanvas) {
+ var validCanvasOrSvgSources = canvasOrSvgSources.filter(isValidSource);
+ pixelRatio = getPixelRatio(destinationCanvas.getContext('2d'));
+
+ var allImgCompositionPromises = validCanvasOrSvgSources.map(function(validCanvasOrSvgSource) {
+ var tempImg = new Image();
+ var currentPromise = new Promise(getGenerateTempImg(tempImg, validCanvasOrSvgSource));
+ return currentPromise;
+ });
+
+ var lastPromise = Promise.all(allImgCompositionPromises).then(getExecuteImgComposition(destinationCanvas), failureCallback);
+ return lastPromise;
+ }
+
+ function isValidSource(canvasOrSvgSource) {
+ var isValidFromCanvas = true;
+ var isValidFromContent = true;
+ if ((canvasOrSvgSource === null) || (canvasOrSvgSource === undefined)) {
+ isValidFromContent = false;
+ } else {
+ if (canvasOrSvgSource.tagName === 'CANVAS') {
+ if ((canvasOrSvgSource.getBoundingClientRect().right === canvasOrSvgSource.getBoundingClientRect().left) ||
+ (canvasOrSvgSource.getBoundingClientRect().bottom === canvasOrSvgSource.getBoundingClientRect().top)) {
+ isValidFromCanvas = false;
+ }
+ }
+ }
+ return isValidFromContent && isValidFromCanvas && (window.getComputedStyle(canvasOrSvgSource).visibility === 'visible');
+ }
+
+ function getGenerateTempImg(tempImg, canvasOrSvgSource) {
+ tempImg.sourceDescription = '<info className="' + canvasOrSvgSource.className + '" tagName="' + canvasOrSvgSource.tagName + '" id="' + canvasOrSvgSource.id + '">';
+ tempImg.sourceComponent = canvasOrSvgSource;
+
+ return function doGenerateTempImg(successCallbackFunc, failureCallbackFunc) {
+ tempImg.onload = function(evt) {
+ tempImg.successfullyLoaded = true;
+ successCallbackFunc(tempImg);
+ };
+
+ tempImg.onabort = function(evt) {
+ tempImg.successfullyLoaded = false;
+ console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
+ successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
+ };
+
+ tempImg.onerror = function(evt) {
+ tempImg.successfullyLoaded = false;
+ console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
+ successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
+ };
+
+ generateTempImageFromCanvasOrSvg(canvasOrSvgSource, tempImg);
+ };
+ }
+
+ function getExecuteImgComposition(destinationCanvas) {
+ return function executeImgComposition(tempImgs) {
+ var compositionResult = copyImgsToCanvas(tempImgs, destinationCanvas);
+ return compositionResult;
+ };
+ }
+
+ function copyCanvasToImg(canvas, img) {
+ img.src = canvas.toDataURL('image/png');
+ }
+
+ function getCSSRules(document) {
+ var styleSheets = document.styleSheets,
+ rulesList = [];
+ for (var i = 0; i < styleSheets.length; i++) {
+ // CORS requests for style sheets throw and an exception on Chrome > 64
+ try {
+ // in Chrome, the external CSS files are empty when the page is directly loaded from disk
+ var rules = styleSheets[i].cssRules || [];
+ for (var j = 0; j < rules.length; j++) {
+ var rule = rules[j];
+ rulesList.push(rule.cssText);
+ }
+ } catch (e) {
+ console.log('Failed to get some css rules');
+ }
+ }
+ return rulesList;
+ }
+
+ function embedCSSRulesInSVG(rules, svg) {
+ var text = [
+ '<svg class="snapshot ' + svg.classList + '" width="' + svg.width.baseVal.value * pixelRatio + '" height="' + svg.height.baseVal.value * pixelRatio + '" viewBox="0 0 ' + svg.width.baseVal.value + ' ' + svg.height.baseVal.value + '" xmlns="http://www.w3.org/2000/svg">',
+ '<style>',
+ '/* <![CDATA[ */',
+ rules.join('\n'),
+ '/* ]]> */',
+ '</style>',
+ svg.innerHTML,
+ '</svg>'
+ ].join('\n');
+ return text;
+ }
+
+ function copySVGToImgMostBrowsers(svg, img) {
+ var rules = getCSSRules(document),
+ source = embedCSSRulesInSVG(rules, svg);
+
+ source = patchSVGSource(source);
+
+ var blob = new Blob([source], {type: "image/svg+xml;charset=utf-8"}),
+ domURL = self.URL || self.webkitURL || self,
+ url = domURL.createObjectURL(blob);
+ img.src = url;
+ }
+
+ function copySVGToImgSafari(svg, img) {
+ // Use this method to convert a string buffer array to a binary string.
+ // Do so by breaking up large strings into smaller substrings; this is necessary to avoid the
+ // "maximum call stack size exceeded" exception that can happen when calling 'String.fromCharCode.apply'
+ // with a very long array.
+ function buildBinaryString (arrayBuffer) {
+ var binaryString = "";
+ const utf8Array = new Uint8Array(arrayBuffer);
+ const blockSize = 16384;
+ for (var i = 0; i < utf8Array.length; i = i + blockSize) {
+ const binarySubString = String.fromCharCode.apply(null, utf8Array.subarray(i, i + blockSize));
+ binaryString = binaryString + binarySubString;
+ }
+ return binaryString;
+ };
+
+ var rules = getCSSRules(document),
+ source = embedCSSRulesInSVG(rules, svg),
+ data,
+ utf8BinaryString;
+
+ source = patchSVGSource(source);
+
+ // Encode the string as UTF-8 and convert it to a binary string. The UTF-8 encoding is required to
+ // capture unicode characters correctly.
+ utf8BinaryString = buildBinaryString(new (TextEncoder || TextEncoderLite)('utf-8').encode(source));
+
+ data = "data:image/svg+xml;base64," + btoa(utf8BinaryString);
+ img.src = data;
+ }
+
+ function patchSVGSource(svgSource) {
+ var source = '';
+ //add name spaces.
+ if (!svgSource.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)) {
+ source = svgSource.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
+ }
+ if (!svgSource.match(/^<svg[^>]+"http:\/\/www\.w3\.org\/1999\/xlink"/)) {
+ source = svgSource.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
+ }
+
+ //add xml declaration
+ return '<?xml version="1.0" standalone="no"?>\r\n' + source;
+ }
+
+ function copySVGToImg(svg, img) {
+ if (browser.isSafari() || browser.isMobileSafari()) {
+ copySVGToImgSafari(svg, img);
+ } else {
+ copySVGToImgMostBrowsers(svg, img);
+ }
+ }
+
+ function adaptDestSizeToZoom(destinationCanvas, sources) {
+ function containsSVGs(source) {
+ return source.srcImgTagName === 'svg';
+ }
+
+ if (sources.find(containsSVGs) !== undefined) {
+ if (pixelRatio < 1) {
+ destinationCanvas.width = destinationCanvas.width * pixelRatio;
+ destinationCanvas.height = destinationCanvas.height * pixelRatio;
+ }
+ }
+ }
+
+ function prepareImagesToBeComposed(sources, destination) {
+ var result = SUCCESSFULIMAGEPREPARATION;
+ if (sources.length === 0) {
+ result = EMPTYARRAYOFIMAGESOURCES; //nothing to do if called without sources
+ } else {
+ var minX = sources[0].genLeft;
+ var minY = sources[0].genTop;
+ var maxX = sources[0].genRight;
+ var maxY = sources[0].genBottom;
+ var i = 0;
+
+ for (i = 1; i < sources.length; i++) {
+ if (minX > sources[i].genLeft) {
+ minX = sources[i].genLeft;
+ }
+
+ if (minY > sources[i].genTop) {
+ minY = sources[i].genTop;
+ }
+ }
+
+ for (i = 1; i < sources.length; i++) {
+ if (maxX < sources[i].genRight) {
+ maxX = sources[i].genRight;
+ }
+
+ if (maxY < sources[i].genBottom) {
+ maxY = sources[i].genBottom;
+ }
+ }
+
+ if ((maxX - minX <= 0) || (maxY - minY <= 0)) {
+ result = NEGATIVEIMAGESIZE; //this might occur on hidden images
+ } else {
+ destination.width = Math.round(maxX - minX);
+ destination.height = Math.round(maxY - minY);
+
+ for (i = 0; i < sources.length; i++) {
+ sources[i].xCompOffset = sources[i].genLeft - minX;
+ sources[i].yCompOffset = sources[i].genTop - minY;
+ }
+
+ adaptDestSizeToZoom(destination, sources);
+ }
+ }
+ return result;
+ }
+
+ function copyImgsToCanvas(sources, destination) {
+ var prepareImagesResult = prepareImagesToBeComposed(sources, destination);
+ if (prepareImagesResult === SUCCESSFULIMAGEPREPARATION) {
+ var destinationCtx = destination.getContext('2d');
+
+ for (var i = 0; i < sources.length; i++) {
+ if (sources[i].successfullyLoaded === true) {
+ destinationCtx.drawImage(sources[i], sources[i].xCompOffset * pixelRatio, sources[i].yCompOffset * pixelRatio);
+ }
+ }
+ }
+ return prepareImagesResult;
+ }
+
+ function adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg) {
+ destImg.genLeft = srcCanvasOrSvg.getBoundingClientRect().left;
+ destImg.genTop = srcCanvasOrSvg.getBoundingClientRect().top;
+
+ if (srcCanvasOrSvg.tagName === 'CANVAS') {
+ destImg.genRight = destImg.genLeft + srcCanvasOrSvg.width;
+ destImg.genBottom = destImg.genTop + srcCanvasOrSvg.height;
+ }
+
+ if (srcCanvasOrSvg.tagName === 'svg') {
+ destImg.genRight = srcCanvasOrSvg.getBoundingClientRect().right;
+ destImg.genBottom = srcCanvasOrSvg.getBoundingClientRect().bottom;
+ }
+ }
+
+ function generateTempImageFromCanvasOrSvg(srcCanvasOrSvg, destImg) {
+ if (srcCanvasOrSvg.tagName === 'CANVAS') {
+ copyCanvasToImg(srcCanvasOrSvg, destImg);
+ }
+
+ if (srcCanvasOrSvg.tagName === 'svg') {
+ copySVGToImg(srcCanvasOrSvg, destImg);
+ }
+
+ destImg.srcImgTagName = srcCanvasOrSvg.tagName;
+ adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg);
+ }
+
+ function failureCallback() {
+ return GENERALFAILURECALLBACKERROR;
+ }
+
+ // used for testing
+ $.plot.composeImages = composeImages;
+
+ function init(plot) {
+ // used to extend the public API of the plot
+ plot.composeImages = composeImages;
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ name: 'composeImages',
+ version: '1.0'
+ });
+})(jQuery);