svgdecanvo
Version:
SVG to Canvas Converter
1,298 lines (1,222 loc) • 54.5 kB
JavaScript
/**!
* SvgDeCanvo 1.0.1 - JavaScript Vector Library
* Copyright (c) 2015-2016 FusionCharts Technologies <http://www.fusioncharts.com>
* Licensed under the MIT license.
*/
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
var SvgDeCanvo = require('./svgdecanvo');
global.SvgDeCanvo = SvgDeCanvo;
module.exports.SvgDeCanvo = SvgDeCanvo;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./svgdecanvo":2}],2:[function(require,module,exports){
(function (global){
var win = typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : null,
doc = win.document,
drawLib = {},
utilLib = {},
SvgDeCanvo;
/*
* The Constructor function
* @constructor
* @param {string/SVG DOM Object} - SVG element to be draw on canvas
* @param {function} - The function to be called after successsfully drawing in canvas
*/
SvgDeCanvo = function (svgElem, canvasElem, x, y, width, height, callback) {
var BLANK = '',
store = {
svg: BLANK,
context: BLANK,
callBack: BLANK,
imageArr: [],
canvas: BLANK,
dimention: {}
};
// Check if call as class or function
if (!(this instanceof SvgDeCanvo)) {
throw "This function should be used as class";
}
this._getStore = function (param) {
if (typeof store[param] != 'undefined') {
return store[param];
} else {
return false;
}
};
this._setStore = function (param, value) {
if (typeof store[param] != 'undefined') {
store[param] = value;
}
};
this._setStore('dimention', {
x: x,
y: y,
width: width,
height: height
});
if (svgElem) {
this.setSVG(svgElem);
}
if (canvasElem) {
this.setContext(canvasElem);
}
if (callback) {
this.setCallback(callback);
}
this.drawOnCanvas();
};
// Setter function for the context element
SvgDeCanvo.prototype.setContext = function (canvasElem) {
var context;
if (canvasElem.getContext && canvasElem.getContext('2d')) {
// Assigning the 2d context
context = canvasElem.getContext('2d');
this._setStore('canvas', canvasElem);
this._setStore('context', context);
} else {
// if canvas is not supported
throw "Please provide valid canvas";
}
};
// Getter function for the context object
SvgDeCanvo.prototype.getContext = function () {
return this._getStore('context');
};
// Setter function for the SVG element
SvgDeCanvo.prototype.setSVG = function (svgElem) {
var svg;
// Create the svg document object also if string is passed
if (typeof svgElem.documentElement != 'undefined') {
svg = svgElem;
this._setStore('svg', svg);
} else if (svgElem.substr(0, 1) == '<') {
svg = utilLib.StrToDom(svgElem);
this._setStore('svg', svg);
} else {
throw "Please provide valid SVG";
}
};
// Getter function for SVG element
SvgDeCanvo.prototype.getSVG = function () {
return this._getStore('svg');
};
SvgDeCanvo.prototype.setCallback = function (callback) {
if (typeof callback === 'function') {
this._setStore('callBack', callback);
}
};
SvgDeCanvo.prototype.getCallback = function () {
return this._getStore('callBack');
};
/*
* Method that draw the element in the dom tree in order
* this is also recursive and will draw only the innermost
* Element of a node.
* @param {array} arr - the dom array
*/
SvgDeCanvo.prototype.drawOnCanvas = function (svgElem, canvasElem, x, y, width, height, callback) {
var context, svg, oriWidth, oriHeight, scaleX, scaleY, dimention, oriDimention;
if (svgElem) {
this.setSVG(svgElem);
}
if (canvasElem) {
this.setContext(canvasElem);
}
if (callback) {
this.setCallback(callback);
}
canvasElem = canvasElem || this._getStore('canvas');
dimention = this._getStore('dimention');
callback = this.getCallback();
context = this.getContext();
svg = this.getSVG();
if (!svg || !context) {
return;
}
oriDimention = utilLib.getSvgDimention(svg);
oriWidth = oriDimention.width;
oriHeight = oriDimention.height;
x = x || dimention.x || 0;
y = y || dimention.y || 0;
width = width || dimention.width || oriWidth;
height = height || dimention.height || oriHeight;
scaleX = oriDimention.width ? width / oriWidth : 1;
scaleY = oriDimention.height ? height / oriHeight : 1;
utilLib.startTransform('translate(' + x + ',' + y + ') scale(' + scaleX + ',' + scaleY + ')', context);
context.save();
context.fillStyle = '#ffffff';
// Clearing the canvas for fresh rendering
context.fillRect(0, 0, width, height);
context.restore();
utilLib.storeImagesInArr(this);
utilLib.drawNodes([svg], [], this, context, function () {
typeof callback === 'function' && callback();
utilLib.resetTransform(context);
});
};
/************************** Draw Methods start ****************************
* Below are the functions that will be used for drawing the relative SVG
* elements on canvas
* function name should be like draw{tagName} for ex - for text element
* name will be drawtext.
* the function will get one argument the respective SVG with all Attributes
* required to draw it perfectly
***************************************************************************/
drawLib.common = function (node, attrib, svgDeCanvo, context, callBack) {
var children = node.childNodes,
fnName,
i,
callBackFn = function () {
//restore if any required
if (node.attributes) {
context.restore();
}
//cal the parent callback
callBack && callBack();
};
// do node specific work
for (i in attrib) {
if (!attrib.hasOwnProperty(i)) {
continue;
}
// Dont copy this parent attribute
if (attrib[i].name == 'class' || attrib[i].name == 'id') {
continue;
}
if (attrib[i].name == 'transform' || attrib[i].name == 'clip-path') {
continue;
}
// If attribute not exist copy parent attribute
if (typeof attrib[i] == 'object' && node.attributes && !node.attributes[attrib[i].name]) {
node.setAttribute([attrib[i].name], attrib[i].value);
}
}
// Include style attribute that are not present in the attribute list
if (node.attributes && node.attributes.style) {
styleArr = node.attributes.style.value.replace(/;$/, '').split(';');
for (i in styleArr) {
if (!styleArr.hasOwnProperty(i)) {
continue;
}
styleName = styleArr[i].split(':')[0].trim();
if (!node.attributes[styleName] || node.attributes[styleName].value == "undefined") {
// bypass the style element starting with -webkit
try {
node.setAttribute(styleName, styleArr[i].split(':')[1].trim());
} catch (e) {}
}
}
}
if (node.attributes) {
context.save();
if (node.attributes.transform) {
utilLib.startTransform(node.attributes.transform.value, context);
}
if (node.attributes['clip-path']) {
utilLib.applyClip(node.attributes['clip-path'].value, context, svgDeCanvo);
}
}
if (children.length === 0 || children.length === 1 && !children[0].tagName) {
if (typeof node.tagName !== 'undefined') {
fnName = 'draw' + node.tagName;
if (drawLib[fnName]) {
if (node.attributes.display && node.attributes.display.value == 'none') {
callBackFn();
} else {
drawLib[fnName](node, context, svgDeCanvo, 'draw', callBackFn);
}
} else {
callBackFn();
}
} else {
callBackFn();
}
} else {
utilLib.drawNodes(children, node.tagName == 'svg' ? [] : node.attributes, svgDeCanvo, context, callBackFn);
}
};
drawLib.drawtext = function (elem, context, svgDeCanvo, pps, callBackFn) {
// Internally calling the drawspan function
this.drawtspan(elem, context, svgDeCanvo, pps, callBackFn);
};
drawLib.drawtspan = function (elem, context, svgDeCanvo, pps, callBackFn) {
// innerHTML for chrome and firefox textContent for safari and IE
var text = elem.innerHTML || elem.textContent,
x = elem.attributes.x ? elem.attributes.x.value : 0,
y = elem.attributes.y ? elem.attributes.y.value : 0,
dx = elem.attributes.dx ? elem.attributes.dx.value : 0,
dy = elem.attributes.dy ? elem.attributes.dy.value : 0,
defFontFamily = 'serief',
defFontWeight = 'normal',
defFontSize = '16px',
bBox = [],
defCSSprop,
fontFamily,
fontWeight,
textAlign,
fontSize;
if (doc.getElementsByTagName('body')[0]) {
defCSSprop = win.getComputedStyle(doc.getElementsByTagName('body')[0], null);
if (defCSSprop.getPropertyValue('font-family')) {
defFontFamily = defCSSprop.getPropertyValue('font-family');
}
if (defCSSprop.getPropertyValue('font-weight')) {
defFontWeight = defCSSprop.getPropertyValue('font-weight');
}
if (defCSSprop.getPropertyValue('font-size')) {
defFontSize = defCSSprop.getPropertyValue('font-size');
}
}
fontFamily = elem.attributes['font-family'] ? elem.attributes['font-family'].value : defFontFamily, fontWeight = elem.attributes['font-weight'] ? elem.attributes['font-weight'].value : defFontWeight, textAlign = elem.attributes['text-anchor'] ? elem.attributes['text-anchor'].value : 'start', fontSize = elem.attributes['font-size'] ? elem.attributes['font-size'].value : defFontSize;
x = Number(x) + Number(dx);
y = Number(y) + Number(dy);
text = text.trim();
textAlign = textAlign == 'middle' ? 'center' : textAlign;
context.save();
context.font = fontWeight + " " + fontSize + " " + fontFamily;
context.textAlign = textAlign;
if (pps === 'draw') {
if (!elem.attributes.fill || elem.attributes.fill && elem.attributes.fill.value != 'none') {
utilLib.applyFillEffect(elem, context, svgDeCanvo, bBox);
context.fillText(text, x, y);
utilLib.endFillEffect(elem, context);
}
if (!elem.attributes.stroke || elem.attributes.stroke && elem.attributes.stroke.value != 'none') {
utilLib.applyStrokeEffect(elem, context, svgDeCanvo, bBox);
context.strokeText(text, x, y);
utilLib.endStrokeEffect(elem, context);
}
}
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
drawLib.drawcircle = function (elem, context, svgDeCanvo, pps, callBackFn) {
var cx = Number(elem.attributes.cx.value),
cy = Number(elem.attributes.cy.value),
r = Number(elem.attributes.r.value),
bBox = [];
context.beginPath();
context.arc(cx, cy, r, 0, Math.PI * 2);
utilLib.bBoxFromPoint([cx, cx * 1 + r * 1, cx * 1 - r * 1], [cy, cy * 1 + r * 1, cy * 1 - r * 1], bBox);
if (pps === 'draw') {
if (!elem.attributes.fill || elem.attributes.fill && elem.attributes.fill.value != 'none') {
utilLib.applyFillEffect(elem, context, svgDeCanvo, bBox);
context.fill();
utilLib.endFillEffect(elem, context);
}
if (!elem.attributes.stroke || elem.attributes.stroke && elem.attributes.stroke.value != 'none') {
utilLib.applyStrokeEffect(elem, context, svgDeCanvo, bBox);
context.stroke();
utilLib.endStrokeEffect(elem, context);
}
}
context.closePath();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
drawLib.drawrect = function (elem, context, svgDeCanvo, pps, callBackFn) {
var x = Number(elem.attributes.x.value),
y = Number(elem.attributes.y.value),
rx = elem.attributes.rx ? Number(elem.attributes.rx.value) : 0,
ry = elem.attributes.ry ? Number(elem.attributes.ry.value) : 0,
height = Number(elem.attributes.height.value),
width = Number(elem.attributes.width.value),
bBox = [];
utilLib.bBoxFromPoint([x, x + width], [y, y + height], bBox);
context.beginPath();
context.moveTo(x + rx, y);
context.lineTo(x + width - rx, y);
context.quadraticCurveTo(x + width, y, x + width, y + ry);
context.lineTo(x + width, y + height - ry);
context.quadraticCurveTo(x + width, y + height, x + width - rx, y + height);
context.lineTo(x + rx, y + height);
context.quadraticCurveTo(x, y + height, x, y + height - ry);
context.lineTo(x, y + ry);
context.quadraticCurveTo(x, y, x + rx, y);
if (pps === 'draw') {
if (!elem.attributes.fill || elem.attributes.fill && elem.attributes.fill.value != 'none') {
utilLib.applyFillEffect(elem, context, svgDeCanvo, bBox);
context.fill();
utilLib.endFillEffect(elem, context);
}
if (!elem.attributes.stroke || elem.attributes.stroke && elem.attributes.stroke.value != 'none') {
utilLib.applyStrokeEffect(elem, context, svgDeCanvo, bBox);
context.stroke();
utilLib.endStrokeEffect(elem, context);
}
}
context.closePath();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
drawLib.drawellipse = function (elem, context, svgDeCanvo, pps, callBackFn) {
var kappa = 0.5522848,
cx = Number(elem.attributes.cx.value),
cy = Number(elem.attributes.cy.value),
rx = Number(elem.attributes.rx.value),
ry = Number(elem.attributes.ry.value),
ox = rx * kappa,
oy = ry * kappa,
xe = cx + rx,
ye = cy + ry,
bBox = [];
context.beginPath();
context.moveTo(cx - rx, cy);
context.bezierCurveTo(cx - rx, cy - oy, cx - ox, cy - ry, cx, cy - ry);
context.bezierCurveTo(cx + ox, cy - ry, xe, cy - oy, xe, cy);
context.bezierCurveTo(xe, cy + oy, cx + ox, ye, cx, ye);
context.bezierCurveTo(cx - ox, ye, cx - rx, cy + oy, cx - rx, cy);
utilLib.bBoxFromPoint([cx + rx, cx - rx], [cy + ry, cy - ry], bBox);
if (pps === 'draw') {
if (!elem.attributes.fill || elem.attributes.fill && elem.attributes.fill.value != 'none') {
utilLib.applyFillEffect(elem, context, svgDeCanvo, bBox);
context.fill();
utilLib.endFillEffect(elem, context);
}
if (!elem.attributes.stroke || elem.attributes.stroke && elem.attributes.stroke.value != 'none') {
utilLib.applyStrokeEffect(elem, context, svgDeCanvo, bBox);
context.stroke();
utilLib.endStrokeEffect(elem, context);
}
}
context.closePath();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
drawLib.drawimage = function (elem, context, svgDeCanvo, pps, callBackFn) {
var x = elem.attributes.x ? Number(elem.attributes.x.value) : 0,
y = elem.attributes.y ? Number(elem.attributes.y.value) : 0,
height = elem.attributes.height ? Number(elem.attributes.height.value) : 0,
width = elem.attributes.width ? Number(elem.attributes.width.value) : 0,
imgUrl,
imageArr = svgDeCanvo._getStore('imageArr');
context.save();
if (elem.attributes.opacity) {
context.globalAlpha = elem.attributes.opacity.value;
}
if (elem.attributes['xlink:href']) {
imgUrl = elem.attributes['xlink:href'].value;
if (imageArr[imgUrl].status === 'complete') {
context.drawImage(imageArr[imgUrl].obj, x, y, width, height);
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
} else if (imageArr[imgUrl].status === 'error') {
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
} else if (imageArr[imgUrl].status === 'progress') {
imageArr[imgUrl].callback = function () {
context.drawImage(imageArr[imgUrl].obj, x, y, width, height);
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
imageArr[imgUrl].errCallback = function () {
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
};
} else {
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
}
} else {
context.globalAlpha = 1;
context.restore();
if (typeof callBackFn === 'function') {
callBackFn();
}
}
};
/*
* method for drawing the path attribute of SVG onto the canvas
* @param {attribute object} elem - the object containing all the attribute required
* the drawing purpose.
*/
drawLib.drawpath = function (elem, context, svgDeCanvo, pps, callBackFn) {
var subPath = elem.attributes.d.value.match(/[a-z][^a-z"]*/ig),
bBox = [],
a,
cmdName,
cmdDetails,
cx = 0,
cy = 0,
i;
context.beginPath();
// The switch statement decide which part to draw.
for (a in subPath) {
if (!subPath.hasOwnProperty(a)) {
continue;
}
cmdName = subPath[a].substring(0, 1);
cmdDetails = utilLib.getArgsAsArray(subPath[a].substring(1, subPath[a].length));
switch (cmdName) {
case 'M':
cx = Number(cmdDetails[0]);
cy = Number(cmdDetails[1]);
context.moveTo(cx, cy);
break;
case 'm':
cx += Number(cmdDetails[0]);
cy += Number(cmdDetails[1]);
context.moveTo(cx, cy);
break;
case 'L':
for (i = 0; cmdDetails[i]; i += 2) {
utilLib.bBoxFromPoint([cx, cmdDetails[i]], [cy, cmdDetails[i + 1]], bBox);
cx = Number(cmdDetails[i]);
cy = Number(cmdDetails[i + 1]);
context.lineTo(cx, cy);
}
break;
case 'l':
for (i = 0; cmdDetails[i]; i += 2) {
utilLib.bBoxFromPoint([cx, cx * 1 + 1 * cmdDetails[i]], [cy, cy * 1 + 1 * cmdDetails[i + 1]], bBox);
cx += Number(cmdDetails[i]);
cy += Number(cmdDetails[i + 1]);
context.lineTo(cx, cy);
}
break;
case 'V':
for (i = 0; cmdDetails[i]; i += 1) {
utilLib.bBoxFromPoint([cx], [cy, cmdDetails[i]], bBox);
cy = Number(cmdDetails[i]);
context.lineTo(cx, cy);
}
break;
case 'v':
for (i = 0; cmdDetails[i]; i += 1) {
utilLib.bBoxFromPoint([cx], [cy, cy * 1 + 1 * cmdDetails[i]], bBox);
cy += Number(cmdDetails[i]);
context.lineTo(cx, cy);
}
break;
case 'H':
for (i = 0; cmdDetails[i]; i += 1) {
utilLib.bBoxFromPoint([cx, cmdDetails[i]], [cy], bBox);
cx = Number(cmdDetails[i]);
context.lineTo(cx, cy);
}
break;
case 'h':
for (i = 0; cmdDetails[i]; i += 1) {
utilLib.bBoxFromPoint([cx, cx * 1 + 1 * cmdDetails[i]], [cy], bBox);
cx += Number(cmdDetails[i]);
context.lineTo(cx, cy);
}
break;
case 'Q':
for (i = 0; cmdDetails[i]; i += 4) {
utilLib.qBezierBBox(cx, cy, cmdDetails[i], cmdDetails[i + 1], cmdDetails[i + 2], cmdDetails[i + 3], bBox);
context.quadraticCurveTo(Number(cmdDetails[i]), Number(cmdDetails[i + 1]), Number(cmdDetails[i + 2]), Number(cmdDetails[i + 3]));
cx = Number(cmdDetails[i + 2]);
cy = Number(cmdDetails[i + 3]);
}
break;
case 'q':
for (i = 0; cmdDetails[i]; i += 4) {
utilLib.qBezierBBox(cx, cy, cx + 1 * cmdDetails[i], cy + 1 * cmdDetails[i + 1], cx * 1 + 1 * cmdDetails[i + 2], cy * 1 + 1 * cmdDetails[i + 3], bBox);
context.quadraticCurveTo(cx + 1 * cmdDetails[i], cy + 1 * cmdDetails[i + 1], cx += Number(cmdDetails[i + 2]), cy += Number(cmdDetails[i + 3]));
}
break;
case 'C':
for (i = 0; cmdDetails[i]; i += 6) {
utilLib.cBezierBBox(cx, cy, cmdDetails[i], cmdDetails[i + 1], cmdDetails[i + 2], cmdDetails[i + 3], cmdDetails[i + 4], cmdDetails[i + 5], bBox);
context.bezierCurveTo(cmdDetails[i], cmdDetails[i + 1], cmdDetails[i + 2], cmdDetails[i + 3], cmdDetails[i + 4], cmdDetails[i + 5]);
cx = Number(cmdDetails[i + 4]);
cy = Number(cmdDetails[i + 5]);
}
break;
case 'c':
for (i = 0; cmdDetails[i]; i += 6) {
utilLib.cBezierBBox(cx, cy, cx + 1 * cmdDetails[i], cy * 1 + 1 * cmdDetails[i + 1], cx + 1 * cmdDetails[i + 2], cy * 1 + 1 * cmdDetails[i + 3], cx + 1 * cmdDetails[i + 4], cy * 1 + 1 * cmdDetails[i + 5], bBox);
context.bezierCurveTo(cx + Number(cmdDetails[i]), cy + Number(cmdDetails[i + 1]), cx + Number(cmdDetails[i + 2]), cy + Number(cmdDetails[i + 3]), cx += Number(cmdDetails[i + 4]), cy += Number(cmdDetails[i + 5]));
}
break;
case 'a':
case 'A':
for (i = 0; cmdDetails[i]; i += 7) {
var rx = Number(cmdDetails[i]),
ry = Number(cmdDetails[i + 1]),
xAngle,
aFlag,
sFlag,
ex,
ey,
x1,
y1,
signValue,
s2sqrt,
centx1,
centy1,
centx,
centy,
startAngle,
dAngle,
rErrFlag,
radius,
xShift,
yShift;
// Converting to radian
xAngle = Number(cmdDetails[i + 2]) * (Math.PI / 180);
aFlag = Number(cmdDetails[i + 3]);
sFlag = Number(cmdDetails[i + 4]);
ex = Number(cmdDetails[i + 5]);
ey = Number(cmdDetails[i + 6]);
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
// Calculation are based on the above link
// Step 1
x1 = Math.cos(xAngle) * (cx - ex) / 2 + Math.sin(xAngle) * (cy - ey) / 2;
y1 = -Math.sin(xAngle) * (cx - ex) / 2 + Math.cos(xAngle) * (cy - ey) / 2;
// moding the radius value
rx = rx < 0 ? -rx : rx;
ry = ry < 0 ? -ry : ry;
rErrFlag = Math.pow(x1, 2) / Math.pow(rx, 2) + Math.pow(y1, 2) / Math.pow(ry, 2);
if (rErrFlag > 1) {
rx *= Math.sqrt(rErrFlag);
ry *= Math.sqrt(rErrFlag);
}
radius = rx > ry ? rx : ry;
xShift = rx > ry ? 1 : rx / ry;
yShift = rx > ry ? ry / rx : 1;
// Step 2
signValue = aFlag == sFlag ? -1 : 1;
// Take the square root part as an variable
s2sqrt = signValue * Math.sqrt((Math.pow(rx, 2) * Math.pow(ry, 2) - Math.pow(rx, 2) * Math.pow(y1, 2) - Math.pow(ry, 2) * Math.pow(x1, 2)) / (Math.pow(rx, 2) * Math.pow(y1, 2) + Math.pow(ry, 2) * Math.pow(x1, 2)));
if (isNaN(s2sqrt)) {
s2sqrt = 0;
}
centx1 = s2sqrt * (rx * y1) / ry;
centy1 = -s2sqrt * (ry * x1) / rx;
// Step 3
centx = centx1 * Math.cos(xAngle) - centy1 * Math.sin(xAngle) + (cx + ex) / 2;
centy = centx1 * Math.sin(xAngle) + centy1 * Math.cos(xAngle) + (cy + ey) / 2;
// Step 4 computing the Angles
startAngle = utilLib.angleBetweenVectors(1, 0, (x1 - centx1) / rx, (y1 - centy1) / ry);
dAngle = utilLib.angleBetweenVectors((x1 - centx1) / rx, (y1 - centy1) / ry, (-x1 - centx1) / rx, (-y1 - centy1) / ry);
// Moding the end angle
if (sFlag === 0 && dAngle > 0) {
dAngle -= 360 * (Math.PI / 180);
}
if (sFlag == 1 && dAngle < 0) {
dAngle += 360 * (Math.PI / 180);
}
// Check the condition for radius
if (rx === 0 && ry === 0) {
context.lineTo(ex, ey);
break;
}
context.save();
/*context.translate( centx, centy );
context.rotate( xAngle );
context.scale(xShift, yShift);*/
var transformMatrix = utilLib.combineTransformMatrix([[1, 0, centx, 0, 1, centy], [Math.cos(xAngle), Math.sin(xAngle), 0, Math.sin(xAngle), Math.cos(xAngle), 0], [xShift, 0, 0, 0, yShift, 0]]);
context.transform(transformMatrix[0], transformMatrix[3], transformMatrix[1], transformMatrix[4], transformMatrix[2], transformMatrix[5]);
context.arc(0, 0, radius, startAngle, startAngle + dAngle, 1 - sFlag);
context.restore();
utilLib.arcBBox(0, 0, radius, startAngle, startAngle + dAngle, 1 - sFlag, [transformMatrix[0], transformMatrix[3], transformMatrix[1], transformMatrix[4], transformMatrix[2], transformMatrix[5]], bBox);
//utilLib.bBoxFromPoint([cx, ex], [cy, ey], bBox);
if (cmdName == 'A') {
cx = Number(cmdDetails[i + 5]);
cy = Number(cmdDetails[i + 6]);
} else {
cx += Number(cmdDetails[i + 5]);
cy += Number(cmdDetails[i + 6]);
}
}
break;
case 'Z':
case 'z':
context.closePath();
break;
default:
}
}
if (pps === 'draw') {
if (!elem.attributes.fill || elem.attributes.fill && elem.attributes.fill.value != 'none') {
utilLib.applyFillEffect(elem, context, svgDeCanvo, bBox);
context.fill();
utilLib.endFillEffect(elem, context);
}
if (!elem.attributes.stroke || elem.attributes.stroke && elem.attributes.stroke.value != 'none') {
utilLib.applyStrokeEffect(elem, context, svgDeCanvo, bBox);
context.stroke();
utilLib.endStrokeEffect(elem, context);
}
callBackFn();
}
};
/************************** Draw Methods End ****************************/
/************************** Support Methods start *************************
* Below are the functions that will be usefull for drawing
* All reusuable method stays here
***************************************************************************/
utilLib.drawNodes = function (nodeArr, attrib, svgDeCanvo, context, callBack) {
var l = nodeArr.length,
i = -1,
dx = 0,
dy = 0,
callBackFn = function () {
var node;
i = i + 1;
if (i < l) {
node = nodeArr[i];
if (node.tagName && node.tagName === 'defs') {
i = i + 1;
node = nodeArr[i];
}
if (node.attributes) {
// adjusting the dx dy
if (node.attributes.dy) {
dy = node.attributes.dy.value = node.attributes.dy.value * 1 + dy * 1;
}
// adjusting the dx dy
if (node.attributes.dx) {
dx = node.attributes.dx.value = node.attributes.dx.value * 1 + dx * 1;
}
}
drawLib.common(node, attrib, svgDeCanvo, context, callBackFn);
} else {
callBack && callBack();
}
};
callBackFn();
};
utilLib.getSvgDimention = function (svg) {
var ret = {
width: 0,
height: 0
};
node = svg.childNodes && svg.childNodes[0] && svg.childNodes[0].attributes;
ret.width = Number(node.width && node.width.value || 0);
ret.height = Number(node.height && node.height.value || 0);
return ret;
};
utilLib.storeImagesInArr = function (svgDeCanvo) {
var svg = svgDeCanvo.getSVG(),
imgElem,
imgUrl,
imageArr,
i;
imageArr = svgDeCanvo._getStore('imageArr');
imgElem = svg.getElementsByTagName('image');
for (i in imgElem) {
if (!imgElem.hasOwnProperty(i)) {
continue;
}
if (imgElem[i].attributes && imgElem[i].attributes['xlink:href']) {
imgUrl = imgElem[i].attributes['xlink:href'].value;
if (!imageArr[imgUrl]) {
imageArr[imgUrl] = [];
imageArr[imgUrl].status = 'progress';
imageArr[imgUrl].callback = null;
imageArr[imgUrl].obj = new Image();
imageArr[imgUrl].obj.onload = function (imgUrl) {
return function () {
var callback = imageArr[imgUrl].callback;
if (callback) {
imageArr[imgUrl].status = 'complete';
callback();
} else {
imageArr[imgUrl].status = 'complete';
}
};
}(imgUrl);
imageArr[imgUrl].obj.onerror = function (imgUrl) {
return function () {
var errCallback = imageArr[imgUrl].errCallback;
if (errCallback) {
imageArr[imgUrl].status = 'error';
errCallback();
} else {
imageArr[imgUrl].status = 'error';
}
};
}(imgUrl);
imageArr[imgUrl].obj.src = imgUrl;
}
}
}
};
/*
* Method to handle the various transformation
* @param {Context} ctx - the context of the canvas where to apply the transformation
* @param {data} the data that contain the transformation information-
* data can be like matrix(1,0,0,1,230,345) rotate(34) seperated by coma.
* @TODO - support the other transformation methods
*/
utilLib.startTransform = function (data, context) {
var prevArgs = [],
t = data.match(/[^\s][a-z,0-9.\-(\s]+\)/gi),
a = 1,
b = 0,
c = 0,
d = 1,
e = 0,
f = 0,
args,
matArr = [],
i;
// Loop through every transformation
for (i in t) {
if (!t.hasOwnProperty(i)) {
continue;
}
if (t[i].indexOf("matrix") > -1) {
args = utilLib.stringToArgs(t[i]);
context.transform(args[0], args[1], args[2], args[3], args[4], args[5]);
}
if (t[i].indexOf("translate") > -1) {
args = utilLib.stringToArgs(t[i]);
context.translate(args[0] || 0, args[1] || 0);
}
if (t[i].indexOf("rotate") > -1) {
args = utilLib.stringToArgs(t[i]);
if (args.length == 3) {
context.translate(args[1], args[2]);
context.rotate(args[0] * (Math.PI / 180));
context.translate(-args[1], -args[2]);
} else {
context.rotate(args[0] * (Math.PI / 180));
}
}
if (t[i].indexOf("scale") > -1) {
args = utilLib.stringToArgs(t[i]);
if (args.length == 1) {
context.scale(args[0] || 1, args[0] || 1);
} else {
context.scale(args[0] || 1, args[1] || 1);
}
}
if (t[i].indexOf("skewX") > -1) {
args = utilLib.stringToArgs(t[i]);
context.transform(1, 0, Math.tan(args[0] * (Math.PI / 180)), 1, 0, 0);
}
if (t[i].indexOf("skewY") > -1) {
args = utilLib.stringToArgs(t[i]);
context.transform(1, Math.tan(args[0] * (Math.PI / 180)), 0, 1, 0, 0);
}
}
};
/*
* Method that restore the canvas to its original position
*/
utilLib.resetTransform = function (context) {
context.setTransform(1, 0, 0, 1, 0, 0);
};
/*
* Method that give argument from a function type definition
* ex - for string function( abc, def ) this function will return
* abc and def in an array.
* @param {string} data - the striing from which the args to be extracted.
*/
utilLib.stringToArgs = function (data) {
var insideBracket = /\(([^\)]+)/.exec(data)[1];
return utilLib.getArgsAsArray(insideBracket);
};
/*
* Method that return coma or space seperated string as array.
* @param {atring} data - the string from which the ars should be extracted
*/
utilLib.getArgsAsArray = function (data) {
var i;
data = data.trim().split(/[\s,]+/);
for (i = 0; i < data.length; i++) {
data[i].trim();
if (data[i].length === 0) {
data.splice(i, 1);
}
}
return data;
};
utilLib.applyFillEffect = function (elem, context, svgDeCanvo, bBox) {
var fillValue;
if (elem.attributes['fill-opacity'] && elem.attributes['fill-opacity'].value != 'none') {
context.globalAlpha = elem.attributes['fill-opacity'].value;
} else {
context.globalAlpha = 1;
}
if (elem.attributes.fill && elem.attributes.fill.value.indexOf("url(") > -1) {
fillValue = utilLib.getFillStyleById(/url\(.*#([^\)'"]+)/.exec(elem.attributes.fill.value)[1], context, svgDeCanvo, bBox);
context.fillStyle = fillValue;
} else {
if (elem.attributes.fill) {
context.fillStyle = elem.attributes.fill.value;
} else {
context.fillStyle = '#000000';
}
}
};
utilLib.endFillEffect = function (elem, context) {
context.globalAlpha = 1;
};
utilLib.applyStrokeEffect = function (elem, context, svgDeCanvo, bBox) {
if (elem.attributes['stroke-opacity'] && elem.attributes['stroke-opacity'].value != 'none') {
context.globalAlpha = elem.attributes['stroke-opacity'].value;
}
if (elem.attributes['stroke-width']) {
context.lineWidth = elem.attributes['stroke-width'].value;
if (elem.attributes['stroke-width'].value === 0) {
context.globalAlpha = 0;
}
}
if (elem.attributes['stroke-linecap'] && elem.attributes['stroke-linecap'].value != 'none') {
context.lineCap = elem.attributes['stroke-linecap'].value;
}
if (elem.attributes['stroke-linejoin'] && elem.attributes['stroke-linejoin'].value != 'none') {
context.lineJoin = elem.attributes['stroke-linejoin'].value;
}
if (elem.attributes['stroke-dasharray'] && elem.attributes['stroke-dasharray'].value != 'none' && context.setLineDash) {
context.setLineDash(utilLib.getArgsAsArray(elem.attributes['stroke-dasharray'].value));
}
if (elem.attributes.stroke) {
context.strokeStyle = elem.attributes.stroke.value;
} else {
context.strokeStyle = '#000000';
}
};
utilLib.endStrokeEffect = function (elem, context) {
if (elem.attributes['stroke-opacity'] && elem.attributes['stroke-opacity'].value != 'none') {
context.globalAlpha = 1;
if (context.setLineDash) {
context.setLineDash([]);
}
context.lineWidth = 1;
}
context.globalAlpha = 1;
};
utilLib.applyClip = function (id, context, svgDeCanvo) {
var svg = svgDeCanvo.getSVG(),
elemId,
elem,
chldrn,
a,
fncName;
if (id.indexOf("url(") === -1) {
return;
}
elemId = /url\(.*#([^\)'"]+)/.exec(id)[1];
elem = svg.getElementById(elemId);
if (elem.attributes) {
context.save();
if (elem.attributes.transform) {
utilLib.startTransform(elem.attributes.transform.value, context);
}
}
chldrn = elem.childNodes;
for (a in chldrn) {
if (!chldrn.hasOwnProperty(a)) {
continue;
}
if (!chldrn[a].tagName) {
continue;
}
if (chldrn[a].constructor !== Array) {
fncName = "draw" + chldrn[a].tagName;
if (chldrn[a].attributes) {
context.save();
if (chldrn[a].attributes.transform) {
utilLib.startTransform(chldrn[a].attributes.transform.value, context);
}
}
if (drawLib[fncName]) {
drawLib[fncName](chldrn[a], context, svgDeCanvo, 'clip');
context.closePath();
}
if (chldrn[a].attributes) {
context.restore();
}
}
}
if (elem.attributes) {
context.restore();
}
context.clip();
};
utilLib.getFillStyleById = function (id, context, svgDeCanvo, bBox) {
var svg = svgDeCanvo.getSVG(),
gradElem = svg.getElementById(id);
/*context.strokeRect(bBox.xMin, bBox.yMin, bBox.xMax - bBox.xMin,
bBox.yMax - bBox.yMin);*/
if (gradElem.tagName == 'linearGradient') {
return utilLib.getLinearGradient(gradElem, context, bBox);
}
if (gradElem.tagName == 'radialGradient') {
return utilLib.getRadialGradient(gradElem, context, bBox);
}
return '#FFFFFF';
};
utilLib.getLinearGradient = function (element, context, bBox) {
var sx = element.attributes.x1 ? utilLib.getPercentValue(element.attributes.x1.value, bBox.xMax - bBox.xMin, bBox.xMin) : 0,
sy = element.attributes.y1 ? utilLib.getPercentValue(element.attributes.y1.value, bBox.yMax - bBox.yMin, bBox.yMin) : 0,
ex = element.attributes.x2 ? utilLib.getPercentValue(element.attributes.x2.value, bBox.xMax - bBox.xMin, bBox.xMin) : 0,
ey = element.attributes.y2 ? utilLib.getPercentValue(element.attributes.y2.value, bBox.yMax - bBox.yMin, bBox.yMin) : 0,
lingrad,
children,
a,
color,
opacity;
linGrad = context.createLinearGradient(sx, sy, ex, ey);
children = element.childNodes;
for (a in children) {
if (!children.hasOwnProperty(a)) {
continue;
}
if (children[a].attributes && children[a].attributes['stop-color']) {
color = utilLib.toRGB(children[a].attributes['stop-color'].value);
opacity = children[a].attributes['stop-opacity'] ? children[a].attributes['stop-opacity'].value : 1;
if (color.status) {
linGrad.addColorStop(utilLib.getPercentValue(children[a].attributes.offset.value, 1, 0), 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + Number(opacity) + ')');
} else {
linGrad.addColorStop(utilLib.getPercentValue(children[a].attributes.offset.value, 1, 0), children[a].attributes['stop-color'].value);
}
}
}
return linGrad;
};
utilLib.getRadialGradient = function (element, context, bBox) {
var cx = element.attributes.cx ? utilLib.getPercentValue(element.attributes.cx.value, bBox.xMax - bBox.xMin, bBox.xMin) : bBox.xMin + (bBox.xMax - bBox.xMin) * 0.5,
cy = element.attributes.cy ? utilLib.getPercentValue(element.attributes.cy.value, bBox.yMax - bBox.yMin, bBox.yMin) : bBox.yMin + (bBox.yMax - bBox.yMin) * 0.5,
fx = element.attributes.fx ? utilLib.getPercentValue(element.attributes.fx.value, bBox.xMax - bBox.xMin, bBox.xMin) : bBox.xMin + (bBox.xMax - bBox.xMin) * 0.5,
fy = element.attributes.fy ? utilLib.getPercentValue(element.attributes.fy.value, bBox.yMax - bBox.yMin, bBox.yMin) : bBox.yMin + (bBox.yMax - bBox.yMin) * 0.5,
r = element.attributes.r ? utilLib.getPercentValue(element.attributes.r.value, (bBox.yMax - bBox.yMin + bBox.xMax - bBox.xMin) / 2, 0) : utilLib.getPercentValue('50%', (bBox.yMax - bBox.yMin + bBox.xMax - bBox.xMin) / 2, 0),
radGrad,
children,
a,
color,
opacity;
radGrad = context.createRadialGradient(fx, fy, 0, cx, cy, r);
children = element.childNodes;
for (a in children) {
if (!children.hasOwnProperty(a)) {
continue;
}
if (children[a].attributes && children[a].attributes['stop-color']) {
color = utilLib.toRGB(children[a].attributes['stop-color'].value);
opacity = children[a].attributes['stop-opacity'] ? children[a].attributes['stop-opacity'].value : 1;
if (color.status) {
radGrad.addColorStop(utilLib.getPercentValue(children[a].attributes.offset.value, 1, 0), 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + Number(opacity) + ')');
} else {
radGrad.addColorStop(utilLib.getPercentValue(children[a].attributes.offset.value, 1, 0), children[a].attributes['stop-color'].value);
}
}
}
return radGrad;
};
utilLib.getPercentValue = function (percent, value, correction) {
var mVal;
if (percent.indexOf('%') != -1) {
mVal = /(\d.*)%/.exec(percent)[1];
if (mVal > 100) {
mVal = 100;
}
return mVal * value / 100 + correction * 1;
} else {
if (percent > 1) {
return percent;
}
return percent * value + correction * 1;
}
};
utilLib.bBoxFromPoint = function (xPointArr, yPointArr, bBox) {
if (typeof bBox.xMin !== 'undefined') {
xPointArr.push(bBox.xMin, bBox.xMax);
yPointArr.push(bBox.yMin, bBox.yMax);
}
bBox.xMin = Math.min.apply(this, xPointArr);
bBox.xMax = Math.max.apply(this, xPointArr);
bBox.yMin = Math.min.apply(this, yPointArr);
bBox.yMax = Math.max.apply(this, yPointArr);
};
/*
* Method to compute the bounding box but its not working as expected
*/
utilLib.arcBBox = function (cx, cy, r, sa, ea, cc, transform, bBox) {
var rsa, rea, startArcX, endArcX, startArcY, endArcY, xMin, yMin, xMax, yMax, xArr, yArr, isBetween;
if (transform instanceof Array) {
cx = cx * transform[0] + cx * transform[2] + transform[4];
cy = cy * transform[1] + cy * transform[3] + transform[5];
}
isBetween = function (start, end, angle) {
// making the start angle and end angle negative
start = (start + 2 * Math.PI) % (2 * Math.PI);
end = (end + 2 * Math.PI) % (2 * Math.PI);
if (start <= end) {
if (start <= angle && angle <= end) {
return true;
} else {
return false;
}
} else if (start >= end) {
if (start >= angle && angle >= end) {
return false;
} else {
return true;
}
}
};
rsa = sa % (2 * Math.PI);
rea = ea % (2 * Math.PI);
if (cc) {
rsa = ea % (2 * Math.PI);
rea = sa % (2 * Math.PI);
}
startArcX = cx + r * Math.cos(rsa);
startArcY = cy + r * Math.sin(rsa);
endArcX = cx + r * Math.cos(rea);
endArcY = cy + r * Math.sin(rea);
xArr = [startArcX, endArcX];
yArr = [startArcY, endArcY];
if (isBetween(rsa, rea, 0)) {
xArr.push(cx * 1 + r * 1);
yArr.push(cy);
}
if (isBetween(rsa, rea, 0.5 * Math.PI)) {
xArr.push(cx);
yArr.push(cy * 1 + r * 1);
}
if (isBetween(rsa, rea, Math.PI)) {
xArr.push(cx - r * 1);
yArr.push(cy);
}
if (isBetween(rsa, rea, 1.5 * Math.PI)) {
xArr.push(cx);
yArr.push(cy - r * 1);
}
xMax = Math.max.apply(this, xArr);
xMin = Math.min.apply(this, xArr);
yMax = Math.max.apply(this, yArr);
yMin = Math.min.apply(this, yArr);
if (typeof bBox.xMin !== 'undefined') {
bBox.xMin = Math.min(xMin, bBox.xMin);
bBox.xMax = Math.max(xMax, bBox.xMax);
bBox.yMin = Math.min(yMin, bBox.yMin);
bBox.yMax = Math.max(yMax, bBox.yMax);
} else {
bBox.xMin = xMin;
bBox.xMax = xMax;
bBox.yMin = yMin;
bBox.yMax = yMax;
}
};
/*
* Method for calculating the bounding box for quadratic bezier curves
* @param {co-ordinate point} sx - starting x coordinate
* @param {co-ordinate point} sy - starting y coordinate
* @param {co-ordinate point} cx - first control x
* @param {co-ordinate point} cy - first control y
* @param {co-ordinate point} ex - end x coordinate
* @param {co-ordinate point} ey - end y coordinate
*/
utilLib.qBezierBBox = function (sx, sy, cx, cy, ex, ey, bBox) {
var txd = sx * 1.0 - 2 * cx + ex * 1.0,
tyd = sy * 1.0 - 2 * cy + ey * 1.0,
tx,
ty,
xMin,
yMin,
xMax,
yMax,
curveX,
curveY;
/*context.beginPath();
context.moveTo(sx,sy)
for (t = 0 ; t <= 10; t++) {
context.lineTo(sx * Math.pow(1 - (t/10), 2) + 2 * cx * (1 - (t/10)) * (t/10) + ex * Math.pow((t/10), 2),
sy * Math.pow(1 - (t/10), 2) + 2 * cy * (1 - (t/10)) * (t/10) + ey * Math.pow((t/10), 2))
}
context.stroke();*/
if (txd === 0 || tyd === 0) {
xMax = Math.max(sx, ex);
xMin = Math.min(sx, ex);
yMax = Math.max(sy, ey);
yMin = Math.min(sy, ey);
} else {
tx = (sx - cx) / txd;
ty = (sy - cy) / tyd;
curveX = sx * Math.pow(1 - tx, 2) + 2 * cx * (1 - tx) * tx + ex * Math.pow(tx, 2);
curveY = sy * Math.pow(1 - ty, 2) + 2 * cy * (1 - ty) * ty + ey * Math.pow(ty, 2);
xMax = Math.max(sx, ex, curveX);
xMin = Math.min(sx, ex, curveX);
yMax = Math.max(sy, ey, curveY);
yMin = Math.min(sy, ey, curveY);
}
if (typeof bBox.xMin !== 'undefined') {
bBox.xMin = Math.min(xMin, bBox.xMin);
bBox.xMax = Math.max(xMax, bBox.xMax);
bBox.yMin = Math.min(yMin, bBox.yMin);
bBox.yMax = Math.max(yMax, bBox.yMax);
} else {
bBox.xMin = xMin;
bBox.xMax = xMax;
bBox.yMin = yMin;
bBox.yMax = yMax;
}
};
/*
* Method for calculating the bounding box for cubic bezier curves
* @param {co-ordinate point} sx - starting x coordinate
* @param {co-ordinate point} sy - starting y coordinate
* @param {co-ordinate point} cx - first control x
* @param {co-ordinate point} cy - first control y
* @param {co-ordinate point} c1x - second control x
* @param {co-ordinate point} c1y - second control y
* @param {co-ordinate point} ex - end x coordinate
* @param {co-ordinate point} ey - end y coordinate
*/
utilLib.cBezierBBox = function (sx, sy, cx, cy, c1x, c1y, ex, ey, bBox) {
var xMin, xMax, yMin, yMax, a, b, c, root, t1, t2, calculateBound, xTemp, yTemp;
// Converting the quadratic curve points to cubic curve points
if (c1x === null && c1y === null) {
cx = sx + 2.0 / 3.0 * (cx - sx);
c1x = sy + 2.0 / 3.0 * (cy - sy);
cy = cx + 1.0 / 3.0 * (ex - sx);
c1y = c1x + 1.0 / 3.0 * (ey - sy);
}
// http://pomax.nihongoresources.com/pages/bezier/
// details formula
calculateBound = function (a, b, c, d, t) {
return a * Math.pow(1 - t, 3) + 3 * b * t * Math.pow(1 - t, 2) + 3 * c * t * t * (1 - t) + d * t * t * t;
};
// For x coordinates
a = 3 * ex - 9 * c1x + 9 * cx - 3 * sx;
b = 6 * sx - 12 * cx + 6 * c1x;
c = 3 * cx - 3 * sx;
root = Math.pow(b, 2) - 4 * a * c;
xMin = sx;
xMax = sx;
if (ex < xMin) {
xMin = ex;
}
if (ex > xMax) {
xMax = ex;
}
if (root >= 0) {
t1 = (-b + Math.sqrt(root)) / (2 * a);
if (t1 > 0 && t1 < 1) {
xTemp = calculateBound(sx, cx, c1x, ex, t1);
if (xTemp < xMin) {
xMin = xTemp;
}
if (xTemp > xMax) {
xMax = xTemp;
}
}
t2 = (-b - Math.sqrt(root)