renaming snap plugin, svg contains text warning

This commit is contained in:
Teja 2015-05-26 13:20:44 +02:00
parent eb8d07f21e
commit 47d257cf24
4 changed files with 531 additions and 546 deletions

View file

@ -143,7 +143,7 @@ class SvgToGcodePlugin(octoprint.plugin.SlicerPlugin,
def get_assets(self):
return {
"js": [ "js/convert.js", "js/working_area.js", "js/gcode_parser.js", "js/lib/snap.svg-min.js", "js/bake_snap_plugin.js"],
"js": [ "js/convert.js", "js/working_area.js", "js/gcode_parser.js", "js/lib/snap.svg.js", "js/matrix_oven.js"],
"less": ["less/svgtogcode.less"],
"css": ["css/svgtogcode.css", "css/mrbeam.css"]
}

View file

@ -1,541 +0,0 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
Snap.plugin(function (Snap, Element, Paper, global) {
// Flattens transformations of element or it's children and sub-children
// toCubics: converts all segments to cubics
// toAbsolute: converts all segments to Absolute
// dec: number of digits after decimal separator
// Returns: no return value
Element.prototype.bake = function (toCubics, toAbsolute, rectAsArgs, dec) {
var elem = this;
if (!elem || !elem.paper) // don't handle unplaced elements. this causes double handling.
return;
if (typeof (rectAsArgs) === 'undefined')
rectAsArgs = false;
if (typeof (toCubics) === 'undefined')
toCubics = false;
if (typeof (toAbsolute) === 'undefined')
toAbsolute = false;
if (typeof (dec) === 'undefined')
dec = 5;
var children = elem.selectAll('*')
if (children.length > 0) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
child.bake(toCubics, toAbsolute, rectAsArgs, dec);
}
elem.attr({transform: ''});
return;
}
if (!(elem.type === "circle" ||
elem.type === "rect" ||
elem.type === "ellipse" ||
elem.type === "line" ||
elem.type === "polygon" ||
elem.type === "polyline" ||
elem.type === "path"))
return;
if(elem.type !== 'path'){
console.log("bake: converting " + elem.type + " to path");
}
var path_elem = elem.convertToPath(rectAsArgs);
if (!path_elem || path_elem.attr(d) === '')
return 'M 0 0';
// Rounding coordinates to dec decimals
if (dec || dec === 0) {
if (dec > 15)
dec = 15;
else if (dec < 0)
dec = 0;
}
else
dec = false;
function r(num) {
if (dec !== false)
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
else
return num;
}
var arr;
var d = path_elem.attr('d').trim();
// If you want to retain current path commans, set toCubics to false
if (!toCubics) { // Set to false to prevent possible re-normalization.
arr = Snap.parsePathString(d); // str to array
var arr_orig = arr;
arr = pathToAbsolute(arr); // mahvstcsqz -> uppercase
}
// If you want to modify path data using nonAffine methods,
// set toCubics to true
else {
arr = path2curve(d); // mahvstcsqz -> MC
var arr_orig = arr;
}
//var svgDOM = pathDOM.ownerSVGElement;
// Get the relation matrix that converts path coordinates
// to SVGroot's coordinate space
var transform = path_elem.transform();
var matrix = transform['totalMatrix'];
// The following code can bake transformations
// both normalized and non-normalized data
// Coordinates have to be Absolute in the following
var j,m = arr.length,
letter = '',
letter_orig = '',
x = 0,
y = 0,
point = {}, newcoords = [],
pt = {x: 0, y: 0},
subpath_start = {},
prevX = 0,
prevY = 0;
subpath_start.x = null;
subpath_start.y = null;
for (var i=0; i < m; i++) {
letter = arr[i][0].toUpperCase();
letter_orig = arr_orig[i][0];
newcoords[i] = [];
newcoords[i][0] = arr[i][0];
if (letter === 'A') {
x = arr[i][6];
y = arr[i][7];
pt.x = arr[i][6];
pt.y = arr[i][7];
newcoords[i] = arc_transform(arr[i][1], arr[i][2], arr[i][3], arr[i][4], arr[i][5], pt, matrix);
// rounding arc parameters
// x,y are rounded normally
// other parameters at least to 5 decimals
// because they affect more than x,y rounding
newcoords[i][1] = newcoords[i][1]; //rx
newcoords[i][2] = newcoords[i][2]; //ry
newcoords[i][3] = newcoords[i][3]; //x-axis-rotation
newcoords[i][6] = newcoords[i][6]; //x
newcoords[i][7] = newcoords[i][7]; //y
}
else if (letter !== 'Z') {
// parse other segs than Z and A
for (j = 1; j < arr[i].length; j = j + 2) {
if (letter === 'V')
y = arr[i][j];
else if (letter === 'H')
x = arr[i][j];
else {
x = arr[i][j];
y = arr[i][j + 1];
}
pt.x = x;
pt.y = y;
point.x = matrix.x(pt.x, pt.y);
point.y = matrix.y(pt.x, pt.y);
//point = pt.matrixTransform(matrix);
if (letter === 'V' || letter === 'H') {
newcoords[i][0] = 'L';
newcoords[i][j] = point.x;
newcoords[i][j + 1] = point.y;
}
else {
newcoords[i][j] = point.x;
newcoords[i][j + 1] = point.y;
}
}
}
if ((letter !== 'Z' && subpath_start.x === null) || letter === 'M') {
subpath_start.x = x;
subpath_start.y = y;
}
if (letter === 'Z') {
x = subpath_start.x;
y = subpath_start.y;
}
}
// Convert all that was relative back to relative
// This could be combined to above, but to make code more readable
// this is made separately.
var prevXtmp = 0;
var prevYtmp = 0;
subpath_start.x = '';
for (i = 0; i < newcoords.length; i++) {
letter_orig = arr_orig[i][0];
if (letter_orig === 'A' || letter_orig === 'M' || letter_orig === 'L' || letter_orig === 'C' || letter_orig === 'S' || letter_orig === 'Q' || letter_orig === 'T' || letter_orig === 'H' || letter_orig === 'V') {
var len = newcoords[i].length;
var lentmp = len;
if (letter_orig === 'A') {
newcoords[i][6] = r(newcoords[i][6]);
newcoords[i][7] = r(newcoords[i][7]);
}
else {
lentmp--;
while (--lentmp)
newcoords[i][lentmp] = r(newcoords[i][lentmp]);
}
prevX = newcoords[i][len - 2];
prevY = newcoords[i][len - 1];
}
else
if (letter_orig === 'a') {
prevXtmp = newcoords[i][6];
prevYtmp = newcoords[i][7];
newcoords[i][0] = letter_orig;
newcoords[i][6] = r(newcoords[i][6] - prevX);
newcoords[i][7] = r(newcoords[i][7] - prevY);
prevX = prevXtmp;
prevY = prevYtmp;
}
else
if (letter_orig === 'm' || letter_orig === 'l' || letter_orig === 'c' || letter_orig === 's' || letter_orig === 'q' || letter_orig === 't' || letter_orig === 'h' || letter_orig === 'v') {
var len = newcoords[i].length;
prevXtmp = newcoords[i][len - 2];
prevYtmp = newcoords[i][len - 1];
for (j = 1; j < len; j = j + 2) {
if (letter_orig === 'h' || letter_orig === 'v')
newcoords[i][0] = 'l';
else
newcoords[i][0] = letter_orig;
newcoords[i][j] = r(newcoords[i][j] - prevX);
newcoords[i][j + 1] = r(newcoords[i][j + 1] - prevY);
}
prevX = prevXtmp;
prevY = prevYtmp;
}
if ((letter_orig.toLowerCase() !== 'z' && subpath_start.x === '') || letter_orig.toLowerCase() === 'm') {
subpath_start.x = prevX;
subpath_start.y = prevY;
}
if (letter_orig.toLowerCase() === 'z') {
prevX = subpath_start.x;
prevY = subpath_start.y;
}
}
if (toAbsolute)
newcoords = pathToAbsolute(newcoords);
var d_str = convertToString(newcoords);
path_elem.attr({d: d_str});
path_elem.attr({transform: ''});
console.log("baked matrix ", matrix, " of ", path_elem.attr('id'));
};
var convertToString = function (arr) {
return arr.join(',').replace(p2s, '$1');
};
Element.prototype.convertToPath = function(rectAsArgs){
var old_element = this;
var pathAttr = old_element.toPath(rectAsArgs);
var path = old_element.paper.path(pathAttr);
old_element.before(path);
old_element.remove();
return path;
};
// Converts all shapes to path retaining attributes.
// old_element - DOM element to be replaced by path. Can be one of the following:
// ellipse, circle, path, line, polyline, polygon and rect.
// rectAsArgs - Boolean. If true, rect roundings will be as arcs. Otherwise as cubics.
// Return value: path element.
// Source: https://github.com/duopixel/Method-Draw/blob/master/editor/src/svgcanvas.js
// Modifications: Timo (https://github.com/timo22345)
Element.prototype.toPath = function (rectAsArgs) {
var old_element = this;
// Create new path element
var path = {};
// All attributes that path element can have
var attrs = ['requiredFeatures', 'requiredExtensions', 'systemLanguage', 'id', 'xml:base', 'xml:lang', 'xml:space', 'onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload', 'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'unicode-bidi', 'visibility', 'word-spacing', 'writing-mode', 'class', 'style', 'externalResourcesRequired', 'transform', 'd', 'pathLength'];
// Copy attributes of old_element to path
for(var attrIdx in attrs){
var attrName = attrs[attrIdx];
var attrValue = old_element.attr(attrName);
if (attrValue)
path[attrName] = attrValue;
}
var d = '';
var valid = function (val) {
return !(typeof (val) !== 'number' || val === Infinity || val < 0);
};
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81;
var tag = old_element.type;
switch (tag) {
case 'ellipse':
case 'circle':
var rx = +parseFloat(old_element.attr('rx')),
ry = +parseFloat(old_element.attr('ry')),
cx = +parseFloat(old_element.attr('cx')),
cy = +old_element.attr('cy');
if (tag === 'circle') {
rx = ry = +old_element.attr('r');
}
d += convertToString([
['M', (cx - rx), (cy)],
['C', (cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)],
['C', (cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)],
['C', (cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)],
['C', (cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)],
['Z']
]);
break;
case 'path':
d = old_element.attr('d');
break;
case 'line':
var x1 = parseFloat(old_element.attr('x1')),
y1 = parseFloat(old_element.attr('y1')),
x2 = parseFloat(old_element.attr('x2')),
y2 = old_element.attr('y2');
d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
break;
case 'polyline':
d = 'M' + old_element.attr('points');
break;
case 'polygon':
d = 'M' + old_element.attr('points') + 'Z';
break;
case 'rect':
var rx = + parseFloat(old_element.attr('rx')),
ry = +parseFloat(old_element.attr('ry')),
x = parseFloat(old_element.attr('x')),
y = parseFloat(old_element.attr('y')),
w = parseFloat(old_element.attr('width')),
h = parseFloat(old_element.attr('height'));
// Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
// If neither rx nor ry are properly specified, then set both rx and ry to 0. (This will result in square corners.)
if (!valid(rx) && !valid(ry))
rx = ry = 0;
// Otherwise, if a properly specified value is provided for rx, but not for ry, then set both rx and ry to the value of rx.
else if (valid(rx) && !valid(ry))
ry = rx;
// Otherwise, if a properly specified value is provided for ry, but not for rx, then set both rx and ry to the value of ry.
else if (valid(ry) && !valid(rx))
rx = ry;
else {
// If rx is greater than half of width, then set rx to half of width.
if (rx > w / 2)
rx = w / 2;
// If ry is greater than half of height, then set ry to half of height.
if (ry > h / 2)
ry = h / 2;
}
if (!rx && !ry) {
d += convertToString([
['M', x, y],
['L', x + w, y],
['L', x + w, y + h],
['L', x, y + h],
['L', x, y],
['Z']
]);
}
else if (rectAsArgs) {
d += convertToString([
['M', x + rx, y],
['H', x + w - rx],
['A', rx, ry, 0, 0, 1, x + w, y + ry],
['V', y + h - ry],
['A', rx, ry, 0, 0, 1, x + w - rx, y + h],
['H', x + rx],
['A', rx, ry, 0, 0, 1, x, y + h - ry],
['V', y + ry],
['A', rx, ry, 0, 0, 1, x + rx, y]
]);
}
else {
var num = 2.19;
if (!ry)
ry = rx;
d += convertToString([
['M', x, y + ry],
['C', x, y + ry / num, x + rx / num, y, x + rx, y],
['L', x + w - rx, y],
['C', x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry],
['L', x + w, y + h - ry],
['C', x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h],
['L', x + rx, y + h],
['C', x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry],
['L', x, y + ry],
['Z']
]);
}
break;
default:
//path.parentNode.removeChild(path);
break;
}
if (d)
path.d = d;
return path;
};
var pathToAbsolute = cacher(function (pathArray) {
//var pth = paths(pathArray); // Timo: commented to prevent multiple caching
// for some reason only FF proceed correctly
// when not cached using cacher() around
// this function.
//if (pth.abs) return pathClone(pth.abs)
if (!R.is(pathArray, 'array') || !R.is(pathArray && pathArray[0], 'array'))
pathArray = parsePathString(pathArray);
if (!pathArray || !pathArray.length)
return [['M', 0, 0]];
var res = [],
x = 0,
y = 0,
mx = 0,
my = 0,
start = 0;
if (pathArray[0][0] === 'M') {
x = +pathArray[0][1];
y = +pathArray[0][2];
mx = x;
my = y;
start++;
res[0] = ['M', x, y];
}
var crz = pathArray.length === 3 && pathArray[0][0] === 'M' && pathArray[1][0].toUpperCase() === 'R' && pathArray[2][0].toUpperCase() === 'Z';
for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
res.push(r = []);
pa = pathArray[i];
if (pa[0] !== String.prototype.toUpperCase.call(pa[0])) {
r[0] = String.prototype.toUpperCase.call(pa[0]);
switch (r[0]) {
case 'A':
r[1] = pa[1];
r[2] = pa[2];
r[3] = pa[3];
r[4] = pa[4];
r[5] = pa[5];
r[6] = +(pa[6] + x);
r[7] = +(pa[7] + y);
break;
case 'V':
r[1] = +pa[1] + y;
break;
case 'H':
r[1] = +pa[1] + x;
break;
case 'R':
var dots = [x, y]['concat'](pa.slice(1));
for (var j = 2, jj = dots.length; j < jj; j++) {
dots[j] = +dots[j] + x;
dots[++j] = +dots[j] + y;
}
res.pop();
res = res['concat'](catmullRom2bezier(dots, crz));
break;
case 'M':
mx = +pa[1] + x;
my = +pa[2] + y;
default:
for (j = 1, jj = pa.length; j < jj; j++)
r[j] = +pa[j] + (j % 2 ? x : y);
}
}
else {
if (pa[0] === 'R') {
dots = [x, y]['concat'](pa.slice(1));
res.pop();
res = res['concat'](catmullRom2bezier(dots, crz));
r = ['R']['concat'](pa.slice(-2));
}
else {
for (var k = 0, kk = pa.length; k < kk; k++)
r[k] = pa[k];
}
}
switch (r[0]) {
case 'Z':
x = mx;
y = my;
break;
case 'H':
x = r[1];
break;
case 'V':
y = r[1];
break;
case 'M':
mx = r[r.length - 2];
my = r[r.length - 1];
default:
x = r[r.length - 2];
y = r[r.length - 1];
}
}
res.toString = R._path2string;
//pth.abs = pathClone(res);
return res;
});
function cacher(f, scope, postprocessor) {
function newf() {
var arg = Array.prototype.slice.call(arguments, 0),
args = arg.join('\u2400');
var cache = newf.cache = newf.cache || {};
var count = newf.count = newf.count || [];
if (cache.hasOwnProperty(args)) {
for (var i = 0, ii = count.length; i < ii; i++)
if (count[i] === args) {
count.push(count.splice(i, 1)[0]);
}
return postprocessor ? postprocessor(cache[args]) : cache[args];
}
count.length >= 1E3 && delete cache[count.shift()];
count.push(args);
cache[args] = f.apply(scope, arg);
return postprocessor ? postprocessor(cache[args]) : cache[args];
}
return newf;
}
var R = {};
var p2s = /,?([achlmqrstvxz]),?/gi;
var pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig;
var pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig;
var isnan = {
'NaN': 1,
'Infinity': 1,
'-Infinity': 1
};
R.is = function (o, type) {
type = String.prototype.toLowerCase.call(type);
if (type === 'finite') {
return !isnan.hasOwnProperty(+o);
}
if (type === 'array') {
return o instanceof Array;
}
return type === 'null' && o === null || type === typeof o && o !== null || type === 'object' && o === Object(o) || type === 'array' && Array.isArray && Array.isArray(o) || Object.prototype.toString.call(o).slice(8, -1).toLowerCase() === type;
};
R._path2string = function () {
return this.join(',').replace(p2s, '$1');
};
});

View file

@ -0,0 +1,512 @@
// Matrix Oven - a snapsvg.io plugin to apply & remove transformations from svg files.
// Copyright (C) 2015 Teja Philipp <osd@tejaphilipp.de>
//
// based on work by https://gist.github.com/timo22345/9413158
// and https://github.com/duopixel/Method-Draw/blob/master/editor/src/svgcanvas.js
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
Snap.plugin(function (Snap, Element, Paper, global) {
/**
* bakes transformations of the element and all sub-elements into coordinates
*
* @param {boolean} toCubics : use only cubic path segments
* @param {integer} dec : number of digits after decimal separator. defaults to 5
* @returns {undefined}
*/
Element.prototype.bake = function (toCubics, dec) {
var elem = this;
if (!elem || !elem.paper) // don't handle unplaced elements. this causes double handling.
return;
if (typeof (toCubics) === 'undefined')
toCubics = false;
if (typeof (dec) === 'undefined')
dec = 5;
var children = elem.selectAll('*')
if (children.length > 0) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
child.bake(toCubics, dec);
}
elem.attr({transform: ''});
return;
}
if (elem.type !== "circle" &&
elem.type !== "rect" &&
elem.type !== "ellipse" &&
elem.type !== "line" &&
elem.type !== "polygon" &&
elem.type !== "polyline" &&
elem.type !== "path"){
if(elem.type !== 'g' && elem.type !== 'desc' && elem.type !== 'defs')
console.log('skipping unsupported element ', elem.type);
return;
}
//if(elem.type !== 'path') console.log("bake: converting " + elem.type + " to path");
var path_elem = elem.convertToPath();
if (!path_elem || path_elem.attr(d) === '')
return 'M 0 0';
// Rounding coordinates to dec decimals
if (dec || dec === 0) {
dec = Math.min(Math.max(0,Math.floor(dec)), 15);
} else {
dec = false;
}
function r(num) {
if (dec !== false) {
return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
} else {
return num;
}
}
var arr;
var d = path_elem.attr('d').trim();
var arr_orig;
arr = Snap.parsePathString(d);
if (!toCubics) {
arr_orig = arr;
arr = Snap.path.toAbsolute(arr);
} else {
arr = Snap.path.toCubic(arr); // implies absolute coordinates
arr_orig = arr;
}
// Get the transformation matrix between SVG root element and current element
var transform = path_elem.transform();
var matrix = transform['totalMatrix'];
// apply the matrix transformation on the path segments
var j;
var m = arr.length;
var letter = '';
var letter_orig = '';
var x = 0;
var y = 0;
var new_segments = [];
var pt = {x: 0, y: 0};
var pt_baked = {};
var subpath_start = {};
var prevX = 0;
var prevY = 0;
subpath_start.x = null;
subpath_start.y = null;
for (var i=0; i < m; i++) {
letter = arr[i][0].toUpperCase();
letter_orig = arr_orig[i][0];
new_segments[i] = [];
new_segments[i][0] = arr[i][0];
if (letter === 'A') {
x = arr[i][6];
y = arr[i][7];
pt.x = arr[i][6];
pt.y = arr[i][7];
new_segments[i] = _arc_transform(arr[i][1], arr[i][2], arr[i][3], arr[i][4], arr[i][5], pt, matrix);
} else if (letter !== 'Z') {
// parse other segs than Z and A
for (j = 1; j < arr[i].length; j = j + 2) {
if (letter === 'V') {
y = arr[i][j];
} else if (letter === 'H') {
x = arr[i][j];
} else {
x = arr[i][j];
y = arr[i][j + 1];
}
pt.x = x;
pt.y = y;
pt_baked.x = matrix.x(pt.x, pt.y);
pt_baked.y = matrix.y(pt.x, pt.y);
if (letter === 'V' || letter === 'H') {
new_segments[i][0] = 'L';
new_segments[i][j] = pt_baked.x;
new_segments[i][j + 1] = pt_baked.y;
} else {
new_segments[i][j] = pt_baked.x;
new_segments[i][j + 1] = pt_baked.y;
}
}
}
if ((letter !== 'Z' && subpath_start.x === null) || letter === 'M') {
subpath_start.x = x;
subpath_start.y = y;
}
if (letter === 'Z') {
x = subpath_start.x;
y = subpath_start.y;
}
}
// Convert all that was relative back to relative
// This could be combined to above, but to make code more readable
// this is made separately.
var prevXtmp = 0;
var prevYtmp = 0;
subpath_start.x = '';
for (i = 0; i < new_segments.length; i++) {
letter_orig = arr_orig[i][0];
if (letter_orig === 'A' || letter_orig === 'M' || letter_orig === 'L' || letter_orig === 'C' || letter_orig === 'S' || letter_orig === 'Q' || letter_orig === 'T' || letter_orig === 'H' || letter_orig === 'V') {
var len = new_segments[i].length;
var lentmp = len;
if (letter_orig === 'A') {
// rounding arc parameters
// only x,y are rounded,
// other parameters are left as they are
// because they are more sensitive to rounding
new_segments[i][6] = r(new_segments[i][6]);
new_segments[i][7] = r(new_segments[i][7]);
} else {
lentmp--;
while (--lentmp){
new_segments[i][lentmp] = r(new_segments[i][lentmp]);
}
}
prevX = new_segments[i][len - 2];
prevY = new_segments[i][len - 1];
} else {
if (letter_orig === 'a') {
// same rounding treatment as above for arcs
prevXtmp = new_segments[i][6];
prevYtmp = new_segments[i][7];
new_segments[i][0] = letter_orig;
new_segments[i][6] = r(new_segments[i][6] - prevX);
new_segments[i][7] = r(new_segments[i][7] - prevY);
prevX = prevXtmp;
prevY = prevYtmp;
} else if (letter_orig === 'm' || letter_orig === 'l' || letter_orig === 'c' || letter_orig === 's' || letter_orig === 'q' || letter_orig === 't' || letter_orig === 'h' || letter_orig === 'v') {
var len = new_segments[i].length;
prevXtmp = new_segments[i][len - 2];
prevYtmp = new_segments[i][len - 1];
for (j = 1; j < len; j = j + 2) {
if (letter_orig === 'h' || letter_orig === 'v') {
new_segments[i][0] = 'l';
} else {
new_segments[i][0] = letter_orig;
}
new_segments[i][j] = r(new_segments[i][j] - prevX);
new_segments[i][j + 1] = r(new_segments[i][j + 1] - prevY);
}
prevX = prevXtmp;
prevY = prevYtmp;
}
}
if ((letter_orig.toLowerCase() !== 'z' && subpath_start.x === '') || letter_orig.toLowerCase() === 'm') {
subpath_start.x = prevX;
subpath_start.y = prevY;
}
if (letter_orig.toLowerCase() === 'z') {
prevX = subpath_start.x;
prevY = subpath_start.y;
}
}
var d_str = _convertToString(new_segments);
path_elem.attr({d: d_str});
path_elem.attr({transform: ''});
//console.log("baked matrix ", matrix, " of ", path_elem.attr('id'));
};
/**
* Helper to apply matrix transformations to arcs.
* From flatten.js (https://gist.github.com/timo22345/9413158), modified a bit.
*
* @param {type} a_rh : r1 of the ellipsis in degree
* @param {type} a_rv : r2 of the ellipsis in degree
* @param {type} a_offsetrot : x-axis rotation in degree
* @param {type} large_arc_flag : 0 or 1
* @param {int} sweep_flag : 0 or 1
* @param {object} endpoint with properties x and y
* @param {type} matrix : transformation matrix
* @returns {Array} : representing the transformed path segment
*/
function _arc_transform(a_rh, a_rv, a_offsetrot, large_arc_flag, sweep_flag, endpoint, matrix) {
function NEARZERO(B) {
return Math.abs(B) < 0.0000000000000001;
}
var m = []; // matrix representation of transformed ellipse
var A; var B; var C; // ellipse implicit equation:
var ac; var A2; var C2; // helpers for angle and halfaxis-extraction.
var rh = a_rh;
var rv = a_rv;
a_offsetrot = a_offsetrot * (Math.PI / 180); // deg->rad
var rot = a_offsetrot;
// sin/cos helper (the former offset rotation)
var s = Math.sin(rot);
var c = Math.cos(rot);
// build ellipse representation matrix (unit circle transformation).
// the 2x2 matrix multiplication with the upper 2x2 of a_mat is inlined.
m[0] = matrix.a * +rh * c + matrix.c * rh * s;
m[1] = matrix.b * +rh * c + matrix.d * rh * s;
m[2] = matrix.a * -rv * s + matrix.c * rv * c;
m[3] = matrix.b * -rv * s + matrix.d * rv * c;
// to implict equation (centered)
A = (m[0] * m[0]) + (m[2] * m[2]);
C = (m[1] * m[1]) + (m[3] * m[3]);
B = (m[0] * m[1] + m[2] * m[3]) * 2.0;
// precalculate distance A to C
ac = A - C;
// convert implicit equation to angle and halfaxis:
// disabled intentionally
if (false && NEARZERO(B)) { // there is a bug in this optimization: does not work for path below
a_offsetrot = 0;
// d="M0,350 l 50,-25
// a25,25 -30 0,1 50,-25 l 50,-25
// a25,50 -30 0,1 50,-25 l 50,-25
// a25,75 -30 0,1 50,-25 l 50,-25
// a25,100 -30 0,1 50,-25 l 50,-25"
// with matrix transform="scale(0.5,2.0)"
A2 = A;
C2 = C;
} else {
if (NEARZERO(ac)) {
A2 = A + B * 0.5;
C2 = A - B * 0.5;
a_offsetrot = Math.PI / 4.0;
} else {
// Precalculate radical:
var K = 1 + B * B / (ac * ac);
// Clamp (precision issues might need this.. not likely, but better save than sorry)
K = (K < 0) ? 0 : Math.sqrt(K);
A2 = 0.5 * (A + C + K * ac);
C2 = 0.5 * (A + C - K * ac);
a_offsetrot = 0.5 * Math.atan2(B, ac);
}
}
// This can get slightly below zero due to rounding issues.
// it's save to clamp to zero in this case (this yields a zero length halfaxis)
A2 = (A2 < 0) ? 0 : Math.sqrt(A2);
C2 = (C2 < 0) ? 0 : Math.sqrt(C2);
// now A2 and C2 are half-axis:
if (ac <= 0){
a_rv = A2;
a_rh = C2;
} else {
a_rv = C2;
a_rh = A2;
}
// If the transformation matrix contain a mirror-component
// winding order of the ellise needs to be changed.
if ((matrix.a * matrix.d) - (matrix.b * matrix.c) < 0){
sweep_flag = !sweep_flag ? 1 : 0;
}
// Finally, transform arc endpoint. This takes care about the
// translational part which we ignored at the whole math-showdown above.
var baked_x = matrix.x(endpoint.x, endpoint.y);
var baked_y = matrix.y(endpoint.x, endpoint.y);
// Radians back to degrees
a_offsetrot = a_offsetrot * 180 / Math.PI;
var r = ['A', a_rh, a_rv, a_offsetrot, large_arc_flag, sweep_flag, baked_x, baked_y];
return r;
}
// just a helper
var _p2s = /,?([achlmqrstvxz]),?/gi;
var _convertToString = function (arr) {
return arr.join(',').replace(_p2s, '$1');
};
/**
* Replaces an element with a path of same shape.
* Supports rect, ellipse, circle, line, polyline, polygon and of course path
* The element will be replaced by the path with same id.
*
* @returns {path}
*/
Element.prototype.convertToPath = function(){
var old_element = this;
if(old_element.type === 'path'){
return old_element;
}
var path = old_element.toPath();
old_element.before(path);
old_element.remove();
return path;
};
/**
* Creates a path in the same shape as the origin element
* Supports rect, ellipse, circle, line, polyline, polygon and of course path
*
* based on
* https://github.com/duopixel/Method-Draw/blob/master/editor/src/svgcanvas.js
* Modifications: Timo (https://github.com/timo22345)
*
* @returns {path} path element
*/
Element.prototype.toPath = function () {
var old_element = this;
// Create new path element
var pathAttr = {};
// All attributes that path element can have
var attrs = ['requiredFeatures', 'requiredExtensions', 'systemLanguage', 'id', 'xml:base', 'xml:lang', 'xml:space', 'onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload', 'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'unicode-bidi', 'visibility', 'word-spacing', 'writing-mode', 'class', 'style', 'externalResourcesRequired', 'transform', 'd', 'pathLength'];
// Copy attributes of old_element to path
for(var attrIdx in attrs){
var attrName = attrs[attrIdx];
var attrValue;
if(attrName === 'transform') {
attrValue = old_element.attr('transform').toString();
} else {
attrValue = old_element.attr(attrName);
}
if (attrValue) {
pathAttr[attrName] = attrValue;
}
}
var d = '';
var valid = function (val) {
return !(typeof (val) !== 'number' || val === Infinity || val < 0);
};
// Possibly the cubed root of 6, but 1.81 works best
var num = 1.81;
var tag = old_element.type;
switch (tag) {
case 'ellipse':
case 'circle':
var rx = +parseFloat(old_element.attr('rx')),
ry = +parseFloat(old_element.attr('ry')),
cx = +parseFloat(old_element.attr('cx')),
cy = +old_element.attr('cy');
if (tag === 'circle') {
rx = ry = +old_element.attr('r');
}
d += _convertToString([
['M', (cx - rx), (cy)],
['C', (cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)],
['C', (cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)],
['C', (cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)],
['C', (cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)],
['Z']
]);
break;
case 'path':
d = old_element.attr('d');
break;
case 'line':
var x1 = parseFloat(old_element.attr('x1')),
y1 = parseFloat(old_element.attr('y1')),
x2 = parseFloat(old_element.attr('x2')),
y2 = old_element.attr('y2');
d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
break;
case 'polyline':
d = 'M' + old_element.attr('points');
break;
case 'polygon':
d = 'M' + old_element.attr('points') + 'Z';
break;
case 'rect':
var rx = parseFloat(old_element.attr('rx')),
ry = parseFloat(old_element.attr('ry')),
x = parseFloat(old_element.attr('x')),
y = parseFloat(old_element.attr('y')),
w = parseFloat(old_element.attr('width')),
h = parseFloat(old_element.attr('height'));
// Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
// If neither rx nor ry are properly specified, then set both rx and ry to 0. (This will result in square corners.)
if (!valid(rx) && !valid(ry)) {
rx = ry = 0;
// Otherwise, if a properly specified value is provided for rx, but not for ry, then set both rx and ry to the value of rx.
} else if (valid(rx) && !valid(ry)) {
ry = rx;
// Otherwise, if a properly specified value is provided for ry, but not for rx, then set both rx and ry to the value of ry.
} else if (valid(ry) && !valid(rx)) {
rx = ry;
} else { // cap values for rx/ry to half of w/h
rx = Math.min(rx, w/2);
ry = Math.min(ry, h/2);
}
if (!rx && !ry) {
d += _convertToString([
['M', x, y],
['L', x + w, y],
['L', x + w, y + h],
['L', x, y + h],
['L', x, y],
['Z']
]);
} else {
var num = 2.19;
if (!ry){
ry = rx;
}
d += _convertToString([
['M', x, y + ry],
['C', x, y + ry / num, x + rx / num, y, x + rx, y],
['L', x + w - rx, y],
['C', x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry],
['L', x + w, y + h - ry],
['C', x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h],
['L', x + rx, y + h],
['C', x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry],
['L', x, y + ry],
['Z']
]);
}
break;
default:
break;
}
if (d){
pathAttr.d = d;
}
var path = old_element.paper.path(pathAttr);
return path;
};
});

View file

@ -236,17 +236,19 @@ $(function(){
newSvgAttrs['transform'] = scaleMatrixStr;
var newSvg = snap.group(f.selectAll("svg>*"));
newSvg.bake();
var hasText = newSvg.selectAll('text,tspan');
if(hasText !== null && hasText.length > 0){
self.svg_contains_text_warning(newSvg);
}
newSvg.bake(); // remove transforms
newSvg.attr(newSvgAttrs);
var id = self.getEntryId(file);
var previewId = self.generateUniqueId(id); // appends -# if multiple times the same design is placed.
newSvg.attr({id: previewId});
snap.select("#userContent").append(newSvg);
//flatten(document.getElementById(previewId), false, true, false, 4)
//newSvg.attr({transform: scaleMatrixStr});
newSvg.drag();// TODO debug drag. should not be affected by scale matrix
file.id = previewId;
file.previewId = previewId;
file.url = url;
@ -274,6 +276,18 @@ $(function(){
// });
};
self.svg_contains_text_warning = function(svg){
var error = "<p>" + gettext("The svg file contains text elements.<br/>Please convert them to paths.<br/>Otherwise they will be ignored.") + "</p>";
//error += pnotifyAdditionalInfo("<pre>" + data.jqXHR.responseText + "</pre>");
new PNotify({
title: "Text elements found",
text: error,
type: "warn",
hide: false
});
svg.selectAll('text,tspan').remove();
};
self.getDocumentDimensionsInPt = function(doc_width, doc_height, doc_viewbox){
if(doc_width === null){
// assume defaults if not set