UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

637 lines (605 loc) • 23.8 kB
/** * DevExtreme (exporter/image_creator.js) * Version: 18.2.18 * Build date: Tue Oct 18 2022 * * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var _renderer = require("../core/renderer"); var _renderer2 = _interopRequireDefault(_renderer); var _color = require("../color"); var _color2 = _interopRequireDefault(_color); var _type = require("../core/utils/type"); var _svg = require("../core/utils/svg"); var _svg2 = _interopRequireDefault(_svg); var _iterator = require("../core/utils/iterator"); var _iterator2 = _interopRequireDefault(_iterator); var _extend = require("../core/utils/extend"); var _dom_adapter = require("../core/dom_adapter"); var _dom_adapter2 = _interopRequireDefault(_dom_adapter); var _dom = require("../core/utils/dom"); var _dom2 = _interopRequireDefault(_dom); var _window = require("../core/utils/window"); var _window2 = _interopRequireDefault(_window); var _inflector = require("../core/utils/inflector"); var _deferred = require("../core/utils/deferred"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj } } var window = _window2.default.getWindow(); var _math = Math; var PI = _math.PI; var _min = _math.min; var _abs = _math.abs; var _sqrt = _math.sqrt; var _pow = _math.pow; var _atan2 = _math.atan2; var _cos = _math.cos; var _sin = _math.sin; var _each = _iterator2.default.each; var _number = Number; var IMAGE_QUALITY = 1; var TEXT_DECORATION_LINE_WIDTH_COEFF = .05; var DEFAULT_FONT_SIZE = "10px"; var DEFAULT_FONT_FAMILY = "sans-serif"; var DEFAULT_TEXT_COLOR = "#000"; function createCanvas(width, height, margin) { var canvas = (0, _renderer2.default)("<canvas>")[0]; canvas.width = width + 2 * margin; canvas.height = height + 2 * margin; canvas.hidden = true; return canvas } function getStringFromCanvas(canvas, mimeType) { var dataURL = canvas.toDataURL(mimeType, IMAGE_QUALITY), imageData = window.atob(dataURL.substring(("data:" + mimeType + ";base64,").length)); return imageData } function arcTo(x1, y1, x2, y2, radius, largeArcFlag, clockwise, context) { var opSide, adjSide, centerX, centerY, startAngle, endAngle, cBx = (x1 + x2) / 2, cBy = (y1 + y2) / 2, aB = _atan2(y1 - y2, x1 - x2), k = largeArcFlag ? 1 : -1; aB += 90 * (PI / 180) * (clockwise ? 1 : -1); opSide = _sqrt(_pow(x2 - x1, 2) + _pow(y2 - y1, 2)) / 2; adjSide = _sqrt(_abs(_pow(radius, 2) - _pow(opSide, 2))); centerX = cBx + k * (adjSide * _cos(aB)); centerY = cBy + k * (adjSide * _sin(aB)); startAngle = _atan2(y1 - centerY, x1 - centerX); endAngle = _atan2(y2 - centerY, x2 - centerX); context.arc(centerX, centerY, radius, startAngle, endAngle, !clockwise) } function getElementOptions(element) { var coords, attr = parseAttributes(element.attributes || {}), options = (0, _extend.extend)({}, attr, { text: element.textContent.replace(/\s+/g, " "), textAlign: "middle" === attr["text-anchor"] ? "center" : attr["text-anchor"] }), transform = attr.transform; if (transform) { coords = transform.match(/translate\(-*\d+([.]\d+)*(,*\s*-*\d+([.]\d+)*)*/); if (coords) { coords = coords[0].match(/-*\d+([.]\d+)*/g); options.translateX = _number(coords[0]); options.translateY = coords[1] ? _number(coords[1]) : 0 } coords = transform.match(/rotate\(-*\d+([.]\d+)*(,*\s*-*\d+([.]\d+)*,*\s*-*\d+([.]\d+)*)*/); if (coords) { coords = coords[0].match(/-*\d+([.]\d+)*/g); options.rotationAngle = _number(coords[0]); options.rotationX = coords[1] && _number(coords[1]); options.rotationY = coords[2] && _number(coords[2]) } } parseStyles(element, options); return options } function drawRect(context, options) { var x = options.x, y = options.y, width = options.width, height = options.height, cornerRadius = options.rx; if (!cornerRadius) { context.rect(options.x, options.y, options.width, options.height) } else { cornerRadius = _min(cornerRadius, width / 2, height / 2); context.save(); context.translate(x, y); context.moveTo(width / 2, 0); context.arcTo(width, 0, width, height, cornerRadius); context.arcTo(width, height, 0, height, cornerRadius); context.arcTo(0, height, 0, 0, cornerRadius); context.arcTo(0, 0, cornerRadius, 0, cornerRadius); context.lineTo(width / 2, 0); context.restore() } } function drawImage(context, options, shared) { var d = new _deferred.Deferred, image = new window.Image; image.onload = function() { context.save(); context.globalAlpha = options.globalAlpha; transformElement(context, options); clipElement(context, options, shared); context.drawImage(image, options.x, options.y, options.width, options.height); context.restore(); d.resolve() }; image.onerror = function() { d.resolve() }; image.setAttribute("crossOrigin", "anonymous"); image.src = options.href || options["xlink:href"]; return d } function drawPath(context, dAttr) { var param1, param2, dArray = dAttr.split(" "), i = 0; do { param1 = _number(dArray[i + 1]); param2 = _number(dArray[i + 2]); switch (dArray[i]) { case "M": context.moveTo(param1, param2); i += 3; break; case "L": context.lineTo(param1, param2); i += 3; break; case "C": context.bezierCurveTo(param1, param2, _number(dArray[i + 3]), _number(dArray[i + 4]), _number(dArray[i + 5]), _number(dArray[i + 6])); i += 7; break; case "A": arcTo(_number(dArray[i - 2]), _number(dArray[i - 1]), _number(dArray[i + 6]), _number(dArray[i + 7]), param1, _number(dArray[i + 4]), _number(dArray[i + 5]), context); i += 8; break; case "Z": context.closePath(); i += 1 } } while (i < dArray.length) } function parseStyles(element, options) { var field, style = element.style || {}; for (field in style) { if ("" !== style[field]) { options[(0, _inflector.camelize)(field)] = style[field] } } if (_dom_adapter2.default.isElementNode(element) && _dom2.default.contains(_dom_adapter2.default.getBody(), element)) { style = window.getComputedStyle(element); ["fill", "stroke", "stroke-width", "font-family", "font-size", "font-style", "font-weight"].forEach(function(prop) { if (prop in style && "" !== style[prop]) { options[(0, _inflector.camelize)(prop)] = style[prop] } }); ["opacity", "fill-opacity", "stroke-opacity"].forEach(function(prop) { if (prop in style && "" !== style[prop] && "1" !== style[prop]) { options[prop] = _number(style[prop]) } }) } options.textDecoration = options.textDecoration || options.textDecorationLine; options.globalAlpha = options.opacity || options.globalAlpha } function parseUrl(urlString) { var matches = urlString && urlString.match(/url\(.*#(.*?)["']?\)/i); return matches && matches[1] } function setFontStyle(context, options) { var fontParams = []; options.fontSize = options.fontSize || DEFAULT_FONT_SIZE; options.fontFamily || DEFAULT_FONT_FAMILY; options.fill = options.fill || DEFAULT_TEXT_COLOR; options.fontStyle && fontParams.push(options.fontStyle); options.fontWeight && fontParams.push(options.fontWeight); fontParams.push(options.fontSize); fontParams.push(options.fontFamily); context.font = fontParams.join(" "); context.textAlign = options.textAlign; context.fillStyle = options.fill; context.globalAlpha = options.globalAlpha } function drawText(context, options, shared) { setFontStyle(context, options); applyFilter(context, options, shared); options.text && context.fillText(options.text, options.x || 0, options.y || 0); strokeElement(context, options, true); drawTextDecoration(context, options, shared) } function drawTextDecoration(context, options, shared) { if (!options.textDecoration || "none" === options.textDecoration) { return } var x = options.x, textWidth = context.measureText(options.text).width, textHeight = parseInt(options.fontSize, 10), lineHeight = textHeight * TEXT_DECORATION_LINE_WIDTH_COEFF < 1 ? 1 : textHeight * TEXT_DECORATION_LINE_WIDTH_COEFF, y = options.y; switch (options.textDecoration) { case "line-through": y -= textHeight / 3 + lineHeight / 2; break; case "overline": y -= textHeight - lineHeight; break; case "underline": y += lineHeight } context.rect(x, y, textWidth, lineHeight); fillElement(context, options, shared); strokeElement(context, options) } function aggregateOpacity(options) { options.strokeOpacity = void 0 !== options["stroke-opacity"] ? options["stroke-opacity"] : 1; options.fillOpacity = void 0 !== options["fill-opacity"] ? options["fill-opacity"] : 1; if (void 0 !== options.opacity) { options.strokeOpacity *= options.opacity; options.fillOpacity *= options.opacity } } function hasTspan(element) { var nodes = element.childNodes; for (var i = 0; i < nodes.length; i++) { if ("tspan" === nodes[i].tagName) { return true } } return false } function drawTextElement(childNodes, context, options, shared) { var line, lines = [], offset = 0; for (var i = 0; i < childNodes.length; i++) { var element = childNodes[i]; if (void 0 === element.tagName) { drawElement(element, context, options, shared) } else { if ("tspan" === element.tagName || "text" === element.tagName) { var elementOptions = getElementOptions(element), mergedOptions = (0, _extend.extend)({}, options, elementOptions); if ("tspan" === element.tagName && hasTspan(element)) { drawTextElement(element.childNodes, context, mergedOptions, shared); continue } mergedOptions.textAlign = "start"; if (!line || void 0 !== elementOptions.x) { line = { elements: [], options: [], widths: [], offsets: [] }; lines.push(line) } if (void 0 !== elementOptions.y) { offset = 0 } if (void 0 !== elementOptions.dy) { offset += parseFloat(elementOptions.dy) } line.elements.push(element); line.options.push(mergedOptions); line.offsets.push(offset); setFontStyle(context, mergedOptions); line.widths.push(context.measureText(mergedOptions.text).width) } } } lines.forEach(function(line) { var commonWidth = line.widths.reduce(function(commonWidth, width) { return commonWidth + width }, 0), xDiff = 0, currentOffset = 0; if ("center" === options.textAlign) { xDiff = commonWidth / 2 } if ("end" === options.textAlign) { xDiff = commonWidth } line.options.forEach(function(o, index) { var width = line.widths[index]; o.x = o.x - xDiff + currentOffset; o.y += line.offsets[index]; currentOffset += width }); line.elements.forEach(function(element, index) { drawTextElement(element.childNodes, context, line.options[index], shared) }) }) } function drawElement(element, context, parentOptions, shared) { var tagName = element.tagName, isText = "text" === tagName || "tspan" === tagName || void 0 === tagName, isImage = "image" === tagName, options = (0, _extend.extend)({}, parentOptions, getElementOptions(element)); if ("hidden" === options.visibility || options["hidden-for-export"]) { return } context.save(); !isImage && transformElement(context, options); clipElement(context, options, shared); aggregateOpacity(options); var promise = void 0; context.beginPath(); switch (element.tagName) { case void 0: drawText(context, options, shared); break; case "text": case "tspan": drawTextElement(element.childNodes, context, options, shared); break; case "image": promise = drawImage(context, options, shared); break; case "path": drawPath(context, options.d); break; case "rect": drawRect(context, options); context.closePath(); break; case "circle": context.arc(options.cx, options.cy, options.r, 0, 2 * PI, 1) } if (!isText) { applyFilter(context, options, shared); fillElement(context, options, shared); strokeElement(context, options) } context.restore(); return promise } function applyFilter(context, options, shared) { var filterOptions, id = parseUrl(options.filter); if (id) { filterOptions = shared.filters[id]; if (!filterOptions) { filterOptions = { offsetX: 0, offsetY: 0, blur: 0, color: "#000" } } context.shadowOffsetX = filterOptions.offsetX; context.shadowOffsetY = filterOptions.offsetY; context.shadowColor = filterOptions.color; context.shadowBlur = filterOptions.blur } } function transformElement(context, options) { context.translate(options.translateX || 0, options.translateY || 0); delete options.translateX; delete options.translateY; if (options.rotationAngle) { context.translate(options.rotationX || 0, options.rotationY || 0); context.rotate(options.rotationAngle * PI / 180); context.translate(-(options.rotationX || 0), -(options.rotationY || 0)); delete options.rotationAngle; delete options.rotationX; delete options.rotationY } } function clipElement(context, options, shared) { if (options["clip-path"]) { drawElement(shared.clipPaths[parseUrl(options["clip-path"])], context, {}, shared); context.clip(); delete options["clip-path"] } } function hex2rgba(hexColor, alpha) { var color = new _color2.default(hexColor); return "rgba(" + color.r + "," + color.g + "," + color.b + "," + alpha + ")" } function createFilter(element) { var color, opacity, filterOptions = {}; _each(element.childNodes, function(_, node) { var attr = node.attributes; if (!attr.result) { return } switch (attr.result.value) { case "gaussianBlurResult": filterOptions.blur = _number(attr.stdDeviation.value); break; case "offsetResult": filterOptions.offsetX = _number(attr.dx.value); filterOptions.offsetY = _number(attr.dy.value); break; case "floodResult": color = attr["flood-color"] ? attr["flood-color"].value : "#000"; opacity = attr["flood-opacity"] ? attr["flood-opacity"].value : 1; filterOptions.color = hex2rgba(color, opacity) } }); return filterOptions } function asyncEach(array, callback) { var d = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : new _deferred.Deferred; if (0 === array.length) { return d.resolve() } var result = callback(array[0]); function next() { asyncEach(Array.prototype.slice.call(array, 1), callback, d) } if ((0, _type.isPromise)(result)) { result.then(next) } else { next() } return d } function drawCanvasElements(elements, context, parentOptions, shared) { return asyncEach(elements, function(element) { switch (element.tagName && element.tagName.toLowerCase()) { case "g": var options = (0, _extend.extend)({}, parentOptions, getElementOptions(element)); context.save(); transformElement(context, options); clipElement(context, options, shared); var onDone = function() { context.restore() }; var d = drawCanvasElements(element.childNodes, context, options, shared); if ((0, _type.isPromise)(d)) { d.then(onDone) } else { onDone() } return d; case "defs": return drawCanvasElements(element.childNodes, context, {}, shared); case "clippath": shared.clipPaths[element.attributes.id.textContent] = element.childNodes[0]; break; case "pattern": shared.patterns[element.attributes.id.textContent] = element; break; case "filter": shared.filters[element.id] = createFilter(element); break; default: return drawElement(element, context, parentOptions, shared) } }) } function setLineDash(context, options) { var matches = options["stroke-dasharray"] && options["stroke-dasharray"].match(/(\d+)/g); if (matches && matches.length) { matches = _iterator2.default.map(matches, function(item) { return _number(item) }); context.setLineDash(matches) } } function strokeElement(context, options, isText) { var stroke = options.stroke; if (stroke && "none" !== stroke && 0 !== options["stroke-width"]) { setLineDash(context, options); context.lineJoin = options["stroke-linejoin"]; context.lineWidth = options["stroke-width"]; context.globalAlpha = options.strokeOpacity; context.strokeStyle = stroke; isText ? context.strokeText(options.text, options.x, options.y) : context.stroke(); context.globalAlpha = 1 } } function getPattern(context, fill, shared) { var pattern = shared.patterns[parseUrl(fill)], options = getElementOptions(pattern), patternCanvas = createCanvas(options.width, options.height, 0), patternContext = patternCanvas.getContext("2d"); drawCanvasElements(pattern.childNodes, patternContext, options, shared); return context.createPattern(patternCanvas, "repeat") } function fillElement(context, options, shared) { var fill = options.fill; if (fill && "none" !== fill) { context.fillStyle = fill.search(/url/) === -1 ? fill : getPattern(context, fill, shared); context.globalAlpha = options.fillOpacity; context.fill(); context.globalAlpha = 1 } } var parseAttributes = function(attributes) { var attr, newAttributes = {}; _iterator2.default.each(attributes, function(index, item) { attr = item.textContent; if (isFinite(attr)) { attr = _number(attr) } newAttributes[item.name.toLowerCase()] = attr }); return newAttributes }; function drawBackground(context, width, height, backgroundColor, margin) { context.fillStyle = backgroundColor || "#ffffff"; context.fillRect(-margin, -margin, width + 2 * margin, height + 2 * margin) } function getCanvasFromSvg(markup, width, height, backgroundColor, margin) { var canvas = createCanvas(width, height, margin), context = canvas.getContext("2d"), svgElem = _svg2.default.getSvgElement(markup); context.translate(margin, margin); _dom_adapter2.default.getBody().appendChild(canvas); if (svgElem.attributes.direction) { canvas.dir = svgElem.attributes.direction.textContent } drawBackground(context, width, height, backgroundColor, margin); return drawCanvasElements(svgElem.childNodes, context, {}, { clipPaths: {}, patterns: {}, filters: {} }).then(function() { _dom_adapter2.default.getBody().removeChild(canvas); return canvas }) } exports.imageCreator = { getImageData: function(markup, options) { var mimeType = "image/" + options.format, width = options.width, height = options.height, backgroundColor = options.backgroundColor; if ((0, _type.isFunction)(options.__parseAttributesFn)) { parseAttributes = options.__parseAttributesFn } var deferred = new _deferred.Deferred; getCanvasFromSvg(markup, width, height, backgroundColor, options.margin).then(function(canvas) { deferred.resolve(getStringFromCanvas(canvas, mimeType)) }); return deferred }, getData: function(markup, options) { var that = this; var deferred = new _deferred.Deferred; exports.imageCreator.getImageData(markup, options).then(function(binaryData) { var mimeType = "image/" + options.format; var data = (0, _type.isFunction)(window.Blob) && !options.forceProxy ? that._getBlob(binaryData, mimeType) : that._getBase64(binaryData); deferred.resolve(data) }); return deferred }, _getBlob: function(binaryData, mimeType) { var i, dataArray = new Uint8Array(binaryData.length); for (i = 0; i < binaryData.length; i++) { dataArray[i] = binaryData.charCodeAt(i) } return new window.Blob([dataArray.buffer], { type: mimeType }) }, _getBase64: function(binaryData) { return window.btoa(binaryData) } }; exports.getData = function(data, options, callback) { return exports.imageCreator.getData(data, options).then(callback) }; exports.testFormats = function(formats) { var canvas = createCanvas(100, 100, 0); return formats.reduce(function(r, f) { var mimeType = ("image/" + f).toLowerCase(); if (canvas.toDataURL(mimeType).indexOf(mimeType) !== -1) { r.supported.push(f) } else { r.unsupported.push(f) } return r }, { supported: [], unsupported: [] }) };