UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

683 lines (566 loc) • 21 kB
"use strict"; var $ = require("../core/renderer"), Color = require("../color"), isFunction = require("../core/utils/type").isFunction, iteratorUtils = require("../core/utils/iterator"), extend = require("../core/utils/extend").extend, domAdapter = require("../core/dom_adapter"), windowUtils = require("../core/utils/window"), window = windowUtils.getWindow(), camelize = require("../core/utils/inflector").camelize, deferredUtils = require("../core/utils/deferred"), when = deferredUtils.when, Deferred = deferredUtils.Deferred, _math = Math, PI = _math.PI, _min = _math.min, _abs = _math.abs, _sqrt = _math.sqrt, _pow = _math.pow, _atan2 = _math.atan2, _cos = _math.cos, _sin = _math.sin, _each = iteratorUtils.each, _extend = extend, _number = Number, IMAGE_QUALITY = 1, TEXT_DECORATION_LINE_WIDTH_COEFF = 0.05, DEFAULT_FONT_SIZE = "10px", DEFAULT_FONT_FAMILY = "sans-serif", DEFAULT_TEXT_COLOR = "#000", clipPaths, imageDeferreds, patterns, filters; function createCanvas(width, height, margin) { var canvas = $("<canvas>")[0]; canvas.width = width + margin * 2; canvas.height = height + margin * 2; 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 cBx = (x1 + x2) / 2, cBy = (y1 + y2) / 2, aB = _atan2(y1 - y2, x1 - x2), k = largeArcFlag ? 1 : -1, opSide, adjSide, centerX, centerY, startAngle, endAngle; 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 attr = parseAttributes(element.attributes || {}), style = element.style || {}, options = _extend({}, attr, { text: element.textContent.replace(/\s+/g, " "), textAlign: attr["text-anchor"] === "middle" ? "center" : attr["text-anchor"] }), transform = attr.transform, coords; 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(style, 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) { var d = new Deferred(), image = new window.Image(); image.onload = function () { context.save(); context.globalAlpha = options.globalAlpha; transformElement(context, options); clipElement(context, options); context.drawImage(image, options.x, options.y, options.width, options.height); context.restore(); d.resolve(); }; image.onerror = function () { // Warning TODO d.resolve(); }; imageDeferreds.push(d); image.setAttribute("crossOrigin", "anonymous"); image.src = options["xlink:href"]; } function drawPath(context, dAttr) { var dArray = dAttr.split(" "), i = 0, param1, param2; 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; break; } } while (i < dArray.length); } function parseStyles(style, options) { _each(style, function (_, field) { if (style[field] !== "") { options[camelize(field)] = style[field]; } }); 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) { setFontStyle(context, options); options.text && context.fillText(options.text, options.x || 0, options.y || 0); strokeElement(context, options, true); drawTextDecoration(context, options); } function drawTextDecoration(context, options) { if (!options.textDecoration || options.textDecoration === "none") { 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; break; } context.rect(x, y, textWidth, lineHeight); fillElement(context, options); strokeElement(context, options); } function createClipPath(element) { clipPaths[element.attributes.id.textContent] = element.childNodes[0]; } function createPattern(element) { patterns[element.attributes.id.textContent] = element; } function aggregateOpacity(options) { options.strokeOpacity = options["stroke-opacity"] !== undefined ? options["stroke-opacity"] : 1; options.fillOpacity = options["fill-opacity"] !== undefined ? options["fill-opacity"] : 1; if (options.opacity !== undefined) { options.strokeOpacity *= options.opacity; options.fillOpacity *= options.opacity; } } function hasTspan(element) { var nodes = element.childNodes; for (var i = 0; i < nodes.length; i++) { if (nodes[i].tagName === "tspan") { return true; } } return false; } function drawTextElement(childNodes, context, options) { var lines = [], line, offset = 0; for (var i = 0; i < childNodes.length; i++) { var element = childNodes[i]; if (element.tagName === undefined) { drawElement(element, context, options); } else if (element.tagName === "tspan" || element.tagName === "text") { var elementOptions = getElementOptions(element), mergedOptions = _extend({}, options, elementOptions); if (element.tagName === "tspan" && hasTspan(element)) { drawTextElement(element.childNodes, context, mergedOptions); continue; } mergedOptions.textAlign = "start"; if (!line || elementOptions.x !== undefined) { line = { elements: [], options: [], widths: [], offsets: [] }; lines.push(line); } if (elementOptions.dy !== undefined) { offset += Number(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 (options.textAlign === "center") { xDiff = commonWidth / 2; } if (options.textAlign === "end") { 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]); }); }); } function drawElement(element, context, parentOptions) { var tagName = element.tagName, isText = tagName === "text" || tagName === "tspan" || tagName === undefined, isImage = tagName === "image", options = _extend({}, parentOptions, getElementOptions(element)); if (options.visibility === "hidden") { return; } context.save(); !isImage && transformElement(context, options); clipElement(context, options); aggregateOpacity(options); context.beginPath(); switch (element.tagName) { case undefined: drawText(context, options); break; case "text": case "tspan": drawTextElement(element.childNodes, context, options); break; case "image": drawImage(context, options); break; case "path": drawPath(context, options.d); break; case "rect": drawRect(context, options); context.closePath(); // for valid clipping break; case "circle": context.arc(options.cx, options.cy, options.r, 0, 2 * PI, 1); break; } applyFilter(context, options); if (!isText) { fillElement(context, options); strokeElement(context, options); } context.restore(); } function applyFilter(context, options) { var filterOptions, id = parseUrl(options.filter); if (id) { filterOptions = filters && 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; } } // translate and clip are the special attributtes, they should not be inherited by child nodes 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) { if (options["clip-path"]) { drawElement(clipPaths[parseUrl(options["clip-path"])], context, {}); context.clip(); delete options["clip-path"]; } } function hex2rgba(hexColor, alpha) { var color = new Color(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": if (attr.stdDeviation) { // T511738, IE10 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); break; } }); filters[element.id] = filterOptions; } function drawCanvasElements(elements, context, parentOptions) { var options; _each(elements, function (_, element) { switch (element.tagName && element.tagName.toLowerCase()) { case "g": options = _extend({}, parentOptions, getElementOptions(element)); context.save(); transformElement(context, options); clipElement(context, options); drawCanvasElements(element.childNodes, context, options); context.restore(); break; case "defs": clipPaths = {}; patterns = {}; filters = {}; drawCanvasElements(element.childNodes, context); break; case "clippath": createClipPath(element); break; case "pattern": createPattern(element); break; case "filter": createFilter(element); break; default: drawElement(element, context, parentOptions); } }); } function setLineDash(context, options) { var matches = options["stroke-dasharray"] && options["stroke-dasharray"].match(/(\d+)/g); if (matches && matches.length && context.setLineDash) { // IE10 does not have setLineDash matches = iteratorUtils.map(matches, function (item) { return _number(item); }); context.setLineDash(matches); } } function strokeElement(context, options, isText) { var stroke = options.stroke; if (stroke && stroke !== "none" && options["stroke-width"] !== 0) { 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(); } } function getPattern(context, fill) { var pattern = patterns[parseUrl(fill)], options = getElementOptions(pattern), patternCanvas = createCanvas(options.width, options.height, 0), patternContext = patternCanvas.getContext("2d"); drawCanvasElements(pattern.childNodes, patternContext, options); return context.createPattern(patternCanvas, "repeat"); } function fillElement(context, options) { var fill = options.fill; if (fill && fill !== "none") { context.fillStyle = fill.search(/url/) === -1 ? fill : getPattern(context, fill); context.globalAlpha = options.fillOpacity; context.fill(); } } var parseAttributes = function parseAttributes(attributes) { var newAttributes = {}, attr; iteratorUtils.each(attributes, function (index, item) { attr = item.textContent; if (isFinite(attr)) { attr = _number(attr); } newAttributes[item.name.toLowerCase()] = attr; // lowerCase for Edge }); return newAttributes; }; function drawBackground(context, width, height, backgroundColor, margin) { context.fillStyle = backgroundColor || "#ffffff"; context.fillRect(-margin, -margin, width + margin * 2, height + margin * 2); } function getCanvasFromSvg(markup, width, height, backgroundColor, margin) { var canvas = createCanvas(width, height, margin), context = canvas.getContext("2d"), parser = new window.DOMParser(), elem = parser.parseFromString(markup, "image/svg+xml"), svgElem = elem.childNodes[0]; context.translate(margin, margin); imageDeferreds = []; domAdapter.getBody().appendChild(canvas); // for rtl mode if (svgElem.attributes.direction) { canvas.dir = svgElem.attributes.direction.textContent; } drawBackground(context, width, height, backgroundColor, margin); drawCanvasElements(svgElem.childNodes, context, {}); domAdapter.getBody().removeChild(canvas); return canvas; } function resolveString(string, canvas, mimeType) { when.apply($, imageDeferreds).done(function () { var resultString = getStringFromCanvas(canvas, mimeType); string.resolve(resultString); }); } exports.imageCreator = { getImageData: function getImageData(markup, options) { var mimeType = "image/" + options.format, string = new Deferred(), width = options.width, height = options.height, backgroundColor = options.backgroundColor; // Injection for testing T403049 if (isFunction(options.__parseAttributesFn)) { parseAttributes = options.__parseAttributesFn; } resolveString(string, getCanvasFromSvg(markup, width, height, backgroundColor, options.margin), mimeType); return string; }, getData: function getData(markup, options) { var that = this, imageData = exports.imageCreator.getImageData(markup, options), mimeType = "image/" + options.format, data = new Deferred(); when(imageData).done(function (binaryData) { imageData = isFunction(window.Blob) ? that._getBlob(binaryData, mimeType) : that._getBase64(binaryData); data.resolve(imageData); }); return data; }, _getBlob: function _getBlob(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 _getBase64(binaryData) { return window.btoa(binaryData); } }; exports.getData = function (data, options, callback) { exports.imageCreator.getData(data, options).done(callback); };