devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
637 lines (605 loc) • 23.8 kB
JavaScript
/**
* 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/
*/
;
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: []
})
};