update master to V7
This commit is contained in:
@@ -44,7 +44,7 @@ export const setBlurNoUndo = function (val) {
|
||||
blurContext_.changeSelectedAttributeNoUndoMethod('filter', 'url(#' + elem.id + '_blur)');
|
||||
}
|
||||
if (blurContext_.isWebkit()) {
|
||||
// console.log('e', elem); // eslint-disable-line no-console
|
||||
// console.log('e', elem);
|
||||
elem.removeAttribute('filter');
|
||||
elem.setAttribute('filter', 'url(#' + elem.id + '_blur)');
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Tools for clear.
|
||||
* @module clear
|
||||
* @license MIT
|
||||
* @copyright 2011 Jeff Schiller
|
||||
*/
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js';
|
||||
import {NS} from '../common/namespaces.js';
|
||||
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
let clearContext_ = null;
|
||||
|
||||
/**
|
||||
@@ -24,20 +20,23 @@ export const init = function (clearContext) {
|
||||
export const clearSvgContentElementInit = function () {
|
||||
const curConfig = clearContext_.getCurConfig();
|
||||
const {dimensions} = curConfig;
|
||||
$(clearContext_.getSVGContent()).empty();
|
||||
const el = clearContext_.getSVGContent();
|
||||
// empty()
|
||||
while(el.firstChild)
|
||||
el.removeChild(el.firstChild);
|
||||
|
||||
// TODO: Clear out all other attributes first?
|
||||
$(clearContext_.getSVGContent()).attr({
|
||||
id: 'svgcontent',
|
||||
width: dimensions[0],
|
||||
height: dimensions[1],
|
||||
x: dimensions[0],
|
||||
y: dimensions[1],
|
||||
overflow: curConfig.show_outside_canvas ? 'visible' : 'hidden',
|
||||
xmlns: NS.SVG,
|
||||
'xmlns:se': NS.SE,
|
||||
'xmlns:xlink': NS.XLINK
|
||||
}).appendTo(clearContext_.getSVGRoot());
|
||||
const pel = clearContext_.getSVGRoot();
|
||||
el.setAttribute('id', 'svgcontent');
|
||||
el.setAttribute('width', dimensions[0]);
|
||||
el.setAttribute('height', dimensions[1]);
|
||||
el.setAttribute('x', dimensions[0]);
|
||||
el.setAttribute('y', dimensions[1]);
|
||||
el.setAttribute('overflow', curConfig.show_outside_canvas ? 'visible' : 'hidden');
|
||||
el.setAttribute('xmlns', NS.SVG);
|
||||
el.setAttribute('xmlns:se', NS.SE);
|
||||
el.setAttribute('xmlns:xlink', NS.XLINK);
|
||||
pel.appendChild(el);
|
||||
|
||||
// TODO: make this string optional and set by the client
|
||||
const comment = clearContext_.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Manipulating coordinates.
|
||||
* @module coords
|
||||
@@ -7,13 +6,11 @@
|
||||
|
||||
import {
|
||||
snapToGrid, assignAttributes, getBBox, getRefElem, findDefs
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
transformPoint, transformListToTransform, matrixMultiply, transformBox
|
||||
} from '../common/math.js';
|
||||
import {getTransformList} from '../common/svgtransformlist.js';
|
||||
|
||||
const $ = jQuery;
|
||||
} from './math.js';
|
||||
import { getTransformList } from './svgtransformlist.js';
|
||||
|
||||
// this is how we map paths to our preferred relative segment types
|
||||
const pathMap = [
|
||||
@@ -71,34 +68,27 @@ export const remapElement = function (selected, changes, m) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const type = i === 0 ? 'fill' : 'stroke';
|
||||
const attrVal = selected.getAttribute(type);
|
||||
if (attrVal && attrVal.startsWith('url(')) {
|
||||
if (m.a < 0 || m.d < 0) {
|
||||
const grad = getRefElem(attrVal);
|
||||
const newgrad = grad.cloneNode(true);
|
||||
if (m.a < 0) {
|
||||
// flip x
|
||||
const x1 = newgrad.getAttribute('x1');
|
||||
const x2 = newgrad.getAttribute('x2');
|
||||
newgrad.setAttribute('x1', -(x1 - 1));
|
||||
newgrad.setAttribute('x2', -(x2 - 1));
|
||||
}
|
||||
|
||||
if (m.d < 0) {
|
||||
// flip y
|
||||
const y1 = newgrad.getAttribute('y1');
|
||||
const y2 = newgrad.getAttribute('y2');
|
||||
newgrad.setAttribute('y1', -(y1 - 1));
|
||||
newgrad.setAttribute('y2', -(y2 - 1));
|
||||
}
|
||||
newgrad.id = editorContext_.getDrawing().getNextId();
|
||||
findDefs().append(newgrad);
|
||||
selected.setAttribute(type, 'url(#' + newgrad.id + ')');
|
||||
if (attrVal && attrVal.startsWith('url(') && (m.a < 0 || m.d < 0)) {
|
||||
const grad = getRefElem(attrVal);
|
||||
const newgrad = grad.cloneNode(true);
|
||||
if (m.a < 0) {
|
||||
// flip x
|
||||
const x1 = newgrad.getAttribute('x1');
|
||||
const x2 = newgrad.getAttribute('x2');
|
||||
newgrad.setAttribute('x1', -(x1 - 1));
|
||||
newgrad.setAttribute('x2', -(x2 - 1));
|
||||
}
|
||||
|
||||
// Not really working :(
|
||||
// if (selected.tagName === 'path') {
|
||||
// reorientGrads(selected, m);
|
||||
// }
|
||||
if (m.d < 0) {
|
||||
// flip y
|
||||
const y1 = newgrad.getAttribute('y1');
|
||||
const y2 = newgrad.getAttribute('y2');
|
||||
newgrad.setAttribute('y1', -(y1 - 1));
|
||||
newgrad.setAttribute('y2', -(y2 - 1));
|
||||
}
|
||||
newgrad.id = editorContext_.getDrawing().getNextId();
|
||||
findDefs().append(newgrad);
|
||||
selected.setAttribute(type, 'url(#' + newgrad.id + ')');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,191 +115,192 @@ export const remapElement = function (selected, changes, m) {
|
||||
// now we have a set of changes and an applied reduced transform list
|
||||
// we apply the changes directly to the DOM
|
||||
switch (elName) {
|
||||
case 'foreignObject':
|
||||
case 'rect':
|
||||
case 'image': {
|
||||
// Allow images to be inverted (give them matrix when flipped)
|
||||
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
|
||||
// Convert to matrix
|
||||
const chlist = getTransformList(selected);
|
||||
const mt = editorContext_.getSVGRoot().createSVGTransform();
|
||||
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m));
|
||||
chlist.clear();
|
||||
chlist.appendItem(mt);
|
||||
} else {
|
||||
const pt1 = remap(changes.x, changes.y);
|
||||
changes.width = scalew(changes.width);
|
||||
changes.height = scaleh(changes.height);
|
||||
changes.x = pt1.x + Math.min(0, changes.width);
|
||||
changes.y = pt1.y + Math.min(0, changes.height);
|
||||
changes.width = Math.abs(changes.width);
|
||||
changes.height = Math.abs(changes.height);
|
||||
}
|
||||
finishUp();
|
||||
break;
|
||||
} case 'ellipse': {
|
||||
const c = remap(changes.cx, changes.cy);
|
||||
changes.cx = c.x;
|
||||
changes.cy = c.y;
|
||||
changes.rx = scalew(changes.rx);
|
||||
changes.ry = scaleh(changes.ry);
|
||||
changes.rx = Math.abs(changes.rx);
|
||||
changes.ry = Math.abs(changes.ry);
|
||||
finishUp();
|
||||
break;
|
||||
} case 'circle': {
|
||||
const c = remap(changes.cx, changes.cy);
|
||||
changes.cx = c.x;
|
||||
changes.cy = c.y;
|
||||
// take the minimum of the new selected box's dimensions for the new circle radius
|
||||
const tbox = transformBox(box.x, box.y, box.width, box.height, m);
|
||||
const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
|
||||
changes.r = Math.min(w / 2, h / 2);
|
||||
|
||||
if (changes.r) { changes.r = Math.abs(changes.r); }
|
||||
finishUp();
|
||||
break;
|
||||
} case 'line': {
|
||||
const pt1 = remap(changes.x1, changes.y1);
|
||||
const pt2 = remap(changes.x2, changes.y2);
|
||||
changes.x1 = pt1.x;
|
||||
changes.y1 = pt1.y;
|
||||
changes.x2 = pt2.x;
|
||||
changes.y2 = pt2.y;
|
||||
} // Fallthrough
|
||||
case 'text':
|
||||
case 'tspan':
|
||||
case 'use': {
|
||||
finishUp();
|
||||
break;
|
||||
} case 'g': {
|
||||
const gsvg = $(selected).data('gsvg');
|
||||
if (gsvg) {
|
||||
assignAttributes(gsvg, changes, 1000, true);
|
||||
}
|
||||
break;
|
||||
} case 'polyline':
|
||||
case 'polygon': {
|
||||
const len = changes.points.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = changes.points[i];
|
||||
const {x, y} = remap(pt.x, pt.y);
|
||||
changes.points[i].x = x;
|
||||
changes.points[i].y = y;
|
||||
}
|
||||
|
||||
// const len = changes.points.length;
|
||||
let pstr = '';
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = changes.points[i];
|
||||
pstr += pt.x + ',' + pt.y + ' ';
|
||||
}
|
||||
selected.setAttribute('points', pstr);
|
||||
break;
|
||||
} case 'path': {
|
||||
const segList = selected.pathSegList;
|
||||
let len = segList.numberOfItems;
|
||||
changes.d = [];
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const seg = segList.getItem(i);
|
||||
changes.d[i] = {
|
||||
type: seg.pathSegType,
|
||||
x: seg.x,
|
||||
y: seg.y,
|
||||
x1: seg.x1,
|
||||
y1: seg.y1,
|
||||
x2: seg.x2,
|
||||
y2: seg.y2,
|
||||
r1: seg.r1,
|
||||
r2: seg.r2,
|
||||
angle: seg.angle,
|
||||
largeArcFlag: seg.largeArcFlag,
|
||||
sweepFlag: seg.sweepFlag
|
||||
};
|
||||
}
|
||||
|
||||
len = changes.d.length;
|
||||
const firstseg = changes.d[0],
|
||||
currentpt = remap(firstseg.x, firstseg.y);
|
||||
changes.d[0].x = currentpt.x;
|
||||
changes.d[0].y = currentpt.y;
|
||||
for (let i = 1; i < len; ++i) {
|
||||
const seg = changes.d[i];
|
||||
const {type} = seg;
|
||||
// if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
|
||||
// if relative, we want to scalew, scaleh
|
||||
if (type % 2 === 0) { // absolute
|
||||
const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands
|
||||
thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands
|
||||
const pt = remap(thisx, thisy);
|
||||
const pt1 = remap(seg.x1, seg.y1);
|
||||
const pt2 = remap(seg.x2, seg.y2);
|
||||
seg.x = pt.x;
|
||||
seg.y = pt.y;
|
||||
seg.x1 = pt1.x;
|
||||
seg.y1 = pt1.y;
|
||||
seg.x2 = pt2.x;
|
||||
seg.y2 = pt2.y;
|
||||
seg.r1 = scalew(seg.r1);
|
||||
seg.r2 = scaleh(seg.r2);
|
||||
} else { // relative
|
||||
seg.x = scalew(seg.x);
|
||||
seg.y = scaleh(seg.y);
|
||||
seg.x1 = scalew(seg.x1);
|
||||
seg.y1 = scaleh(seg.y1);
|
||||
seg.x2 = scalew(seg.x2);
|
||||
seg.y2 = scaleh(seg.y2);
|
||||
seg.r1 = scalew(seg.r1);
|
||||
seg.r2 = scaleh(seg.r2);
|
||||
case 'foreignObject':
|
||||
case 'rect':
|
||||
case 'image': {
|
||||
// Allow images to be inverted (give them matrix when flipped)
|
||||
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
|
||||
// Convert to matrix
|
||||
const chlist = getTransformList(selected);
|
||||
const mt = editorContext_.getSVGRoot().createSVGTransform();
|
||||
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m));
|
||||
chlist.clear();
|
||||
chlist.appendItem(mt);
|
||||
} else {
|
||||
const pt1 = remap(changes.x, changes.y);
|
||||
changes.width = scalew(changes.width);
|
||||
changes.height = scaleh(changes.height);
|
||||
changes.x = pt1.x + Math.min(0, changes.width);
|
||||
changes.y = pt1.y + Math.min(0, changes.height);
|
||||
changes.width = Math.abs(changes.width);
|
||||
changes.height = Math.abs(changes.height);
|
||||
}
|
||||
} // for each segment
|
||||
finishUp();
|
||||
break;
|
||||
} case 'ellipse': {
|
||||
const c = remap(changes.cx, changes.cy);
|
||||
changes.cx = c.x;
|
||||
changes.cy = c.y;
|
||||
changes.rx = scalew(changes.rx);
|
||||
changes.ry = scaleh(changes.ry);
|
||||
changes.rx = Math.abs(changes.rx);
|
||||
changes.ry = Math.abs(changes.ry);
|
||||
finishUp();
|
||||
break;
|
||||
} case 'circle': {
|
||||
const c = remap(changes.cx, changes.cy);
|
||||
changes.cx = c.x;
|
||||
changes.cy = c.y;
|
||||
// take the minimum of the new selected box's dimensions for the new circle radius
|
||||
const tbox = transformBox(box.x, box.y, box.width, box.height, m);
|
||||
const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y;
|
||||
changes.r = Math.min(w / 2, h / 2);
|
||||
|
||||
let dstr = '';
|
||||
len = changes.d.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const seg = changes.d[i];
|
||||
const {type} = seg;
|
||||
dstr += pathMap[type];
|
||||
switch (type) {
|
||||
case 13: // relative horizontal line (h)
|
||||
case 12: // absolute horizontal line (H)
|
||||
dstr += seg.x + ' ';
|
||||
break;
|
||||
case 15: // relative vertical line (v)
|
||||
case 14: // absolute vertical line (V)
|
||||
dstr += seg.y + ' ';
|
||||
break;
|
||||
case 3: // relative move (m)
|
||||
case 5: // relative line (l)
|
||||
case 19: // relative smooth quad (t)
|
||||
case 2: // absolute move (M)
|
||||
case 4: // absolute line (L)
|
||||
case 18: // absolute smooth quad (T)
|
||||
dstr += seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 7: // relative cubic (c)
|
||||
case 6: // absolute cubic (C)
|
||||
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' +
|
||||
seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 9: // relative quad (q)
|
||||
case 8: // absolute quad (Q)
|
||||
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 11: // relative elliptical arc (a)
|
||||
case 10: // absolute elliptical arc (A)
|
||||
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) +
|
||||
' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 17: // relative smooth cubic (s)
|
||||
case 16: // absolute smooth cubic (S)
|
||||
dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
if (changes.r) { changes.r = Math.abs(changes.r); }
|
||||
finishUp();
|
||||
break;
|
||||
} case 'line': {
|
||||
const pt1 = remap(changes.x1, changes.y1);
|
||||
const pt2 = remap(changes.x2, changes.y2);
|
||||
changes.x1 = pt1.x;
|
||||
changes.y1 = pt1.y;
|
||||
changes.x2 = pt2.x;
|
||||
changes.y2 = pt2.y;
|
||||
} // Fallthrough
|
||||
case 'text':
|
||||
case 'tspan':
|
||||
case 'use': {
|
||||
finishUp();
|
||||
break;
|
||||
} case 'g': {
|
||||
const dataStorage = editorContext_.getDataStorage();
|
||||
const gsvg = dataStorage.get(selected, 'gsvg');
|
||||
if (gsvg) {
|
||||
assignAttributes(gsvg, changes, 1000, true);
|
||||
}
|
||||
break;
|
||||
} case 'polyline':
|
||||
case 'polygon': {
|
||||
const len = changes.points.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = changes.points[i];
|
||||
const { x, y } = remap(pt.x, pt.y);
|
||||
changes.points[i].x = x;
|
||||
changes.points[i].y = y;
|
||||
}
|
||||
}
|
||||
|
||||
selected.setAttribute('d', dstr);
|
||||
break;
|
||||
}
|
||||
// const len = changes.points.length;
|
||||
let pstr = '';
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = changes.points[i];
|
||||
pstr += pt.x + ',' + pt.y + ' ';
|
||||
}
|
||||
selected.setAttribute('points', pstr);
|
||||
break;
|
||||
} case 'path': {
|
||||
const segList = selected.pathSegList;
|
||||
let len = segList.numberOfItems;
|
||||
changes.d = [];
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const seg = segList.getItem(i);
|
||||
changes.d[i] = {
|
||||
type: seg.pathSegType,
|
||||
x: seg.x,
|
||||
y: seg.y,
|
||||
x1: seg.x1,
|
||||
y1: seg.y1,
|
||||
x2: seg.x2,
|
||||
y2: seg.y2,
|
||||
r1: seg.r1,
|
||||
r2: seg.r2,
|
||||
angle: seg.angle,
|
||||
largeArcFlag: seg.largeArcFlag,
|
||||
sweepFlag: seg.sweepFlag
|
||||
};
|
||||
}
|
||||
|
||||
len = changes.d.length;
|
||||
const firstseg = changes.d[0],
|
||||
currentpt = remap(firstseg.x, firstseg.y);
|
||||
changes.d[0].x = currentpt.x;
|
||||
changes.d[0].y = currentpt.y;
|
||||
for (let i = 1; i < len; ++i) {
|
||||
const seg = changes.d[i];
|
||||
const { type } = seg;
|
||||
// if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
|
||||
// if relative, we want to scalew, scaleh
|
||||
if (type % 2 === 0) { // absolute
|
||||
const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands
|
||||
thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands
|
||||
const pt = remap(thisx, thisy);
|
||||
const pt1 = remap(seg.x1, seg.y1);
|
||||
const pt2 = remap(seg.x2, seg.y2);
|
||||
seg.x = pt.x;
|
||||
seg.y = pt.y;
|
||||
seg.x1 = pt1.x;
|
||||
seg.y1 = pt1.y;
|
||||
seg.x2 = pt2.x;
|
||||
seg.y2 = pt2.y;
|
||||
seg.r1 = scalew(seg.r1);
|
||||
seg.r2 = scaleh(seg.r2);
|
||||
} else { // relative
|
||||
seg.x = scalew(seg.x);
|
||||
seg.y = scaleh(seg.y);
|
||||
seg.x1 = scalew(seg.x1);
|
||||
seg.y1 = scaleh(seg.y1);
|
||||
seg.x2 = scalew(seg.x2);
|
||||
seg.y2 = scaleh(seg.y2);
|
||||
seg.r1 = scalew(seg.r1);
|
||||
seg.r2 = scaleh(seg.r2);
|
||||
}
|
||||
} // for each segment
|
||||
|
||||
let dstr = '';
|
||||
len = changes.d.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const seg = changes.d[i];
|
||||
const { type } = seg;
|
||||
dstr += pathMap[type];
|
||||
switch (type) {
|
||||
case 13: // relative horizontal line (h)
|
||||
case 12: // absolute horizontal line (H)
|
||||
dstr += seg.x + ' ';
|
||||
break;
|
||||
case 15: // relative vertical line (v)
|
||||
case 14: // absolute vertical line (V)
|
||||
dstr += seg.y + ' ';
|
||||
break;
|
||||
case 3: // relative move (m)
|
||||
case 5: // relative line (l)
|
||||
case 19: // relative smooth quad (t)
|
||||
case 2: // absolute move (M)
|
||||
case 4: // absolute line (L)
|
||||
case 18: // absolute smooth quad (T)
|
||||
dstr += seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 7: // relative cubic (c)
|
||||
case 6: // absolute cubic (C)
|
||||
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' +
|
||||
seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 9: // relative quad (q)
|
||||
case 8: // absolute quad (Q)
|
||||
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 11: // relative elliptical arc (a)
|
||||
case 10: // absolute elliptical arc (A)
|
||||
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) +
|
||||
' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
case 17: // relative smooth cubic (s)
|
||||
case 16: // absolute smooth cubic (S)
|
||||
dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
selected.setAttribute('d', dstr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
/* globals jQuery */
|
||||
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
|
||||
import {isWebkit} from '../common/browser.js';
|
||||
import {convertPath} from './path.js';
|
||||
import {preventClickDefault} from '../common/utilities.js';
|
||||
|
||||
// Constants
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
import {preventClickDefault} from './utilities.js';
|
||||
|
||||
/**
|
||||
* Create a clone of an element, updating its ID and its children's IDs when needed.
|
||||
@@ -18,10 +12,8 @@ const $ = jQueryPluginSVG(jQuery);
|
||||
export const copyElem = function (el, getNextId) {
|
||||
// manually create a copy of the element
|
||||
const newEl = document.createElementNS(el.namespaceURI, el.nodeName);
|
||||
$.each(el.attributes, function (i, attr) {
|
||||
if (attr.localName !== '-moz-math-font-style') {
|
||||
Object.values(el.attributes).forEach((attr) => {
|
||||
newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value);
|
||||
}
|
||||
});
|
||||
// set the copied element's new id
|
||||
newEl.removeAttribute('id');
|
||||
@@ -35,7 +27,7 @@ export const copyElem = function (el, getNextId) {
|
||||
}
|
||||
|
||||
// now create copies of all children
|
||||
$.each(el.childNodes, function (i, child) {
|
||||
el.childNodes.forEach(function(child){
|
||||
switch (child.nodeType) {
|
||||
case 1: // element node
|
||||
newEl.append(copyElem(child, getNextId));
|
||||
@@ -48,11 +40,12 @@ export const copyElem = function (el, getNextId) {
|
||||
}
|
||||
});
|
||||
|
||||
if ($(el).data('gsvg')) {
|
||||
$(newEl).data('gsvg', newEl.firstChild);
|
||||
} else if ($(el).data('symbol')) {
|
||||
const ref = $(el).data('symbol');
|
||||
$(newEl).data('ref', ref).data('symbol', ref);
|
||||
if (el.dataset.gsvg) {
|
||||
newEl.dataset.gsvg = newEl.firstChild
|
||||
} else if (el.dataset.symbol) {
|
||||
const ref = el.dataset.symbol;
|
||||
newEl.dataset.ref = ref;
|
||||
newEl.dataset.symbol = ref;
|
||||
} else if (newEl.tagName === 'image') {
|
||||
preventClickDefault(newEl);
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* @module jQueryPluginDBox
|
||||
*/
|
||||
/**
|
||||
* @param {external:jQuery} $
|
||||
* @param {PlainObject} [strings]
|
||||
* @param {PlainObject} [strings.ok]
|
||||
* @param {PlainObject} [strings.cancel]
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
export default function jQueryPluginDBox ($, {
|
||||
ok: okString = 'Ok',
|
||||
cancel: cancelString = 'Cancel'
|
||||
} = {}) {
|
||||
// This sets up alternative dialog boxes. They mostly work the same way as
|
||||
// their UI counterparts, expect instead of returning the result, a callback
|
||||
// needs to be included that returns the result as its first parameter.
|
||||
// In the future we may want to add additional types of dialog boxes, since
|
||||
// they should be easy to handle this way.
|
||||
$('#dialog_container').draggable({
|
||||
cancel: '#dialog_content, #dialog_buttons *',
|
||||
containment: 'window'
|
||||
}).css('position', 'absolute');
|
||||
|
||||
const box = $('#dialog_box'),
|
||||
btnHolder = $('#dialog_buttons'),
|
||||
dialogContent = $('#dialog_content');
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:jQueryPluginDBox.PromiseResultObject
|
||||
* @property {string|true} response
|
||||
* @property {boolean} checked
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves to `false` (if cancelled), for prompts and selects
|
||||
* without checkboxes, it resolves to the value of the form control. For other
|
||||
* types without checkboxes, it resolves to `true`. For checkboxes, it resolves
|
||||
* to an object with the `response` key containing the same value as the previous
|
||||
* mentioned (string or `true`) and a `checked` (boolean) property.
|
||||
* @typedef {Promise<boolean|string|module:jQueryPluginDBox.PromiseResultObject>} module:jQueryPluginDBox.ResultPromise
|
||||
*/
|
||||
/**
|
||||
* @typedef {PlainObject} module:jQueryPluginDBox.SelectOption
|
||||
* @property {string} text
|
||||
* @property {string} value
|
||||
*/
|
||||
/**
|
||||
* @typedef {PlainObject} module:jQueryPluginDBox.CheckboxInfo
|
||||
* @property {string} label Label for the checkbox
|
||||
* @property {string} value Value of the checkbox
|
||||
* @property {string} tooltip Tooltip on the checkbox label
|
||||
* @property {boolean} checked Whether the checkbox is checked by default
|
||||
*/
|
||||
/**
|
||||
* Triggered upon a change of value for the select pull-down.
|
||||
* @callback module:jQueryPluginDBox.SelectChangeListener
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* Creates a dialog of the specified type with a given message
|
||||
* and any defaults and type-specific metadata. Returns a `Promise`
|
||||
* which resolves differently depending on whether the dialog
|
||||
* was cancelled or okayed (with the response and any checked state).
|
||||
* @param {"alert"|"prompt"|"select"|"process"} type
|
||||
* @param {string} msg
|
||||
* @param {string} [defaultVal]
|
||||
* @param {module:jQueryPluginDBox.SelectOption[]} [opts]
|
||||
* @param {module:jQueryPluginDBox.SelectChangeListener} [changeListener]
|
||||
* @param {module:jQueryPluginDBox.CheckboxInfo} [checkbox]
|
||||
* @returns {jQueryPluginDBox.ResultPromise}
|
||||
*/
|
||||
function dbox (type, msg, defaultVal, opts, changeListener, checkbox) {
|
||||
dialogContent.html('<p>' + msg.replace(/\n/g, '</p><p>') + '</p>')
|
||||
.toggleClass('prompt', (type === 'prompt'));
|
||||
btnHolder.empty();
|
||||
|
||||
const ok = $('<input type="button" data-ok="" value="' + okString + '">').appendTo(btnHolder);
|
||||
|
||||
return new Promise((resolve, reject) => { // eslint-disable-line promise/avoid-new
|
||||
if (type !== 'alert') {
|
||||
$('<input type="button" value="' + cancelString + '">')
|
||||
.appendTo(btnHolder)
|
||||
.click(function () {
|
||||
box.hide();
|
||||
resolve(false);
|
||||
});
|
||||
}
|
||||
|
||||
let ctrl, chkbx;
|
||||
if (type === 'prompt') {
|
||||
ctrl = $('<input type="text">').prependTo(btnHolder);
|
||||
ctrl.val(defaultVal || '');
|
||||
ctrl.bind('keydown', 'return', function () { ok.click(); });
|
||||
} else if (type === 'select') {
|
||||
const div = $('<div style="text-align:center;">');
|
||||
ctrl = $(`<select aria-label="${msg}">`).appendTo(div);
|
||||
if (checkbox) {
|
||||
const label = $('<label>').text(checkbox.label);
|
||||
chkbx = $('<input type="checkbox">').appendTo(label);
|
||||
chkbx.val(checkbox.value);
|
||||
if (checkbox.tooltip) {
|
||||
label.attr('title', checkbox.tooltip);
|
||||
}
|
||||
chkbx.prop('checked', Boolean(checkbox.checked));
|
||||
div.append($('<div>').append(label));
|
||||
}
|
||||
$.each(opts || [], function (opt, val) {
|
||||
if (typeof val === 'object') {
|
||||
ctrl.append($('<option>').val(val.value).html(val.text));
|
||||
} else {
|
||||
ctrl.append($('<option>').html(val));
|
||||
}
|
||||
});
|
||||
dialogContent.append(div);
|
||||
if (defaultVal) {
|
||||
ctrl.val(defaultVal);
|
||||
}
|
||||
if (changeListener) {
|
||||
ctrl.bind('change', 'return', changeListener);
|
||||
}
|
||||
ctrl.bind('keydown', 'return', function () { ok.click(); });
|
||||
} else if (type === 'process') {
|
||||
ok.hide();
|
||||
}
|
||||
|
||||
box.show();
|
||||
|
||||
ok.click(function () {
|
||||
box.hide();
|
||||
const response = (type === 'prompt' || type === 'select') ? ctrl.val() : true;
|
||||
if (chkbx) {
|
||||
resolve({response, checked: chkbx.prop('checked')});
|
||||
return;
|
||||
}
|
||||
resolve(response);
|
||||
}).focus();
|
||||
|
||||
if (type === 'prompt' || type === 'select') {
|
||||
ctrl.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} msg Message to alert
|
||||
* @returns {jQueryPluginDBox.ResultPromise}
|
||||
*/
|
||||
$.alert = function (msg) {
|
||||
return dbox('alert', msg);
|
||||
};
|
||||
/**
|
||||
* @param {string} msg Message for which to ask confirmation
|
||||
* @returns {jQueryPluginDBox.ResultPromise}
|
||||
*/
|
||||
$.confirm = function (msg) {
|
||||
return dbox('confirm', msg);
|
||||
};
|
||||
/**
|
||||
* @param {string} msg Message to indicate upon cancelable indicator
|
||||
* @returns {jQueryPluginDBox.ResultPromise}
|
||||
*/
|
||||
$.process_cancel = function (msg) {
|
||||
return dbox('process', msg);
|
||||
};
|
||||
/**
|
||||
* @param {string} msg Message to accompany the prompt
|
||||
* @param {string} [defaultText=""] The default text to show for the prompt
|
||||
* @returns {jQueryPluginDBox.ResultPromise}
|
||||
*/
|
||||
$.prompt = function (msg, defaultText = '') {
|
||||
return dbox('prompt', msg, defaultText);
|
||||
};
|
||||
$.select = function (msg, opts, changeListener, txt, checkbox) {
|
||||
return dbox('select', msg, txt, opts, changeListener, checkbox);
|
||||
};
|
||||
return $;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Tools for drawing.
|
||||
* @module draw
|
||||
@@ -6,22 +5,21 @@
|
||||
* @copyright 2011 Jeff Schiller
|
||||
*/
|
||||
|
||||
import Layer from '../common/layer.js';
|
||||
import Layer from './layer.js';
|
||||
import HistoryRecordingService from './historyrecording.js';
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {isOpera} from '../common/browser.js';
|
||||
import {
|
||||
toXml, getElem
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
copyElem as utilCopyElem
|
||||
} from './copy-elem.js';
|
||||
import {
|
||||
BatchCommand, RemoveElementCommand, MoveElementCommand, ChangeElementCommand
|
||||
} from './history.js';
|
||||
|
||||
const $ = jQuery;
|
||||
import {getParentsUntil} from '../editor/components/jgraduate/Util.js';
|
||||
|
||||
const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(',');
|
||||
|
||||
@@ -49,10 +47,10 @@ function historyRecordingService (hrService) {
|
||||
* @returns {string} The layer name or empty string.
|
||||
*/
|
||||
function findLayerNameInGroup (group) {
|
||||
return $('title', group).text() ||
|
||||
return group.querySelector('title').textContent ||
|
||||
(isOpera() && group.querySelectorAll
|
||||
// Hack for Opera 10.60
|
||||
? $(group.querySelectorAll('title')).text()
|
||||
? group.querySelector('title').textContent
|
||||
: '');
|
||||
}
|
||||
|
||||
@@ -158,7 +156,7 @@ export class Drawing {
|
||||
return this.svgElem_.querySelector('#' + id);
|
||||
}
|
||||
// jQuery lookup: twice as slow as xpath in FF
|
||||
return $(this.svgElem_).find('[id=' + id + ']')[0];
|
||||
return this.svgElem_.querySelector('[id=' + id + ']');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,29 +338,6 @@ export class Drawing {
|
||||
return finalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all objects of the currently selected layer. If an error occurs, an empty array is returned.
|
||||
* @returns {SVGGElement[] | any[]} The objects of the currently active layer (or an empty array if no objects found).
|
||||
*/
|
||||
getCurrentLayerChildren () {
|
||||
return this.current_layer
|
||||
? [...this.current_layer.getChildren()].filter((object) => { return object.tagName !== 'title'; })
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object at the current layer with the given 'objectId'. If none is found 'null' is returned.
|
||||
* @param {string} objectId The id of the object
|
||||
* @returns {?SVGGElement} The found object or 'null' if none is found.
|
||||
*/
|
||||
getCurrentLayerChild (objectId) {
|
||||
const foundElements = this.getCurrentLayerChildren()
|
||||
.filter((obj) => { return obj.id === objectId; });
|
||||
if (!foundElements) { return null; }
|
||||
|
||||
return foundElements[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current layer's position.
|
||||
* @param {Integer} newpos - The zero-based index of the new position of the layer. Range should be 0 to layers-1
|
||||
@@ -414,7 +389,7 @@ export class Drawing {
|
||||
*/
|
||||
mergeLayer (hrService) {
|
||||
const currentGroup = this.current_layer.getGroup();
|
||||
const prevGroup = $(currentGroup).prev()[0];
|
||||
const prevGroup = currentGroup.previousElementSibling;
|
||||
if (!prevGroup) { return; }
|
||||
|
||||
hrService.startBatchCommand('Merge Layer');
|
||||
@@ -653,38 +628,6 @@ export class Drawing {
|
||||
return layer.getGroup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the object. If the object id is not valid, this
|
||||
* function returns `null`, otherwise it returns the `SVGElement` representing
|
||||
* the object. This is an undo-able action.
|
||||
* @param {string} objectId - The id of the object to change the visibility
|
||||
* @param {boolean} bVisible - Whether the object should be visible
|
||||
* @returns {?SVGGElement} The SVGGElement representing the object if the
|
||||
* `objectId` was valid, otherwise `null`.
|
||||
*/
|
||||
setLayerChildrenVisible (objectId, bVisible) {
|
||||
if (typeof bVisible !== 'boolean') {
|
||||
return null;
|
||||
}
|
||||
const element = this.getCurrentLayerChild(objectId);
|
||||
const expected = bVisible ? 'inline' : 'none';
|
||||
const oldDisplay = element.getAttribute('display');
|
||||
if (oldDisplay !== expected) {
|
||||
element.setAttribute('display', expected);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object with the given id is visible or not.
|
||||
* @param {string} objectId - id of the object on which to get the visibility.
|
||||
* @returns {false|boolean} The visibility of the object, or `false` if the objects id was invalid.
|
||||
*/
|
||||
isLayerChildrenVisible (objectId) {
|
||||
const element = this.getCurrentLayerChild(objectId);
|
||||
return element.getAttribute('display') !== 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the opacity of the given layer. If the input name is not a layer, `null` is returned.
|
||||
* @param {string} layerName - name of the layer on which to get the opacity
|
||||
@@ -765,7 +708,6 @@ export const randomizeIds = function (enableRandomization, currentDrawing) {
|
||||
/**
|
||||
* @interface module:draw.DrawCanvasInit
|
||||
* @property {module:path.pathActions} pathActions
|
||||
* @property {external:jQuery.data} elData
|
||||
* @property {module:history.UndoManager} undoMgr
|
||||
*/
|
||||
/**
|
||||
@@ -1044,10 +986,11 @@ export const mergeAllLayers = function (hrService) {
|
||||
*/
|
||||
export const leaveContext = function () {
|
||||
const len = disabledElems.length;
|
||||
const dataStorage = canvas_.getDataStorage();
|
||||
if (len) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const elem = disabledElems[i];
|
||||
const orig = canvas_.elData(elem, 'orig_opac');
|
||||
const orig = dataStorage.get(elem, 'orig_opac');
|
||||
if (orig !== 1) {
|
||||
elem.setAttribute('opacity', orig);
|
||||
} else {
|
||||
@@ -1070,6 +1013,7 @@ export const leaveContext = function () {
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setContext = function (elem) {
|
||||
const dataStorage = canvas_.getDataStorage();
|
||||
leaveContext();
|
||||
if (typeof elem === 'string') {
|
||||
elem = getElem(elem);
|
||||
@@ -1079,15 +1023,25 @@ export const setContext = function (elem) {
|
||||
canvas_.setCurrentGroup(elem);
|
||||
|
||||
// Disable other elements
|
||||
$(elem).parentsUntil('#svgcontent').andSelf().siblings().each(function () {
|
||||
const opac = this.getAttribute('opacity') || 1;
|
||||
// Store the original's opacity
|
||||
canvas_.elData(this, 'orig_opac', opac);
|
||||
this.setAttribute('opacity', opac * 0.33);
|
||||
this.setAttribute('style', 'pointer-events: none');
|
||||
disabledElems.push(this);
|
||||
const parentsUntil = getParentsUntil(elem, '#svgcontent');
|
||||
let siblings = [];
|
||||
parentsUntil.forEach(function (parent) {
|
||||
const elements = Array.prototype.filter.call(parent.parentNode.children, function(child){
|
||||
return child !== parent;
|
||||
});
|
||||
elements.forEach(function (element) {
|
||||
siblings.push(element);
|
||||
});
|
||||
});
|
||||
|
||||
siblings.forEach(function (curthis) {
|
||||
const opac = curthis.getAttribute('opacity') || 1;
|
||||
// Store the original's opacity
|
||||
dataStorage.put(curthis, 'orig_opac', opac);
|
||||
curthis.setAttribute('opacity', opac * 0.33);
|
||||
curthis.setAttribute('style', 'pointer-events: none');
|
||||
disabledElems.push(curthis);
|
||||
});
|
||||
canvas_.clearSelection();
|
||||
canvas_.call('contextset', canvas_.getCurrentGroup());
|
||||
};
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* @module elem-get-set get and set methods.
|
||||
* @license MIT
|
||||
* @copyright 2011 Jeff Schiller
|
||||
*/
|
||||
|
||||
/* globals jQuery */
|
||||
import { jGraduate } from '../editor/components/jgraduate/jQuery.jGraduate.js';
|
||||
|
||||
import * as hstry from './history.js';
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js';
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import jQueryPluginSVG from './jQuery.attr.js';
|
||||
import { NS } from '../common/namespaces.js';
|
||||
import {
|
||||
getVisibleElements, getStrokedBBoxDefaultVisible, findDefs,
|
||||
walkTree, isNullish, getHref, setHref, getElem
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
convertToNum
|
||||
} from '../common/units.js';
|
||||
import { getParents } from '../editor/components/jgraduate/Util.js';
|
||||
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
@@ -58,9 +61,14 @@ export const getResolutionMethod = function () {
|
||||
*/
|
||||
export const getTitleMethod = function (elem) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const dataStorage = elemContext_.getDataStorage();
|
||||
elem = elem || selectedElements[0];
|
||||
if (!elem) { return undefined; }
|
||||
elem = $(elem).data('gsvg') || $(elem).data('symbol') || elem;
|
||||
if (dataStorage.has(elem, 'gsvg')) {
|
||||
elem = dataStorage.get(elem, 'gsvg');
|
||||
} else if (dataStorage.has(elem, 'symbol')) {
|
||||
elem = dataStorage.get(elem, 'symbol');
|
||||
}
|
||||
const childs = elem.childNodes;
|
||||
for (const child of childs) {
|
||||
if (child.nodeName === 'title') {
|
||||
@@ -79,10 +87,13 @@ export const getTitleMethod = function (elem) {
|
||||
*/
|
||||
export const setGroupTitleMethod = function (val) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const dataStorage = elemContext_.getDataStorage();
|
||||
let elem = selectedElements[0];
|
||||
elem = $(elem).data('gsvg') || elem;
|
||||
if (dataStorage.has(elem, 'gsvg')) {
|
||||
elem = dataStorage.get(elem, 'gsvg');
|
||||
}
|
||||
|
||||
const ts = $(elem).children('title');
|
||||
const ts = elem.querySelectorAll('title');
|
||||
|
||||
const batchCmd = new BatchCommand('Set Label');
|
||||
|
||||
@@ -95,13 +106,13 @@ export const setGroupTitleMethod = function (val) {
|
||||
} else if (ts.length) {
|
||||
// Change title contents
|
||||
title = ts[0];
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(title, {'#text': title.textContent}));
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(title, { '#text': title.textContent }));
|
||||
title.textContent = val;
|
||||
} else {
|
||||
// Add title element
|
||||
title = elemContext_.getDOMDocument().createElementNS(NS.SVG, 'title');
|
||||
title.textContent = val;
|
||||
$(elem).prepend(title);
|
||||
elem.insertBefore(title, elem.firstChild);
|
||||
batchCmd.addSubCommand(new InsertElementCommand(title));
|
||||
}
|
||||
|
||||
@@ -140,7 +151,7 @@ export const setDocumentTitleMethod = function (newTitle) {
|
||||
// No title given, so element is not necessary
|
||||
docTitle.remove();
|
||||
}
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(docTitle, {'#text': oldTitle}));
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(docTitle, { '#text': oldTitle }));
|
||||
elemContext_.addCommandToHistory(batchCmd);
|
||||
};
|
||||
|
||||
@@ -157,7 +168,7 @@ export const setDocumentTitleMethod = function (newTitle) {
|
||||
export const setResolutionMethod = function (x, y) {
|
||||
const currentZoom = elemContext_.getCurrentZoom();
|
||||
const res = elemContext_.getCanvas().getResolution();
|
||||
const {w, h} = res;
|
||||
const { w, h } = res;
|
||||
let batchCmd;
|
||||
|
||||
if (x === 'fit') {
|
||||
@@ -169,7 +180,7 @@ export const setResolutionMethod = function (x, y) {
|
||||
const visEls = getVisibleElements();
|
||||
elemContext_.getCanvas().addToSelection(visEls);
|
||||
const dx = [], dy = [];
|
||||
$.each(visEls, function (i, item) {
|
||||
visEls.forEach(function(_item, _i){
|
||||
dx.push(bbox.x * -1);
|
||||
dy.push(bbox.y * -1);
|
||||
});
|
||||
@@ -197,10 +208,10 @@ export const setResolutionMethod = function (x, y) {
|
||||
|
||||
this.contentW = x;
|
||||
this.contentH = y;
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), {width: w, height: h}));
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), { width: w, height: h }));
|
||||
|
||||
elemContext_.getSVGContent().setAttribute('viewBox', [0, 0, x / currentZoom, y / currentZoom].join(' '));
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), {viewBox: ['0 0', w, h].join(' ')}));
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elemContext_.getSVGContent(), { viewBox: ['0 0', w, h].join(' ') }));
|
||||
|
||||
elemContext_.addCommandToHistory(batchCmd);
|
||||
elemContext_.call('changed', [elemContext_.getSVGContent()]);
|
||||
@@ -239,13 +250,13 @@ export const setBBoxZoomMethod = function (val, editorW, editorH) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
let spacer = 0.85;
|
||||
let bb;
|
||||
const calcZoom = function (bb) { // eslint-disable-line no-shadow
|
||||
const calcZoom = function (bb) {
|
||||
if (!bb) { return false; }
|
||||
const wZoom = Math.round((editorW / bb.width) * 100 * spacer) / 100;
|
||||
const hZoom = Math.round((editorH / bb.height) * 100 * spacer) / 100;
|
||||
const zoom = Math.min(wZoom, hZoom);
|
||||
elemContext_.getCanvas().setZoom(zoom);
|
||||
return {zoom, bbox: bb};
|
||||
return { zoom, bbox: bb };
|
||||
};
|
||||
|
||||
if (typeof val === 'object') {
|
||||
@@ -253,35 +264,35 @@ export const setBBoxZoomMethod = function (val, editorW, editorH) {
|
||||
if (bb.width === 0 || bb.height === 0) {
|
||||
const newzoom = bb.zoom ? bb.zoom : currentZoom * bb.factor;
|
||||
elemContext_.getCanvas().setZoom(newzoom);
|
||||
return {zoom: currentZoom, bbox: bb};
|
||||
return { zoom: currentZoom, bbox: bb };
|
||||
}
|
||||
return calcZoom(bb);
|
||||
}
|
||||
|
||||
switch (val) {
|
||||
case 'selection': {
|
||||
if (!selectedElements[0]) { return undefined; }
|
||||
const selectedElems = $.map(selectedElements, function (n) {
|
||||
if (n) {
|
||||
return n;
|
||||
}
|
||||
case 'selection': {
|
||||
if (!selectedElements[0]) { return undefined; }
|
||||
const selectedElems = $.map(selectedElements, function (n) {
|
||||
if (n) {
|
||||
return n;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
bb = getStrokedBBoxDefaultVisible(selectedElems);
|
||||
break;
|
||||
} case 'canvas': {
|
||||
const res = elemContext_.getCanvas().getResolution();
|
||||
spacer = 0.95;
|
||||
bb = { width: res.w, height: res.h, x: 0, y: 0 };
|
||||
break;
|
||||
} case 'content':
|
||||
bb = getStrokedBBoxDefaultVisible();
|
||||
break;
|
||||
case 'layer':
|
||||
bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer()));
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
});
|
||||
bb = getStrokedBBoxDefaultVisible(selectedElems);
|
||||
break;
|
||||
} case 'canvas': {
|
||||
const res = elemContext_.getCanvas().getResolution();
|
||||
spacer = 0.95;
|
||||
bb = {width: res.w, height: res.h, x: 0, y: 0};
|
||||
break;
|
||||
} case 'content':
|
||||
bb = getStrokedBBoxDefaultVisible();
|
||||
break;
|
||||
case 'layer':
|
||||
bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer()));
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return calcZoom(bb);
|
||||
};
|
||||
@@ -298,7 +309,7 @@ export const setZoomMethod = function (zoomLevel) {
|
||||
const res = elemContext_.getCanvas().getResolution();
|
||||
elemContext_.getSVGContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel);
|
||||
elemContext_.setCurrentZoom(zoomLevel);
|
||||
$.each(selectedElements, function (i, elem) {
|
||||
selectedElements.forEach(function(elem){
|
||||
if (!elem) { return; }
|
||||
elemContext_.getCanvas().selectorManager.requestSelector(elem).resize();
|
||||
});
|
||||
@@ -318,14 +329,14 @@ export const setZoomMethod = function (zoomLevel) {
|
||||
export const setColorMethod = function (type, val, preventUndo) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
elemContext_.setCurShape(type, val);
|
||||
elemContext_.setCurProperties(type + '_paint', {type: 'solidColor'});
|
||||
elemContext_.setCurProperties(type + '_paint', { type: 'solidColor' });
|
||||
const elems = [];
|
||||
/**
|
||||
*
|
||||
* @param {Element} e
|
||||
* @returns {void}
|
||||
*/
|
||||
function addNonG (e) {
|
||||
function addNonG(e) {
|
||||
if (e.nodeName !== 'g') {
|
||||
elems.push(e);
|
||||
}
|
||||
@@ -372,7 +383,8 @@ export const setGradientMethod = function (type) {
|
||||
// no duplicate found, so import gradient into defs
|
||||
if (!duplicateGrad) {
|
||||
// const origGrad = grad;
|
||||
grad = defs.appendChild(elemContext_.getDOMDocument().importNode(grad, true));
|
||||
grad = elemContext_.getDOMDocument().importNode(grad, true);
|
||||
defs.append(grad);
|
||||
// get next id and set it on the grad
|
||||
grad.id = elemContext_.getCanvas().getNextId();
|
||||
} else { // use existing gradient
|
||||
@@ -389,25 +401,37 @@ export const setGradientMethod = function (type) {
|
||||
*/
|
||||
export const findDuplicateGradient = function (grad) {
|
||||
const defs = findDefs();
|
||||
const existingGrads = $(defs).find('linearGradient, radialGradient');
|
||||
const existingGrads = defs.querySelectorAll('linearGradient, radialGradient');
|
||||
let i = existingGrads.length;
|
||||
const radAttrs = ['r', 'cx', 'cy', 'fx', 'fy'];
|
||||
while (i--) {
|
||||
const og = existingGrads[i];
|
||||
if (grad.tagName === 'linearGradient') {
|
||||
if (grad.getAttribute('x1') !== og.getAttribute('x1') ||
|
||||
grad.getAttribute('y1') !== og.getAttribute('y1') ||
|
||||
grad.getAttribute('x2') !== og.getAttribute('x2') ||
|
||||
grad.getAttribute('y2') !== og.getAttribute('y2')
|
||||
grad.getAttribute('y1') !== og.getAttribute('y1') ||
|
||||
grad.getAttribute('x2') !== og.getAttribute('x2') ||
|
||||
grad.getAttribute('y2') !== og.getAttribute('y2')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
const gradAttrs = $(grad).attr(radAttrs);
|
||||
const ogAttrs = $(og).attr(radAttrs);
|
||||
const gradAttrs = {
|
||||
r: grad.getAttribute('r'),
|
||||
cx: grad.getAttribute('cx'),
|
||||
cy: grad.getAttribute('cy'),
|
||||
fx: grad.getAttribute('fx'),
|
||||
fy: grad.getAttribute('fy')
|
||||
};
|
||||
const ogAttrs = {
|
||||
r: og.getAttribute('r'),
|
||||
cx: og.getAttribute('cx'),
|
||||
cy: og.getAttribute('cy'),
|
||||
fx: og.getAttribute('fx'),
|
||||
fy: og.getAttribute('fy')
|
||||
};
|
||||
|
||||
let diff = false;
|
||||
$.each(radAttrs, function (j, attr) {
|
||||
radAttrs.forEach(function (attr) {
|
||||
if (gradAttrs[attr] !== ogAttrs[attr]) { diff = true; }
|
||||
});
|
||||
|
||||
@@ -428,8 +452,8 @@ export const findDuplicateGradient = function (grad) {
|
||||
const ostop = ostops[j];
|
||||
|
||||
if (stop.getAttribute('offset') !== ostop.getAttribute('offset') ||
|
||||
stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') ||
|
||||
stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) {
|
||||
stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') ||
|
||||
stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -451,20 +475,20 @@ export const findDuplicateGradient = function (grad) {
|
||||
*/
|
||||
export const setPaintMethod = function (type, paint) {
|
||||
// make a copy
|
||||
const p = new $.jGraduate.Paint(paint);
|
||||
const p = new jGraduate.Paint(paint);
|
||||
this.setPaintOpacity(type, p.alpha / 100, true);
|
||||
|
||||
// now set the current paint object
|
||||
elemContext_.setCurProperties(type + '_paint', p);
|
||||
switch (p.type) {
|
||||
case 'solidColor':
|
||||
this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none');
|
||||
break;
|
||||
case 'linearGradient':
|
||||
case 'radialGradient':
|
||||
elemContext_.setCanvas(type + 'Grad', p[p.type]);
|
||||
elemContext_.getCanvas().setGradient(type);
|
||||
break;
|
||||
case 'solidColor':
|
||||
this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none');
|
||||
break;
|
||||
case 'linearGradient':
|
||||
case 'radialGradient':
|
||||
elemContext_.setCanvas(type + 'Grad', p[p.type]);
|
||||
elemContext_.getCanvas().setGradient(type);
|
||||
break;
|
||||
}
|
||||
};
|
||||
/**
|
||||
@@ -489,7 +513,8 @@ export const setStrokeWidthMethod = function (val) {
|
||||
* @param {Element} e
|
||||
* @returns {void}
|
||||
*/
|
||||
function addNonG (e) {
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
function addNonG(e) {
|
||||
if (e.nodeName !== 'g') {
|
||||
elems.push(e);
|
||||
}
|
||||
@@ -550,7 +575,7 @@ export const getBoldMethod = function () {
|
||||
// should only have one element selected
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
isNullish(selectedElements[1])) {
|
||||
return (selected.getAttribute('font-weight') === 'bold');
|
||||
}
|
||||
return false;
|
||||
@@ -566,7 +591,7 @@ export const setBoldMethod = function (b) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('font-weight', b ? 'bold' : 'normal');
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
@@ -583,7 +608,7 @@ export const getItalicMethod = function () {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
isNullish(selectedElements[1])) {
|
||||
return (selected.getAttribute('font-style') === 'italic');
|
||||
}
|
||||
return false;
|
||||
@@ -599,7 +624,7 @@ export const setItalicMethod = function (i) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('font-style', i ? 'italic' : 'normal');
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
@@ -608,68 +633,7 @@ isNullish(selectedElements[1])) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the selected element has the given text decoration value or not
|
||||
* @function module:svgcanvas.SvgCanvas#hasTextDecoration
|
||||
* @param {string} value - The value that should be checked
|
||||
* @returns {boolean} Indicates whether or not element has the text decoration value
|
||||
*/
|
||||
export const hasTextDecorationMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
const attribute = selected.getAttribute('text-decoration');
|
||||
if (attribute) {
|
||||
return attribute.includes(value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the given value to the text decoration
|
||||
* @function module:svgcanvas.SvgCanvas#addTextDecoration
|
||||
* @param {string} value - The value that should be added
|
||||
* @returns {void}
|
||||
*/
|
||||
export const addTextDecorationMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
const oldValue = selected.getAttribute('text-decoration') || '';
|
||||
elemContext_.getCanvas().changeSelectedAttribute('text-decoration', oldValue + ' ' + value);
|
||||
}
|
||||
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the given value from the text decoration
|
||||
* @function module:svgcanvas.SvgCanvas#removeTextDecoration
|
||||
* @param {string} value - The value that should be removed
|
||||
* @returns {void}
|
||||
*/
|
||||
export const removeTextDecorationMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
const actualValues = selected.getAttribute('text-decoration');
|
||||
elemContext_.getCanvas().changeSelectedAttribute('text-decoration', actualValues.replace(value, ''));
|
||||
}
|
||||
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new text anchor
|
||||
* @function module:svgcanvas.SvgCanvas#setTextAnchor
|
||||
* @function module:svgcanvas.SvgCanvas#setTextAnchorMethod Set the new text anchor
|
||||
* @param {string} value - The text anchor value (start, middle or end)
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -677,7 +641,7 @@ export const setTextAnchorMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('text-anchor', value);
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
@@ -685,134 +649,6 @@ export const setTextAnchorMethod = function (value) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the letter spacing value
|
||||
* @function module:svgcanvas.SvgCanvas#getLetterSpacing
|
||||
* @returns {string} The letter spacing value
|
||||
*/
|
||||
export const getLetterSpacingMethod = function () {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
return selected.getAttribute('letter-spacing') || 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new letter spacing.
|
||||
* @function module:svgcanvas.SvgCanvas#setLetterSpacing
|
||||
* @param {string} value - The letter spacing
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setLetterSpacingMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('letter-spacing', value);
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the word spacing value
|
||||
* @function module:svgcanvas.SvgCanvas#getWordSpacing
|
||||
* @returns {string} The word spacing value
|
||||
*/
|
||||
export const getWordSpacingMethod = function () {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
return selected.getAttribute('word-spacing') || 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new word spacing.
|
||||
* @function module:svgcanvas.SvgCanvas#setWordSpacing
|
||||
* @param {string} value - The word spacing
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setWordSpacingMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('word-spacing', value);
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the text length value
|
||||
* @function module:svgcanvas.SvgCanvas#getTextLength
|
||||
* @returns {string} The text length value
|
||||
*/
|
||||
export const getTextLengthMethod = function () {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
return selected.getAttribute('textLength') || 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new text length.
|
||||
* @function module:svgcanvas.SvgCanvas#setTextLength
|
||||
* @param {string} value - The text length
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setTextLengthMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('textLength', value);
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the length adjust value
|
||||
* @function module:svgcanvas.SvgCanvas#getLengthAdjust
|
||||
* @returns {string} The length adjust value
|
||||
*/
|
||||
export const getLengthAdjustMethod = function () {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' && isNullish(selectedElements[1])) {
|
||||
return selected.getAttribute('lengthAdjust') || 0;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new length adjust.
|
||||
* @function module:svgcanvas.SvgCanvas#setLengthAdjust
|
||||
* @param {string} value - The length adjust
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setLengthAdjustMethod = function (value) {
|
||||
const selectedElements = elemContext_.getSelectedElements();
|
||||
const selected = selectedElements[0];
|
||||
if (!isNullish(selected) && selected.tagName === 'text' &&
|
||||
isNullish(selectedElements[1])) {
|
||||
elemContext_.getCanvas().changeSelectedAttribute('lengthAdjust', value);
|
||||
}
|
||||
if (!selectedElements[0].textContent) {
|
||||
elemContext_.getCanvas().textActions.setCursor();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @function module:svgcanvas.SvgCanvas#getFontFamily
|
||||
* @returns {string} The current font family
|
||||
@@ -914,7 +750,10 @@ export const setImageURLMethod = function (val) {
|
||||
const elem = selectedElements[0];
|
||||
if (!elem) { return; }
|
||||
|
||||
const attrs = $(elem).attr(['width', 'height']);
|
||||
const attrs = {
|
||||
width: elem.getAttribute('width'),
|
||||
height: elem.getAttribute('height'),
|
||||
};
|
||||
const setsize = (!attrs.width || !attrs.height);
|
||||
|
||||
const curHref = getHref(elem);
|
||||
@@ -930,21 +769,22 @@ export const setImageURLMethod = function (val) {
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elem, {
|
||||
'#href': curHref
|
||||
}));
|
||||
|
||||
$(new Image()).load(function () {
|
||||
const changes = $(elem).attr(['width', 'height']);
|
||||
|
||||
$(elem).attr({
|
||||
width: this.width,
|
||||
height: this.height
|
||||
});
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
const changes = {
|
||||
width: elem.getAttribute('width'),
|
||||
height: elem.getAttribute('height'),
|
||||
};
|
||||
elem.setAttribute('width', this.width);
|
||||
elem.setAttribute('height', this.height);
|
||||
|
||||
elemContext_.getCanvas().selectorManager.requestSelector(elem).resize();
|
||||
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
|
||||
elemContext_.addCommandToHistory(batchCmd);
|
||||
elemContext_.call('changed', [elem]);
|
||||
}).attr('src', val);
|
||||
};
|
||||
img.src = val;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -959,7 +799,7 @@ export const setLinkURLMethod = function (val) {
|
||||
if (!elem) { return; }
|
||||
if (elem.tagName !== 'a') {
|
||||
// See if parent is an anchor
|
||||
const parentsA = $(elem).parents('a');
|
||||
const parentsA = getParents(elem.parentNode, 'a');
|
||||
if (parentsA.length) {
|
||||
elem = parentsA[0];
|
||||
} else {
|
||||
@@ -997,7 +837,7 @@ export const setRectRadiusMethod = function (val) {
|
||||
if (r !== String(val)) {
|
||||
selected.setAttribute('rx', val);
|
||||
selected.setAttribute('ry', val);
|
||||
elemContext_.addCommandToHistory(new ChangeElementCommand(selected, {rx: r, ry: r}, 'Radius'));
|
||||
elemContext_.addCommandToHistory(new ChangeElementCommand(selected, { rx: r, ry: r }, 'Radius'));
|
||||
elemContext_.call('changed', [selected]);
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +887,7 @@ export const setSegTypeMethod = function (newType) {
|
||||
*/
|
||||
export const setBackgroundMethod = function (color, url) {
|
||||
const bg = getElem('canvasBackground');
|
||||
const border = $(bg).find('rect')[0];
|
||||
const border = bg.querySelector('rect');
|
||||
let bgImg = getElem('background_image');
|
||||
let bgPattern = getElem('background_pattern');
|
||||
border.setAttribute('fill', color === 'chessboard' ? '#fff' : color);
|
||||
@@ -1064,11 +904,11 @@ export const setBackgroundMethod = function (color, url) {
|
||||
const div = document.createElement('div');
|
||||
elemContext_.getCanvas().assignAttributes(div, {
|
||||
style: 'pointer-events:none;width:100%;height:100%;' +
|
||||
'background-image:url(data:image/gif;base64,' +
|
||||
'R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+' +
|
||||
'gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7);'
|
||||
'background-image:url(data:image/gif;base64,' +
|
||||
'R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+' +
|
||||
'gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7);'
|
||||
});
|
||||
bgPattern.appendChild(div);
|
||||
bgPattern.append(div);
|
||||
bg.append(bgPattern);
|
||||
}
|
||||
} else if (bgPattern) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,8 @@
|
||||
* @copyright 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {getHref, setHref, getRotationAngle, isNullish} from '../common/utilities.js';
|
||||
import {removeElementFromListMap} from '../common/svgtransformlist.js';
|
||||
import {getHref, setHref, getRotationAngle, isNullish} from './utilities.js';
|
||||
import {removeElementFromListMap} from './svgtransformlist.js';
|
||||
|
||||
/**
|
||||
* Group: Undo/Redo history management.
|
||||
@@ -260,10 +260,8 @@ export class RemoveElementCommand extends Command {
|
||||
unapply (handler) {
|
||||
super.unapply(handler, () => {
|
||||
removeElementFromListMap(this.elem);
|
||||
if (isNullish(this.nextSibling)) {
|
||||
if (window.console) {
|
||||
console.error('Reference element was lost');
|
||||
}
|
||||
if (isNullish(this.nextSibling) && window.console) {
|
||||
console.error('Reference element was lost');
|
||||
}
|
||||
this.parent.insertBefore(this.elem, this.nextSibling); // Don't use `before` or `prepend` as `this.nextSibling` may be `null`
|
||||
});
|
||||
|
||||
79
src/svgcanvas/jQuery.attr.js
Normal file
79
src/svgcanvas/jQuery.attr.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* A jQuery module to work with SVG attributes.
|
||||
* @module jQueryAttr
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* This fixes `$(...).attr()` to work as expected with SVG elements.
|
||||
* Does not currently use `*AttributeNS()` since we rarely need that.
|
||||
* Adds {@link external:jQuery.fn.attr}.
|
||||
* See {@link https://api.jquery.com/attr/} for basic documentation of `.attr()`.
|
||||
*
|
||||
* Additional functionality:
|
||||
* - When getting attributes, a string that's a number is returned as type number.
|
||||
* - If an array is supplied as the first parameter, multiple values are returned
|
||||
* as an object with values for each given attribute.
|
||||
* @function module:jQueryAttr.jQueryAttr
|
||||
* @param {external:jQuery} $ The jQuery object to which to add the plug-in
|
||||
* @returns {external:jQuery}
|
||||
*/
|
||||
export default function jQueryPluginSVG ($) {
|
||||
const proxied = $.fn.attr,
|
||||
svgns = 'http://www.w3.org/2000/svg';
|
||||
/**
|
||||
* @typedef {PlainObject<string, string|Float>} module:jQueryAttr.Attributes
|
||||
*/
|
||||
/**
|
||||
* @function external:jQuery.fn.attr
|
||||
* @param {string|string[]|PlainObject<string, string>} key
|
||||
* @param {string} value
|
||||
* @returns {external:jQuery|module:jQueryAttr.Attributes}
|
||||
*/
|
||||
$.fn.attr = function (key, value) {
|
||||
const len = this.length;
|
||||
if (!len) { return proxied.call(this, key, value); }
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const elem = this[i];
|
||||
// set/get SVG attribute
|
||||
if (elem.namespaceURI === svgns) {
|
||||
// Setting attribute
|
||||
if (value !== undefined) {
|
||||
elem.setAttribute(key, value);
|
||||
} else if (Array.isArray(key)) {
|
||||
// Getting attributes from array
|
||||
const obj = {};
|
||||
let j = key.length;
|
||||
|
||||
while (j--) {
|
||||
const aname = key[j];
|
||||
let attr = elem.getAttribute(aname);
|
||||
// This returns a number when appropriate
|
||||
if (attr || attr === '0') {
|
||||
attr = isNaN(attr) ? attr : (attr - 0);
|
||||
}
|
||||
obj[aname] = attr;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
if (typeof key === 'object') {
|
||||
// Setting attributes from object
|
||||
for (const [name, val] of Object.entries(key)) {
|
||||
elem.setAttribute(name, val);
|
||||
}
|
||||
// Getting attribute
|
||||
} else {
|
||||
let attr = elem.getAttribute(key);
|
||||
if (attr || attr === '0') {
|
||||
attr = isNaN(attr) ? attr : (attr - 0);
|
||||
}
|
||||
return attr;
|
||||
}
|
||||
} else {
|
||||
return proxied.call(this, key, value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
return $;
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
import {getElem, assignAttributes, cleanupElement} from '../common/utilities.js';
|
||||
import {getElem, assignAttributes, cleanupElement} from './utilities.js';
|
||||
import {NS} from '../common/namespaces.js';
|
||||
|
||||
let jsonContext_ = null;
|
||||
|
||||
229
src/svgcanvas/layer.js
Normal file
229
src/svgcanvas/layer.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Provides tools for the layer concept.
|
||||
* @module layer
|
||||
* @license MIT
|
||||
*
|
||||
* @copyright 2011 Jeff Schiller, 2016 Flint O'Brien
|
||||
*/
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {toXml, walkTree, isNullish} from './utilities.js';
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates the concept of a layer in the drawing. It can be constructed with
|
||||
* an existing group element or, with three parameters, will create a new layer group element.
|
||||
*
|
||||
* @example
|
||||
* const l1 = new Layer('name', group); // Use the existing group for this layer.
|
||||
* const l2 = new Layer('name', group, svgElem); // Create a new group and add it to the DOM after group.
|
||||
* const l3 = new Layer('name', null, svgElem); // Create a new group and add it to the DOM as the last layer.
|
||||
* @memberof module:layer
|
||||
*/
|
||||
class Layer {
|
||||
/**
|
||||
* @param {string} name - Layer name
|
||||
* @param {SVGGElement|null} group - An existing SVG group element or null.
|
||||
* If group and no svgElem, use group for this layer.
|
||||
* If group and svgElem, create a new group element and insert it in the DOM after group.
|
||||
* If no group and svgElem, create a new group element and insert it in the DOM as the last layer.
|
||||
* @param {SVGGElement} [svgElem] - The SVG DOM element. If defined, use this to add
|
||||
* a new layer to the document.
|
||||
*/
|
||||
constructor (name, group, svgElem) {
|
||||
this.name_ = name;
|
||||
this.group_ = svgElem ? null : group;
|
||||
|
||||
if (svgElem) {
|
||||
// Create a group element with title and add it to the DOM.
|
||||
const svgdoc = svgElem.ownerDocument;
|
||||
this.group_ = svgdoc.createElementNS(NS.SVG, 'g');
|
||||
const layerTitle = svgdoc.createElementNS(NS.SVG, 'title');
|
||||
layerTitle.textContent = name;
|
||||
this.group_.append(layerTitle);
|
||||
if (group) {
|
||||
group.insertAdjacentElement('afterend', this.group_);
|
||||
} else {
|
||||
svgElem.append(this.group_);
|
||||
}
|
||||
}
|
||||
|
||||
addLayerClass(this.group_);
|
||||
walkTree(this.group_, function (e) {
|
||||
e.setAttribute('style', 'pointer-events:inherit');
|
||||
});
|
||||
|
||||
this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the layer's name.
|
||||
* @returns {string} The layer name
|
||||
*/
|
||||
getName () {
|
||||
return this.name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the group element for this layer.
|
||||
* @returns {SVGGElement} The layer SVG group
|
||||
*/
|
||||
getGroup () {
|
||||
return this.group_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Active this layer so it takes pointer events.
|
||||
* @returns {void}
|
||||
*/
|
||||
activate () {
|
||||
this.group_.setAttribute('style', 'pointer-events:all');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactive this layer so it does NOT take pointer events.
|
||||
* @returns {void}
|
||||
*/
|
||||
deactivate () {
|
||||
this.group_.setAttribute('style', 'pointer-events:none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this layer visible or hidden based on 'visible' parameter.
|
||||
* @param {boolean} visible - If true, make visible; otherwise, hide it.
|
||||
* @returns {void}
|
||||
*/
|
||||
setVisible (visible) {
|
||||
const expected = visible === undefined || visible ? 'inline' : 'none';
|
||||
const oldDisplay = this.group_.getAttribute('display');
|
||||
if (oldDisplay !== expected) {
|
||||
this.group_.setAttribute('display', expected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this layer visible?
|
||||
* @returns {boolean} True if visible.
|
||||
*/
|
||||
isVisible () {
|
||||
return this.group_.getAttribute('display') !== 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get layer opacity.
|
||||
* @returns {Float} Opacity value.
|
||||
*/
|
||||
getOpacity () {
|
||||
const opacity = this.group_.getAttribute('opacity');
|
||||
if (isNullish(opacity)) {
|
||||
return 1;
|
||||
}
|
||||
return Number.parseFloat(opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0,
|
||||
* nothing happens.
|
||||
* @param {Float} opacity - A float value in the range 0.0-1.0
|
||||
* @returns {void}
|
||||
*/
|
||||
setOpacity (opacity) {
|
||||
if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) {
|
||||
this.group_.setAttribute('opacity', opacity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append children to this layer.
|
||||
* @param {SVGGElement} children - The children to append to this layer.
|
||||
* @returns {void}
|
||||
*/
|
||||
appendChildren (children) {
|
||||
for (const child of children) {
|
||||
this.group_.append(child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {SVGTitleElement|null}
|
||||
*/
|
||||
getTitleElement () {
|
||||
const len = this.group_.childNodes.length;
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const child = this.group_.childNodes.item(i);
|
||||
if (child && child.tagName === 'title') {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of this layer.
|
||||
* @param {string} name - The new name.
|
||||
* @param {module:history.HistoryRecordingService} hrService - History recording service
|
||||
* @returns {string|null} The new name if changed; otherwise, null.
|
||||
*/
|
||||
setName (name, hrService) {
|
||||
const previousName = this.name_;
|
||||
name = toXml(name);
|
||||
// now change the underlying title element contents
|
||||
const title = this.getTitleElement();
|
||||
if (title) {
|
||||
while(title.firstChild)
|
||||
title.removeChild(title.firstChild);
|
||||
title.textContent = name;
|
||||
this.name_ = name;
|
||||
if (hrService) {
|
||||
hrService.changeElement(title, {'#text': previousName});
|
||||
}
|
||||
return this.name_;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this layer's group from the DOM. No more functions on group can be called after this.
|
||||
* @returns {SVGGElement} The layer SVG group that was just removed.
|
||||
*/
|
||||
removeGroup () {
|
||||
const group = this.group_;
|
||||
this.group_.remove();
|
||||
this.group_ = undefined;
|
||||
return group;
|
||||
}
|
||||
/**
|
||||
* Test whether an element is a layer or not.
|
||||
* @param {SVGGElement} elem - The SVGGElement to test.
|
||||
* @returns {boolean} True if the element is a layer
|
||||
*/
|
||||
static isLayer (elem) {
|
||||
return elem && elem.tagName === 'g' && Layer.CLASS_REGEX.test(elem.getAttribute('class'));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @property {string} CLASS_NAME - class attribute assigned to all layer groups.
|
||||
*/
|
||||
Layer.CLASS_NAME = 'layer';
|
||||
|
||||
/**
|
||||
* @property {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME
|
||||
*/
|
||||
Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)');
|
||||
|
||||
/**
|
||||
* Add class `Layer.CLASS_NAME` to the element (usually `class='layer'`).
|
||||
*
|
||||
* @param {SVGGElement} elem - The SVG element to update
|
||||
* @returns {void}
|
||||
*/
|
||||
function addLayerClass (elem) {
|
||||
const classes = elem.getAttribute('class');
|
||||
if (isNullish(classes) || !classes.length) {
|
||||
elem.setAttribute('class', Layer.CLASS_NAME);
|
||||
} else if (!Layer.CLASS_REGEX.test(classes)) {
|
||||
elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
export default Layer;
|
||||
222
src/svgcanvas/math.js
Normal file
222
src/svgcanvas/math.js
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Mathematical utilities.
|
||||
* @module math
|
||||
* @license MIT
|
||||
*
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:math.AngleCoord45
|
||||
* @property {Float} x - The angle-snapped x value
|
||||
* @property {Float} y - The angle-snapped y value
|
||||
* @property {Integer} a - The angle at which to snap
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:math.XYObject
|
||||
* @property {Float} x
|
||||
* @property {Float} y
|
||||
*/
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {getTransformList} from './svgtransformlist.js';
|
||||
|
||||
// Constants
|
||||
const NEAR_ZERO = 1e-14;
|
||||
|
||||
// Throw away SVGSVGElement used for creating matrices/transforms.
|
||||
const svg = document.createElementNS(NS.SVG, 'svg');
|
||||
|
||||
/**
|
||||
* A (hopefully) quicker function to transform a point by a matrix
|
||||
* (this function avoids any DOM calls and just does the math).
|
||||
* @function module:math.transformPoint
|
||||
* @param {Float} x - Float representing the x coordinate
|
||||
* @param {Float} y - Float representing the y coordinate
|
||||
* @param {SVGMatrix} m - Matrix object to transform the point with
|
||||
* @returns {module:math.XYObject} An x, y object representing the transformed point
|
||||
*/
|
||||
export const transformPoint = function (x, y, m) {
|
||||
return {x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to check if the matrix performs no actual transform
|
||||
* (i.e. exists for identity purposes).
|
||||
* @function module:math.isIdentity
|
||||
* @param {SVGMatrix} m - The matrix object to check
|
||||
* @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
|
||||
*/
|
||||
export const isIdentity = function (m) {
|
||||
return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function tries to return a `SVGMatrix` that is the multiplication `m1 * m2`.
|
||||
* We also round to zero when it's near zero.
|
||||
* @function module:math.matrixMultiply
|
||||
* @param {...SVGMatrix} args - Matrix objects to multiply
|
||||
* @returns {SVGMatrix} The matrix object resulting from the calculation
|
||||
*/
|
||||
export const matrixMultiply = function (...args) {
|
||||
const m = args.reduceRight((prev, m1) => {
|
||||
return m1.multiply(prev);
|
||||
});
|
||||
|
||||
if (Math.abs(m.a) < NEAR_ZERO) { m.a = 0; }
|
||||
if (Math.abs(m.b) < NEAR_ZERO) { m.b = 0; }
|
||||
if (Math.abs(m.c) < NEAR_ZERO) { m.c = 0; }
|
||||
if (Math.abs(m.d) < NEAR_ZERO) { m.d = 0; }
|
||||
if (Math.abs(m.e) < NEAR_ZERO) { m.e = 0; }
|
||||
if (Math.abs(m.f) < NEAR_ZERO) { m.f = 0; }
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* See if the given transformlist includes a non-indentity matrix transform.
|
||||
* @function module:math.hasMatrixTransform
|
||||
* @param {SVGTransformList} [tlist] - The transformlist to check
|
||||
* @returns {boolean} Whether or not a matrix transform was found
|
||||
*/
|
||||
export const hasMatrixTransform = function (tlist) {
|
||||
if (!tlist) { return false; }
|
||||
let num = tlist.numberOfItems;
|
||||
while (num--) {
|
||||
const xform = tlist.getItem(num);
|
||||
if (xform.type === 1 && !isIdentity(xform.matrix)) { return true; }
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {PlainObject} module:math.TransformedBox An object with the following values
|
||||
* @property {module:math.XYObject} tl - The top left coordinate
|
||||
* @property {module:math.XYObject} tr - The top right coordinate
|
||||
* @property {module:math.XYObject} bl - The bottom left coordinate
|
||||
* @property {module:math.XYObject} br - The bottom right coordinate
|
||||
* @property {PlainObject} aabox - Object with the following values:
|
||||
* @property {Float} aabox.x - Float with the axis-aligned x coordinate
|
||||
* @property {Float} aabox.y - Float with the axis-aligned y coordinate
|
||||
* @property {Float} aabox.width - Float with the axis-aligned width coordinate
|
||||
* @property {Float} aabox.height - Float with the axis-aligned height coordinate
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms a rectangle based on the given matrix.
|
||||
* @function module:math.transformBox
|
||||
* @param {Float} l - Float with the box's left coordinate
|
||||
* @param {Float} t - Float with the box's top coordinate
|
||||
* @param {Float} w - Float with the box width
|
||||
* @param {Float} h - Float with the box height
|
||||
* @param {SVGMatrix} m - Matrix object to transform the box by
|
||||
* @returns {module:math.TransformedBox}
|
||||
*/
|
||||
export const transformBox = function (l, t, w, h, m) {
|
||||
const tl = transformPoint(l, t, m),
|
||||
tr = transformPoint((l + w), t, m),
|
||||
bl = transformPoint(l, (t + h), m),
|
||||
br = transformPoint((l + w), (t + h), m),
|
||||
|
||||
minx = Math.min(tl.x, tr.x, bl.x, br.x),
|
||||
maxx = Math.max(tl.x, tr.x, bl.x, br.x),
|
||||
miny = Math.min(tl.y, tr.y, bl.y, br.y),
|
||||
maxy = Math.max(tl.y, tr.y, bl.y, br.y);
|
||||
|
||||
return {
|
||||
tl,
|
||||
tr,
|
||||
bl,
|
||||
br,
|
||||
aabox: {
|
||||
x: minx,
|
||||
y: miny,
|
||||
width: (maxx - minx),
|
||||
height: (maxy - miny)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This returns a single matrix Transform for a given Transform List
|
||||
* (this is the equivalent of `SVGTransformList.consolidate()` but unlike
|
||||
* that method, this one does not modify the actual `SVGTransformList`).
|
||||
* This function is very liberal with its `min`, `max` arguments.
|
||||
* @function module:math.transformListToTransform
|
||||
* @param {SVGTransformList} tlist - The transformlist object
|
||||
* @param {Integer} [min=0] - Optional integer indicating start transform position
|
||||
* @param {Integer} [max] - Optional integer indicating end transform position;
|
||||
* defaults to one less than the tlist's `numberOfItems`
|
||||
* @returns {SVGTransform} A single matrix transform object
|
||||
*/
|
||||
export const transformListToTransform = function (tlist, min, max) {
|
||||
if (!tlist) {
|
||||
// Or should tlist = null have been prevented before this?
|
||||
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
|
||||
}
|
||||
min = min || 0;
|
||||
max = max || (tlist.numberOfItems - 1);
|
||||
min = Number.parseInt(min);
|
||||
max = Number.parseInt(max);
|
||||
if (min > max) { const temp = max; max = min; min = temp; }
|
||||
let m = svg.createSVGMatrix();
|
||||
for (let i = min; i <= max; ++i) {
|
||||
// if our indices are out of range, just use a harmless identity matrix
|
||||
const mtom = (i >= 0 && i < tlist.numberOfItems
|
||||
? tlist.getItem(i).matrix
|
||||
: svg.createSVGMatrix());
|
||||
m = matrixMultiply(m, mtom);
|
||||
}
|
||||
return svg.createSVGTransformFromMatrix(m);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the matrix object for a given element.
|
||||
* @function module:math.getMatrix
|
||||
* @param {Element} elem - The DOM element to check
|
||||
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
|
||||
*/
|
||||
export const getMatrix = function (elem) {
|
||||
const tlist = getTransformList(elem);
|
||||
return transformListToTransform(tlist).matrix;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a 45 degree angle coordinate associated with the two given
|
||||
* coordinates.
|
||||
* @function module:math.snapToAngle
|
||||
* @param {Integer} x1 - First coordinate's x value
|
||||
* @param {Integer} y1 - First coordinate's y value
|
||||
* @param {Integer} x2 - Second coordinate's x value
|
||||
* @param {Integer} y2 - Second coordinate's y value
|
||||
* @returns {module:math.AngleCoord45}
|
||||
*/
|
||||
export const snapToAngle = function (x1, y1, x2, y2) {
|
||||
const snap = Math.PI / 4; // 45 degrees
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const angle = Math.atan2(dy, dx);
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
const snapangle = Math.round(angle / snap) * snap;
|
||||
|
||||
return {
|
||||
x: x1 + dist * Math.cos(snapangle),
|
||||
y: y1 + dist * Math.sin(snapangle),
|
||||
a: snapangle
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if two rectangles (BBoxes objects) intersect each other.
|
||||
* @function module:math.rectsIntersect
|
||||
* @param {SVGRect} r1 - The first BBox-like object
|
||||
* @param {SVGRect} r2 - The second BBox-like object
|
||||
* @returns {boolean} True if rectangles intersect
|
||||
*/
|
||||
export const rectsIntersect = function (r1, r2) {
|
||||
return r2.x < (r1.x + r1.width) &&
|
||||
(r2.x + r2.width) > r1.x &&
|
||||
r2.y < (r1.y + r1.height) &&
|
||||
(r2.y + r2.height) > r1.y;
|
||||
};
|
||||
@@ -1,12 +1,7 @@
|
||||
/* globals jQuery */
|
||||
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
|
||||
import {
|
||||
getStrokedBBoxDefaultVisible
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import * as hstry from './history.js';
|
||||
// Constants
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
const {
|
||||
InsertElementCommand, BatchCommand
|
||||
@@ -118,7 +113,7 @@ export const pasteElementsMethod = function (type, x, y) {
|
||||
dx = [],
|
||||
dy = [];
|
||||
|
||||
$.each(pasted, function (i, item) {
|
||||
pasted.forEach(function(_item){
|
||||
dx.push(cx);
|
||||
dy.push(cy);
|
||||
});
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {shortFloat} from '../common/units.js';
|
||||
import {getTransformList} from '../common/svgtransformlist.js';
|
||||
import {getTransformList} from './svgtransformlist.js';
|
||||
import {ChangeElementCommand, BatchCommand} from './history.js';
|
||||
import {
|
||||
transformPoint, snapToAngle, rectsIntersect,
|
||||
transformListToTransform
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
import {
|
||||
assignAttributes, getElem, getRotationAngle, snapToGrid, isNullish,
|
||||
getBBox as utilsGetBBox
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
isWebkit
|
||||
} from '../common/browser.js';
|
||||
@@ -114,6 +114,7 @@ export const convertPath = function (pth, toRel) {
|
||||
case 2: // absolute move (M)
|
||||
case 4: // absolute line (L)
|
||||
case 18: // absolute smooth quad (T)
|
||||
case 10: // absolute elliptical arc (A)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
@@ -165,10 +166,6 @@ export const convertPath = function (pth, toRel) {
|
||||
}
|
||||
d += pathDSegment(letter, [[x1, y1], [x, y]]);
|
||||
break;
|
||||
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
||||
case 10: // absolute elliptical arc (A)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
case 11: // relative elliptical arc (a)
|
||||
if (toRel) {
|
||||
@@ -217,7 +214,7 @@ export const convertPath = function (pth, toRel) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function pathDSegment (letter, points, morePoints, lastPoint) {
|
||||
$.each(points, function (i, pnt) {
|
||||
points.forEach(function(pnt, i){
|
||||
points[i] = shortFloat(pnt);
|
||||
});
|
||||
let segment = letter + points.join(' ');
|
||||
@@ -230,7 +227,6 @@ function pathDSegment (letter, points, morePoints, lastPoint) {
|
||||
return segment;
|
||||
}
|
||||
|
||||
/* eslint-disable jsdoc/require-property */
|
||||
/**
|
||||
* Group: Path edit functions.
|
||||
* Functions relating to editing path elements.
|
||||
@@ -238,7 +234,6 @@ function pathDSegment (letter, points, morePoints, lastPoint) {
|
||||
* @memberof module:path
|
||||
*/
|
||||
export const pathActionsMethod = (function () {
|
||||
/* eslint-enable jsdoc/require-property */
|
||||
let subpath = false;
|
||||
let newPoint, firstCtrl;
|
||||
|
||||
@@ -361,7 +356,7 @@ export const pathActionsMethod = (function () {
|
||||
'stroke-width': '0.5',
|
||||
fill: 'none'
|
||||
});
|
||||
stretchy = getElem('selectorParentGroup').appendChild(stretchy);
|
||||
getElem('selectorParentGroup').append(stretchy);
|
||||
}
|
||||
stretchy.setAttribute('display', 'inline');
|
||||
|
||||
@@ -437,7 +432,7 @@ export const pathActionsMethod = (function () {
|
||||
keep = false;
|
||||
return keep;
|
||||
}
|
||||
$(stretchy).remove();
|
||||
stretchy.remove();
|
||||
|
||||
// This will signal to commit the path
|
||||
// const element = newpath; // Other event handlers define own `element`, so this was probably not meant to interact with them or one which shares state (as there were none); I therefore adding a missing `var` to avoid a global
|
||||
@@ -450,9 +445,9 @@ export const pathActionsMethod = (function () {
|
||||
}
|
||||
|
||||
const newD = newpath.getAttribute('d');
|
||||
const origD = $(path.elem).attr('d');
|
||||
$(path.elem).attr('d', origD + newD);
|
||||
$(newpath).remove();
|
||||
const origD = path.elem.getAttribute('d');
|
||||
path.elem.setAttribute('d', origD + newD);
|
||||
newpath.parentNode.removeChild();
|
||||
if (path.matrix) {
|
||||
pathActionsContext_.recalcRotatedPath();
|
||||
}
|
||||
@@ -675,7 +670,7 @@ export const pathActionsMethod = (function () {
|
||||
}
|
||||
} else {
|
||||
path.selected_pts = [];
|
||||
path.eachSeg(function (i) {
|
||||
path.eachSeg(function (_i) {
|
||||
const seg = this;
|
||||
if (!seg.next && !seg.prev) { return; }
|
||||
|
||||
@@ -708,11 +703,11 @@ export const pathActionsMethod = (function () {
|
||||
/**
|
||||
* @param {Event} evt
|
||||
* @param {Element} element
|
||||
* @param {Float} mouseX
|
||||
* @param {Float} mouseY
|
||||
* @param {Float} _mouseX
|
||||
* @param {Float} _mouseY
|
||||
* @returns {module:path.keepElement|void}
|
||||
*/
|
||||
mouseUp (evt, element, mouseX, mouseY) {
|
||||
mouseUp (evt, element, _mouseX, _mouseY) {
|
||||
editorContext_ = pathActionsContext_.getEditorContext();
|
||||
const drawnPath = editorContext_.getDrawnPath();
|
||||
// Create mode
|
||||
@@ -862,15 +857,20 @@ export const pathActionsMethod = (function () {
|
||||
* @param {boolean} remove Not in use
|
||||
* @returns {void}
|
||||
*/
|
||||
clear (remove) {
|
||||
clear () {
|
||||
editorContext_ = pathActionsContext_.getEditorContext();
|
||||
const drawnPath = editorContext_.getDrawnPath();
|
||||
currentPath = null;
|
||||
if (drawnPath) {
|
||||
const elem = getElem(editorContext_.getId());
|
||||
$(getElem('path_stretch_line')).remove();
|
||||
$(elem).remove();
|
||||
$(getElem('pathpointgrip_container')).find('*').attr('display', 'none');
|
||||
const psl = getElem('path_stretch_line');
|
||||
psl.parentNode.removeChild(psl);
|
||||
elem.parentNode.removeChild(elem);
|
||||
const pathpointgripContainer = getElem('pathpointgrip_container');
|
||||
const elements = pathpointgripContainer.querySelectorAll('*');
|
||||
Array.prototype.forEach.call(elements, function(el){
|
||||
el.style.display = 'none';
|
||||
});
|
||||
firstCtrl = null;
|
||||
editorContext_.setDrawnPath(null);
|
||||
editorContext_.setStarted(false);
|
||||
@@ -909,7 +909,7 @@ export const pathActionsMethod = (function () {
|
||||
const type = seg.pathSegType;
|
||||
if (type === 1) { continue; }
|
||||
const pts = [];
|
||||
$.each(['', 1, 2], function (j, n) {
|
||||
['', 1, 2].forEach(function(n){
|
||||
const x = seg['x' + n], y = seg['y' + n];
|
||||
if (x !== undefined && y !== undefined) {
|
||||
const pt = transformPoint(x, y, m);
|
||||
@@ -1134,20 +1134,18 @@ export const pathActionsMethod = (function () {
|
||||
cleanup();
|
||||
break;
|
||||
}
|
||||
} else if (item.pathSegType === 2) {
|
||||
if (len > 0) {
|
||||
const prevType = segList.getItem(len - 1).pathSegType;
|
||||
// Path has M M
|
||||
if (prevType === 2) {
|
||||
remItems(len - 1, 1);
|
||||
cleanup();
|
||||
break;
|
||||
} else if (item.pathSegType === 2 && len > 0) {
|
||||
const prevType = segList.getItem(len - 1).pathSegType;
|
||||
// Path has M M
|
||||
if (prevType === 2) {
|
||||
remItems(len - 1, 1);
|
||||
cleanup();
|
||||
break;
|
||||
// Entire path ends with Z M
|
||||
} else if (prevType === 1 && segList.numberOfItems - 1 === len) {
|
||||
remItems(len, 1);
|
||||
cleanup();
|
||||
break;
|
||||
}
|
||||
} else if (prevType === 1 && segList.numberOfItems - 1 === len) {
|
||||
remItems(len, 1);
|
||||
cleanup();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1170,12 +1168,10 @@ export const pathActionsMethod = (function () {
|
||||
// TODO: Find right way to select point now
|
||||
// path.selectPt(selPt);
|
||||
if (window.opera) { // Opera repaints incorrectly
|
||||
const cp = $(path.elem);
|
||||
cp.attr('d', cp.attr('d'));
|
||||
path.elem.setAttribute('d', path.elem.getAttribute('d'));
|
||||
}
|
||||
path.endChanges('Delete path node(s)');
|
||||
},
|
||||
/* eslint-disable jsdoc/require-returns */
|
||||
// Can't seem to use `@borrows` here, so using `@see`
|
||||
/**
|
||||
* Smooth polyline into path.
|
||||
@@ -1183,7 +1179,7 @@ export const pathActionsMethod = (function () {
|
||||
* @see module:path~smoothPolylineIntoPath
|
||||
*/
|
||||
smoothPolylineIntoPath,
|
||||
/* eslint-enable jsdoc/require-returns */
|
||||
/* eslint-enable */
|
||||
/**
|
||||
* @param {?Integer} v See {@link https://www.w3.org/TR/SVG/single-page.html#paths-InterfaceSVGPathSeg}
|
||||
* @returns {void}
|
||||
@@ -1242,7 +1238,6 @@ export const pathActionsMethod = (function () {
|
||||
editorContext_ = pathActionsContext_.getEditorContext();
|
||||
if (isWebkit()) { editorContext_.resetD(elem); }
|
||||
},
|
||||
/* eslint-disable jsdoc/require-returns */
|
||||
// Can't seem to use `@borrows` here, so using `@see`
|
||||
/**
|
||||
* Convert a path to one with only absolute or relative values.
|
||||
@@ -1250,7 +1245,6 @@ export const pathActionsMethod = (function () {
|
||||
* @see module:path.convertPath
|
||||
*/
|
||||
convertPath
|
||||
/* eslint-enable jsdoc/require-returns */
|
||||
});
|
||||
})();
|
||||
// end pathActions
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Path functionality.
|
||||
* @module path
|
||||
@@ -11,16 +10,15 @@ import {NS} from '../common/namespaces.js';
|
||||
import {ChangeElementCommand} from './history.js';
|
||||
import {
|
||||
transformPoint, getMatrix
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
import {
|
||||
assignAttributes, getRotationAngle, isNullish,
|
||||
getElem
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
supportsPathInsertItemBefore, supportsPathReplaceItem, isWebkit
|
||||
} from '../common/browser.js';
|
||||
|
||||
const $ = jQuery;
|
||||
let pathMethodsContext_ = null;
|
||||
let editorContext_ = null;
|
||||
|
||||
@@ -134,7 +132,8 @@ export const getGripContainerMethod = function () {
|
||||
let c = getElem('pathpointgrip_container');
|
||||
if (!c) {
|
||||
const parentElement = getElem('selectorParentGroup');
|
||||
c = parentElement.appendChild(document.createElementNS(NS.SVG, 'g'));
|
||||
c = document.createElementNS(NS.SVG, 'g');
|
||||
parentElement.append(c);
|
||||
c.id = 'pathpointgrip_container';
|
||||
}
|
||||
return c;
|
||||
@@ -171,10 +170,10 @@ export const addPointGripMethod = function (index, x, y) {
|
||||
atts['xlink:title'] = uiStrings.pathNodeTooltip;
|
||||
}
|
||||
assignAttributes(pointGrip, atts);
|
||||
pointGrip = pointGripContainer.appendChild(pointGrip);
|
||||
pointGripContainer.append(pointGrip);
|
||||
|
||||
const grip = $('#pathpointgrip_' + index);
|
||||
grip.dblclick(function () {
|
||||
const grip = document.getElementById('pathpointgrip_' + index);
|
||||
grip?.addEventListener("dblclick", () => {
|
||||
const path = pathMethodsContext_.getPathObj();
|
||||
if (path) {
|
||||
path.setSegType();
|
||||
@@ -428,8 +427,8 @@ export class Segment {
|
||||
* @returns {void}
|
||||
*/
|
||||
selectCtrls (y) {
|
||||
$('#ctrlpointgrip_' + this.index + 'c1, #ctrlpointgrip_' + this.index + 'c2')
|
||||
.attr('fill', y ? '#0FF' : '#EEE');
|
||||
document.getElementById('ctrlpointgrip_' + this.index + 'c1').setAttribute('fill', y ? '#0FF' : '#EEE');
|
||||
document.getElementById('ctrlpointgrip_' + this.index + 'c2').setAttribute('fill', y ? '#0FF' : '#EEE')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -637,8 +636,10 @@ export class Path {
|
||||
// Hide all grips, etc
|
||||
|
||||
// fixed, needed to work on all found elements, not just first
|
||||
$(getGripContainerMethod()).find('*').each(function () {
|
||||
$(this).attr('display', 'none');
|
||||
const pointGripContainer = getGripContainerMethod();
|
||||
const elements = pointGripContainer.querySelectorAll('*');
|
||||
Array.prototype.forEach.call(elements, function(el){
|
||||
el.style.display = 'none';
|
||||
});
|
||||
|
||||
const segList = this.elem.pathSegList;
|
||||
@@ -1012,10 +1013,8 @@ export class Path {
|
||||
if (!Array.isArray(indexes)) { indexes = [indexes]; }
|
||||
indexes.forEach((index) => {
|
||||
const seg = this.segs[index];
|
||||
if (seg.ptgrip) {
|
||||
if (!this.selected_pts.includes(index) && index >= 0) {
|
||||
this.selected_pts.push(index);
|
||||
}
|
||||
if (seg.ptgrip && !this.selected_pts.includes(index) && index >= 0) {
|
||||
this.selected_pts.push(index);
|
||||
}
|
||||
});
|
||||
this.selected_pts.sort();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Path functionality.
|
||||
* @module path
|
||||
@@ -7,14 +6,14 @@
|
||||
* @copyright 2011 Alexis Deveria, 2011 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {getTransformList} from '../common/svgtransformlist.js';
|
||||
import {getTransformList} from './svgtransformlist.js';
|
||||
import {shortFloat} from '../common/units.js';
|
||||
import {transformPoint} from '../common/math.js';
|
||||
import {transformPoint} from './math.js';
|
||||
import {
|
||||
getRotationAngle, getBBox,
|
||||
getRefElem, findDefs, isNullish,
|
||||
getBBox as utilsGetBBox
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
init as pathMethodInit, insertItemBeforeMethod, ptObjToArrMethod, getGripPtMethod,
|
||||
getPointFromGripMethod, addPointGripMethod, getGripContainerMethod, addCtrlGripMethod,
|
||||
@@ -25,8 +24,6 @@ import {
|
||||
init as pathActionsInit, pathActionsMethod
|
||||
} from './path-actions.js';
|
||||
|
||||
const $ = jQuery;
|
||||
|
||||
const segData = {
|
||||
2: ['x', 'y'], // PATHSEG_MOVETO_ABS
|
||||
4: ['x', 'y'], // PATHSEG_LINETO_ABS
|
||||
@@ -77,7 +74,7 @@ export const setLinkControlPoints = function (lcp) {
|
||||
* @type {null|module:path.Path}
|
||||
* @memberof module:path
|
||||
*/
|
||||
export let path = null; // eslint-disable-line import/no-mutable-exports
|
||||
export let path = null;
|
||||
|
||||
let editorContext_ = null;
|
||||
|
||||
@@ -244,7 +241,7 @@ export const init = function (editorContext) {
|
||||
'Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
|
||||
'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'
|
||||
];
|
||||
$.each(pathFuncsStrs, function (i, s) {
|
||||
pathFuncsStrs.forEach(function(s){
|
||||
pathFuncs.push(s + 'Abs');
|
||||
pathFuncs.push(s + 'Rel');
|
||||
});
|
||||
@@ -581,8 +578,9 @@ export const reorientGrads = function (elem, m) {
|
||||
};
|
||||
|
||||
const newgrad = grad.cloneNode(true);
|
||||
$(newgrad).attr(gCoords);
|
||||
|
||||
for (const [key, value] of Object.entries(gCoords)) {
|
||||
newgrad.setAttribute(key, value);
|
||||
}
|
||||
newgrad.id = editorContext_.getNextId();
|
||||
findDefs().append(newgrad);
|
||||
elem.setAttribute(type, 'url(#' + newgrad.id + ')');
|
||||
@@ -674,6 +672,7 @@ export const convertPath = function (pth, toRel) {
|
||||
case 2: // absolute move (M)
|
||||
case 4: // absolute line (L)
|
||||
case 18: // absolute smooth quad (T)
|
||||
case 10: // absolute elliptical arc (A)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
@@ -725,10 +724,6 @@ export const convertPath = function (pth, toRel) {
|
||||
}
|
||||
d += pathDSegment(letter, [[x1, y1], [x, y]]);
|
||||
break;
|
||||
// eslint-disable-next-line sonarjs/no-duplicated-branches
|
||||
case 10: // absolute elliptical arc (A)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
case 11: // relative elliptical arc (a)
|
||||
if (toRel) {
|
||||
@@ -777,7 +772,7 @@ export const convertPath = function (pth, toRel) {
|
||||
* @returns {string}
|
||||
*/
|
||||
function pathDSegment (letter, points, morePoints, lastPoint) {
|
||||
$.each(points, function (i, pnt) {
|
||||
points.forEach(function(pnt, i){
|
||||
points[i] = shortFloat(pnt);
|
||||
});
|
||||
let segment = letter + points.join(' ');
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {convertToNum} from '../common/units.js';
|
||||
import {isWebkit} from '../common/browser.js';
|
||||
import {getTransformList} from '../common/svgtransformlist.js';
|
||||
import {getRotationAngle, getHref, getBBox, getRefElem, isNullish} from '../common/utilities.js';
|
||||
import {BatchCommand, ChangeElementCommand} from './history.js';
|
||||
import {remapElement} from './coords.js';
|
||||
import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
|
||||
import { NS } from '../common/namespaces.js';
|
||||
import { convertToNum } from '../common/units.js';
|
||||
import { isWebkit } from '../common/browser.js';
|
||||
import { getTransformList } from './svgtransformlist.js';
|
||||
import { getRotationAngle, getHref, getBBox, getRefElem, isNullish } from './utilities.js';
|
||||
import { BatchCommand, ChangeElementCommand } from './history.js';
|
||||
import { remapElement } from './coords.js';
|
||||
import {
|
||||
isIdentity, matrixMultiply, transformPoint, transformListToTransform,
|
||||
hasMatrixTransform
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
@@ -83,6 +83,7 @@ export const recalculateDimensions = function (selected) {
|
||||
}
|
||||
|
||||
const svgroot = context_.getSVGRoot();
|
||||
const dataStorage = context_.getDataStorage();
|
||||
const tlist = getTransformList(selected);
|
||||
|
||||
// remove any unnecessary transforms
|
||||
@@ -93,7 +94,7 @@ export const recalculateDimensions = function (selected) {
|
||||
const xform = tlist.getItem(k);
|
||||
if (xform.type === 0) {
|
||||
tlist.removeItem(k);
|
||||
// remove identity matrices
|
||||
// remove identity matrices
|
||||
} else if (xform.type === 1) {
|
||||
if (isIdentity(xform.matrix)) {
|
||||
if (noi === 1) {
|
||||
@@ -106,16 +107,14 @@ export const recalculateDimensions = function (selected) {
|
||||
}
|
||||
tlist.removeItem(k);
|
||||
}
|
||||
// remove zero-degree rotations
|
||||
} else if (xform.type === 4) {
|
||||
if (xform.angle === 0) {
|
||||
tlist.removeItem(k);
|
||||
}
|
||||
// remove zero-degree rotations
|
||||
} else if (xform.type === 4 && xform.angle === 0) {
|
||||
tlist.removeItem(k);
|
||||
}
|
||||
}
|
||||
// End here if all it has is a rotation
|
||||
if (tlist.numberOfItems === 1 &&
|
||||
getRotationAngle(selected)) { return null; }
|
||||
getRotationAngle(selected)) { return null; }
|
||||
}
|
||||
|
||||
// if this element had no transforms, we are done
|
||||
@@ -165,22 +164,20 @@ export const recalculateDimensions = function (selected) {
|
||||
|
||||
// If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned).
|
||||
switch (selected.tagName) {
|
||||
// Ignore these elements, as they can absorb the [M]
|
||||
case 'line':
|
||||
case 'polyline':
|
||||
case 'polygon':
|
||||
case 'path':
|
||||
break;
|
||||
default:
|
||||
if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) ||
|
||||
// Ignore these elements, as they can absorb the [M]
|
||||
case 'line':
|
||||
case 'polyline':
|
||||
case 'polygon':
|
||||
case 'path':
|
||||
break;
|
||||
default:
|
||||
if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) ||
|
||||
(tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Grouped SVG element
|
||||
const gsvg = $(selected).data('gsvg');
|
||||
|
||||
const gsvg = (dataStorage.has(selected, 'gsvg')) ? dataStorage.get(selected, 'gsvg') : undefined;
|
||||
// we know we have some transforms, so set up return variable
|
||||
const batchCmd = new BatchCommand('Transform');
|
||||
|
||||
@@ -189,54 +186,56 @@ export const recalculateDimensions = function (selected) {
|
||||
let initial = null;
|
||||
let attrs = [];
|
||||
switch (selected.tagName) {
|
||||
case 'line':
|
||||
attrs = ['x1', 'y1', 'x2', 'y2'];
|
||||
break;
|
||||
case 'circle':
|
||||
attrs = ['cx', 'cy', 'r'];
|
||||
break;
|
||||
case 'ellipse':
|
||||
attrs = ['cx', 'cy', 'rx', 'ry'];
|
||||
break;
|
||||
case 'foreignObject':
|
||||
case 'rect':
|
||||
case 'image':
|
||||
attrs = ['width', 'height', 'x', 'y'];
|
||||
break;
|
||||
case 'use':
|
||||
case 'text':
|
||||
case 'tspan':
|
||||
attrs = ['x', 'y'];
|
||||
break;
|
||||
case 'polygon':
|
||||
case 'polyline': {
|
||||
initial = {};
|
||||
initial.points = selected.getAttribute('points');
|
||||
const list = selected.points;
|
||||
const len = list.numberOfItems;
|
||||
changes.points = new Array(len);
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = list.getItem(i);
|
||||
changes.points[i] = {x: pt.x, y: pt.y};
|
||||
}
|
||||
break;
|
||||
} case 'path':
|
||||
initial = {};
|
||||
initial.d = selected.getAttribute('d');
|
||||
changes.d = selected.getAttribute('d');
|
||||
break;
|
||||
case 'line':
|
||||
attrs = ['x1', 'y1', 'x2', 'y2'];
|
||||
break;
|
||||
case 'circle':
|
||||
attrs = ['cx', 'cy', 'r'];
|
||||
break;
|
||||
case 'ellipse':
|
||||
attrs = ['cx', 'cy', 'rx', 'ry'];
|
||||
break;
|
||||
case 'foreignObject':
|
||||
case 'rect':
|
||||
case 'image':
|
||||
attrs = ['width', 'height', 'x', 'y'];
|
||||
break;
|
||||
case 'use':
|
||||
case 'text':
|
||||
case 'tspan':
|
||||
attrs = ['x', 'y'];
|
||||
break;
|
||||
case 'polygon':
|
||||
case 'polyline': {
|
||||
initial = {};
|
||||
initial.points = selected.getAttribute('points');
|
||||
const list = selected.points;
|
||||
const len = list.numberOfItems;
|
||||
changes.points = new Array(len);
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = list.getItem(i);
|
||||
changes.points[i] = { x: pt.x, y: pt.y };
|
||||
}
|
||||
break;
|
||||
} case 'path':
|
||||
initial = {};
|
||||
initial.d = selected.getAttribute('d');
|
||||
changes.d = selected.getAttribute('d');
|
||||
break;
|
||||
} // switch on element type to get initial values
|
||||
|
||||
if (attrs.length) {
|
||||
changes = $(selected).attr(attrs);
|
||||
$.each(changes, function (attr, val) {
|
||||
changes[attr] = convertToNum(attr, val);
|
||||
Array.prototype.forEach.call(attrs, function (attr) {
|
||||
changes[attr] = selected.getAttribute(attr);
|
||||
});
|
||||
for (const [attr, val] of Object.entries(changes)) {
|
||||
changes[attr] = convertToNum(attr, val);
|
||||
}
|
||||
} else if (gsvg) {
|
||||
// GSVG exception
|
||||
changes = {
|
||||
x: $(gsvg).attr('x') || 0,
|
||||
y: $(gsvg).attr('y') || 0
|
||||
x: gsvg.getAttribute('x') || 0,
|
||||
y: gsvg.getAttribute('y') || 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,9 +243,9 @@ export const recalculateDimensions = function (selected) {
|
||||
// make a copy of initial values and include the transform
|
||||
if (isNullish(initial)) {
|
||||
initial = $.extend(true, {}, changes);
|
||||
$.each(initial, function (attr, val) {
|
||||
for (const [attr, val] of Object.entries(initial)) {
|
||||
initial[attr] = convertToNum(attr, val);
|
||||
});
|
||||
}
|
||||
}
|
||||
// save the start transform value too
|
||||
initial.transform = context_.getStartTransform() || '';
|
||||
@@ -257,7 +256,7 @@ export const recalculateDimensions = function (selected) {
|
||||
if ((selected.tagName === 'g' && !gsvg) || selected.tagName === 'a') {
|
||||
const box = getBBox(selected);
|
||||
|
||||
oldcenter = {x: box.x + box.width / 2, y: box.y + box.height / 2};
|
||||
oldcenter = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
newcenter = transformPoint(
|
||||
box.x + box.width / 2,
|
||||
box.y + box.height / 2,
|
||||
@@ -342,7 +341,7 @@ export const recalculateDimensions = function (selected) {
|
||||
childTlist.clear();
|
||||
childTlist.appendItem(e2t);
|
||||
// childxforms.push(e2t);
|
||||
// if not rotated or skewed, push the [T][S][-T] down to the child
|
||||
// if not rotated or skewed, push the [T][S][-T] down to the child
|
||||
} else {
|
||||
// update the transform list with translate,scale,translate
|
||||
|
||||
@@ -407,9 +406,9 @@ export const recalculateDimensions = function (selected) {
|
||||
e2t.setMatrix(m);
|
||||
tlist.clear();
|
||||
tlist.appendItem(e2t);
|
||||
// next, check if the first transform was a translate
|
||||
// if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
|
||||
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
|
||||
// next, check if the first transform was a translate
|
||||
// if we had [ T1 ] [ M ] we want to transform this into [ M ] [ T2 ]
|
||||
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ]
|
||||
} else if ((N === 1 || (N > 1 && tlist.getItem(1).type !== 3)) &&
|
||||
tlist.getItem(0).type === 2) {
|
||||
operation = 2; // translate
|
||||
@@ -475,8 +474,8 @@ export const recalculateDimensions = function (selected) {
|
||||
}
|
||||
context_.setStartTransform(oldStartTransform);
|
||||
}
|
||||
// else, a matrix imposition from a parent group
|
||||
// keep pushing it down to the children
|
||||
// else, a matrix imposition from a parent group
|
||||
// keep pushing it down to the children
|
||||
} else if (N === 1 && tlist.getItem(0).type === 1 && !gangle) {
|
||||
operation = 1;
|
||||
const m = tlist.getItem(0).matrix,
|
||||
@@ -510,7 +509,7 @@ export const recalculateDimensions = function (selected) {
|
||||
}
|
||||
}
|
||||
tlist.clear();
|
||||
// else it was just a rotate
|
||||
// else it was just a rotate
|
||||
} else {
|
||||
if (gangle) {
|
||||
const newRot = svgroot.createSVGTransform();
|
||||
@@ -543,7 +542,7 @@ export const recalculateDimensions = function (selected) {
|
||||
tlist.appendItem(newRot);
|
||||
}
|
||||
}
|
||||
// if it was a resize
|
||||
// if it was a resize
|
||||
} else if (operation === 3) {
|
||||
const m = transformListToTransform(tlist).matrix;
|
||||
const roldt = svgroot.createSVGTransform();
|
||||
@@ -591,7 +590,7 @@ export const recalculateDimensions = function (selected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// else, it's a non-group
|
||||
// else, it's a non-group
|
||||
} else {
|
||||
// TODO: box might be null for some elements (<metadata> etc), need to handle this
|
||||
const box = getBBox(selected);
|
||||
@@ -607,7 +606,7 @@ export const recalculateDimensions = function (selected) {
|
||||
// temporarily strip off the rotate and save the old center
|
||||
const angle = getRotationAngle(selected);
|
||||
if (angle) {
|
||||
oldcenter = {x: box.x + box.width / 2, y: box.y + box.height / 2};
|
||||
oldcenter = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
||||
newcenter = transformPoint(
|
||||
box.x + box.width / 2,
|
||||
box.y + box.height / 2,
|
||||
@@ -673,8 +672,8 @@ export const recalculateDimensions = function (selected) {
|
||||
tlist.removeItem(N - 1);
|
||||
tlist.removeItem(N - 2);
|
||||
tlist.removeItem(N - 3);
|
||||
// if we had [T][S][-T][M], then this was a skewed element being resized
|
||||
// Thus, we simply combine it all into one matrix
|
||||
// if we had [T][S][-T][M], then this was a skewed element being resized
|
||||
// Thus, we simply combine it all into one matrix
|
||||
} else if (N === 4 && tlist.getItem(N - 1).type === 1) {
|
||||
operation = 3; // scale
|
||||
m = transformListToTransform(tlist).matrix;
|
||||
@@ -684,10 +683,10 @@ export const recalculateDimensions = function (selected) {
|
||||
tlist.appendItem(e2t);
|
||||
// reset the matrix so that the element is not re-mapped
|
||||
m = svgroot.createSVGMatrix();
|
||||
// if we had [R][T][S][-T][M], then this was a rotated matrix-element
|
||||
// if we had [T1][M] we want to transform this into [M][T2]
|
||||
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
|
||||
// down to the element
|
||||
// if we had [R][T][S][-T][M], then this was a rotated matrix-element
|
||||
// if we had [T1][M] we want to transform this into [M][T2]
|
||||
// therefore [ T2 ] = [ M_inv ] [ T1 ] [ M ] and we can push [T2]
|
||||
// down to the element
|
||||
} else if ((N === 1 || (N > 1 && tlist.getItem(1).type !== 3)) &&
|
||||
tlist.getItem(0).type === 2) {
|
||||
operation = 2; // translate
|
||||
@@ -696,38 +695,43 @@ export const recalculateDimensions = function (selected) {
|
||||
meqInv = meq.inverse();
|
||||
m = matrixMultiply(meqInv, oldxlate, meq);
|
||||
tlist.removeItem(0);
|
||||
// else if this child now has a matrix imposition (from a parent group)
|
||||
// we might be able to simplify
|
||||
// else if this child now has a matrix imposition (from a parent group)
|
||||
// we might be able to simplify
|
||||
} else if (N === 1 && tlist.getItem(0).type === 1 && !angle) {
|
||||
// Remap all point-based elements
|
||||
m = transformListToTransform(tlist).matrix;
|
||||
switch (selected.tagName) {
|
||||
case 'line':
|
||||
changes = $(selected).attr(['x1', 'y1', 'x2', 'y2']);
|
||||
// Fallthrough
|
||||
case 'polyline':
|
||||
case 'polygon':
|
||||
changes.points = selected.getAttribute('points');
|
||||
if (changes.points) {
|
||||
const list = selected.points;
|
||||
const len = list.numberOfItems;
|
||||
changes.points = new Array(len);
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = list.getItem(i);
|
||||
changes.points[i] = {x: pt.x, y: pt.y};
|
||||
case 'line':
|
||||
changes = {
|
||||
x1: selected.getAttribute('x1'),
|
||||
y1: selected.getAttribute('y1'),
|
||||
x2: selected.getAttribute('x2'),
|
||||
y2: selected.getAttribute('y2'),
|
||||
}
|
||||
}
|
||||
// Fallthrough
|
||||
case 'path':
|
||||
changes.d = selected.getAttribute('d');
|
||||
operation = 1;
|
||||
tlist.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case 'polyline':
|
||||
case 'polygon':
|
||||
changes.points = selected.getAttribute('points');
|
||||
if (changes.points) {
|
||||
const list = selected.points;
|
||||
const len = list.numberOfItems;
|
||||
changes.points = new Array(len);
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const pt = list.getItem(i);
|
||||
changes.points[i] = { x: pt.x, y: pt.y };
|
||||
}
|
||||
}
|
||||
// Fallthrough
|
||||
case 'path':
|
||||
changes.d = selected.getAttribute('d');
|
||||
operation = 1;
|
||||
tlist.clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// if it was a rotation, put the rotate back and return without a command
|
||||
// (this function has zero work to do for a rotate())
|
||||
// if it was a rotation, put the rotate back and return without a command
|
||||
// (this function has zero work to do for a rotate())
|
||||
} else {
|
||||
// operation = 4; // rotation
|
||||
if (angle) {
|
||||
@@ -778,19 +782,19 @@ export const recalculateDimensions = function (selected) {
|
||||
const child = children.item(c);
|
||||
if (child.tagName === 'tspan') {
|
||||
const tspanChanges = {
|
||||
x: $(child).attr('x') || 0,
|
||||
y: $(child).attr('y') || 0
|
||||
x: child.getAttribute('x') || 0,
|
||||
y: child.getAttribute('y') || 0
|
||||
};
|
||||
remapElement(child, tspanChanges, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
// [Rold][M][T][S][-T] became [Rold][M]
|
||||
// we want it to be [Rnew][M][Tr] where Tr is the
|
||||
// translation required to re-center it
|
||||
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
|
||||
// [Rold][M][T][S][-T] became [Rold][M]
|
||||
// we want it to be [Rnew][M][Tr] where Tr is the
|
||||
// translation required to re-center it
|
||||
// Therefore, [Tr] = [M_inv][Rnew_inv][Rold][M]
|
||||
} else if (operation === 3 && angle) {
|
||||
const {matrix} = transformListToTransform(tlist);
|
||||
const { matrix } = transformListToTransform(tlist);
|
||||
const roldt = svgroot.createSVGTransform();
|
||||
roldt.setRotate(angle, oldcenter.x, oldcenter.y);
|
||||
const rold = roldt.matrix;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
/**
|
||||
* Tools for SVG sanitization.
|
||||
* @module sanitize
|
||||
@@ -8,7 +9,7 @@
|
||||
|
||||
import {getReverseNS, NS} from '../common/namespaces.js';
|
||||
import {isGecko} from '../common/browser.js';
|
||||
import {getHref, setHref, getUrlFromAttr} from '../common/utilities.js';
|
||||
import {getHref, setHref, getUrlFromAttr} from './utilities.js';
|
||||
|
||||
const REVERSE_NS = getReverseNS();
|
||||
|
||||
@@ -21,38 +22,44 @@ const REVERSE_NS = getReverseNS();
|
||||
/* eslint-disable max-len */
|
||||
const svgWhiteList_ = {
|
||||
// SVG Elements
|
||||
a: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'xlink:href', 'xlink:title', 'display'],
|
||||
circle: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'display'],
|
||||
a: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'xlink:href', 'xlink:title'],
|
||||
circle: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'],
|
||||
clipPath: ['class', 'clipPathUnits', 'id'],
|
||||
defs: [],
|
||||
style: ['type'],
|
||||
desc: [],
|
||||
ellipse: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'display'],
|
||||
ellipse: ['class', 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'],
|
||||
feBlend: ['in', 'in2'],
|
||||
feComposite: ['operator', 'result', 'in2'],
|
||||
feFlood: ['flood-color', 'in'],
|
||||
feGaussianBlur: ['class', 'color-interpolation-filters', 'id', 'requiredFeatures', 'stdDeviation'],
|
||||
feMerge: [],
|
||||
feMergeNode: ['in'],
|
||||
feMorphology: ['class', 'in', 'operator', 'radius'],
|
||||
feOffset: ['dx', 'in', 'dy', 'result'],
|
||||
filter: ['class', 'color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'id', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
||||
foreignObject: ['class', 'font-size', 'height', 'id', 'opacity', 'requiredFeatures', 'style', 'transform', 'width', 'x', 'y'],
|
||||
g: ['class', 'clip-path', 'clip-rule', 'id', 'display', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
|
||||
image: ['class', 'clip-path', 'clip-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'style', 'systemLanguage', 'transform', 'width', 'x', 'xlink:href', 'xlink:title', 'y', 'display'],
|
||||
line: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'x1', 'x2', 'y1', 'y2', 'display'],
|
||||
image: ['class', 'clip-path', 'clip-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'style', 'systemLanguage', 'transform', 'width', 'x', 'xlink:href', 'xlink:title', 'y'],
|
||||
line: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'x1', 'x2', 'y1', 'y2'],
|
||||
linearGradient: ['class', 'id', 'gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'],
|
||||
marker: ['id', 'class', 'markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'systemLanguage', 'viewBox'],
|
||||
mask: ['class', 'height', 'id', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'],
|
||||
metadata: ['class', 'id'],
|
||||
path: ['class', 'clip-path', 'clip-rule', 'd', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'display'],
|
||||
path: ['class', 'clip-path', 'clip-rule', 'd', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'],
|
||||
pattern: ['class', 'height', 'id', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'],
|
||||
polygon: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'class', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'display'],
|
||||
polyline: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'display'],
|
||||
polygon: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'id', 'class', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'],
|
||||
polyline: ['class', 'clip-path', 'clip-rule', 'id', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform'],
|
||||
radialGradient: ['class', 'cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'id', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'],
|
||||
rect: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'width', 'x', 'y', 'display'],
|
||||
rect: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'width', 'x', 'y'],
|
||||
stop: ['class', 'id', 'offset', 'requiredFeatures', 'stop-color', 'stop-opacity', 'style', 'systemLanguage'],
|
||||
svg: ['class', 'clip-path', 'clip-rule', 'filter', 'id', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'y'],
|
||||
svg: ['class', 'clip-path', 'clip-rule', 'filter', 'id', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'style', 'systemLanguage', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xmlns:oi', 'oi:animations', 'y'],
|
||||
switch: ['class', 'id', 'requiredFeatures', 'systemLanguage'],
|
||||
symbol: ['class', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'opacity', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'transform', 'viewBox'],
|
||||
text: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-decoration', 'letter-spacing', 'word-spacing', 'textLength', 'lengthAdjust', 'style', 'systemLanguage', 'text-anchor', 'transform', 'x', 'xml:space', 'y', 'display'],
|
||||
textPath: ['class', 'id', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'style', 'systemLanguage', 'transform', 'xlink:href', 'display'],
|
||||
text: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'transform', 'x', 'xml:space', 'y'],
|
||||
textPath: ['class', 'id', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'style', 'systemLanguage', 'transform', 'xlink:href'],
|
||||
title: [],
|
||||
tspan: ['class', 'clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'textLength', 'transform', 'x', 'xml:space', 'y', 'display'],
|
||||
tspan: ['class', 'clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'id', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'systemLanguage', 'text-anchor', 'textLength', 'transform', 'x', 'xml:space', 'y'],
|
||||
use: ['class', 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'id', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'style', 'transform', 'width', 'x', 'xlink:href', 'y'],
|
||||
|
||||
// MathML Elements
|
||||
@@ -94,7 +101,7 @@ const svgWhiteList_ = {
|
||||
const svgWhiteListNS_ = {};
|
||||
Object.entries(svgWhiteList_).forEach(function ([elt, atts]) {
|
||||
const attNS = {};
|
||||
Object.entries(atts).forEach(function ([i, att]) {
|
||||
Object.entries(atts).forEach(function ([_i, att]) {
|
||||
if (att.includes(':')) {
|
||||
const v = att.split(':');
|
||||
attNS[v[1]] = NS[(v[0]).toUpperCase()];
|
||||
@@ -204,13 +211,10 @@ export const sanitizeSvg = function (node) {
|
||||
const href = getHref(node);
|
||||
if (href &&
|
||||
['filter', 'linearGradient', 'pattern',
|
||||
'radialGradient', 'textPath', 'use'].includes(node.nodeName)) {
|
||||
// TODO: we simply check if the first character is a #, is this bullet-proof?
|
||||
if (href[0] !== '#') {
|
||||
// remove the attribute (but keep the element)
|
||||
setHref(node, '');
|
||||
node.removeAttributeNS(NS.XLINK, 'href');
|
||||
}
|
||||
'radialGradient', 'textPath', 'use'].includes(node.nodeName) && href[0] !== '#') {
|
||||
// remove the attribute (but keep the element)
|
||||
setHref(node, '');
|
||||
node.removeAttributeNS(NS.XLINK, 'href');
|
||||
}
|
||||
|
||||
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* DOM element selection box tools.
|
||||
* @module select
|
||||
@@ -7,12 +6,10 @@
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {isTouch, isWebkit} from '../common/browser.js'; // , isOpera
|
||||
import {getRotationAngle, getBBox, getStrokedBBox, isNullish} from '../common/utilities.js';
|
||||
import {transformListToTransform, transformBox, transformPoint} from '../common/math.js';
|
||||
import {getTransformList} from '../common/svgtransformlist.js';
|
||||
|
||||
const $ = jQuery;
|
||||
import { isTouch, isWebkit } from '../common/browser.js'; // , isOpera
|
||||
import { getRotationAngle, getBBox, getStrokedBBox, isNullish } from './utilities.js';
|
||||
import { transformListToTransform, transformBox, transformPoint } from './math.js';
|
||||
import { getTransformList } from './svgtransformlist.js';
|
||||
|
||||
let svgFactory_;
|
||||
let config_;
|
||||
@@ -28,7 +25,7 @@ export class Selector {
|
||||
* @param {Element} elem - DOM element associated with this selector
|
||||
* @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for initialization (prevents duplicate `getBBox` call).
|
||||
*/
|
||||
constructor (id, elem, bbox) {
|
||||
constructor(id, elem, bbox) {
|
||||
// this is the selector's unique number
|
||||
this.id = id;
|
||||
|
||||
@@ -41,24 +38,23 @@ export class Selector {
|
||||
// this holds a reference to the <g> element that holds all visual elements of the selector
|
||||
this.selectorGroup = svgFactory_.createSVGElement({
|
||||
element: 'g',
|
||||
attr: {id: ('selectorGroup' + this.id)}
|
||||
attr: { id: ('selectorGroup' + this.id) }
|
||||
});
|
||||
|
||||
// this holds a reference to the path rect
|
||||
this.selectorRect = this.selectorGroup.appendChild(
|
||||
svgFactory_.createSVGElement({
|
||||
element: 'path',
|
||||
attr: {
|
||||
id: ('selectedBox' + this.id),
|
||||
fill: 'none',
|
||||
stroke: '#22C',
|
||||
'stroke-width': '1',
|
||||
'stroke-dasharray': '5,5',
|
||||
// need to specify this so that the rect is not selectable
|
||||
style: 'pointer-events:none'
|
||||
}
|
||||
})
|
||||
);
|
||||
this.selectorRect = svgFactory_.createSVGElement({
|
||||
element: 'path',
|
||||
attr: {
|
||||
id: ('selectedBox' + this.id),
|
||||
fill: 'none',
|
||||
stroke: '#22C',
|
||||
'stroke-width': '1',
|
||||
'stroke-dasharray': '5,5',
|
||||
// need to specify this so that the rect is not selectable
|
||||
style: 'pointer-events:none'
|
||||
}
|
||||
});
|
||||
this.selectorGroup.append(this.selectorRect);
|
||||
|
||||
// this holds a reference to the grip coordinates for this selector
|
||||
this.gripCoords = {
|
||||
@@ -81,7 +77,7 @@ export class Selector {
|
||||
* @param {module:utilities.BBoxObject} bbox - Optional bbox to use for reset (prevents duplicate getBBox call).
|
||||
* @returns {void}
|
||||
*/
|
||||
reset (e, bbox) {
|
||||
reset(e, bbox) {
|
||||
this.locked = true;
|
||||
this.selectedElement = e;
|
||||
this.resize(bbox);
|
||||
@@ -93,7 +89,7 @@ export class Selector {
|
||||
* @param {boolean} show - Indicates whether grips should be shown or not
|
||||
* @returns {void}
|
||||
*/
|
||||
showGrips (show) {
|
||||
showGrips(show) {
|
||||
const bShow = show ? 'inline' : 'none';
|
||||
selectorManager_.selectorGripsGroup.setAttribute('display', bShow);
|
||||
const elem = this.selectedElement;
|
||||
@@ -109,7 +105,8 @@ export class Selector {
|
||||
* @param {module:utilities.BBoxObject} [bbox] - BBox to use for resize (prevents duplicate getBBox call).
|
||||
* @returns {void}
|
||||
*/
|
||||
resize (bbox) {
|
||||
resize(bbox) {
|
||||
const dataStorage = svgFactory_.getDataStorage();
|
||||
const selectedBox = this.selectorRect,
|
||||
mgr = selectorManager_,
|
||||
selectedGrips = mgr.selectorGrips,
|
||||
@@ -121,7 +118,7 @@ export class Selector {
|
||||
offset += (sw / 2);
|
||||
}
|
||||
|
||||
const {tagName} = selected;
|
||||
const { tagName } = selected;
|
||||
if (tagName === 'text') {
|
||||
offset += 2 / currentZoom;
|
||||
}
|
||||
@@ -140,7 +137,7 @@ export class Selector {
|
||||
}
|
||||
// TODO: getBBox (previous line) already knows to call getStrokedBBox when tagName === 'g'. Remove this?
|
||||
// TODO: getBBox doesn't exclude 'gsvg' and calls getStrokedBBox for any 'g'. Should getBBox be updated?
|
||||
if (tagName === 'g' && !$.data(selected, 'gsvg')) {
|
||||
if (tagName === 'g' && !dataStorage.has(selected, 'gsvg')) {
|
||||
// The bbox for a group does not include stroke vals, so we
|
||||
// get the bbox based on its children.
|
||||
const strokedBbox = getStrokedBBox([selected.childNodes]);
|
||||
@@ -160,7 +157,7 @@ export class Selector {
|
||||
offset *= currentZoom;
|
||||
|
||||
const nbox = transformBox(l * currentZoom, t * currentZoom, w * currentZoom, h * currentZoom, m),
|
||||
{aabox} = nbox;
|
||||
{ aabox } = nbox;
|
||||
let nbax = aabox.x - offset,
|
||||
nbay = aabox.y - offset,
|
||||
nbaw = aabox.width + (offset * 2),
|
||||
@@ -181,13 +178,13 @@ export class Selector {
|
||||
nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm);
|
||||
|
||||
// calculate the axis-aligned bbox
|
||||
const {tl} = nbox;
|
||||
const { tl } = nbox;
|
||||
let minx = tl.x,
|
||||
miny = tl.y,
|
||||
maxx = tl.x,
|
||||
maxy = tl.y;
|
||||
|
||||
const {min, max} = Math;
|
||||
const { min, max } = Math;
|
||||
|
||||
minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset;
|
||||
miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset;
|
||||
@@ -243,7 +240,7 @@ export class Selector {
|
||||
* @param {Float} angle - Current rotation angle in degrees
|
||||
* @returns {void}
|
||||
*/
|
||||
static updateGripCursors (angle) {
|
||||
static updateGripCursors(angle) {
|
||||
const dirArr = Object.keys(selectorManager_.selectorGrips);
|
||||
let steps = Math.round(angle / 45);
|
||||
if (steps < 0) { steps += 8; }
|
||||
@@ -264,7 +261,7 @@ export class SelectorManager {
|
||||
/**
|
||||
* Sets up properties and calls `initGroup`.
|
||||
*/
|
||||
constructor () {
|
||||
constructor() {
|
||||
// this will hold the <g> element that contains all selector rects/grips
|
||||
this.selectorParentGroup = null;
|
||||
|
||||
@@ -300,7 +297,8 @@ export class SelectorManager {
|
||||
* Resets the parent selector group element.
|
||||
* @returns {void}
|
||||
*/
|
||||
initGroup () {
|
||||
initGroup() {
|
||||
const dataStorage = svgFactory_.getDataStorage();
|
||||
// remove old selector parent group if it existed
|
||||
if (this.selectorParentGroup && this.selectorParentGroup.parentNode) {
|
||||
this.selectorParentGroup.remove();
|
||||
@@ -309,11 +307,11 @@ export class SelectorManager {
|
||||
// create parent selector group and add it to svgroot
|
||||
this.selectorParentGroup = svgFactory_.createSVGElement({
|
||||
element: 'g',
|
||||
attr: {id: 'selectorParentGroup'}
|
||||
attr: { id: 'selectorParentGroup' }
|
||||
});
|
||||
this.selectorGripsGroup = svgFactory_.createSVGElement({
|
||||
element: 'g',
|
||||
attr: {display: 'none'}
|
||||
attr: { display: 'none' }
|
||||
});
|
||||
this.selectorParentGroup.append(this.selectorGripsGroup);
|
||||
svgFactory_.svgRoot().append(this.selectorParentGroup);
|
||||
@@ -340,13 +338,14 @@ export class SelectorManager {
|
||||
}
|
||||
});
|
||||
|
||||
$.data(grip, 'dir', dir);
|
||||
$.data(grip, 'type', 'resize');
|
||||
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
|
||||
dataStorage.put(grip, 'dir', dir);
|
||||
dataStorage.put(grip, 'type', 'resize');
|
||||
this.selectorGrips[dir] = grip;
|
||||
this.selectorGripsGroup.append(grip);
|
||||
});
|
||||
|
||||
// add rotator elems
|
||||
this.rotateGripConnector = this.selectorGripsGroup.appendChild(
|
||||
this.rotateGripConnector =
|
||||
svgFactory_.createSVGElement({
|
||||
element: 'line',
|
||||
attr: {
|
||||
@@ -354,10 +353,10 @@ export class SelectorManager {
|
||||
stroke: '#22C',
|
||||
'stroke-width': '1'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
this.selectorGripsGroup.append(this.rotateGripConnector);
|
||||
|
||||
this.rotateGrip = this.selectorGripsGroup.appendChild(
|
||||
this.rotateGrip =
|
||||
svgFactory_.createSVGElement({
|
||||
element: 'circle',
|
||||
attr: {
|
||||
@@ -366,13 +365,13 @@ export class SelectorManager {
|
||||
r: gripRadius,
|
||||
stroke: '#22C',
|
||||
'stroke-width': 2,
|
||||
style: 'cursor:url(' + config_.imgPath + 'rotate.png) 12 12, auto;'
|
||||
style: 'cursor:url(' + config_.imgPath + 'rotate.svg) 12 12, auto;'
|
||||
}
|
||||
})
|
||||
);
|
||||
$.data(this.rotateGrip, 'type', 'rotate');
|
||||
});
|
||||
this.selectorGripsGroup.append(this.rotateGrip);
|
||||
dataStorage.put(this.rotateGrip, 'type', 'rotate');
|
||||
|
||||
if ($('#canvasBackground').length) { return; }
|
||||
if (document.getElementById('canvasBackground')) { return; }
|
||||
|
||||
const [width, height] = config_.dimensions;
|
||||
const canvasbg = svgFactory_.createSVGElement({
|
||||
@@ -416,7 +415,7 @@ export class SelectorManager {
|
||||
* @param {module:utilities.BBoxObject} [bbox] - Optional bbox to use for reset (prevents duplicate getBBox call).
|
||||
* @returns {Selector} The selector based on the given element
|
||||
*/
|
||||
requestSelector (elem, bbox) {
|
||||
requestSelector(elem, bbox) {
|
||||
if (isNullish(elem)) { return null; }
|
||||
|
||||
const N = this.selectors.length;
|
||||
@@ -446,13 +445,13 @@ export class SelectorManager {
|
||||
* @param {Element} elem - DOM element to remove the selector for
|
||||
* @returns {void}
|
||||
*/
|
||||
releaseSelector (elem) {
|
||||
releaseSelector(elem) {
|
||||
if (isNullish(elem)) { return; }
|
||||
const N = this.selectors.length,
|
||||
sel = this.selectorMap[elem.id];
|
||||
if (sel && !sel.locked) {
|
||||
// TODO(codedread): Ensure this exists in this module.
|
||||
console.log('WARNING! selector was released but was already unlocked'); // eslint-disable-line no-console
|
||||
console.log('WARNING! selector was released but was already unlocked');
|
||||
}
|
||||
for (let i = 0; i < N; ++i) {
|
||||
if (this.selectors[i] && this.selectors[i] === sel) {
|
||||
@@ -464,7 +463,7 @@ export class SelectorManager {
|
||||
// remove from DOM and store reference in JS but only if it exists in the DOM
|
||||
try {
|
||||
sel.selectorGroup.setAttribute('display', 'none');
|
||||
} catch (e) {}
|
||||
} catch (e) {/* empty fn */ }
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -475,9 +474,9 @@ export class SelectorManager {
|
||||
* @returns {SVGRectElement} The rubberBandBox DOM element. This is the rectangle drawn by
|
||||
* the user for selecting/zooming
|
||||
*/
|
||||
getRubberBandBox () {
|
||||
getRubberBandBox() {
|
||||
if (!this.rubberBandBox) {
|
||||
this.rubberBandBox = this.selectorParentGroup.appendChild(
|
||||
this.rubberBandBox =
|
||||
svgFactory_.createSVGElement({
|
||||
element: 'rect',
|
||||
attr: {
|
||||
@@ -489,8 +488,8 @@ export class SelectorManager {
|
||||
display: 'none',
|
||||
style: 'pointer-events:none'
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
this.selectorParentGroup.append(this.rubberBandBox);
|
||||
}
|
||||
return this.rubberBandBox;
|
||||
}
|
||||
|
||||
@@ -6,26 +6,27 @@
|
||||
*
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js'; // Needed for SVG attribute
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute
|
||||
import { NS } from '../common/namespaces.js';
|
||||
import * as hstry from './history.js';
|
||||
import * as pathModule from './path.js';
|
||||
import {
|
||||
isNullish, getStrokedBBoxDefaultVisible, setHref, getElem, getHref, getVisibleElements,
|
||||
findDefs, getRotationAngle, getRefElem, getBBox as utilsGetBBox, walkTreePost, assignAttributes
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
transformPoint, matrixMultiply, transformListToTransform
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
import {
|
||||
getTransformList
|
||||
} from '../common/svgtransformlist.js';
|
||||
} from './svgtransformlist.js';
|
||||
import {
|
||||
recalculateDimensions
|
||||
} from './recalculate.js';
|
||||
import {
|
||||
isGecko
|
||||
} from '../common/browser.js'; // , supportsEditableText
|
||||
import { getParents } from '../editor/components/jgraduate/Util.js';
|
||||
|
||||
const {
|
||||
MoveElementCommand, BatchCommand, InsertElementCommand, RemoveElementCommand, ChangeElementCommand
|
||||
@@ -53,10 +54,10 @@ export const init = function (elementContext) {
|
||||
export const moveToTopSelectedElem = function () {
|
||||
const [selected] = elementContext_.getSelectedElements();
|
||||
if (!isNullish(selected)) {
|
||||
let t = selected;
|
||||
const t = selected;
|
||||
const oldParent = t.parentNode;
|
||||
const oldNextSibling = t.nextSibling;
|
||||
t = t.parentNode.appendChild(t);
|
||||
t.parentNode.append(t);
|
||||
// If the element actually moved position, add the command and fire the changed
|
||||
// event handler.
|
||||
if (oldNextSibling !== t.nextSibling) {
|
||||
@@ -79,7 +80,7 @@ export const moveToBottomSelectedElem = function () {
|
||||
let t = selected;
|
||||
const oldParent = t.parentNode;
|
||||
const oldNextSibling = t.nextSibling;
|
||||
let {firstChild} = t.parentNode;
|
||||
let { firstChild } = t.parentNode;
|
||||
if (firstChild.tagName === 'title') {
|
||||
firstChild = firstChild.nextSibling;
|
||||
}
|
||||
@@ -115,17 +116,17 @@ export const moveUpDownSelected = function (dir) {
|
||||
// curBBoxes = [];
|
||||
let closest, foundCur;
|
||||
// jQuery sorts this list
|
||||
const list = $(elementContext_.getIntersectionList(getStrokedBBoxDefaultVisible([selected]))).toArray();
|
||||
const list = elementContext_.getIntersectionList(getStrokedBBoxDefaultVisible([selected]));
|
||||
if (dir === 'Down') { list.reverse(); }
|
||||
|
||||
$.each(list, function () {
|
||||
Array.prototype.forEach.call(list, function (el) {
|
||||
if (!foundCur) {
|
||||
if (this === selected) {
|
||||
if (el === selected) {
|
||||
foundCur = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
closest = this;
|
||||
closest = el;
|
||||
return false;
|
||||
});
|
||||
if (!closest) { return; }
|
||||
@@ -133,7 +134,11 @@ export const moveUpDownSelected = function (dir) {
|
||||
const t = selected;
|
||||
const oldParent = t.parentNode;
|
||||
const oldNextSibling = t.nextSibling;
|
||||
$(closest)[dir === 'Down' ? 'before' : 'after'](t);
|
||||
if (dir === 'Down') {
|
||||
closest.insertAdjacentElement('beforebegin', t);
|
||||
} else {
|
||||
closest.insertAdjacentElement('afterend', t);
|
||||
}
|
||||
// If the element actually moved position, add the command and fire the changed
|
||||
// event handler.
|
||||
if (oldNextSibling !== t.nextSibling) {
|
||||
@@ -231,14 +236,24 @@ export const cloneSelectedElements = function (x, y) {
|
||||
const batchCmd = new BatchCommand('Clone Elements');
|
||||
// find all the elements selected (stop at first null)
|
||||
const len = selectedElements.length;
|
||||
|
||||
function index(el) {
|
||||
if (!el) return -1;
|
||||
var i = 0;
|
||||
do {
|
||||
i++;
|
||||
} while (el == el.previousElementSibling);
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an array numerically and ascending.
|
||||
* @param {Element} a
|
||||
* @param {Element} b
|
||||
* @returns {Integer}
|
||||
*/
|
||||
function sortfunction (a, b) {
|
||||
return ($(b).index() - $(a).index());
|
||||
function sortfunction(a, b) {
|
||||
return (index(b) - index(a));
|
||||
}
|
||||
selectedElements.sort(sortfunction);
|
||||
for (i = 0; i < len; ++i) {
|
||||
@@ -287,40 +302,40 @@ export const alignSelectedElements = function (type, relativeTo) {
|
||||
|
||||
// now bbox is axis-aligned and handles rotation
|
||||
switch (relativeTo) {
|
||||
case 'smallest':
|
||||
if (((type === 'l' || type === 'c' || type === 'r') &&
|
||||
(curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) ||
|
||||
((type === 't' || type === 'm' || type === 'b') &&
|
||||
(curheight === Number.MIN_VALUE || curheight > bboxes[i].height))
|
||||
) {
|
||||
minx = bboxes[i].x;
|
||||
miny = bboxes[i].y;
|
||||
maxx = bboxes[i].x + bboxes[i].width;
|
||||
maxy = bboxes[i].y + bboxes[i].height;
|
||||
curwidth = bboxes[i].width;
|
||||
curheight = bboxes[i].height;
|
||||
}
|
||||
break;
|
||||
case 'largest':
|
||||
if (((type === 'l' || type === 'c' || type === 'r') &&
|
||||
(curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) ||
|
||||
((type === 't' || type === 'm' || type === 'b') &&
|
||||
(curheight === Number.MIN_VALUE || curheight < bboxes[i].height))
|
||||
) {
|
||||
minx = bboxes[i].x;
|
||||
miny = bboxes[i].y;
|
||||
maxx = bboxes[i].x + bboxes[i].width;
|
||||
maxy = bboxes[i].y + bboxes[i].height;
|
||||
curwidth = bboxes[i].width;
|
||||
curheight = bboxes[i].height;
|
||||
}
|
||||
break;
|
||||
default: // 'selected'
|
||||
if (bboxes[i].x < minx) { minx = bboxes[i].x; }
|
||||
if (bboxes[i].y < miny) { miny = bboxes[i].y; }
|
||||
if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width; }
|
||||
if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height; }
|
||||
break;
|
||||
case 'smallest':
|
||||
if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') &&
|
||||
(curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) ||
|
||||
((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') &&
|
||||
(curheight === Number.MIN_VALUE || curheight > bboxes[i].height))
|
||||
) {
|
||||
minx = bboxes[i].x;
|
||||
miny = bboxes[i].y;
|
||||
maxx = bboxes[i].x + bboxes[i].width;
|
||||
maxy = bboxes[i].y + bboxes[i].height;
|
||||
curwidth = bboxes[i].width;
|
||||
curheight = bboxes[i].height;
|
||||
}
|
||||
break;
|
||||
case 'largest':
|
||||
if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') &&
|
||||
(curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) ||
|
||||
((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') &&
|
||||
(curheight === Number.MIN_VALUE || curheight < bboxes[i].height))
|
||||
) {
|
||||
minx = bboxes[i].x;
|
||||
miny = bboxes[i].y;
|
||||
maxx = bboxes[i].x + bboxes[i].width;
|
||||
maxy = bboxes[i].y + bboxes[i].height;
|
||||
curwidth = bboxes[i].width;
|
||||
curheight = bboxes[i].height;
|
||||
}
|
||||
break;
|
||||
default: // 'selected'
|
||||
if (bboxes[i].x < minx) { minx = bboxes[i].x; }
|
||||
if (bboxes[i].y < miny) { miny = bboxes[i].y; }
|
||||
if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width; }
|
||||
if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height; }
|
||||
break;
|
||||
}
|
||||
} // loop for each element to find the bbox and adjust min/max
|
||||
|
||||
@@ -340,24 +355,30 @@ export const alignSelectedElements = function (type, relativeTo) {
|
||||
dx[i] = 0;
|
||||
dy[i] = 0;
|
||||
switch (type) {
|
||||
case 'l': // left (horizontal)
|
||||
dx[i] = minx - bbox.x;
|
||||
break;
|
||||
case 'c': // center (horizontal)
|
||||
dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2);
|
||||
break;
|
||||
case 'r': // right (horizontal)
|
||||
dx[i] = maxx - (bbox.x + bbox.width);
|
||||
break;
|
||||
case 't': // top (vertical)
|
||||
dy[i] = miny - bbox.y;
|
||||
break;
|
||||
case 'm': // middle (vertical)
|
||||
dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2);
|
||||
break;
|
||||
case 'b': // bottom (vertical)
|
||||
dy[i] = maxy - (bbox.y + bbox.height);
|
||||
break;
|
||||
case 'l': // left (horizontal)
|
||||
case 'left': // left (horizontal)
|
||||
dx[i] = minx - bbox.x;
|
||||
break;
|
||||
case 'c': // center (horizontal)
|
||||
case 'center': // center (horizontal)
|
||||
dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2);
|
||||
break;
|
||||
case 'r': // right (horizontal)
|
||||
case 'right': // right (horizontal)
|
||||
dx[i] = maxx - (bbox.x + bbox.width);
|
||||
break;
|
||||
case 't': // top (vertical)
|
||||
case 'top': // top (vertical)
|
||||
dy[i] = miny - bbox.y;
|
||||
break;
|
||||
case 'm': // middle (vertical)
|
||||
case 'middle': // middle (vertical)
|
||||
dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2);
|
||||
break;
|
||||
case 'b': // bottom (vertical)
|
||||
case 'bottom': // bottom (vertical)
|
||||
dy[i] = maxy - (bbox.y + bbox.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
moveSelectedElements(dx, dy);
|
||||
@@ -395,7 +416,7 @@ export const deleteSelectedElements = function () {
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
|
||||
const {nextSibling} = t;
|
||||
const { nextSibling } = t;
|
||||
t.remove();
|
||||
const elem = t;
|
||||
selectedCopy.push(selected); // for the copy
|
||||
@@ -416,16 +437,14 @@ export const deleteSelectedElements = function () {
|
||||
export const copySelectedElements = function () {
|
||||
const selectedElements = elementContext_.getSelectedElements();
|
||||
const data =
|
||||
JSON.stringify(selectedElements.map((x) => elementContext_.getJsonFromSvgElement(x)));
|
||||
JSON.stringify(selectedElements.map((x) => elementContext_.getJsonFromSvgElement(x)));
|
||||
// Use sessionStorage for the clipboard data.
|
||||
sessionStorage.setItem(elementContext_.getClipboardID(), data);
|
||||
elementContext_.flashStorage();
|
||||
|
||||
const menu = $('#cmenu_canvas');
|
||||
// Context menu might not exist (it is provided by editor.js).
|
||||
if (menu.enableContextMenuItems) {
|
||||
menu.enableContextMenuItems('#paste,#paste_in_place');
|
||||
}
|
||||
const canvMenu = document.getElementById('se-cmenu_canvas');
|
||||
canvMenu.setAttribute('enablemenuitems', '#paste,#paste_in_place');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -441,16 +460,17 @@ export const groupSelectedElements = function (type, urlArg) {
|
||||
let cmdStr = '';
|
||||
let url;
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-small-switch
|
||||
switch (type) {
|
||||
case 'a': {
|
||||
cmdStr = 'Make hyperlink';
|
||||
url = urlArg || '';
|
||||
break;
|
||||
} default: {
|
||||
type = 'g';
|
||||
cmdStr = 'Group Elements';
|
||||
break;
|
||||
}
|
||||
case 'a': {
|
||||
cmdStr = 'Make hyperlink';
|
||||
url = urlArg || '';
|
||||
break;
|
||||
} default: {
|
||||
type = 'g';
|
||||
cmdStr = 'Group Elements';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const batchCmd = new BatchCommand(cmdStr);
|
||||
@@ -515,7 +535,10 @@ export const pushGroupProperty = function (g, undoable) {
|
||||
|
||||
const gangle = getRotationAngle(g);
|
||||
|
||||
const gattrs = $(g).attr(['filter', 'opacity']);
|
||||
const gattrs = {
|
||||
filter: g.getAttribute('filter'),
|
||||
opacity: g.getAttribute('opacity'),
|
||||
};
|
||||
let gfilter, gblur, changes;
|
||||
const drawing = elementContext_.getDrawing();
|
||||
|
||||
@@ -688,17 +711,20 @@ export const convertToGroup = function (elem) {
|
||||
if (!elem) {
|
||||
elem = selectedElements[0];
|
||||
}
|
||||
const $elem = $(elem);
|
||||
const $elem = elem;
|
||||
const batchCmd = new BatchCommand();
|
||||
let ts;
|
||||
|
||||
if ($elem.data('gsvg')) {
|
||||
const dataStorage = elementContext_.getDataStorage();
|
||||
if (dataStorage.has($elem, 'gsvg')) {
|
||||
// Use the gsvg as the new group
|
||||
const svg = elem.firstChild;
|
||||
const pt = $(svg).attr(['x', 'y']);
|
||||
const pt = {
|
||||
x: svg.getAttribute('x'),
|
||||
y: svg.getAttribute('y'),
|
||||
};
|
||||
|
||||
$(elem.firstChild.firstChild).unwrap();
|
||||
$(elem).removeData('gsvg');
|
||||
dataStorage.remove(elem, 'gsvg');
|
||||
|
||||
const tlist = getTransformList(elem);
|
||||
const xform = elementContext_.getSVGRoot().createSVGTransform();
|
||||
@@ -706,8 +732,8 @@ export const convertToGroup = function (elem) {
|
||||
tlist.appendItem(xform);
|
||||
recalculateDimensions(elem);
|
||||
elementContext_.call('selected', [elem]);
|
||||
} else if ($elem.data('symbol')) {
|
||||
elem = $elem.data('symbol');
|
||||
} else if (dataStorage.has($elem, 'symbol')) {
|
||||
elem = dataStorage.get($elem, 'symbol')
|
||||
|
||||
ts = $elem.attr('transform');
|
||||
const pos = $elem.attr(['x', 'y']);
|
||||
@@ -731,7 +757,7 @@ export const convertToGroup = function (elem) {
|
||||
|
||||
// See if other elements reference this symbol
|
||||
const svgcontent = elementContext_.getSVGContent();
|
||||
const hasMore = $(svgcontent).find('use:data(symbol)').length;
|
||||
const hasMore = svgcontent.querySelectorAll('use:data(symbol)').length;
|
||||
|
||||
const g = elementContext_.getDOMDocument().createElementNS(NS.SVG, 'g');
|
||||
const childs = elem.childNodes;
|
||||
@@ -743,8 +769,11 @@ export const convertToGroup = function (elem) {
|
||||
|
||||
// Duplicate the gradients for Gecko, since they weren't included in the <symbol>
|
||||
if (isGecko()) {
|
||||
const dupeGrads = $(findDefs()).children('linearGradient,radialGradient,pattern').clone();
|
||||
$(g).append(dupeGrads);
|
||||
const svgElement = findDefs();
|
||||
const gradients = svgElement.querySelectorAll('linearGradient,radialGradient,pattern');
|
||||
for (let i = 0, im = gradients.length; im > i; i++) {
|
||||
g.appendChild(gradients[i].cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (ts) {
|
||||
@@ -757,7 +786,11 @@ export const convertToGroup = function (elem) {
|
||||
|
||||
// Put the dupe gradients back into <defs> (after uniquifying them)
|
||||
if (isGecko()) {
|
||||
$(findDefs()).append($(g).find('linearGradient,radialGradient,pattern'));
|
||||
const svgElement = findDefs();
|
||||
const elements = g.querySelectorAll('linearGradient,radialGradient,pattern');
|
||||
for (let i = 0, im = elements.length; im > i; i++) {
|
||||
svgElement.appendChild(elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// now give the g itself a new id
|
||||
@@ -768,7 +801,7 @@ export const convertToGroup = function (elem) {
|
||||
if (parent) {
|
||||
if (!hasMore) {
|
||||
// remove symbol/svg element
|
||||
const {nextSibling} = elem;
|
||||
const { nextSibling } = elem;
|
||||
elem.remove();
|
||||
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
|
||||
}
|
||||
@@ -789,14 +822,14 @@ export const convertToGroup = function (elem) {
|
||||
try {
|
||||
recalculateDimensions(n);
|
||||
} catch (e) {
|
||||
console.log(e); // eslint-disable-line no-console
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Give ID for any visible element missing one
|
||||
const visElems = elementContext_.getVisElems();
|
||||
$(g).find(visElems).each(function () {
|
||||
if (!this.id) { this.id = elementContext_.getNextId(); }
|
||||
const visElems = g.querySelectorAll(elementContext_.getVisElems());
|
||||
Array.prototype.forEach.call(visElems, function (el) {
|
||||
if (!el.id) { el.id = elementContext_.getNextId(); }
|
||||
});
|
||||
|
||||
elementContext_.selectOnly([g]);
|
||||
@@ -808,7 +841,7 @@ export const convertToGroup = function (elem) {
|
||||
|
||||
elementContext_.addCommandToHistory(batchCmd);
|
||||
} else {
|
||||
console.log('Unexpected element to ungroup:', elem); // eslint-disable-line no-console
|
||||
console.log('Unexpected element to ungroup:', elem);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -820,11 +853,12 @@ export const convertToGroup = function (elem) {
|
||||
*/
|
||||
export const ungroupSelectedElement = function () {
|
||||
const selectedElements = elementContext_.getSelectedElements();
|
||||
const dataStorage = elementContext_.getDataStorage();
|
||||
let g = selectedElements[0];
|
||||
if (!g) {
|
||||
return;
|
||||
}
|
||||
if ($(g).data('gsvg') || $(g).data('symbol')) {
|
||||
if (dataStorage.has(g, 'gsvg') || dataStorage.has(g, 'symbol')) {
|
||||
// Is svg, so actually convert to group
|
||||
convertToGroup(g);
|
||||
return;
|
||||
@@ -832,11 +866,12 @@ export const ungroupSelectedElement = function () {
|
||||
if (g.tagName === 'use') {
|
||||
// Somehow doesn't have data set, so retrieve
|
||||
const symbol = getElem(getHref(g).substr(1));
|
||||
$(g).data('symbol', symbol).data('ref', symbol);
|
||||
dataStorage.put(g, 'symbol', symbol);
|
||||
dataStorage.put(g, 'ref', symbol);
|
||||
convertToGroup(g);
|
||||
return;
|
||||
}
|
||||
const parentsA = $(g).parents('a');
|
||||
const parentsA = getParents(g.parentNode, 'a');
|
||||
if (parentsA.length) {
|
||||
g = parentsA[0];
|
||||
}
|
||||
@@ -859,7 +894,7 @@ export const ungroupSelectedElement = function () {
|
||||
|
||||
// Remove child title elements
|
||||
if (elem.tagName === 'title') {
|
||||
const {nextSibling} = elem;
|
||||
const { nextSibling } = elem;
|
||||
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent));
|
||||
elem.remove();
|
||||
continue;
|
||||
@@ -900,7 +935,7 @@ export const updateCanvas = function (w, h) {
|
||||
elementContext_.getSVGRoot().setAttribute('width', w);
|
||||
elementContext_.getSVGRoot().setAttribute('height', h);
|
||||
const currentZoom = elementContext_.getCurrentZoom();
|
||||
const bg = $('#canvasBackground')[0];
|
||||
const bg = document.getElementById('canvasBackground');
|
||||
const oldX = elementContext_.getSVGContent().getAttribute('x');
|
||||
const oldY = elementContext_.getSVGContent().getAttribute('y');
|
||||
const x = ((w - this.contentW * currentZoom) / 2);
|
||||
@@ -947,9 +982,9 @@ export const updateCanvas = function (w, h) {
|
||||
/**
|
||||
* @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated}
|
||||
*/
|
||||
{new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY}
|
||||
{ new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY }
|
||||
);
|
||||
return {x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY};
|
||||
return { x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY };
|
||||
};
|
||||
/**
|
||||
* Select the next/previous element within the current layer.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* Tools for selection.
|
||||
* @module selection
|
||||
@@ -6,19 +5,18 @@
|
||||
* @copyright 2011 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import { NS } from '../common/namespaces.js';
|
||||
import {
|
||||
isNullish, getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible
|
||||
} from '../common/utilities.js';
|
||||
import {transformPoint, transformListToTransform, rectsIntersect} from '../common/math.js';
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js';
|
||||
} from './utilities.js';
|
||||
import { transformPoint, transformListToTransform, rectsIntersect } from './math.js';
|
||||
import {
|
||||
getTransformList
|
||||
} from '../common/svgtransformlist.js';
|
||||
} from './svgtransformlist.js';
|
||||
import * as hstry from './history.js';
|
||||
import { getClosest } from '../editor/components/jgraduate/Util.js';
|
||||
|
||||
const {BatchCommand} = hstry;
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
const { BatchCommand } = hstry;
|
||||
let selectionContext_ = null;
|
||||
|
||||
/**
|
||||
@@ -139,7 +137,7 @@ export const getMouseTargetMethod = function (evt) {
|
||||
// for foreign content, go up until we find the foreignObject
|
||||
// WebKit browsers set the mouse target to the svgcanvas div
|
||||
if ([NS.MATH, NS.HTML].includes(mouseTarget.namespaceURI) &&
|
||||
mouseTarget.id !== 'svgcanvas'
|
||||
mouseTarget.id !== 'svgcanvas'
|
||||
) {
|
||||
while (mouseTarget.nodeName !== 'foreignObject') {
|
||||
mouseTarget = mouseTarget.parentNode;
|
||||
@@ -157,10 +155,10 @@ mouseTarget.id !== 'svgcanvas'
|
||||
return selectionContext_.getSVGRoot();
|
||||
}
|
||||
|
||||
const $target = $(mouseTarget);
|
||||
const $target = mouseTarget;
|
||||
|
||||
// If it's a selection grip, return the grip parent
|
||||
if ($target.closest('#selectorParentGroup').length) {
|
||||
if (getClosest($target.parentNode, '#selectorParentGroup')) {
|
||||
// While we could instead have just returned mouseTarget,
|
||||
// this makes it easier to indentify as being a selector grip
|
||||
return selectionContext_.getCanvas().selectorManager.selectorParentGroup;
|
||||
@@ -209,7 +207,7 @@ mouseTarget.id !== 'svgcanvas'
|
||||
*/
|
||||
export const runExtensionsMethod = function (action, vars, returnArray, nameFilter) {
|
||||
let result = returnArray ? [] : false;
|
||||
$.each(selectionContext_.getExtensions(), function (name, ext) {
|
||||
for (const [name, ext] of Object.entries(selectionContext_.getExtensions())) {
|
||||
if (nameFilter && !nameFilter(name)) {
|
||||
return;
|
||||
}
|
||||
@@ -223,7 +221,7 @@ export const runExtensionsMethod = function (action, vars, returnArray, nameFilt
|
||||
result = ext[action](vars);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -237,12 +235,14 @@ export const runExtensionsMethod = function (action, vars, returnArray, nameFilt
|
||||
*/
|
||||
export const getVisibleElementsAndBBoxes = function (parent) {
|
||||
if (!parent) {
|
||||
parent = $(selectionContext_.getSVGContent()).children(); // Prevent layers from being included
|
||||
const svgcontent = selectionContext_.getSVGContent();
|
||||
parent = svgcontent.children; // Prevent layers from being included
|
||||
}
|
||||
const contentElems = [];
|
||||
$(parent).children().each(function (i, elem) {
|
||||
const elements = parent.children;
|
||||
Array.prototype.forEach.call(elements, function (elem) {
|
||||
if (elem.getBBox) {
|
||||
contentElems.push({elem, bbox: getStrokedBBoxDefaultVisible([elem])});
|
||||
contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([elem]) });
|
||||
}
|
||||
});
|
||||
return contentElems.reverse();
|
||||
@@ -284,15 +284,6 @@ export const getIntersectionListMethod = function (rect) {
|
||||
}
|
||||
|
||||
let resultList = null;
|
||||
if (!selectionContext_.isIE()) {
|
||||
if (typeof selectionContext_.getSVGRoot().getIntersectionList === 'function') {
|
||||
// Offset the bbox of the rubber box by the offset of the svgcontent element.
|
||||
rubberBBox.x += Number.parseInt(selectionContext_.getSVGContent().getAttribute('x'));
|
||||
rubberBBox.y += Number.parseInt(selectionContext_.getSVGContent().getAttribute('y'));
|
||||
|
||||
resultList = selectionContext_.getSVGRoot().getIntersectionList(rubberBBox, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (isNullish(resultList) || typeof resultList.item !== 'function') {
|
||||
resultList = [];
|
||||
@@ -330,9 +321,12 @@ export const getIntersectionListMethod = function (rect) {
|
||||
* @returns {void}
|
||||
*/
|
||||
export const groupSvgElem = function (elem) {
|
||||
const dataStorage = selectionContext_.getDataStorage();
|
||||
const g = document.createElementNS(NS.SVG, 'g');
|
||||
elem.replaceWith(g);
|
||||
$(g).append(elem).data('gsvg', elem)[0].id = selectionContext_.getCanvas().getNextId();
|
||||
g.appendChild(elem);
|
||||
dataStorage.put(g, 'gsvg', elem);
|
||||
g.id = selectionContext_.getCanvas().getNextId();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -351,12 +345,6 @@ export const prepareSvg = function (newDoc) {
|
||||
selectionContext_.getCanvas().pathActions.fixEnd(path);
|
||||
});
|
||||
};
|
||||
// `this.each` is deprecated, if any extension used this it can be recreated by doing this:
|
||||
// * @example $(canvas.getRootElem()).children().each(...)
|
||||
// * @function module:svgcanvas.SvgCanvas#each
|
||||
// this.each = function (cb) {
|
||||
// $(svgroot).children().each(cb);
|
||||
// };
|
||||
|
||||
/**
|
||||
* Removes any old rotations if present, prepends a new rotation at the
|
||||
@@ -402,7 +390,11 @@ export const setRotationAngle = function (val, preventUndo) {
|
||||
// we need to undo it, then redo it so it can be undo-able! :)
|
||||
// TODO: figure out how to make changes to transform list undo-able cross-browser?
|
||||
const newTransform = elem.getAttribute('transform');
|
||||
elem.setAttribute('transform', oldTransform);
|
||||
if (oldTransform) {
|
||||
elem.setAttribute('transform', oldTransform);
|
||||
} else {
|
||||
elem.removeAttribute('transform');
|
||||
}
|
||||
selectionContext_.getCanvas().changeSelectedAttribute('transform', newTransform, selectedElements);
|
||||
selectionContext_.getCanvas().call('changed', selectedElements);
|
||||
}
|
||||
|
||||
@@ -6,29 +6,30 @@
|
||||
* @copyright 2011 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {jsPDF} from 'jspdf/dist/jspdf.es.min.js';
|
||||
import { jsPDF } from 'jspdf/dist/jspdf.es.min.js';
|
||||
import 'svg2pdf.js/dist/svg2pdf.es.js';
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js';
|
||||
import jQueryPluginSVG from './jQuery.attr.js';
|
||||
import * as hstry from './history.js';
|
||||
import {
|
||||
text2xml, cleanupElement, findDefs, getHref, preventClickDefault,
|
||||
toXml, getStrokedBBoxDefaultVisible, encode64, createObjectURL,
|
||||
dataURLToObjectURL, walkTree, getBBox as utilsGetBBox
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
transformPoint, transformListToTransform
|
||||
} from '../common/math.js';
|
||||
import {resetListMap} from '../common/svgtransformlist.js';
|
||||
} from './math.js';
|
||||
import { resetListMap } from './svgtransformlist.js';
|
||||
import {
|
||||
convertUnit, shortFloat, convertToNum
|
||||
} from '../common/units.js';
|
||||
import {isGecko, isChrome, isWebkit} from '../common/browser.js';
|
||||
import { isGecko, isChrome, isWebkit } from '../common/browser.js';
|
||||
import * as pathModule from './path.js';
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import { NS } from '../common/namespaces.js';
|
||||
import * as draw from './draw.js';
|
||||
import {
|
||||
recalculateDimensions
|
||||
} from './recalculate.js';
|
||||
import { getParents, getClosest } from '../editor/components/jgraduate/Util.js';
|
||||
|
||||
const {
|
||||
InsertElementCommand, RemoveElementCommand,
|
||||
@@ -38,6 +39,7 @@ const {
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
let svgContext_ = null;
|
||||
let $id = null;
|
||||
|
||||
/**
|
||||
* @function module:svg-exec.init
|
||||
@@ -46,6 +48,8 @@ let svgContext_ = null;
|
||||
*/
|
||||
export const init = function (svgContext) {
|
||||
svgContext_ = svgContext;
|
||||
const svgCanvas = svgContext_.getCanvas();
|
||||
$id = svgCanvas.$id;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,12 +59,13 @@ export const init = function (svgContext) {
|
||||
*/
|
||||
export const svgCanvasToString = function () {
|
||||
// keep calling it until there are none to remove
|
||||
while (svgContext_.getCanvas().removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty
|
||||
while (svgContext_.getCanvas().removeUnusedDefElems() > 0) { } // eslint-disable-line no-empty
|
||||
|
||||
svgContext_.getCanvas().pathActions.clear(true);
|
||||
|
||||
// Keep SVG-Edit comment on top
|
||||
$.each(svgContext_.getSVGContent().childNodes, function (i, node) {
|
||||
const childNodesElems = svgContext_.getSVGContent().childNodes;
|
||||
childNodesElems.forEach(function (node, i) {
|
||||
if (i && node.nodeType === 8 && node.data.includes('Created with')) {
|
||||
svgContext_.getSVGContent().firstChild.before(node);
|
||||
}
|
||||
@@ -75,8 +80,9 @@ export const svgCanvasToString = function () {
|
||||
const nakedSvgs = [];
|
||||
|
||||
// Unwrap gsvg if it has no special attributes (only id and style)
|
||||
$(svgContext_.getSVGContent()).find('g:data(gsvg)').each(function () {
|
||||
const attrs = this.attributes;
|
||||
const gsvgElems = svgContext_.getSVGContent().querySelectorAll('g[data-gsvg]');
|
||||
Array.prototype.forEach.call(gsvgElems, function (element) {
|
||||
const attrs = element.attributes;
|
||||
let len = attrs.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (attrs[i].nodeName === 'id' || attrs[i].nodeName === 'style') {
|
||||
@@ -85,17 +91,17 @@ export const svgCanvasToString = function () {
|
||||
}
|
||||
// No significant attributes, so ungroup
|
||||
if (len <= 0) {
|
||||
const svg = this.firstChild;
|
||||
const svg = element.firstChild;
|
||||
nakedSvgs.push(svg);
|
||||
$(this).replaceWith(svg);
|
||||
element.replaceWith(svg);
|
||||
}
|
||||
});
|
||||
const output = this.svgToString(svgContext_.getSVGContent(), 0);
|
||||
|
||||
// Rewrap gsvg
|
||||
if (nakedSvgs.length) {
|
||||
$(nakedSvgs).each(function () {
|
||||
svgContext_.getCanvas().groupSvgElem(this);
|
||||
Array.prototype.forEach.call(nakedSvgs, function (el) {
|
||||
svgContext_.getCanvas().groupSvgElem(el);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,22 +159,26 @@ export const svgToString = function (elem, indent) {
|
||||
const nsuris = {};
|
||||
|
||||
// Check elements for namespaces, add if found
|
||||
$(elem).find('*').andSelf().each(function () {
|
||||
const csElements = elem.querySelectorAll('*');
|
||||
const cElements = Array.prototype.slice.call(csElements);
|
||||
cElements.push(elem);
|
||||
Array.prototype.forEach.call(cElements, function (el) {
|
||||
// const el = this;
|
||||
// for some elements have no attribute
|
||||
const uri = this.namespaceURI;
|
||||
const uri = el.namespaceURI;
|
||||
if (uri && !nsuris[uri] && nsMap[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') {
|
||||
nsuris[uri] = true;
|
||||
out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"');
|
||||
}
|
||||
|
||||
$.each(this.attributes, function (i, attr) {
|
||||
const u = attr.namespaceURI;
|
||||
if (u && !nsuris[u] && nsMap[u] !== 'xmlns' && nsMap[u] !== 'xml') {
|
||||
nsuris[u] = true;
|
||||
out.push(' xmlns:' + nsMap[u] + '="' + u + '"');
|
||||
if (el.attributes.length > 0) {
|
||||
for (const [, attr] of Object.entries(el.attributes)) {
|
||||
const u = attr.namespaceURI;
|
||||
if (u && !nsuris[u] && nsMap[u] !== 'xmlns' && nsMap[u] !== 'xml') {
|
||||
nsuris[u] = true;
|
||||
out.push(' xmlns:' + nsMap[u] + '="' + u + '"');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let i = attrs.length;
|
||||
@@ -181,12 +191,10 @@ export const svgToString = function (elem, indent) {
|
||||
if (attr.nodeName.startsWith('xmlns:')) { continue; }
|
||||
|
||||
// only serialize attributes we don't use internally
|
||||
if (attrVal !== '' && !attrNames.includes(attr.localName)) {
|
||||
if (!attr.namespaceURI || nsMap[attr.namespaceURI]) {
|
||||
out.push(' ');
|
||||
out.push(attr.nodeName); out.push('="');
|
||||
out.push(attrVal); out.push('"');
|
||||
}
|
||||
if (attrVal !== '' && !attrNames.includes(attr.localName) && (!attr.namespaceURI || nsMap[attr.namespaceURI])) {
|
||||
out.push(' ');
|
||||
out.push(attr.nodeName); out.push('="');
|
||||
out.push(attrVal); out.push('"');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -216,10 +224,10 @@ export const svgToString = function (elem, indent) {
|
||||
|
||||
// Embed images when saving
|
||||
if (svgContext_.getSvgOptionApply() &&
|
||||
elem.nodeName === 'image' &&
|
||||
attr.localName === 'href' &&
|
||||
svgContext_.getSvgOptionImages() &&
|
||||
svgContext_.getSvgOptionImages() === 'embed'
|
||||
elem.nodeName === 'image' &&
|
||||
attr.localName === 'href' &&
|
||||
svgContext_.getSvgOptionImages() &&
|
||||
svgContext_.getSvgOptionImages() === 'embed'
|
||||
) {
|
||||
const img = svgContext_.getEncodableImages(attrVal);
|
||||
if (img) { attrVal = img; }
|
||||
@@ -243,31 +251,31 @@ export const svgToString = function (elem, indent) {
|
||||
for (let i = 0; i < childs.length; i++) {
|
||||
const child = childs.item(i);
|
||||
switch (child.nodeType) {
|
||||
case 1: // element node
|
||||
out.push('\n');
|
||||
out.push(this.svgToString(child, indent));
|
||||
break;
|
||||
case 3: { // text node
|
||||
const str = child.nodeValue.replace(/^\s+|\s+$/g, '');
|
||||
if (str !== '') {
|
||||
bOneLine = true;
|
||||
out.push(String(toXml(str)));
|
||||
}
|
||||
break;
|
||||
} case 4: // cdata node
|
||||
out.push('\n');
|
||||
out.push(new Array(indent + 1).join(' '));
|
||||
out.push('<![CDATA[');
|
||||
out.push(child.nodeValue);
|
||||
out.push(']]>');
|
||||
break;
|
||||
case 8: // comment
|
||||
out.push('\n');
|
||||
out.push(new Array(indent + 1).join(' '));
|
||||
out.push('<!--');
|
||||
out.push(child.data);
|
||||
out.push('-->');
|
||||
break;
|
||||
case 1: // element node
|
||||
out.push('\n');
|
||||
out.push(this.svgToString(child, indent));
|
||||
break;
|
||||
case 3: { // text node
|
||||
const str = child.nodeValue.replace(/^\s+|\s+$/g, '');
|
||||
if (str !== '') {
|
||||
bOneLine = true;
|
||||
out.push(String(toXml(str)));
|
||||
}
|
||||
break;
|
||||
} case 4: // cdata node
|
||||
out.push('\n');
|
||||
out.push(new Array(indent + 1).join(' '));
|
||||
out.push('<![CDATA[');
|
||||
out.push(child.nodeValue);
|
||||
out.push(']]>');
|
||||
break;
|
||||
case 8: // comment
|
||||
out.push('\n');
|
||||
out.push(new Array(indent + 1).join(' '));
|
||||
out.push('<!--');
|
||||
out.push(child.data);
|
||||
out.push('-->');
|
||||
break;
|
||||
} // switch on node type
|
||||
}
|
||||
indent--;
|
||||
@@ -298,11 +306,12 @@ export const svgToString = function (elem, indent) {
|
||||
*/
|
||||
export const setSvgString = function (xmlString, preventUndo) {
|
||||
const curConfig = svgContext_.getCurConfig();
|
||||
const dataStorage = svgContext_.getDataStorage();
|
||||
try {
|
||||
// convert string into XML document
|
||||
const newDoc = text2xml(xmlString);
|
||||
if (newDoc.firstElementChild &&
|
||||
newDoc.firstElementChild.namespaceURI !== NS.SVG) {
|
||||
newDoc.firstElementChild.namespaceURI !== NS.SVG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -311,7 +320,7 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
const batchCmd = new BatchCommand('Change Source');
|
||||
|
||||
// remove old svg document
|
||||
const {nextSibling} = svgContext_.getSVGContent();
|
||||
const { nextSibling } = svgContext_.getSVGContent();
|
||||
|
||||
svgContext_.getSVGContent().remove();
|
||||
const oldzoom = svgContext_.getSVGContent();
|
||||
@@ -326,7 +335,7 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
}
|
||||
|
||||
svgContext_.getSVGRoot().append(svgContext_.getSVGContent());
|
||||
const content = $(svgContext_.getSVGContent());
|
||||
const content = svgContext_.getSVGContent();
|
||||
|
||||
svgContext_.getCanvas().current_drawing_ = new draw.Drawing(svgContext_.getSVGContent(), svgContext_.getIdPrefix());
|
||||
|
||||
@@ -339,8 +348,8 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
}
|
||||
|
||||
// change image href vals if possible
|
||||
content.find('image').each(function () {
|
||||
const image = this;
|
||||
const elements = content.querySelectorAll('image');
|
||||
Array.prototype.forEach.call(elements, function (image) {
|
||||
preventClickDefault(image);
|
||||
const val = svgContext_.getCanvas().getHref(this);
|
||||
if (val) {
|
||||
@@ -351,9 +360,11 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
if (m) {
|
||||
const url = decodeURIComponent(m[1]);
|
||||
// const url = decodeURIComponent(m.groups.url);
|
||||
$(new Image()).load(function () {
|
||||
const iimg = new Image();
|
||||
iimg.addEventListener("load", () => {
|
||||
image.setAttributeNS(NS.XLINK, 'xlink:href', url);
|
||||
}).attr('src', url);
|
||||
});
|
||||
iimg.src = url;
|
||||
}
|
||||
}
|
||||
// Add to encodableImages if it loads
|
||||
@@ -362,25 +373,30 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
});
|
||||
|
||||
// Wrap child SVGs in group elements
|
||||
content.find('svg').each(function () {
|
||||
const svgElements = content.querySelectorAll('svg');
|
||||
Array.prototype.forEach.call(svgElements, function (element) {
|
||||
// Skip if it's in a <defs>
|
||||
if ($(this).closest('defs').length) { return; }
|
||||
if (getClosest(element.parentNode, 'defs')) { return; }
|
||||
|
||||
svgContext_.getCanvas().uniquifyElems(this);
|
||||
svgContext_.getCanvas().uniquifyElems(element);
|
||||
|
||||
// Check if it already has a gsvg group
|
||||
const pa = this.parentNode;
|
||||
const pa = element.parentNode;
|
||||
if (pa.childNodes.length === 1 && pa.nodeName === 'g') {
|
||||
$(pa).data('gsvg', this);
|
||||
dataStorage.put(pa, 'gsvg', element);
|
||||
pa.id = pa.id || svgContext_.getCanvas().getNextId();
|
||||
} else {
|
||||
svgContext_.getCanvas().groupSvgElem(this);
|
||||
svgContext_.getCanvas().groupSvgElem(element);
|
||||
}
|
||||
});
|
||||
|
||||
// For Firefox: Put all paint elems in defs
|
||||
if (isGecko()) {
|
||||
content.find('linearGradient, radialGradient, pattern').appendTo(findDefs());
|
||||
const svgDefs = findDefs();
|
||||
const findElems = content.querySelectorAll('linearGradient, radialGradient, pattern');
|
||||
Array.prototype.forEach.call(findElems, function (ele) {
|
||||
svgDefs.appendChild(ele);
|
||||
});
|
||||
}
|
||||
|
||||
// Set ref element for <use> elements
|
||||
@@ -388,7 +404,7 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
// TODO: This should also be done if the object is re-added through "redo"
|
||||
svgContext_.getCanvas().setUseData(content);
|
||||
|
||||
svgContext_.getCanvas().convertGradients(content[0]);
|
||||
svgContext_.getCanvas().convertGradients(content);
|
||||
|
||||
const attrs = {
|
||||
id: 'svgcontent',
|
||||
@@ -398,16 +414,16 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
let percs = false;
|
||||
|
||||
// determine proper size
|
||||
if (content.attr('viewBox')) {
|
||||
const vb = content.attr('viewBox').split(' ');
|
||||
if (content.getAttribute('viewBox')) {
|
||||
const viBox = content.getAttribute('viewBox');
|
||||
const vb = viBox.split(' ');
|
||||
attrs.width = vb[2];
|
||||
attrs.height = vb[3];
|
||||
// handle content that doesn't have a viewBox
|
||||
} else {
|
||||
$.each(['width', 'height'], function (i, dim) {
|
||||
['width', 'height'].forEach(function (dim) {
|
||||
// Set to 100 if not given
|
||||
const val = content.attr(dim) || '100%';
|
||||
|
||||
const val = content.getAttribute(dim) || '100%';
|
||||
if (String(val).substr(-1) === '%') {
|
||||
// Use user units if percentage given
|
||||
percs = true;
|
||||
@@ -421,8 +437,12 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
draw.identifyLayers();
|
||||
|
||||
// Give ID for any visible layer children missing one
|
||||
content.children().find(svgContext_.getVisElems()).each(function () {
|
||||
if (!this.id) { this.id = svgContext_.getCanvas().getNextId(); }
|
||||
const chiElems = content.children;
|
||||
Array.prototype.forEach.call(chiElems, function (chiElem) {
|
||||
const visElems = chiElem.querySelectorAll(svgContext_.getVisElems());
|
||||
Array.prototype.forEach.call(visElems, function (elem) {
|
||||
if (!elem.id) { elem.id = svgContext_.getCanvas().getNextId(); }
|
||||
});
|
||||
});
|
||||
|
||||
// Percentage width/height, so let's base it on visible elements
|
||||
@@ -437,13 +457,17 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
if (attrs.width <= 0) { attrs.width = 100; }
|
||||
if (attrs.height <= 0) { attrs.height = 100; }
|
||||
|
||||
content.attr(attrs);
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
content.setAttribute(key, value);
|
||||
}
|
||||
this.contentW = attrs.width;
|
||||
this.contentH = attrs.height;
|
||||
|
||||
batchCmd.addSubCommand(new InsertElementCommand(svgContext_.getSVGContent()));
|
||||
// update root to the correct size
|
||||
const changes = content.attr(['width', 'height']);
|
||||
const width = content.getAttribute('width');
|
||||
const height = content.getAttribute('height');
|
||||
const changes = { width: width, height: height };
|
||||
batchCmd.addSubCommand(new ChangeElementCommand(svgContext_.getSVGRoot(), changes));
|
||||
|
||||
// reset zoom
|
||||
@@ -458,7 +482,7 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
if (!preventUndo) svgContext_.addCommandToHistory(batchCmd);
|
||||
svgContext_.call('changed', [svgContext_.getSVGContent()]);
|
||||
} catch (e) {
|
||||
console.log(e); // eslint-disable-line no-console
|
||||
console.log(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -480,6 +504,7 @@ export const setSvgString = function (xmlString, preventUndo) {
|
||||
* was obtained
|
||||
*/
|
||||
export const importSvgString = function (xmlString) {
|
||||
const dataStorage = svgContext_.getDataStorage();
|
||||
let j, ts, useEl;
|
||||
try {
|
||||
// Get unique ID
|
||||
@@ -487,8 +512,9 @@ export const importSvgString = function (xmlString) {
|
||||
|
||||
let useExisting = false;
|
||||
// Look for symbol and make sure symbol exists in image
|
||||
if (svgContext_.getImportIds(uid)) {
|
||||
if ($(svgContext_.getImportIds(uid).symbol).parents('#svgroot').length) {
|
||||
if (svgContext_.getImportIds(uid) && svgContext_.getImportIds(uid).symbol) {
|
||||
const parents = getParents(svgContext_.getImportIds(uid).symbol, '#svgroot');
|
||||
if (parents.length) {
|
||||
useExisting = true;
|
||||
}
|
||||
}
|
||||
@@ -496,7 +522,7 @@ export const importSvgString = function (xmlString) {
|
||||
const batchCmd = new BatchCommand('Import Image');
|
||||
let symbol;
|
||||
if (useExisting) {
|
||||
({symbol} = svgContext_.getImportIds());
|
||||
({ symbol } = svgContext_.getImportIds());
|
||||
ts = svgContext_.getImportIds(uid).xform;
|
||||
} else {
|
||||
// convert string into XML document
|
||||
@@ -538,7 +564,10 @@ export const importSvgString = function (xmlString) {
|
||||
// Move all gradients into root for Firefox, workaround for this bug:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=353575
|
||||
// TODO: Make this properly undo-able.
|
||||
$(svg).find('linearGradient, radialGradient, pattern').appendTo(defs);
|
||||
const elements = svg.querySelectorAll('linearGradient, radialGradient, pattern');
|
||||
Array.prototype.forEach.call(elements, function (el) {
|
||||
defs.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
while (svg.firstChild) {
|
||||
@@ -571,7 +600,8 @@ export const importSvgString = function (xmlString) {
|
||||
|
||||
useEl.setAttribute('transform', ts);
|
||||
recalculateDimensions(useEl);
|
||||
$(useEl).data('symbol', symbol).data('ref', symbol);
|
||||
dataStorage.put(useEl, 'symbol', symbol);
|
||||
dataStorage.put(useEl, 'ref', symbol);
|
||||
svgContext_.getCanvas().addToSelection([useEl]);
|
||||
|
||||
// TODO: Find way to add this in a recalculateDimensions-parsable way
|
||||
@@ -581,7 +611,7 @@ export const importSvgString = function (xmlString) {
|
||||
svgContext_.addCommandToHistory(batchCmd);
|
||||
svgContext_.call('changed', [svgContext_.getSVGContent()]);
|
||||
} catch (e) {
|
||||
console.log(e); // eslint-disable-line no-console
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -602,20 +632,16 @@ export const importSvgString = function (xmlString) {
|
||||
*/
|
||||
export const embedImage = function (src) {
|
||||
// Todo: Remove this Promise in favor of making an async/await `Image.load` utility
|
||||
// eslint-disable-next-line promise/avoid-new
|
||||
return new Promise(function (resolve, reject) {
|
||||
// load in the image and once it's loaded, get the dimensions
|
||||
$(new Image()).load(function (response, status, xhr) {
|
||||
if (status === 'error') {
|
||||
reject(new Error('Error loading image: ' + xhr.status + ' ' + xhr.statusText));
|
||||
return;
|
||||
}
|
||||
const imgI = new Image();
|
||||
imgI.addEventListener("load", (e) => {
|
||||
// create a canvas the same size as the raster image
|
||||
const cvs = document.createElement('canvas');
|
||||
cvs.width = this.width;
|
||||
cvs.height = this.height;
|
||||
cvs.width = e.currentTarget.width;
|
||||
cvs.height = e.currentTarget.height;
|
||||
// load the raster image into the canvas
|
||||
cvs.getContext('2d').drawImage(this, 0, 0);
|
||||
cvs.getContext('2d').drawImage(e.currentTarget, 0, 0);
|
||||
// retrieve the data: URL
|
||||
try {
|
||||
let urldata = ';svgedit_url=' + encodeURIComponent(src);
|
||||
@@ -626,7 +652,11 @@ export const embedImage = function (src) {
|
||||
}
|
||||
svgContext_.getCanvas().setGoodImage(src);
|
||||
resolve(svgContext_.getEncodableImages(src));
|
||||
}).attr('src', src);
|
||||
});
|
||||
imgI.addEventListener("error", () => {
|
||||
reject(new Error('Error loading image: '));
|
||||
});
|
||||
imgI.setAttribute('src', src);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -660,7 +690,7 @@ export const save = function (opts) {
|
||||
* Codes only is useful for locale-independent detection.
|
||||
* @returns {module:svgcanvas.IssuesAndCodes}
|
||||
*/
|
||||
function getIssues () {
|
||||
function getIssues() {
|
||||
const uiStrings = svgContext_.getUIStrings();
|
||||
// remove the selected outline before serializing
|
||||
svgContext_.getCanvas().clearSelection();
|
||||
@@ -675,20 +705,20 @@ function getIssues () {
|
||||
foreignObject: uiStrings.exportNoforeignObject,
|
||||
'[stroke-dasharray]': uiStrings.exportNoDashArray
|
||||
};
|
||||
const content = $(svgContext_.getSVGContent());
|
||||
const content = svgContext_.getSVGContent();
|
||||
|
||||
// Add font/text check if Canvas Text API is not implemented
|
||||
if (!('font' in $('<canvas>')[0].getContext('2d'))) {
|
||||
if (!('font' in document.querySelector('CANVAS').getContext('2d'))) {
|
||||
issueList.text = uiStrings.exportNoText;
|
||||
}
|
||||
|
||||
$.each(issueList, function (sel, descr) {
|
||||
if (content.find(sel).length) {
|
||||
for (const [sel, descr] of Object.entries(issueList)) {
|
||||
if (content.querySelectorAll(sel).length) {
|
||||
issueCodes.push(sel);
|
||||
issues.push(descr);
|
||||
}
|
||||
});
|
||||
return {issues, issueCodes};
|
||||
}
|
||||
return { issues, issueCodes };
|
||||
}
|
||||
/**
|
||||
* @typedef {PlainObject} module:svgcanvas.ImageExportedResults
|
||||
@@ -720,20 +750,25 @@ function getIssues () {
|
||||
export const rasterExport = async function (imgType, quality, exportWindowName, opts = {}) {
|
||||
const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG');
|
||||
const mimeType = 'image/' + type.toLowerCase();
|
||||
const {issues, issueCodes} = getIssues();
|
||||
const { issues, issueCodes } = getIssues();
|
||||
const svg = this.svgCanvasToString();
|
||||
|
||||
if (!$('#export_canvas').length) {
|
||||
$('<canvas>', {id: 'export_canvas'}).hide().appendTo('body');
|
||||
if (!$id('export_canvas')) {
|
||||
const canvasEx = document.createElement('CANVAS');
|
||||
canvasEx.id = 'export_canvas';
|
||||
canvasEx.style.display = 'none';
|
||||
document.body.appendChild(canvasEx);
|
||||
}
|
||||
const c = $('#export_canvas')[0];
|
||||
c.width = svgContext_.getCanvas().contentW;
|
||||
c.height = svgContext_.getCanvas().contentH;
|
||||
const c = $id('export_canvas');
|
||||
c.style.width = svgContext_.getCanvas().contentW + "px";
|
||||
c.style.height = svgContext_.getCanvas().contentH + "px";
|
||||
const canvg = svgContext_.getcanvg();
|
||||
await canvg(c, svg);
|
||||
const ctx = c.getContext('2d');
|
||||
const v = canvg.fromString(ctx, svg);
|
||||
// Render only first frame, ignoring animations.
|
||||
await v.render();
|
||||
// Todo: Make async/await utility in place of `toBlob`, so we can remove this constructor
|
||||
// eslint-disable-next-line promise/avoid-new
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
const dataURLType = type.toLowerCase();
|
||||
const datauri = quality
|
||||
? c.toDataURL('image/' + dataURLType, quality)
|
||||
@@ -743,7 +778,7 @@ export const rasterExport = async function (imgType, quality, exportWindowName,
|
||||
* Called when `bloburl` is available for export.
|
||||
* @returns {void}
|
||||
*/
|
||||
function done () {
|
||||
function done() {
|
||||
const obj = {
|
||||
datauri, bloburl, svg, issues, issueCodes, type: imgType,
|
||||
mimeType, quality, exportWindowName
|
||||
@@ -820,17 +855,17 @@ export const exportPDF = async (
|
||||
keywords: '',
|
||||
creator: '' */
|
||||
});
|
||||
const {issues, issueCodes} = getIssues();
|
||||
const { issues, issueCodes } = getIssues();
|
||||
// const svg = this.svgCanvasToString();
|
||||
// await doc.addSvgAsImage(svg)
|
||||
await doc.svg(svgContext_.getSVGContent(), {x: 0, y: 0, width: res.w, height: res.h});
|
||||
await doc.svg(svgContext_.getSVGContent(), { x: 0, y: 0, width: res.w, height: res.h });
|
||||
|
||||
// doc.output('save'); // Works to open in a new
|
||||
// window; todo: configure this and other export
|
||||
// options to optionally work in this manner as
|
||||
// opposed to opening a new tab
|
||||
outputType = outputType || 'dataurlstring';
|
||||
const obj = {issues, issueCodes, exportWindowName, outputType};
|
||||
const obj = { issues, issueCodes, exportWindowName, outputType };
|
||||
obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined);
|
||||
svgContext_.call('exportedPDF', obj);
|
||||
return obj;
|
||||
@@ -861,14 +896,14 @@ export const uniquifyElemsMethod = function (g) {
|
||||
// and we haven't tracked this ID yet
|
||||
if (!(n.id in ids)) {
|
||||
// add this id to our map
|
||||
ids[n.id] = {elem: null, attrs: [], hrefs: []};
|
||||
ids[n.id] = { elem: null, attrs: [], hrefs: [] };
|
||||
}
|
||||
ids[n.id].elem = n;
|
||||
}
|
||||
|
||||
// now search for all attributes on this element that might refer
|
||||
// to other elements
|
||||
$.each(svgContext_.getrefAttrs(), function (i, attr) {
|
||||
svgContext_.getrefAttrs().forEach(function(attr){
|
||||
const attrnode = n.getAttributeNode(attr);
|
||||
if (attrnode) {
|
||||
// the incoming file has been sanitized, so we should be able to safely just strip off the leading #
|
||||
@@ -877,7 +912,7 @@ export const uniquifyElemsMethod = function (g) {
|
||||
if (refid) {
|
||||
if (!(refid in ids)) {
|
||||
// add this id to our map
|
||||
ids[refid] = {elem: null, attrs: [], hrefs: []};
|
||||
ids[refid] = { elem: null, attrs: [], hrefs: [] };
|
||||
}
|
||||
ids[refid].attrs.push(attrnode);
|
||||
}
|
||||
@@ -892,7 +927,7 @@ export const uniquifyElemsMethod = function (g) {
|
||||
if (refid) {
|
||||
if (!(refid in ids)) {
|
||||
// add this id to our map
|
||||
ids[refid] = {elem: null, attrs: [], hrefs: []};
|
||||
ids[refid] = { elem: null, attrs: [], hrefs: [] };
|
||||
}
|
||||
ids[refid].hrefs.push(n);
|
||||
}
|
||||
@@ -903,7 +938,7 @@ export const uniquifyElemsMethod = function (g) {
|
||||
// in ids, we now have a map of ids, elements and attributes, let's re-identify
|
||||
for (const oldid in ids) {
|
||||
if (!oldid) { continue; }
|
||||
const {elem} = ids[oldid];
|
||||
const { elem } = ids[oldid];
|
||||
if (elem) {
|
||||
const newid = svgContext_.getCanvas().getNextId();
|
||||
|
||||
@@ -911,7 +946,7 @@ export const uniquifyElemsMethod = function (g) {
|
||||
elem.id = newid;
|
||||
|
||||
// remap all url() attributes
|
||||
const {attrs} = ids[oldid];
|
||||
const { attrs } = ids[oldid];
|
||||
let j = attrs.length;
|
||||
while (j--) {
|
||||
const attr = attrs[j];
|
||||
@@ -936,19 +971,22 @@ export const uniquifyElemsMethod = function (g) {
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setUseDataMethod = function (parent) {
|
||||
let elems = $(parent);
|
||||
let elems = parent;
|
||||
|
||||
if (parent.tagName !== 'use') {
|
||||
elems = elems.find('use');
|
||||
// elems = elems.find('use');
|
||||
elems = elems.querySelectorAll('use');
|
||||
}
|
||||
|
||||
elems.each(function () {
|
||||
const id = svgContext_.getCanvas().getHref(this).substr(1);
|
||||
Array.prototype.forEach.call(elems, function (el, _) {
|
||||
const dataStorage = svgContext_.getDataStorage();
|
||||
const id = svgContext_.getCanvas().getHref(el).substr(1);
|
||||
const refElem = svgContext_.getCanvas().getElem(id);
|
||||
if (!refElem) { return; }
|
||||
$(this).data('ref', refElem);
|
||||
dataStorage.put(el, 'ref', refElem);
|
||||
if (refElem.tagName === 'symbol' || refElem.tagName === 'svg') {
|
||||
$(this).data('symbol', refElem).data('ref', refElem);
|
||||
dataStorage.put(el, 'symbol', refElem);
|
||||
dataStorage.put(el, 'ref', refElem);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -990,18 +1028,20 @@ export const removeUnusedDefElemsMethod = function () {
|
||||
}
|
||||
}
|
||||
|
||||
const defelems = $(defs).find('linearGradient, radialGradient, filter, marker, svg, symbol');
|
||||
i = defelems.length;
|
||||
while (i--) {
|
||||
const defelem = defelems[i];
|
||||
const {id} = defelem;
|
||||
if (!defelemUses.includes(id)) {
|
||||
// Not found, so remove (but remember)
|
||||
svgContext_.setRemovedElements(id, defelem);
|
||||
defelem.remove();
|
||||
numRemoved++;
|
||||
Array.prototype.forEach.call(defs, function (def, i) {
|
||||
const defelems = def.querySelectorAll('linearGradient, radialGradient, filter, marker, svg, symbol');
|
||||
i = defelems.length;
|
||||
while (i--) {
|
||||
const defelem = defelems[i];
|
||||
const { id } = defelem;
|
||||
if (!defelemUses.includes(id)) {
|
||||
// Not found, so remove (but remember)
|
||||
svgContext_.setRemovedElements(id, defelem);
|
||||
defelem.remove();
|
||||
numRemoved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return numRemoved;
|
||||
};
|
||||
@@ -1012,20 +1052,19 @@ export const removeUnusedDefElemsMethod = function () {
|
||||
* @returns {void}
|
||||
*/
|
||||
export const convertGradientsMethod = function (elem) {
|
||||
let elems = $(elem).find('linearGradient, radialGradient');
|
||||
let elems = elem.querySelectorAll('linearGradient, radialGradient');
|
||||
if (!elems.length && isWebkit()) {
|
||||
// Bug in webkit prevents regular *Gradient selector search
|
||||
elems = $(elem).find('*').filter(function () {
|
||||
return (this.tagName.includes('Gradient'));
|
||||
elems = Array.prototype.filter.call(elem.querySelectorAll('*'), function (curThis) {
|
||||
return (curThis.tagName.includes('Gradient'));
|
||||
});
|
||||
}
|
||||
|
||||
elems.each(function () {
|
||||
const grad = this;
|
||||
if ($(grad).attr('gradientUnits') === 'userSpaceOnUse') {
|
||||
Array.prototype.forEach.call(elems, function (grad) {
|
||||
if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') {
|
||||
const svgcontent = svgContext_.getSVGContent();
|
||||
// TODO: Support more than one element with this ref by duplicating parent grad
|
||||
const fillStrokeElems = $(svgcontent).find('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
|
||||
const fillStrokeElems = svgcontent.querySelectorAll('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]');
|
||||
if (!fillStrokeElems.length) { return; }
|
||||
|
||||
// get object's bounding box
|
||||
@@ -1036,7 +1075,12 @@ export const convertGradientsMethod = function (elem) {
|
||||
if (!bb) { return; }
|
||||
|
||||
if (grad.tagName === 'linearGradient') {
|
||||
const gCoords = $(grad).attr(['x1', 'y1', 'x2', 'y2']);
|
||||
const gCoords = {
|
||||
x1: grad.getAttribute('x1'),
|
||||
y1: grad.getAttribute('y1'),
|
||||
x2: grad.getAttribute('x2'),
|
||||
y2: grad.getAttribute('y2'),
|
||||
};
|
||||
|
||||
// If has transform, convert
|
||||
const tlist = grad.gradientTransform.baseVal;
|
||||
@@ -1051,35 +1095,12 @@ export const convertGradientsMethod = function (elem) {
|
||||
gCoords.y2 = pt2.y;
|
||||
grad.removeAttribute('gradientTransform');
|
||||
}
|
||||
|
||||
$(grad).attr({
|
||||
x1: (gCoords.x1 - bb.x) / bb.width,
|
||||
y1: (gCoords.y1 - bb.y) / bb.height,
|
||||
x2: (gCoords.x2 - bb.x) / bb.width,
|
||||
y2: (gCoords.y2 - bb.y) / bb.height
|
||||
});
|
||||
grad.setAttribute('x1', (gCoords.x1 - bb.x) / bb.width);
|
||||
grad.setAttribute('y1', (gCoords.y1 - bb.y) / bb.height);
|
||||
grad.setAttribute('x2', (gCoords.x2 - bb.x) / bb.width);
|
||||
grad.setAttribute('y2', (gCoords.y2 - bb.y) / bb.height);
|
||||
grad.removeAttribute('gradientUnits');
|
||||
}
|
||||
// else {
|
||||
// Note: radialGradient elements cannot be easily converted
|
||||
// because userSpaceOnUse will keep circular gradients, while
|
||||
// objectBoundingBox will x/y scale the gradient according to
|
||||
// its bbox.
|
||||
//
|
||||
// For now we'll do nothing, though we should probably have
|
||||
// the gradient be updated as the element is moved, as
|
||||
// inkscape/illustrator do.
|
||||
//
|
||||
// const gCoords = $(grad).attr(['cx', 'cy', 'r']);
|
||||
//
|
||||
// $(grad).attr({
|
||||
// cx: (gCoords.cx - bb.x) / bb.width,
|
||||
// cy: (gCoords.cy - bb.y) / bb.height,
|
||||
// r: gCoords.r
|
||||
// });
|
||||
//
|
||||
// grad.removeAttribute('gradientUnits');
|
||||
// }
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {text2xml} from '../common/utilities.js';
|
||||
import {text2xml} from './utilities.js';
|
||||
|
||||
/**
|
||||
* @function module:svgcanvas.svgRootElement svgRootElement the svg node and its children.
|
||||
|
||||
393
src/svgcanvas/svgtransformlist.js
Normal file
393
src/svgcanvas/svgtransformlist.js
Normal file
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Partial polyfill of `SVGTransformList`
|
||||
* @module SVGTransformList
|
||||
*
|
||||
* @license MIT
|
||||
*
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {supportsNativeTransformLists} from '../common/browser.js';
|
||||
|
||||
const svgroot = document.createElementNS(NS.SVG, 'svg');
|
||||
|
||||
/**
|
||||
* Helper function to convert `SVGTransform` to a string.
|
||||
* @param {SVGTransform} xform
|
||||
* @returns {string}
|
||||
*/
|
||||
function transformToString (xform) {
|
||||
const m = xform.matrix;
|
||||
let text = '';
|
||||
switch (xform.type) {
|
||||
case 1: // MATRIX
|
||||
text = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')';
|
||||
break;
|
||||
case 2: // TRANSLATE
|
||||
text = 'translate(' + m.e + ',' + m.f + ')';
|
||||
break;
|
||||
case 3: // SCALE
|
||||
text = (m.a === m.d) ? `scale(${m.a})` : `scale(${m.a},${m.d})`;
|
||||
break;
|
||||
case 4: { // ROTATE
|
||||
let cx = 0;
|
||||
let cy = 0;
|
||||
// this prevents divide by zero
|
||||
if (xform.angle !== 0) {
|
||||
const K = 1 - m.a;
|
||||
cy = (K * m.f + m.b * m.e) / (K * K + m.b * m.b);
|
||||
cx = (m.e - m.b * cy) / K;
|
||||
}
|
||||
text = 'rotate(' + xform.angle + ' ' + cx + ',' + cy + ')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of SVGTransformList objects.
|
||||
*/
|
||||
let listMap_ = {};
|
||||
|
||||
/**
|
||||
* @interface module:SVGTransformList.SVGEditTransformList
|
||||
* @property {Integer} numberOfItems unsigned long
|
||||
*/
|
||||
/**
|
||||
* @function module:SVGTransformList.SVGEditTransformList#clear
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* @function module:SVGTransformList.SVGEditTransformList#initialize
|
||||
* @param {SVGTransform} newItem
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
|
||||
* @function module:SVGTransformList.SVGEditTransformList#getItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
|
||||
* @function module:SVGTransformList.SVGEditTransformList#insertItemBefore
|
||||
* @param {SVGTransform} newItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
|
||||
* @function module:SVGTransformList.SVGEditTransformList#replaceItem
|
||||
* @param {SVGTransform} newItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
|
||||
* @function module:SVGTransformList.SVGEditTransformList#removeItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* @function module:SVGTransformList.SVGEditTransformList#appendItem
|
||||
* @param {SVGTransform} newItem
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* NOT IMPLEMENTED.
|
||||
* @ignore
|
||||
* @function module:SVGTransformList.SVGEditTransformList#createSVGTransformFromMatrix
|
||||
* @param {SVGMatrix} matrix
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
/**
|
||||
* NOT IMPLEMENTED.
|
||||
* @ignore
|
||||
* @function module:SVGTransformList.SVGEditTransformList#consolidate
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
|
||||
/**
|
||||
* SVGTransformList implementation for Webkit.
|
||||
* These methods do not currently raise any exceptions.
|
||||
* These methods also do not check that transforms are being inserted. This is basically
|
||||
* implementing as much of SVGTransformList that we need to get the job done.
|
||||
* @implements {module:SVGTransformList.SVGEditTransformList}
|
||||
*/
|
||||
export class SVGTransformList {
|
||||
/**
|
||||
* @param {Element} elem
|
||||
* @returns {SVGTransformList}
|
||||
*/
|
||||
constructor (elem) {
|
||||
this._elem = elem || null;
|
||||
this._xforms = [];
|
||||
// TODO: how do we capture the undo-ability in the changed transform list?
|
||||
this._update = function () {
|
||||
let tstr = '';
|
||||
// /* const concatMatrix = */ svgroot.createSVGMatrix();
|
||||
for (let i = 0; i < this.numberOfItems; ++i) {
|
||||
const xform = this._list.getItem(i);
|
||||
tstr += transformToString(xform) + ' ';
|
||||
}
|
||||
this._elem.setAttribute('transform', tstr);
|
||||
};
|
||||
this._list = this;
|
||||
this._init = function () {
|
||||
// Transform attribute parser
|
||||
let str = this._elem.getAttribute('transform');
|
||||
if (!str) { return; }
|
||||
|
||||
// TODO: Add skew support in future
|
||||
const re = /\s*((scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
|
||||
// const re = /\s*(?<xform>(?:scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
|
||||
let m = true;
|
||||
while (m) {
|
||||
m = str.match(re);
|
||||
str = str.replace(re, '');
|
||||
if (m && m[1]) {
|
||||
const x = m[1];
|
||||
const bits = x.split(/\s*\(/);
|
||||
const name = bits[0];
|
||||
const valBits = bits[1].match(/\s*(.*?)\s*\)/);
|
||||
valBits[1] = valBits[1].replace(/(\d)-/g, '$1 -');
|
||||
const valArr = valBits[1].split(/[, ]+/);
|
||||
const letters = 'abcdef'.split('');
|
||||
/*
|
||||
if (m && m.groups.xform) {
|
||||
const x = m.groups.xform;
|
||||
const [name, bits] = x.split(/\s*\(/);
|
||||
const valBits = bits.match(/\s*(?<nonWhitespace>.*?)\s*\)/);
|
||||
valBits.groups.nonWhitespace = valBits.groups.nonWhitespace.replace(
|
||||
/(?<digit>\d)-/g, '$<digit> -'
|
||||
);
|
||||
const valArr = valBits.groups.nonWhitespace.split(/[, ]+/);
|
||||
const letters = [...'abcdef'];
|
||||
*/
|
||||
const mtx = svgroot.createSVGMatrix();
|
||||
Object.values(valArr).forEach(function (item, i) {
|
||||
valArr[i] = Number.parseFloat(item);
|
||||
if (name === 'matrix') {
|
||||
mtx[letters[i]] = valArr[i];
|
||||
}
|
||||
});
|
||||
const xform = svgroot.createSVGTransform();
|
||||
const fname = 'set' + name.charAt(0).toUpperCase() + name.slice(1);
|
||||
const values = name === 'matrix' ? [mtx] : valArr;
|
||||
|
||||
if (name === 'scale' && values.length === 1) {
|
||||
values.push(values[0]);
|
||||
} else if (name === 'translate' && values.length === 1) {
|
||||
values.push(0);
|
||||
} else if (name === 'rotate' && values.length === 1) {
|
||||
values.push(0, 0);
|
||||
}
|
||||
xform[fname](...values);
|
||||
this._list.appendItem(xform);
|
||||
}
|
||||
}
|
||||
};
|
||||
this._removeFromOtherLists = function (item) {
|
||||
if (item) {
|
||||
// Check if this transform is already in a transformlist, and
|
||||
// remove it if so.
|
||||
Object.values(listMap_).some((tl) => {
|
||||
for (let i = 0, len = tl._xforms.length; i < len; ++i) {
|
||||
if (tl._xforms[i] === item) {
|
||||
tl.removeItem(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.numberOfItems = 0;
|
||||
}
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
clear () {
|
||||
this.numberOfItems = 0;
|
||||
this._xforms = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVGTransform} newItem
|
||||
* @returns {void}
|
||||
*/
|
||||
initialize (newItem) {
|
||||
this.numberOfItems = 1;
|
||||
this._removeFromOtherLists(newItem);
|
||||
this._xforms = [newItem];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Integer} index unsigned long
|
||||
* @throws {Error}
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
getItem (index) {
|
||||
if (index < this.numberOfItems && index >= 0) {
|
||||
return this._xforms[index];
|
||||
}
|
||||
const err = new Error('DOMException with code=INDEX_SIZE_ERR');
|
||||
err.code = 1;
|
||||
throw err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVGTransform} newItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
insertItemBefore (newItem, index) {
|
||||
let retValue = null;
|
||||
if (index >= 0) {
|
||||
if (index < this.numberOfItems) {
|
||||
this._removeFromOtherLists(newItem);
|
||||
const newxforms = new Array(this.numberOfItems + 1);
|
||||
// TODO: use array copying and slicing
|
||||
let i;
|
||||
for (i = 0; i < index; ++i) {
|
||||
newxforms[i] = this._xforms[i];
|
||||
}
|
||||
newxforms[i] = newItem;
|
||||
for (let j = i + 1; i < this.numberOfItems; ++j, ++i) {
|
||||
newxforms[j] = this._xforms[i];
|
||||
}
|
||||
this.numberOfItems++;
|
||||
this._xforms = newxforms;
|
||||
retValue = newItem;
|
||||
this._list._update();
|
||||
} else {
|
||||
retValue = this._list.appendItem(newItem);
|
||||
}
|
||||
}
|
||||
return retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVGTransform} newItem
|
||||
* @param {Integer} index unsigned long
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
replaceItem (newItem, index) {
|
||||
let retValue = null;
|
||||
if (index < this.numberOfItems && index >= 0) {
|
||||
this._removeFromOtherLists(newItem);
|
||||
this._xforms[index] = newItem;
|
||||
retValue = newItem;
|
||||
this._list._update();
|
||||
}
|
||||
return retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Integer} index unsigned long
|
||||
* @throws {Error}
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
removeItem (index) {
|
||||
if (index < this.numberOfItems && index >= 0) {
|
||||
const retValue = this._xforms[index];
|
||||
const newxforms = new Array(this.numberOfItems - 1);
|
||||
let i;
|
||||
for (i = 0; i < index; ++i) {
|
||||
newxforms[i] = this._xforms[i];
|
||||
}
|
||||
for (let j = i; j < this.numberOfItems - 1; ++j, ++i) {
|
||||
newxforms[j] = this._xforms[i + 1];
|
||||
}
|
||||
this.numberOfItems--;
|
||||
this._xforms = newxforms;
|
||||
this._list._update();
|
||||
return retValue;
|
||||
}
|
||||
const err = new Error('DOMException with code=INDEX_SIZE_ERR');
|
||||
err.code = 1;
|
||||
throw err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVGTransform} newItem
|
||||
* @returns {SVGTransform}
|
||||
*/
|
||||
appendItem (newItem) {
|
||||
this._removeFromOtherLists(newItem);
|
||||
this._xforms.push(newItem);
|
||||
this.numberOfItems++;
|
||||
this._list._update();
|
||||
return newItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function module:SVGTransformList.resetListMap
|
||||
* @returns {void}
|
||||
*/
|
||||
export const resetListMap = function () {
|
||||
listMap_ = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes transforms of the given element from the map.
|
||||
* @function module:SVGTransformList.removeElementFromListMap
|
||||
* @param {Element} elem - a DOM Element
|
||||
* @returns {void}
|
||||
*/
|
||||
export let removeElementFromListMap = function (elem) {
|
||||
if (elem.id && listMap_[elem.id]) {
|
||||
delete listMap_[elem.id];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object that behaves like a `SVGTransformList` for the given DOM element.
|
||||
* @function module:SVGTransformList.getTransformList
|
||||
* @param {Element} elem - DOM element to get a transformlist from
|
||||
* @todo The polyfill should have `SVGAnimatedTransformList` and this should use it
|
||||
* @returns {SVGAnimatedTransformList|SVGTransformList}
|
||||
*/
|
||||
export const getTransformList = function (elem) {
|
||||
if (!supportsNativeTransformLists()) {
|
||||
const id = elem.id || 'temp';
|
||||
let t = listMap_[id];
|
||||
if (!t || id === 'temp') {
|
||||
listMap_[id] = new SVGTransformList(elem);
|
||||
listMap_[id]._init();
|
||||
t = listMap_[id];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
if (elem.transform) {
|
||||
return elem.transform.baseVal;
|
||||
}
|
||||
if (elem.gradientTransform) {
|
||||
return elem.gradientTransform.baseVal;
|
||||
}
|
||||
if (elem.patternTransform) {
|
||||
return elem.patternTransform.baseVal;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @callback module:SVGTransformList.removeElementFromListMap
|
||||
* @param {Element} elem
|
||||
* @returns {void}
|
||||
*/
|
||||
/**
|
||||
* Replace `removeElementFromListMap` for unit-testing.
|
||||
* @function module:SVGTransformList.changeRemoveElementFromListMap
|
||||
* @param {module:SVGTransformList.removeElementFromListMap} cb Passed a single argument `elem`
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
export const changeRemoveElementFromListMap = function (cb) {
|
||||
removeElementFromListMap = cb;
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
/* globals jQuery */
|
||||
/**
|
||||
* @module text-actions Tools for Text edit functions
|
||||
* @license MIT
|
||||
@@ -6,20 +5,18 @@
|
||||
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
||||
*/
|
||||
|
||||
import jQueryPluginSVG from '../common/jQuery.attr.js';
|
||||
|
||||
import {NS} from '../common/namespaces.js';
|
||||
import {
|
||||
transformPoint, getMatrix
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
import {
|
||||
assignAttributes, getElem, getBBox as utilsGetBBox
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
supportsGoodTextCharPos
|
||||
} from '../common/browser.js';
|
||||
|
||||
const $ = jQueryPluginSVG(jQuery);
|
||||
|
||||
let textActionsContext_ = null;
|
||||
|
||||
/**
|
||||
@@ -31,7 +28,6 @@ export const init = function (textActionsContext) {
|
||||
textActionsContext_ = textActionsContext;
|
||||
};
|
||||
|
||||
/* eslint-disable jsdoc/require-property */
|
||||
/**
|
||||
* Group: Text edit functions
|
||||
* Functions relating to editing text elements.
|
||||
@@ -39,7 +35,6 @@ export const init = function (textActionsContext) {
|
||||
* @memberof module:svgcanvas.SvgCanvas#
|
||||
*/
|
||||
export const textActionsMethod = (function () {
|
||||
/* eslint-enable jsdoc/require-property */
|
||||
let curtext;
|
||||
let textinput;
|
||||
let cursor;
|
||||
@@ -58,7 +53,7 @@ export const textActionsMethod = (function () {
|
||||
*/
|
||||
function setCursor (index) {
|
||||
const empty = (textinput.value === '');
|
||||
$(textinput).focus();
|
||||
textinput.focus();
|
||||
|
||||
if (!arguments.length) {
|
||||
if (empty) {
|
||||
@@ -81,7 +76,7 @@ export const textActionsMethod = (function () {
|
||||
stroke: '#333',
|
||||
'stroke-width': 1
|
||||
});
|
||||
cursor = getElem('selectorParentGroup').appendChild(cursor);
|
||||
getElem('selectorParentGroup').append(cursor);
|
||||
}
|
||||
|
||||
if (!blinker) {
|
||||
@@ -263,15 +258,6 @@ export const textActionsMethod = (function () {
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
// Not currently in use
|
||||
function hideCursor () {
|
||||
if (cursor) {
|
||||
cursor.setAttribute('visibility', 'hidden');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Event} evt
|
||||
@@ -279,7 +265,7 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
*/
|
||||
function selectAll (evt) {
|
||||
setSelection(0, curtext.textContent.length);
|
||||
$(this).unbind(evt);
|
||||
evt.target.removeEventListener('click', selectAll);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,9 +289,10 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
setSelection(first, last);
|
||||
|
||||
// Set tripleclick
|
||||
$(evt.target).click(selectAll);
|
||||
evt.target.addEventListener('click', selectAll);
|
||||
|
||||
setTimeout(function () {
|
||||
$(evt.target).unbind('click', selectAll);
|
||||
evt.target.removeEventListener('click', selectAll);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
@@ -402,7 +389,7 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
|
||||
textActionsContext_.getCanvas().textActions.init();
|
||||
|
||||
$(curtext).css('cursor', 'text');
|
||||
curtext.style.cursor = 'text';
|
||||
|
||||
// if (supportsEditableText()) {
|
||||
// curtext.setAttribute('editable', 'simple');
|
||||
@@ -429,13 +416,13 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
textActionsContext_.setCurrentMode('select');
|
||||
clearInterval(blinker);
|
||||
blinker = null;
|
||||
if (selblock) { $(selblock).attr('display', 'none'); }
|
||||
if (cursor) { $(cursor).attr('visibility', 'hidden'); }
|
||||
$(curtext).css('cursor', 'move');
|
||||
if (selblock) { selblock.setAttribute('display', 'none'); }
|
||||
if (cursor) { cursor.setAttribute('visibility', 'hidden'); }
|
||||
curtext.style.cursor = 'move';
|
||||
|
||||
if (selectElem) {
|
||||
textActionsContext_.getCanvas().clearSelection();
|
||||
$(curtext).css('cursor', 'move');
|
||||
curtext.style.cursor = 'move';
|
||||
|
||||
textActionsContext_.call('selected', [curtext]);
|
||||
textActionsContext_.getCanvas().addToSelection([curtext], true);
|
||||
@@ -445,7 +432,7 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
textActionsContext_.getCanvas().deleteSelectedElements();
|
||||
}
|
||||
|
||||
$(textinput).blur();
|
||||
textinput.blur();
|
||||
|
||||
curtext = false;
|
||||
|
||||
@@ -459,7 +446,6 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
*/
|
||||
setInputElem (elem) {
|
||||
textinput = elem;
|
||||
// $(textinput).blur(hideCursor);
|
||||
},
|
||||
/**
|
||||
* @returns {void}
|
||||
@@ -470,10 +456,10 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Element} inputElem Not in use
|
||||
* @param {Element} _inputElem Not in use
|
||||
* @returns {void}
|
||||
*/
|
||||
init (inputElem) {
|
||||
init (_inputElem) {
|
||||
if (!curtext) { return; }
|
||||
let i, end;
|
||||
// if (supportsEditableText()) {
|
||||
@@ -501,7 +487,8 @@ cursor.setAttribute('visibility', 'hidden');
|
||||
chardata.length = len;
|
||||
textinput.focus();
|
||||
|
||||
$(curtext).unbind('dblclick', selectWord).dblclick(selectWord);
|
||||
curtext.removeEventListener("dblclick", selectWord);
|
||||
curtext.addEventListener("dblclick", selectWord);
|
||||
|
||||
if (!len) {
|
||||
end = {x: textbb.x + (textbb.width / 2), width: 0};
|
||||
|
||||
@@ -8,16 +8,16 @@ import * as draw from './draw.js';
|
||||
import * as hstry from './history.js';
|
||||
import {
|
||||
getRotationAngle, getBBox as utilsGetBBox, isNullish, setHref, getStrokedBBoxDefaultVisible
|
||||
} from '../common/utilities.js';
|
||||
} from './utilities.js';
|
||||
import {
|
||||
isGecko
|
||||
} from '../common/browser.js';
|
||||
import {
|
||||
transformPoint, transformListToTransform
|
||||
} from '../common/math.js';
|
||||
} from './math.js';
|
||||
import {
|
||||
getTransformList
|
||||
} from '../common/svgtransformlist.js';
|
||||
} from './svgtransformlist.js';
|
||||
|
||||
const {
|
||||
UndoManager, HistoryEventTypes
|
||||
@@ -188,7 +188,13 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
|
||||
// }
|
||||
} else if (attr === '#href') {
|
||||
setHref(elem, newValue);
|
||||
} else { elem.setAttribute(attr, newValue); }
|
||||
} else if (newValue) {
|
||||
elem.setAttribute(attr, newValue);
|
||||
} else if (typeof newValue === 'number') {
|
||||
elem.setAttribute(attr, newValue);
|
||||
} else {
|
||||
elem.removeAttribute(attr);
|
||||
}
|
||||
|
||||
// Go into "select" mode for text changes
|
||||
// NOTE: Important that this happens AFTER elem.setAttribute() or else attributes like
|
||||
@@ -204,12 +210,11 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
|
||||
|
||||
// Use the Firefox ffClone hack for text elements with gradients or
|
||||
// where other text attributes are changed.
|
||||
if (isGecko() && elem.nodeName === 'text' && (/rotate/).test(elem.getAttribute('transform'))) {
|
||||
if (
|
||||
String(newValue).startsWith('url') || (['font-size', 'font-family', 'x', 'y'].includes(attr) && elem.textContent)
|
||||
) {
|
||||
elem = ffClone(elem);
|
||||
}
|
||||
if (isGecko() &&
|
||||
elem.nodeName === 'text' &&
|
||||
(/rotate/).test(elem.getAttribute('transform')) &&
|
||||
(String(newValue).startsWith('url') || (['font-size', 'font-family', 'x', 'y'].includes(attr) && elem.textContent))) {
|
||||
elem = ffClone(elem);
|
||||
}
|
||||
// Timeout needed for Opera & Firefox
|
||||
// codedread: it is now possible for this function to be called with elements
|
||||
|
||||
1368
src/svgcanvas/utilities.js
Normal file
1368
src/svgcanvas/utilities.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user