move to standard linter for simpler configuration

This commit is contained in:
JFH
2021-12-28 11:02:29 -03:00
parent 258e2bd6a1
commit fdcfc8a253
251 changed files with 19760 additions and 19935 deletions

View File

@@ -5,7 +5,7 @@
* @copyright 2011 Jeff Schiller
*/
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:blur.init
@@ -13,8 +13,8 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
};
svgCanvas = canvas
}
/**
* Sets the `stdDeviation` blur value on the selected element without being undoable.
@@ -23,41 +23,41 @@ export const init = function (canvas) {
* @returns {void}
*/
export const setBlurNoUndo = function (val) {
const selectedElements = svgCanvas.getSelectedElements();
const selectedElements = svgCanvas.getSelectedElements()
if (!svgCanvas.getFilter()) {
svgCanvas.setBlur(val);
return;
svgCanvas.setBlur(val)
return
}
if (val === 0) {
// Don't change the StdDev, as that will hide the element.
// Instead, just remove the value for "filter"
svgCanvas.changeSelectedAttributeNoUndoMethod('filter', '');
svgCanvas.setFilterHidden(true);
svgCanvas.changeSelectedAttributeNoUndoMethod('filter', '')
svgCanvas.setFilterHidden(true)
} else {
const elem = selectedElements[0];
const elem = selectedElements[0]
if (svgCanvas.getFilterHidden()) {
svgCanvas.changeSelectedAttributeNoUndoMethod('filter', 'url(#' + elem.id + '_blur)');
svgCanvas.changeSelectedAttributeNoUndoMethod('filter', 'url(#' + elem.id + '_blur)')
}
if (svgCanvas.isWebkit()) {
elem.removeAttribute('filter');
elem.setAttribute('filter', 'url(#' + elem.id + '_blur)');
elem.removeAttribute('filter')
elem.setAttribute('filter', 'url(#' + elem.id + '_blur)')
}
const filter = svgCanvas.getFilter();
svgCanvas.changeSelectedAttributeNoUndoMethod('stdDeviation', val, [ filter.firstChild ]);
svgCanvas.setBlurOffsets(filter, val);
const filter = svgCanvas.getFilter()
svgCanvas.changeSelectedAttributeNoUndoMethod('stdDeviation', val, [filter.firstChild])
svgCanvas.setBlurOffsets(filter, val)
}
};
}
/**
*
* @returns {void}
*/
function finishChange () {
const bCmd = svgCanvas.undoMgr.finishUndoableChange();
svgCanvas.getCurCommand().addSubCommand(bCmd);
svgCanvas.addCommandToHistory(svgCanvas.getCurCommand());
svgCanvas.setCurCommand(null);
svgCanvas.setFilter(null);
const bCmd = svgCanvas.undoMgr.finishUndoableChange()
svgCanvas.getCurCommand().addSubCommand(bCmd)
svgCanvas.addCommandToHistory(svgCanvas.getCurCommand())
svgCanvas.setCurCommand(null)
svgCanvas.setFilter(null)
}
/**
@@ -76,15 +76,15 @@ export const setBlurOffsets = function (filterElem, stdDev) {
y: '-50%',
width: '200%',
height: '200%'
}, 100);
}, 100)
// Removing these attributes hides text in Chrome (see Issue 579)
} else if (!svgCanvas.isWebkit()) {
filterElem.removeAttribute('x');
filterElem.removeAttribute('y');
filterElem.removeAttribute('width');
filterElem.removeAttribute('height');
filterElem.removeAttribute('x')
filterElem.removeAttribute('y')
filterElem.removeAttribute('width')
filterElem.removeAttribute('height')
}
};
}
/**
* Adds/updates the blur filter to the selected element.
@@ -96,64 +96,66 @@ export const setBlurOffsets = function (filterElem, stdDev) {
export const setBlur = function (val, complete) {
const {
InsertElementCommand, ChangeElementCommand, BatchCommand
} = svgCanvas.history;
} = svgCanvas.history
const selectedElements = svgCanvas.getSelectedElements();
const selectedElements = svgCanvas.getSelectedElements()
if (svgCanvas.getCurCommand()) {
finishChange();
return;
finishChange()
return
}
// Looks for associated blur, creates one if not found
const elem = selectedElements[0];
const elemId = elem.id;
svgCanvas.setFilter(svgCanvas.getElem(elemId + '_blur'));
const elem = selectedElements[0]
const elemId = elem.id
svgCanvas.setFilter(svgCanvas.getElem(elemId + '_blur'))
val -= 0;
val -= 0
const batchCmd = new BatchCommand();
const batchCmd = new BatchCommand()
// Blur found!
if (svgCanvas.getFilter()) {
if (val === 0) {
svgCanvas.setFilter(null);
svgCanvas.setFilter(null)
}
} else {
// Not found, so create
const newblur = svgCanvas.addSVGElemensFromJson({ element: 'feGaussianBlur',
const newblur = svgCanvas.addSVGElemensFromJson({
element: 'feGaussianBlur',
attr: {
in: 'SourceGraphic',
stdDeviation: val
}
});
})
svgCanvas.setFilter(svgCanvas.addSVGElemensFromJson({ element: 'filter',
svgCanvas.setFilter(svgCanvas.addSVGElemensFromJson({
element: 'filter',
attr: {
id: elemId + '_blur'
}
}));
svgCanvas.getFilter().append(newblur);
svgCanvas.findDefs().append(svgCanvas.getFilter());
}))
svgCanvas.getFilter().append(newblur)
svgCanvas.findDefs().append(svgCanvas.getFilter())
batchCmd.addSubCommand(new InsertElementCommand(svgCanvas.getFilter()));
batchCmd.addSubCommand(new InsertElementCommand(svgCanvas.getFilter()))
}
const changes = { filter: elem.getAttribute('filter') };
const changes = { filter: elem.getAttribute('filter') }
if (val === 0) {
elem.removeAttribute('filter');
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
return;
elem.removeAttribute('filter')
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
return
}
svgCanvas.changeSelectedAttribute('filter', 'url(#' + elemId + '_blur)');
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes));
svgCanvas.setBlurOffsets(svgCanvas.getFilter(), val);
const filter = svgCanvas.getFilter();
svgCanvas.setCurCommand(batchCmd);
svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [ filter ? filter.firstChild : null ]);
svgCanvas.changeSelectedAttribute('filter', 'url(#' + elemId + '_blur)')
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
svgCanvas.setBlurOffsets(svgCanvas.getFilter(), val)
const filter = svgCanvas.getFilter()
svgCanvas.setCurCommand(batchCmd)
svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [filter ? filter.firstChild : null])
if (complete) {
svgCanvas.setBlurNoUndo(val);
finishChange();
svgCanvas.setBlurNoUndo(val)
finishChange()
}
};
}

View File

@@ -4,9 +4,9 @@
* @license MIT
* @copyright 2011 Jeff Schiller
*/
import { NS } from './namespaces.js';
import { NS } from './namespaces.js'
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:clear.init
@@ -14,31 +14,30 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = (canvas) => {
svgCanvas = canvas;
};
svgCanvas = canvas
}
export const clearSvgContentElementInit = () => {
const curConfig = svgCanvas.getCurConfig();
const { dimensions } = curConfig;
const el = svgCanvas.getSvgContent();
const curConfig = svgCanvas.getCurConfig()
const { dimensions } = curConfig
const el = svgCanvas.getSvgContent()
// empty
while(el.firstChild)
el.removeChild(el.firstChild);
while (el.firstChild) { el.removeChild(el.firstChild) }
// TODO: Clear out all other attributes first?
const pel = svgCanvas.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);
const pel = svgCanvas.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 = svgCanvas.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit');
svgCanvas.getSvgContent().append(comment);
};
const comment = svgCanvas.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit')
svgCanvas.getSvgContent().append(comment)
}

View File

@@ -6,16 +6,16 @@
import {
snapToGrid, assignAttributes, getBBox, getRefElem, findDefs
} from './utilities.js';
} from './utilities.js'
import {
transformPoint, transformListToTransform, matrixMultiply, transformBox
} from './math.js';
} from './math.js'
// this is how we map paths to our preferred relative segment types
const pathMap = [
0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
];
]
/**
* @interface module:coords.EditorContext
@@ -33,7 +33,7 @@ const pathMap = [
* @returns {SVGSVGElement}
*/
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:coords.init
@@ -41,8 +41,8 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
};
svgCanvas = canvas
}
/**
* Applies coordinate changes to an element based on the given matrix.
@@ -50,253 +50,253 @@ export const init = function (canvas) {
* @type {module:path.EditorContext#remapElement}
*/
export const remapElement = function (selected, changes, m) {
const remap = (x, y) => transformPoint(x, y, m);
const scalew = (w) => m.a * w;
const scaleh = (h) => m.d * h;
const doSnapping = svgCanvas.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg';
const remap = (x, y) => transformPoint(x, y, m)
const scalew = (w) => m.a * w
const scaleh = (h) => m.d * h
const doSnapping = svgCanvas.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg'
const finishUp = () => {
if (doSnapping) {
Object.entries(changes).forEach(([ o, value ]) => {
changes[o] = snapToGrid(value);
});
Object.entries(changes).forEach(([o, value]) => {
changes[o] = snapToGrid(value)
})
}
assignAttributes(selected, changes, 1000, true);
};
assignAttributes(selected, changes, 1000, true)
}
const box = getBBox(selected);
[ 'fill', 'stroke' ].forEach( (type) => {
const attrVal = selected.getAttribute(type);
['fill', 'stroke'].forEach((type) => {
const attrVal = selected.getAttribute(type)
if (attrVal && attrVal.startsWith('url(') && (m.a < 0 || m.d < 0)) {
const grad = getRefElem(attrVal);
const newgrad = grad.cloneNode(true);
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));
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));
const y1 = newgrad.getAttribute('y1')
const y2 = newgrad.getAttribute('y2')
newgrad.setAttribute('y1', -(y1 - 1))
newgrad.setAttribute('y2', -(y2 - 1))
}
newgrad.id = svgCanvas.getDrawing().getNextId();
findDefs().append(newgrad);
selected.setAttribute(type, 'url(#' + newgrad.id + ')');
newgrad.id = svgCanvas.getDrawing().getNextId()
findDefs().append(newgrad)
selected.setAttribute(type, 'url(#' + newgrad.id + ')')
}
});
})
const elName = selected.tagName;
const elName = selected.tagName
if (elName === 'g' || elName === 'text' || elName === 'tspan' || elName === 'use') {
// if it was a translate, then just update x,y
if (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && (m.e !== 0 || m.f !== 0)) {
// [T][M] = [M][T']
// therefore [T'] = [M_inv][T][M]
const existing = transformListToTransform(selected).matrix;
const tNew = matrixMultiply(existing.inverse(), m, existing);
changes.x = Number.parseFloat(changes.x) + tNew.e;
changes.y = Number.parseFloat(changes.y) + tNew.f;
const existing = transformListToTransform(selected).matrix
const tNew = matrixMultiply(existing.inverse(), m, existing)
changes.x = Number.parseFloat(changes.x) + tNew.e
changes.y = Number.parseFloat(changes.y) + tNew.f
} else {
// we just absorb all matrices into the element and don't do any remapping
const chlist = selected.transform.baseVal;
const mt = svgCanvas.getSvgRoot().createSVGTransform();
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m));
chlist.clear();
chlist.appendItem(mt);
const chlist = selected.transform.baseVal
const mt = svgCanvas.getSvgRoot().createSVGTransform()
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
chlist.clear()
chlist.appendItem(mt)
}
}
// 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': {
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)) {
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
// Convert to matrix
const chlist = selected.transform.baseVal;
const mt = svgCanvas.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; const 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 dataStorage = svgCanvas.getDataStorage();
const gsvg = dataStorage.get(selected, 'gsvg');
if (gsvg) {
assignAttributes(gsvg, changes, 1000, true);
}
break;
} case 'polyline':
case 'polygon': {
changes.points.forEach( (pt) => {
const { x, y } = remap(pt.x, pt.y);
pt.x = x;
pt.y = y;
});
// const len = changes.points.length;
let pstr = '';
changes.points.forEach( (pt) => {
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];
let currentpt;
if (len > 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
const 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);
const chlist = selected.transform.baseVal
const mt = svgCanvas.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; const h = tbox.bl.y - tbox.tl.y
changes.r = Math.min(w / 2, h / 2)
let dstr = '';
changes.d.forEach( (seg) => {
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 = svgCanvas.getDataStorage()
const gsvg = dataStorage.get(selected, 'gsvg')
if (gsvg) {
assignAttributes(gsvg, changes, 1000, true)
}
});
break
} case 'polyline':
case 'polygon': {
changes.points.forEach((pt) => {
const { x, y } = remap(pt.x, pt.y)
pt.x = x
pt.y = y
})
selected.setAttribute('d', dstr);
break;
// const len = changes.points.length;
let pstr = ''
changes.points.forEach((pt) => {
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]
let currentpt
if (len > 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
const 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 = ''
changes.d.forEach((seg) => {
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
}
}
}
};
}

View File

@@ -1,4 +1,4 @@
import { preventClickDefault } from './utilities.js';
import { preventClickDefault } from './utilities.js'
/**
* Create a clone of an element, updating its ID and its children's IDs when needed.
@@ -9,37 +9,37 @@ import { preventClickDefault } from './utilities.js';
*/
export const copyElem = function (el, getNextId) {
// manually create a copy of the element
const newEl = document.createElementNS(el.namespaceURI, el.nodeName);
const newEl = document.createElementNS(el.namespaceURI, el.nodeName)
Object.values(el.attributes).forEach((attr) => {
newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value);
});
newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
})
// set the copied element's new id
newEl.removeAttribute('id');
newEl.id = getNextId();
newEl.removeAttribute('id')
newEl.id = getNextId()
// now create copies of all children
el.childNodes.forEach(function(child){
el.childNodes.forEach(function (child) {
switch (child.nodeType) {
case 1: // element node
newEl.append(copyElem(child, getNextId));
break;
case 3: // text node
newEl.textContent = child.nodeValue;
break;
default:
break;
case 1: // element node
newEl.append(copyElem(child, getNextId))
break
case 3: // text node
newEl.textContent = child.nodeValue
break
default:
break
}
});
})
if (el.dataset.gsvg) {
newEl.dataset.gsvg = newEl.firstChild;
newEl.dataset.gsvg = newEl.firstChild
} else if (el.dataset.symbol) {
const ref = el.dataset.symbol;
newEl.dataset.ref = ref;
newEl.dataset.symbol = ref;
const ref = el.dataset.symbol
newEl.dataset.ref = ref
newEl.dataset.symbol = ref
} else if (newEl.tagName === 'image') {
preventClickDefault(newEl);
preventClickDefault(newEl)
}
return newEl;
};
return newEl
}

View File

@@ -6,23 +6,23 @@ const dataStorage = {
_storage: new WeakMap(),
put: function (element, key, obj) {
if (!this._storage.has(element)) {
this._storage.set(element, new Map());
this._storage.set(element, new Map())
}
this._storage.get(element).set(key, obj);
this._storage.get(element).set(key, obj)
},
get: function (element, key) {
return this._storage.get(element)?.get(key);
return this._storage.get(element)?.get(key)
},
has: function (element, key) {
return this._storage.has(element) && this._storage.get(element).has(key);
return this._storage.has(element) && this._storage.get(element).has(key)
},
remove: function (element, key) {
const ret = this._storage.get(element).delete(key);
const ret = this._storage.get(element).delete(key)
if (!this._storage.get(element).size === 0) {
this._storage.delete(element);
this._storage.delete(element)
}
return ret;
return ret
}
};
}
export default dataStorage;
export default dataStorage

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
* @copyright 2010 Jeff Schiller
*/
import { getHref, setHref, getRotationAngle, isNullish, getBBox } from './utilities.js';
import { getHref, setHref, getRotationAngle, isNullish, getBBox } from './utilities.js'
/**
* Group: Undo/Redo history management.
@@ -16,7 +16,7 @@ export const HistoryEventTypes = {
AFTER_APPLY: 'after_apply',
BEFORE_UNAPPLY: 'before_unapply',
AFTER_UNAPPLY: 'after_unapply'
};
}
/**
* Base class for commands.
@@ -26,17 +26,18 @@ export class Command {
* @returns {string}
*/
getText () {
return this.text;
return this.text
}
/**
* @param {module:history.HistoryEventHandler} handler
* @param {callback} applyFunction
* @returns {void}
*/
apply (handler, applyFunction) {
handler && handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this);
applyFunction(handler);
handler && handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this);
handler && handler.handleHistoryEvent(HistoryEventTypes.BEFORE_APPLY, this)
applyFunction(handler)
handler && handler.handleHistoryEvent(HistoryEventTypes.AFTER_APPLY, this)
}
/**
@@ -45,9 +46,9 @@ export class Command {
* @returns {void}
*/
unapply (handler, unapplyFunction) {
handler && handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this);
unapplyFunction();
handler && handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this);
handler && handler.handleHistoryEvent(HistoryEventTypes.BEFORE_UNAPPLY, this)
unapplyFunction()
handler && handler.handleHistoryEvent(HistoryEventTypes.AFTER_UNAPPLY, this)
}
/**
@@ -55,14 +56,14 @@ export class Command {
* This function needs to be surcharged if multiple elements are returned.
*/
elements () {
return [ this.elem ];
return [this.elem]
}
/**
* @returns {string} String with element associated with this command
*/
type () {
return this.constructor.name;
return this.constructor.name
}
}
@@ -137,13 +138,13 @@ export class MoveElementCommand extends Command {
* @param {string} [text] - An optional string visible to user related to this change
*/
constructor (elem, oldNextSibling, oldParent, text) {
super();
this.elem = elem;
this.text = text ? ('Move ' + elem.tagName + ' to ' + text) : ('Move ' + elem.tagName);
this.oldNextSibling = oldNextSibling;
this.oldParent = oldParent;
this.newNextSibling = elem.nextSibling;
this.newParent = elem.parentNode;
super()
this.elem = elem
this.text = text ? ('Move ' + elem.tagName + ' to ' + text) : ('Move ' + elem.tagName)
this.oldNextSibling = oldNextSibling
this.oldParent = oldParent
this.newNextSibling = elem.nextSibling
this.newParent = elem.parentNode
}
/**
@@ -154,8 +155,8 @@ export class MoveElementCommand extends Command {
*/
apply (handler) {
super.apply(handler, () => {
this.elem = this.newParent.insertBefore(this.elem, this.newNextSibling);
});
this.elem = this.newParent.insertBefore(this.elem, this.newNextSibling)
})
}
/**
@@ -166,8 +167,8 @@ export class MoveElementCommand extends Command {
*/
unapply (handler) {
super.unapply(handler, () => {
this.elem = this.oldParent.insertBefore(this.elem, this.oldNextSibling);
});
this.elem = this.oldParent.insertBefore(this.elem, this.oldNextSibling)
})
}
}
@@ -181,11 +182,11 @@ export class InsertElementCommand extends Command {
* @param {string} text - An optional string visible to user related to this change
*/
constructor (elem, text) {
super();
this.elem = elem;
this.text = text || ('Create ' + elem.tagName);
this.parent = elem.parentNode;
this.nextSibling = this.elem.nextSibling;
super()
this.elem = elem
this.text = text || ('Create ' + elem.tagName)
this.parent = elem.parentNode
this.nextSibling = this.elem.nextSibling
}
/**
@@ -196,8 +197,8 @@ export class InsertElementCommand extends Command {
*/
apply (handler) {
super.apply(handler, () => {
this.elem = this.parent.insertBefore(this.elem, this.nextSibling);
});
this.elem = this.parent.insertBefore(this.elem, this.nextSibling)
})
}
/**
@@ -208,13 +209,12 @@ export class InsertElementCommand extends Command {
*/
unapply (handler) {
super.unapply(handler, () => {
this.parent = this.elem.parentNode;
this.elem.remove();
});
this.parent = this.elem.parentNode
this.elem.remove()
})
}
}
/**
* History command for an element removed from the DOM.
* @implements {module:history.HistoryCommand}
@@ -227,11 +227,11 @@ export class RemoveElementCommand extends Command {
* @param {string} [text] - An optional string visible to user related to this change
*/
constructor (elem, oldNextSibling, oldParent, text) {
super();
this.elem = elem;
this.text = text || ('Delete ' + elem.tagName);
this.nextSibling = oldNextSibling;
this.parent = oldParent;
super()
this.elem = elem
this.text = text || ('Delete ' + elem.tagName)
this.nextSibling = oldNextSibling
this.parent = oldParent
}
/**
@@ -242,9 +242,9 @@ export class RemoveElementCommand extends Command {
*/
apply (handler) {
super.apply(handler, () => {
this.parent = this.elem.parentNode;
this.elem.remove();
});
this.parent = this.elem.parentNode
this.elem.remove()
})
}
/**
@@ -256,10 +256,10 @@ export class RemoveElementCommand extends Command {
unapply (handler) {
super.unapply(handler, () => {
if (isNullish(this.nextSibling) && window.console) {
console.error('Reference element was lost');
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`
});
this.parent.insertBefore(this.elem, this.nextSibling) // Don't use `before` or `prepend` as `this.nextSibling` may be `null`
})
}
}
@@ -282,18 +282,18 @@ export class ChangeElementCommand extends Command {
* @param {string} text - An optional string visible to user related to this change
*/
constructor (elem, attrs, text) {
super();
this.elem = elem;
this.text = text ? ('Change ' + elem.tagName + ' ' + text) : ('Change ' + elem.tagName);
this.newValues = {};
this.oldValues = attrs;
super()
this.elem = elem
this.text = text ? ('Change ' + elem.tagName + ' ' + text) : ('Change ' + elem.tagName)
this.newValues = {}
this.oldValues = attrs
for (const attr in attrs) {
if (attr === '#text') {
this.newValues[attr] = (elem) ? elem.textContent : '';
this.newValues[attr] = (elem) ? elem.textContent : ''
} else if (attr === '#href') {
this.newValues[attr] = getHref(elem);
this.newValues[attr] = getHref(elem)
} else {
this.newValues[attr] = elem.getAttribute(attr);
this.newValues[attr] = elem.getAttribute(attr)
}
}
}
@@ -306,40 +306,40 @@ export class ChangeElementCommand extends Command {
*/
apply (handler) {
super.apply(handler, () => {
let bChangedTransform = false;
Object.entries(this.newValues).forEach(([ attr, value ]) => {
let bChangedTransform = false
Object.entries(this.newValues).forEach(([attr, value]) => {
if (value) {
if (attr === '#text') {
this.elem.textContent = value;
this.elem.textContent = value
} else if (attr === '#href') {
setHref(this.elem, value);
setHref(this.elem, value)
} else {
this.elem.setAttribute(attr, value);
this.elem.setAttribute(attr, value)
}
} else if (attr === '#text') {
this.elem.textContent = '';
this.elem.textContent = ''
} else {
this.elem.setAttribute(attr, '');
this.elem.removeAttribute(attr);
this.elem.setAttribute(attr, '')
this.elem.removeAttribute(attr)
}
if (attr === 'transform') { bChangedTransform = true; }
});
if (attr === 'transform') { bChangedTransform = true }
})
// relocate rotational transform, if necessary
if (!bChangedTransform) {
const angle = getRotationAngle(this.elem);
const angle = getRotationAngle(this.elem)
if (angle) {
const bbox = getBBox(this.elem);
const cx = bbox.x + bbox.width / 2;
const cy = bbox.y + bbox.height / 2;
const rotate = [ 'rotate(', angle, ' ', cx, ',', cy, ')' ].join('');
const bbox = getBBox(this.elem)
const cx = bbox.x + bbox.width / 2
const cy = bbox.y + bbox.height / 2
const rotate = ['rotate(', angle, ' ', cx, ',', cy, ')'].join('')
if (rotate !== this.elem.getAttribute('transform')) {
this.elem.setAttribute('transform', rotate);
this.elem.setAttribute('transform', rotate)
}
}
}
});
})
}
/**
@@ -350,37 +350,37 @@ export class ChangeElementCommand extends Command {
*/
unapply (handler) {
super.unapply(handler, () => {
let bChangedTransform = false;
Object.entries(this.oldValues).forEach(([ attr, value ]) => {
let bChangedTransform = false
Object.entries(this.oldValues).forEach(([attr, value]) => {
if (value) {
if (attr === '#text') {
this.elem.textContent = value;
this.elem.textContent = value
} else if (attr === '#href') {
setHref(this.elem, value);
setHref(this.elem, value)
} else {
this.elem.setAttribute(attr, value);
this.elem.setAttribute(attr, value)
}
} else if (attr === '#text') {
this.elem.textContent = '';
this.elem.textContent = ''
} else {
this.elem.removeAttribute(attr);
this.elem.removeAttribute(attr)
}
if (attr === 'transform') { bChangedTransform = true; }
});
if (attr === 'transform') { bChangedTransform = true }
})
// relocate rotational transform, if necessary
if (!bChangedTransform) {
const angle = getRotationAngle(this.elem);
const angle = getRotationAngle(this.elem)
if (angle) {
const bbox = getBBox(this.elem);
const cx = bbox.x + bbox.width / 2;
const cy = bbox.y + bbox.height / 2;
const rotate = [ 'rotate(', angle, ' ', cx, ',', cy, ')' ].join('');
const bbox = getBBox(this.elem)
const cx = bbox.x + bbox.width / 2
const cy = bbox.y + bbox.height / 2
const rotate = ['rotate(', angle, ' ', cx, ',', cy, ')'].join('')
if (rotate !== this.elem.getAttribute('transform')) {
this.elem.setAttribute('transform', rotate);
this.elem.setAttribute('transform', rotate)
}
}
}
});
})
}
}
@@ -397,9 +397,9 @@ export class BatchCommand extends Command {
* @param {string} [text] - An optional string visible to user related to this change
*/
constructor (text) {
super();
this.text = text || 'Batch Command';
this.stack = [];
super()
this.text = text || 'Batch Command'
this.stack = []
}
/**
@@ -411,10 +411,10 @@ export class BatchCommand extends Command {
apply (handler) {
super.apply(handler, () => {
this.stack.forEach((stackItem) => {
console.assert(stackItem, 'stack item should not be null');
stackItem && stackItem.apply(handler);
});
});
console.assert(stackItem, 'stack item should not be null')
stackItem && stackItem.apply(handler)
})
})
}
/**
@@ -426,10 +426,10 @@ export class BatchCommand extends Command {
unapply (handler) {
super.unapply(handler, () => {
this.stack.reverse().forEach((stackItem) => {
console.assert(stackItem, 'stack item should not be null');
stackItem && stackItem.unapply(handler);
});
});
console.assert(stackItem, 'stack item should not be null')
stackItem && stackItem.unapply(handler)
})
})
}
/**
@@ -437,17 +437,17 @@ export class BatchCommand extends Command {
* @returns {Element[]} All the elements we are changing
*/
elements () {
const elems = [];
let cmd = this.stack.length;
const elems = []
let cmd = this.stack.length
while (cmd--) {
if (!this.stack[cmd]) continue;
const thisElems = this.stack[cmd].elements();
let elem = thisElems.length;
if (!this.stack[cmd]) continue
const thisElems = this.stack[cmd].elements()
let elem = thisElems.length
while (elem--) {
if (!elems.includes(thisElems[elem])) { elems.push(thisElems[elem]); }
if (!elems.includes(thisElems[elem])) { elems.push(thisElems[elem]) }
}
}
return elems;
return elems
}
/**
@@ -456,15 +456,15 @@ export class BatchCommand extends Command {
* @returns {void}
*/
addSubCommand (cmd) {
console.assert(cmd !== null, 'cmd should not be null');
this.stack.push(cmd);
console.assert(cmd !== null, 'cmd should not be null')
this.stack.push(cmd)
}
/**
* @returns {boolean} Indicates whether or not the batch command is empty
*/
isEmpty () {
return !this.stack.length;
return !this.stack.length
}
}
@@ -476,14 +476,14 @@ export class UndoManager {
* @param {module:history.HistoryEventHandler} historyEventHandler
*/
constructor (historyEventHandler) {
this.handler_ = historyEventHandler || null;
this.undoStackPointer = 0;
this.undoStack = [];
this.handler_ = historyEventHandler || null
this.undoStackPointer = 0
this.undoStack = []
// this is the stack that stores the original values, the elements and
// the attribute name for begin/finish
this.undoChangeStackPointer = -1;
this.undoableChangeStack = [];
this.undoChangeStackPointer = -1
this.undoableChangeStack = []
}
/**
@@ -491,36 +491,36 @@ export class UndoManager {
* @returns {void}
*/
resetUndoStack () {
this.undoStack = [];
this.undoStackPointer = 0;
this.undoStack = []
this.undoStackPointer = 0
}
/**
* @returns {Integer} Current size of the undo history stack
*/
getUndoStackSize () {
return this.undoStackPointer;
return this.undoStackPointer
}
/**
* @returns {Integer} Current size of the redo history stack
*/
getRedoStackSize () {
return this.undoStack.length - this.undoStackPointer;
return this.undoStack.length - this.undoStackPointer
}
/**
* @returns {string} String associated with the next undo command
*/
getNextUndoCommandText () {
return this.undoStackPointer > 0 ? this.undoStack[this.undoStackPointer - 1].getText() : '';
return this.undoStackPointer > 0 ? this.undoStack[this.undoStackPointer - 1].getText() : ''
}
/**
* @returns {string} String associated with the next redo command
*/
getNextRedoCommandText () {
return this.undoStackPointer < this.undoStack.length ? this.undoStack[this.undoStackPointer].getText() : '';
return this.undoStackPointer < this.undoStack.length ? this.undoStack[this.undoStackPointer].getText() : ''
}
/**
@@ -529,8 +529,8 @@ export class UndoManager {
*/
undo () {
if (this.undoStackPointer > 0) {
const cmd = this.undoStack[--this.undoStackPointer];
cmd.unapply(this.handler_);
const cmd = this.undoStack[--this.undoStackPointer]
cmd.unapply(this.handler_)
}
}
@@ -540,8 +540,8 @@ export class UndoManager {
*/
redo () {
if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
const cmd = this.undoStack[this.undoStackPointer++];
cmd.apply(this.handler_);
const cmd = this.undoStack[this.undoStackPointer++]
cmd.apply(this.handler_)
}
}
@@ -559,10 +559,10 @@ export class UndoManager {
// if our stack pointer is not at the end, then we have to remove
// all commands after the pointer and insert the new command
if (this.undoStackPointer < this.undoStack.length && this.undoStack.length > 0) {
this.undoStack = this.undoStack.splice(0, this.undoStackPointer);
this.undoStack = this.undoStack.splice(0, this.undoStackPointer)
}
this.undoStack.push(cmd);
this.undoStackPointer = this.undoStack.length;
this.undoStack.push(cmd)
this.undoStackPointer = this.undoStack.length
}
/**
@@ -576,20 +576,20 @@ export class UndoManager {
* @returns {void}
*/
beginUndoableChange (attrName, elems) {
const p = ++this.undoChangeStackPointer;
let i = elems.length;
const oldValues = new Array(i); const elements = new Array(i);
const p = ++this.undoChangeStackPointer
let i = elems.length
const oldValues = new Array(i); const elements = new Array(i)
while (i--) {
const elem = elems[i];
if (isNullish(elem)) { continue; }
elements[i] = elem;
oldValues[i] = elem.getAttribute(attrName);
const elem = elems[i]
if (isNullish(elem)) { continue }
elements[i] = elem
oldValues[i] = elem.getAttribute(attrName)
}
this.undoableChangeStack[p] = {
attrName,
oldValues,
elements
};
}
}
/**
@@ -599,21 +599,21 @@ export class UndoManager {
* @returns {BatchCommand} Batch command object with resulting changes
*/
finishUndoableChange () {
const p = this.undoChangeStackPointer--;
const changeset = this.undoableChangeStack[p];
const { attrName } = changeset;
const batchCmd = new BatchCommand('Change ' + attrName);
let i = changeset.elements.length;
const p = this.undoChangeStackPointer--
const changeset = this.undoableChangeStack[p]
const { attrName } = changeset
const batchCmd = new BatchCommand('Change ' + attrName)
let i = changeset.elements.length
while (i--) {
const elem = changeset.elements[i];
if (isNullish(elem)) { continue; }
const changes = {};
changes[attrName] = changeset.oldValues[i];
const elem = changeset.elements[i]
if (isNullish(elem)) { continue }
const changes = {}
changes[attrName] = changeset.oldValues[i]
if (changes[attrName] !== elem.getAttribute(attrName)) {
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attrName));
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attrName))
}
}
this.undoableChangeStack[p] = null;
return batchCmd;
this.undoableChangeStack[p] = null
return batchCmd
}
}

View File

@@ -8,7 +8,7 @@
import {
BatchCommand, MoveElementCommand, InsertElementCommand, RemoveElementCommand,
ChangeElementCommand
} from './history.js';
} from './history.js'
/**
* History recording service.
@@ -49,9 +49,9 @@ class HistoryRecordingService {
* See singleton: {@link module:history.HistoryRecordingService.HistoryRecordingService.NO_HISTORY}
*/
constructor (undoManager) {
this.undoManager_ = undoManager;
this.currentBatchCommand_ = null;
this.batchCommandStack_ = [];
this.undoManager_ = undoManager
this.currentBatchCommand_ = null
this.batchCommandStack_ = []
}
/**
@@ -62,10 +62,10 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
startBatchCommand (text) {
if (!this.undoManager_) { return this; }
this.currentBatchCommand_ = new BatchCommand(text);
this.batchCommandStack_.push(this.currentBatchCommand_);
return this;
if (!this.undoManager_) { return this }
this.currentBatchCommand_ = new BatchCommand(text)
this.batchCommandStack_.push(this.currentBatchCommand_)
return this
}
/**
@@ -73,15 +73,15 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
endBatchCommand () {
if (!this.undoManager_) { return this; }
if (!this.undoManager_) { return this }
if (this.currentBatchCommand_) {
const batchCommand = this.currentBatchCommand_;
this.batchCommandStack_.pop();
const { length: len } = this.batchCommandStack_;
this.currentBatchCommand_ = len ? this.batchCommandStack_[len - 1] : null;
this.addCommand_(batchCommand);
const batchCommand = this.currentBatchCommand_
this.batchCommandStack_.pop()
const { length: len } = this.batchCommandStack_
this.currentBatchCommand_ = len ? this.batchCommandStack_[len - 1] : null
this.addCommand_(batchCommand)
}
return this;
return this
}
/**
@@ -93,9 +93,9 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
moveElement (elem, oldNextSibling, oldParent, text) {
if (!this.undoManager_) { return this; }
this.addCommand_(new MoveElementCommand(elem, oldNextSibling, oldParent, text));
return this;
if (!this.undoManager_) { return this }
this.addCommand_(new MoveElementCommand(elem, oldNextSibling, oldParent, text))
return this
}
/**
@@ -105,9 +105,9 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
insertElement (elem, text) {
if (!this.undoManager_) { return this; }
this.addCommand_(new InsertElementCommand(elem, text));
return this;
if (!this.undoManager_) { return this }
this.addCommand_(new InsertElementCommand(elem, text))
return this
}
/**
@@ -119,9 +119,9 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
removeElement (elem, oldNextSibling, oldParent, text) {
if (!this.undoManager_) { return this; }
this.addCommand_(new RemoveElementCommand(elem, oldNextSibling, oldParent, text));
return this;
if (!this.undoManager_) { return this }
this.addCommand_(new RemoveElementCommand(elem, oldNextSibling, oldParent, text))
return this
}
/**
@@ -132,9 +132,9 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService}
*/
changeElement (elem, attrs, text) {
if (!this.undoManager_) { return this; }
this.addCommand_(new ChangeElementCommand(elem, attrs, text));
return this;
if (!this.undoManager_) { return this }
this.addCommand_(new ChangeElementCommand(elem, attrs, text))
return this
}
/**
@@ -144,18 +144,18 @@ class HistoryRecordingService {
* @returns {module:history.HistoryRecordingService|void}
*/
addCommand_ (cmd) {
if (!this.undoManager_) { return this; }
if (!this.undoManager_) { return this }
if (this.currentBatchCommand_) {
this.currentBatchCommand_.addSubCommand(cmd);
this.currentBatchCommand_.addSubCommand(cmd)
} else {
this.undoManager_.addCommandToHistory(cmd);
this.undoManager_.addCommandToHistory(cmd)
}
return undefined;
return undefined
}
}
/**
* @memberof module:history.HistoryRecordingService
* @property {module:history.HistoryRecordingService} NO_HISTORY - Singleton that can be passed to functions that record history, but the caller requires that no history be recorded.
*/
HistoryRecordingService.NO_HISTORY = new HistoryRecordingService();
export default HistoryRecordingService;
HistoryRecordingService.NO_HISTORY = new HistoryRecordingService()
export default HistoryRecordingService

View File

@@ -19,8 +19,8 @@
* @returns {external:jQuery}
*/
export default function jQueryPluginSVG ($) {
const proxied = $.fn.attr;
const svgns = 'http://www.w3.org/2000/svg';
const proxied = $.fn.attr
const svgns = 'http://www.w3.org/2000/svg'
/**
* @typedef {PlainObject<string, string|Float>} module:jQueryAttr.Attributes
*/
@@ -31,49 +31,49 @@ export default function jQueryPluginSVG ($) {
* @returns {external:jQuery|module:jQueryAttr.Attributes}
*/
$.fn.attr = function (key, value) {
const len = this.length;
if (!len) { return proxied.call(this, 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];
const elem = this[i]
// set/get SVG attribute
if (elem.namespaceURI === svgns) {
// Setting attribute
if (value !== undefined) {
elem.setAttribute(key, value);
elem.setAttribute(key, value)
} else if (Array.isArray(key)) {
// Getting attributes from array
const obj = {};
let j = key.length;
const obj = {}
let j = key.length
while (j--) {
const aname = key[j];
let attr = elem.getAttribute(aname);
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);
attr = isNaN(attr) ? attr : (attr - 0)
}
obj[aname] = attr;
obj[aname] = attr
}
return obj;
return obj
}
if (typeof key === 'object') {
// Setting attributes from object
for (const [ name, val ] of Object.entries(key)) {
elem.setAttribute(name, val);
for (const [name, val] of Object.entries(key)) {
elem.setAttribute(name, val)
}
// Getting attribute
} else {
let attr = elem.getAttribute(key);
let attr = elem.getAttribute(key)
if (attr || attr === '0') {
attr = isNaN(attr) ? attr : (attr - 0);
attr = isNaN(attr) ? attr : (attr - 0)
}
return attr;
return attr
}
} else {
return proxied.call(this, key, value);
return proxied.call(this, key, value)
}
}
return this;
};
return $;
return this
}
return $
}

View File

@@ -5,11 +5,11 @@
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import { getElem, assignAttributes, cleanupElement } from './utilities.js';
import { NS } from './namespaces.js';
import { getElem, assignAttributes, cleanupElement } from './utilities.js'
import { NS } from './namespaces.js'
let svgCanvas = null;
let svgdoc_ = null;
let svgCanvas = null
let svgdoc_ = null
/**
* @function module:json.jsonContext#getSelectedElements
@@ -26,9 +26,9 @@ let svgdoc_ = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
svgdoc_ = canvas.getDOMDocument();
};
svgCanvas = canvas
svgdoc_ = canvas.getDOMDocument()
}
/**
* @function module:json.getJsonFromSvgElements Iterate element and return json format
* @param {ArgumentsArray} data - element
@@ -36,27 +36,27 @@ export const init = function (canvas) {
*/
export const getJsonFromSvgElements = (data) => {
// Text node
if (data.nodeType === 3) return data.nodeValue;
if (data.nodeType === 3) return data.nodeValue
const retval = {
element: data.tagName,
// namespace: nsMap[data.namespaceURI],
attr: {},
children: []
};
}
// Iterate attributes
for (let i = 0, attr; (attr = data.attributes[i]); i++) {
retval.attr[attr.name] = attr.value;
retval.attr[attr.name] = attr.value
}
// Iterate children
for (let i = 0, node; (node = data.childNodes[i]); i++) {
retval.children[i] = getJsonFromSvgElements(node);
retval.children[i] = getJsonFromSvgElements(node)
}
return retval;
};
return retval
}
/**
* This should really be an intersection implementing all rather than a union.
@@ -65,23 +65,23 @@ export const getJsonFromSvgElements = (data) => {
*/
export const addSVGElementsFromJson = function (data) {
if (typeof data === 'string') return svgdoc_.createTextNode(data);
if (typeof data === 'string') return svgdoc_.createTextNode(data)
let shape = getElem(data.attr.id);
let shape = getElem(data.attr.id)
// if shape is a path but we need to create a rect/ellipse, then remove the path
const currentLayer = svgCanvas.getDrawing().getCurrentLayer();
const currentLayer = svgCanvas.getDrawing().getCurrentLayer()
if (shape && data.element !== shape.tagName) {
shape.remove();
shape = null;
shape.remove()
shape = null
}
if (!shape) {
const ns = data.namespace || NS.SVG;
shape = svgdoc_.createElementNS(ns, data.element);
const ns = data.namespace || NS.SVG
shape = svgdoc_.createElementNS(ns, data.element)
if (currentLayer) {
(svgCanvas.getCurrentGroup() || currentLayer).append(shape);
(svgCanvas.getCurrentGroup() || currentLayer).append(shape)
}
}
const curShape = svgCanvas.getCurShape();
const curShape = svgCanvas.getCurShape()
if (data.curStyles) {
assignAttributes(shape, {
fill: curShape.fill,
@@ -94,17 +94,17 @@ export const addSVGElementsFromJson = function (data) {
'fill-opacity': curShape.fill_opacity,
opacity: curShape.opacity / 2,
style: 'pointer-events:inherit'
}, 100);
}, 100)
}
assignAttributes(shape, data.attr, 100);
cleanupElement(shape);
assignAttributes(shape, data.attr, 100)
cleanupElement(shape)
// Children
if (data.children) {
data.children.forEach((child) => {
shape.append(addSVGElementsFromJson(child));
});
shape.append(addSVGElementsFromJson(child))
})
}
return shape;
};
return shape
}

View File

@@ -6,9 +6,8 @@
* @copyright 2011 Jeff Schiller, 2016 Flint O'Brien
*/
import { NS } from './namespaces.js';
import { toXml, walkTree, isNullish } from './utilities.js';
import { NS } from './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
@@ -31,29 +30,29 @@ class Layer {
* a new layer to the document.
*/
constructor (name, group, svgElem) {
this.name_ = name;
this.group_ = svgElem ? null : group;
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);
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_);
group.insertAdjacentElement('afterend', this.group_)
} else {
svgElem.append(this.group_);
svgElem.append(this.group_)
}
}
addLayerClass(this.group_);
addLayerClass(this.group_)
walkTree(this.group_, function (e) {
e.setAttribute('style', 'pointer-events:inherit');
});
e.setAttribute('style', 'pointer-events:inherit')
})
this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none');
this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none')
}
/**
@@ -61,7 +60,7 @@ class Layer {
* @returns {string} The layer name
*/
getName () {
return this.name_;
return this.name_
}
/**
@@ -69,7 +68,7 @@ class Layer {
* @returns {SVGGElement} The layer SVG group
*/
getGroup () {
return this.group_;
return this.group_
}
/**
@@ -77,7 +76,7 @@ class Layer {
* @returns {void}
*/
activate () {
this.group_.setAttribute('style', 'pointer-events:all');
this.group_.setAttribute('style', 'pointer-events:all')
}
/**
@@ -85,7 +84,7 @@ class Layer {
* @returns {void}
*/
deactivate () {
this.group_.setAttribute('style', 'pointer-events:none');
this.group_.setAttribute('style', 'pointer-events:none')
}
/**
@@ -94,10 +93,10 @@ class Layer {
* @returns {void}
*/
setVisible (visible) {
const expected = visible === undefined || visible ? 'inline' : 'none';
const oldDisplay = this.group_.getAttribute('display');
const expected = visible === undefined || visible ? 'inline' : 'none'
const oldDisplay = this.group_.getAttribute('display')
if (oldDisplay !== expected) {
this.group_.setAttribute('display', expected);
this.group_.setAttribute('display', expected)
}
}
@@ -106,7 +105,7 @@ class Layer {
* @returns {boolean} True if visible.
*/
isVisible () {
return this.group_.getAttribute('display') !== 'none';
return this.group_.getAttribute('display') !== 'none'
}
/**
@@ -114,11 +113,11 @@ class Layer {
* @returns {Float} Opacity value.
*/
getOpacity () {
const opacity = this.group_.getAttribute('opacity');
const opacity = this.group_.getAttribute('opacity')
if (isNullish(opacity)) {
return 1;
return 1
}
return Number.parseFloat(opacity);
return Number.parseFloat(opacity)
}
/**
@@ -129,7 +128,7 @@ class Layer {
*/
setOpacity (opacity) {
if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) {
this.group_.setAttribute('opacity', opacity);
this.group_.setAttribute('opacity', opacity)
}
}
@@ -140,7 +139,7 @@ class Layer {
*/
appendChildren (children) {
for (const child of children) {
this.group_.append(child);
this.group_.append(child)
}
}
@@ -148,14 +147,14 @@ class Layer {
* @returns {SVGTitleElement|null}
*/
getTitleElement () {
const len = this.group_.childNodes.length;
const len = this.group_.childNodes.length
for (let i = 0; i < len; ++i) {
const child = this.group_.childNodes.item(i);
const child = this.group_.childNodes.item(i)
if (child && child.tagName === 'title') {
return child;
return child
}
}
return null;
return null
}
/**
@@ -165,21 +164,20 @@ class Layer {
* @returns {string|null} The new name if changed; otherwise, null.
*/
setName (name, hrService) {
const previousName = this.name_;
name = toXml(name);
const previousName = this.name_
name = toXml(name)
// now change the underlying title element contents
const title = this.getTitleElement();
const title = this.getTitleElement()
if (title) {
while(title.firstChild)
title.removeChild(title.firstChild);
title.textContent = name;
this.name_ = name;
while (title.firstChild) { title.removeChild(title.firstChild) }
title.textContent = name
this.name_ = name
if (hrService) {
hrService.changeElement(title, { '#text': previousName });
hrService.changeElement(title, { '#text': previousName })
}
return this.name_;
return this.name_
}
return null;
return null
}
/**
@@ -187,29 +185,30 @@ class Layer {
* @returns {SVGGElement} The layer SVG group that was just removed.
*/
removeGroup () {
const group = this.group_;
this.group_.remove();
this.group_ = undefined;
return group;
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'));
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';
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|$)');
Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)')
/**
* Add class `Layer.CLASS_NAME` to the element (usually `class='layer'`).
@@ -218,12 +217,12 @@ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)');
* @returns {void}
*/
function addLayerClass (elem) {
const classes = elem.getAttribute('class');
const classes = elem.getAttribute('class')
if (isNullish(classes) || !classes.length) {
elem.setAttribute('class', Layer.CLASS_NAME);
elem.setAttribute('class', Layer.CLASS_NAME)
} else if (!Layer.CLASS_REGEX.test(classes)) {
elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME);
elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME)
}
}
export default Layer;
export default Layer

View File

@@ -19,13 +19,13 @@
* @property {Float} y
*/
import { NS } from './namespaces.js';
import { NS } from './namespaces.js'
// Constants
const NEAR_ZERO = 1e-14;
const NEAR_ZERO = 1e-14
// Throw away SVGSVGElement used for creating matrices/transforms.
const svg = document.createElementNS(NS.SVG, 'svg');
const svg = document.createElementNS(NS.SVG, 'svg')
/**
* A (hopefully) quicker function to transform a point by a matrix
@@ -37,8 +37,8 @@ const svg = document.createElementNS(NS.SVG, 'svg');
* @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 };
};
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
@@ -48,8 +48,8 @@ export const transformPoint = function (x, y, m) {
* @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);
};
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`.
@@ -60,18 +60,18 @@ export const isIdentity = function (m) {
*/
export const matrixMultiply = function (...args) {
const m = args.reduceRight((prev, m1) => {
return m1.multiply(prev);
});
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; }
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;
};
return m
}
/**
* See if the given transformlist includes a non-indentity matrix transform.
@@ -80,14 +80,14 @@ export const matrixMultiply = function (...args) {
* @returns {boolean} Whether or not a matrix transform was found
*/
export const hasMatrixTransform = function (tlist) {
if (!tlist) { return false; }
let num = tlist.numberOfItems;
if (!tlist) { return false }
let num = tlist.numberOfItems
while (num--) {
const xform = tlist.getItem(num);
if (xform.type === 1 && !isIdentity(xform.matrix)) { return true; }
const xform = tlist.getItem(num)
if (xform.type === 1 && !isIdentity(xform.matrix)) { return true }
}
return false;
};
return false
}
/**
* @typedef {PlainObject} module:math.TransformedBox An object with the following values
@@ -113,15 +113,15 @@ export const hasMatrixTransform = function (tlist) {
* @returns {module:math.TransformedBox}
*/
export const transformBox = function (l, t, w, h, m) {
const tl = transformPoint(l, t, m);
const tr = transformPoint((l + w), t, m);
const bl = transformPoint(l, (t + h), m);
const br = transformPoint((l + w), (t + h), m);
const tl = transformPoint(l, t, m)
const tr = transformPoint((l + w), t, m)
const bl = transformPoint(l, (t + h), m)
const br = transformPoint((l + w), (t + h), m)
const minx = Math.min(tl.x, tr.x, bl.x, br.x);
const maxx = Math.max(tl.x, tr.x, bl.x, br.x);
const miny = Math.min(tl.y, tr.y, bl.y, br.y);
const maxy = Math.max(tl.y, tr.y, bl.y, br.y);
const minx = Math.min(tl.x, tr.x, bl.x, br.x)
const maxx = Math.max(tl.x, tr.x, bl.x, br.x)
const miny = Math.min(tl.y, tr.y, bl.y, br.y)
const maxy = Math.max(tl.y, tr.y, bl.y, br.y)
return {
tl,
@@ -134,8 +134,8 @@ export const transformBox = function (l, t, w, h, m) {
width: (maxx - minx),
height: (maxy - miny)
}
};
};
}
}
/**
* This returns a single matrix Transform for a given Transform List
@@ -152,23 +152,23 @@ export const transformBox = function (l, t, w, h, m) {
export const transformListToTransform = function (tlist, min, max) {
if (!tlist) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
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();
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);
: svg.createSVGMatrix())
m = matrixMultiply(m, mtom)
}
return svg.createSVGTransformFromMatrix(m);
};
return svg.createSVGTransformFromMatrix(m)
}
/**
* Get the matrix object for a given element.
@@ -177,9 +177,9 @@ export const transformListToTransform = function (tlist, min, max) {
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
*/
export const getMatrix = (elem) => {
const tlist = elem.transform.baseVal;
return transformListToTransform(tlist).matrix;
};
const tlist = elem.transform.baseVal
return transformListToTransform(tlist).matrix
}
/**
* Returns a 45 degree angle coordinate associated with the two given
@@ -192,19 +192,19 @@ export const getMatrix = (elem) => {
* @returns {module:math.AngleCoord45}
*/
export const snapToAngle = (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;
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.
@@ -217,5 +217,5 @@ export const rectsIntersect = (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;
};
(r2.y + r2.height) > r1.y
}

View File

@@ -25,16 +25,16 @@ export const NS = {
// OSB: 'http://www.openswatchbook.org/uri/2009/osb',
// CC: 'http://creativecommons.org/ns#',
// DC: 'http://purl.org/dc/elements/1.1/'
};
}
/**
* @function module:namespaces.getReverseNS
* @returns {string} The NS with key values switched and lowercase
*/
export const getReverseNS = function () {
const reverseNS = {};
Object.entries(NS).forEach(([ name, URI ]) => {
reverseNS[URI] = name.toLowerCase();
});
return reverseNS;
};
const reverseNS = {}
Object.entries(NS).forEach(([name, URI]) => {
reverseNS[URI] = name.toLowerCase()
})
return reverseNS
}

View File

@@ -1,13 +1,13 @@
import {
getStrokedBBoxDefaultVisible
} from './utilities.js';
import * as hstry from './history.js';
} from './utilities.js'
import * as hstry from './history.js'
const {
InsertElementCommand, BatchCommand
} = hstry;
} = hstry
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:paste-elem.init
@@ -15,8 +15,8 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
};
svgCanvas = canvas
}
/**
* @function module:svgcanvas.SvgCanvas#pasteElements
@@ -28,13 +28,13 @@ export const init = function (canvas) {
* @returns {void}
*/
export const pasteElementsMethod = function (type, x, y) {
let clipb = JSON.parse(sessionStorage.getItem(svgCanvas.getClipboardID()));
if (!clipb) return;
let len = clipb.length;
if (!len) return;
let clipb = JSON.parse(sessionStorage.getItem(svgCanvas.getClipboardID()))
if (!clipb) return
let len = clipb.length
if (!len) return
const pasted = [];
const batchCmd = new BatchCommand('Paste elements');
const pasted = []
const batchCmd = new BatchCommand('Paste elements')
// const drawing = getCurrentDrawing();
/**
* @typedef {PlainObject<string, string>} module:svgcanvas.ChangedIDs
@@ -42,7 +42,7 @@ export const pasteElementsMethod = function (type, x, y) {
/**
* @type {module:svgcanvas.ChangedIDs}
*/
const changedIDs = {};
const changedIDs = {}
// Recursively replace IDs and record the changes
/**
@@ -52,12 +52,12 @@ export const pasteElementsMethod = function (type, x, y) {
*/
function checkIDs (elem) {
if (elem.attr && elem.attr.id) {
changedIDs[elem.attr.id] = svgCanvas.getNextId();
elem.attr.id = changedIDs[elem.attr.id];
changedIDs[elem.attr.id] = svgCanvas.getNextId()
elem.attr.id = changedIDs[elem.attr.id]
}
if (elem.children) elem.children.forEach((child) => checkIDs(child));
if (elem.children) elem.children.forEach((child) => checkIDs(child))
}
clipb.forEach((elem) => checkIDs(elem));
clipb.forEach((elem) => checkIDs(elem))
// Give extensions like the connector extension a chance to reflect new IDs and remove invalid elements
/**
@@ -73,55 +73,55 @@ export const pasteElementsMethod = function (type, x, y) {
{ elems: clipb, changes: changedIDs },
true
).forEach(function (extChanges) {
if (!extChanges || !('remove' in extChanges)) return;
if (!extChanges || !('remove' in extChanges)) return
extChanges.remove.forEach(function (removeID) {
clipb = clipb.filter(function (clipBoardItem) {
return clipBoardItem.attr.id !== removeID;
});
});
});
return clipBoardItem.attr.id !== removeID
})
})
})
// Move elements to lastClickPoint
while (len--) {
const elem = clipb[len];
if (!elem) { continue; }
const elem = clipb[len]
if (!elem) { continue }
const copy = svgCanvas.addSVGElemensFromJson(elem);
pasted.push(copy);
batchCmd.addSubCommand(new InsertElementCommand(copy));
const copy = svgCanvas.addSVGElemensFromJson(elem)
pasted.push(copy)
batchCmd.addSubCommand(new InsertElementCommand(copy))
svgCanvas.restoreRefElements(copy);
svgCanvas.restoreRefElements(copy)
}
svgCanvas.selectOnly(pasted);
svgCanvas.selectOnly(pasted)
if (type !== 'in_place') {
let ctrX; let ctrY;
let ctrX; let ctrY
if (!type) {
ctrX = svgCanvas.getLastClickPoint('x');
ctrY = svgCanvas.getLastClickPoint('y');
ctrX = svgCanvas.getLastClickPoint('x')
ctrY = svgCanvas.getLastClickPoint('y')
} else if (type === 'point') {
ctrX = x;
ctrY = y;
ctrX = x
ctrY = y
}
const bbox = getStrokedBBoxDefaultVisible(pasted);
const cx = ctrX - (bbox.x + bbox.width / 2);
const cy = ctrY - (bbox.y + bbox.height / 2);
const dx = [];
const dy = [];
const bbox = getStrokedBBoxDefaultVisible(pasted)
const cx = ctrX - (bbox.x + bbox.width / 2)
const cy = ctrY - (bbox.y + bbox.height / 2)
const dx = []
const dy = []
pasted.forEach(function(_item){
dx.push(cx);
dy.push(cy);
});
pasted.forEach(function (_item) {
dx.push(cx)
dy.push(cy)
})
const cmd = svgCanvas.moveSelectedElements(dx, dy, false);
if (cmd) batchCmd.addSubCommand(cmd);
const cmd = svgCanvas.moveSelectedElements(dx, dy, false)
if (cmd) batchCmd.addSubCommand(cmd)
}
svgCanvas.addCommandToHistory(batchCmd);
svgCanvas.call('changed', pasted);
};
svgCanvas.addCommandToHistory(batchCmd)
svgCanvas.call('changed', pasted)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,59 +6,59 @@
* @copyright 2011 Alexis Deveria, 2011 Jeff Schiller
*/
import { shortFloat } from '../common/units.js';
import { transformPoint } from './math.js';
import { shortFloat } from '../common/units.js'
import { transformPoint } from './math.js'
import {
getRotationAngle, getBBox,
getRefElem, findDefs, isNullish,
getBBox as utilsGetBBox
} from './utilities.js';
} from './utilities.js'
import {
init as pathMethodInit, ptObjToArrMethod, getGripPtMethod,
getPointFromGripMethod, addPointGripMethod, getGripContainerMethod, addCtrlGripMethod,
getCtrlLineMethod, getPointGripMethod, getControlPointsMethod, replacePathSegMethod,
getSegSelectorMethod, Path
} from './path-method.js';
} from './path-method.js'
import {
init as pathActionsInit, pathActionsMethod
} from './path-actions.js';
} from './path-actions.js'
const segData = {
2: [ 'x', 'y' ], // PATHSEG_MOVETO_ABS
4: [ 'x', 'y' ], // PATHSEG_LINETO_ABS
6: [ 'x', 'y', 'x1', 'y1', 'x2', 'y2' ], // PATHSEG_CURVETO_CUBIC_ABS
8: [ 'x', 'y', 'x1', 'y1' ], // PATHSEG_CURVETO_QUADRATIC_ABS
10: [ 'x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag' ], // PATHSEG_ARC_ABS
12: [ 'x' ], // PATHSEG_LINETO_HORIZONTAL_ABS
14: [ 'y' ], // PATHSEG_LINETO_VERTICAL_ABS
16: [ 'x', 'y', 'x2', 'y2' ], // PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
18: [ 'x', 'y' ] // PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
};
2: ['x', 'y'], // PATHSEG_MOVETO_ABS
4: ['x', 'y'], // PATHSEG_LINETO_ABS
6: ['x', 'y', 'x1', 'y1', 'x2', 'y2'], // PATHSEG_CURVETO_CUBIC_ABS
8: ['x', 'y', 'x1', 'y1'], // PATHSEG_CURVETO_QUADRATIC_ABS
10: ['x', 'y', 'r1', 'r2', 'angle', 'largeArcFlag', 'sweepFlag'], // PATHSEG_ARC_ABS
12: ['x'], // PATHSEG_LINETO_HORIZONTAL_ABS
14: ['y'], // PATHSEG_LINETO_VERTICAL_ABS
16: ['x', 'y', 'x2', 'y2'], // PATHSEG_CURVETO_CUBIC_SMOOTH_ABS
18: ['x', 'y'] // PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS
}
let svgCanvas;
let svgCanvas
/**
* @tutorial LocaleDocs
* @typedef {module:locale.LocaleStrings|PlainObject} module:path.uiStrings
* @property {PlainObject<string, string>} ui
*/
const uiStrings = {};
const uiStrings = {}
/**
* @function module:path.setUiStrings
* @param {module:path.uiStrings} strs
* @returns {void}
*/
export const setUiStrings = (strs) => {
Object.assign(uiStrings, strs.ui);
};
Object.assign(uiStrings, strs.ui)
}
let pathFuncs = [];
let pathFuncs = []
let linkControlPts = true;
let linkControlPts = true
// Stores references to paths via IDs.
// TODO: Make this cross-document happy.
let pathData = {};
let pathData = {}
/**
* @function module:path.setLinkControlPoints
@@ -66,15 +66,15 @@ let pathData = {};
* @returns {void}
*/
export const setLinkControlPoints = (lcp) => {
linkControlPts = lcp;
};
linkControlPts = lcp
}
/**
* @name module:path.path
* @type {null|module:path.Path}
* @memberof module:path
*/
export let path = null;
export let path = null
/**
* @external MouseEvent
@@ -229,36 +229,35 @@ export let path = null;
* @returns {void}
*/
export const init = (canvas) => {
svgCanvas = canvas;
svgCanvas.replacePathSeg = replacePathSegMethod;
svgCanvas.addPointGrip = addPointGripMethod;
svgCanvas.removePath_ = removePath_;
svgCanvas.getPath_ = getPath_;
svgCanvas.addCtrlGrip = addCtrlGripMethod;
svgCanvas.getCtrlLine = getCtrlLineMethod;
svgCanvas.getGripPt = getGripPt;
svgCanvas.getPointFromGrip = getPointFromGripMethod;
svgCanvas.setLinkControlPoints = setLinkControlPoints;
svgCanvas.reorientGrads = reorientGrads;
svgCanvas.getSegData = () => { return segData; };
svgCanvas.getUIStrings= () => { return uiStrings; };
svgCanvas.getPathObj = () => { return path; };
svgCanvas.setPathObj = (obj) => { path = obj; };
svgCanvas.getPathFuncs = () => { return pathFuncs; };
svgCanvas.getLinkControlPts = () => { return linkControlPts; };
pathFuncs = [ 0, 'ClosePath' ];
svgCanvas = canvas
svgCanvas.replacePathSeg = replacePathSegMethod
svgCanvas.addPointGrip = addPointGripMethod
svgCanvas.removePath_ = removePath_
svgCanvas.getPath_ = getPath_
svgCanvas.addCtrlGrip = addCtrlGripMethod
svgCanvas.getCtrlLine = getCtrlLineMethod
svgCanvas.getGripPt = getGripPt
svgCanvas.getPointFromGrip = getPointFromGripMethod
svgCanvas.setLinkControlPoints = setLinkControlPoints
svgCanvas.reorientGrads = reorientGrads
svgCanvas.getSegData = () => { return segData }
svgCanvas.getUIStrings = () => { return uiStrings }
svgCanvas.getPathObj = () => { return path }
svgCanvas.setPathObj = (obj) => { path = obj }
svgCanvas.getPathFuncs = () => { return pathFuncs }
svgCanvas.getLinkControlPts = () => { return linkControlPts }
pathFuncs = [0, 'ClosePath']
const pathFuncsStrs = [
'Moveto', 'Lineto', 'CurvetoCubic', 'CurvetoQuadratic', 'Arc',
'LinetoHorizontal', 'LinetoVertical', 'CurvetoCubicSmooth', 'CurvetoQuadraticSmooth'
];
]
pathFuncsStrs.forEach((s) => {
pathFuncs.push(s + 'Abs');
pathFuncs.push(s + 'Rel');
});
pathActionsInit(svgCanvas);
pathMethodInit(svgCanvas);
};
pathFuncs.push(s + 'Abs')
pathFuncs.push(s + 'Rel')
})
pathActionsInit(svgCanvas)
pathMethodInit(svgCanvas)
}
/* eslint-disable max-len */
/**
@@ -269,7 +268,7 @@ export const init = (canvas) => {
* @returns {ArgumentsArray}
*/
/* eslint-enable max-len */
export const ptObjToArr = ptObjToArrMethod;
export const ptObjToArr = ptObjToArrMethod
/**
* @function module:path.getGripPt
@@ -277,7 +276,7 @@ export const ptObjToArr = ptObjToArrMethod;
* @param {module:math.XYObject} altPt
* @returns {module:math.XYObject}
*/
export const getGripPt = getGripPtMethod;
export const getGripPt = getGripPtMethod
/**
* @function module:path.getPointFromGrip
@@ -285,7 +284,7 @@ export const getGripPt = getGripPtMethod;
* @param {module:path.Path} pth
* @returns {module:math.XYObject}
*/
export const getPointFromGrip = getPointFromGripMethod;
export const getPointFromGrip = getPointFromGripMethod
/**
* Requires prior call to `setUiStrings` if `xlink:title`
@@ -296,13 +295,13 @@ export const getPointFromGrip = getPointFromGripMethod;
* @param {Integer} y
* @returns {SVGCircleElement}
*/
export const addPointGrip = addPointGripMethod;
export const addPointGrip = addPointGripMethod
/**
* @function module:path.getGripContainer
* @returns {Element}
*/
export const getGripContainer = getGripContainerMethod;
export const getGripContainer = getGripContainerMethod
/**
* Requires prior call to `setUiStrings` if `xlink:title`
@@ -311,14 +310,14 @@ export const getGripContainer = getGripContainerMethod;
* @param {string} id
* @returns {SVGCircleElement}
*/
export const addCtrlGrip = addCtrlGripMethod;
export const addCtrlGrip = addCtrlGripMethod
/**
* @function module:path.getCtrlLine
* @param {string} id
* @returns {SVGLineElement}
*/
export const getCtrlLine = getCtrlLineMethod;
export const getCtrlLine = getCtrlLineMethod
/**
* @function module:path.getPointGrip
@@ -326,14 +325,14 @@ export const getCtrlLine = getCtrlLineMethod;
* @param {boolean} update
* @returns {SVGCircleElement}
*/
export const getPointGrip = getPointGripMethod;
export const getPointGrip = getPointGripMethod
/**
* @function module:path.getControlPoints
* @param {Segment} seg
* @returns {PlainObject<string, SVGLineElement|SVGCircleElement>}
*/
export const getControlPoints = getControlPointsMethod;
export const getControlPoints = getControlPointsMethod
/**
* This replaces the segment at the given index. Type is given as number.
@@ -344,7 +343,7 @@ export const getControlPoints = getControlPointsMethod;
* @param {SVGPathElement} elem
* @returns {void}
*/
export const replacePathSeg = replacePathSegMethod;
export const replacePathSeg = replacePathSegMethod
/**
* @function module:path.getSegSelector
@@ -352,7 +351,7 @@ export const replacePathSeg = replacePathSegMethod;
* @param {boolean} update
* @returns {SVGPathElement}
*/
export const getSegSelector = getSegSelectorMethod;
export const getSegSelector = getSegSelectorMethod
/**
* @typedef {PlainObject} Point
@@ -370,44 +369,44 @@ export const getSegSelector = getSegSelectorMethod;
*/
export const smoothControlPoints = (ct1, ct2, pt) => {
// each point must not be the origin
const x1 = ct1.x - pt.x;
const y1 = ct1.y - pt.y;
const x2 = ct2.x - pt.x;
const y2 = ct2.y - pt.y;
const x1 = ct1.x - pt.x
const y1 = ct1.y - pt.y
const x2 = ct2.x - pt.x
const y2 = ct2.y - pt.y
if ((x1 !== 0 || y1 !== 0) && (x2 !== 0 || y2 !== 0)) {
const
r1 = Math.sqrt(x1 * x1 + y1 * y1);
const r2 = Math.sqrt(x2 * x2 + y2 * y2);
const nct1 = svgCanvas.getSvgRoot().createSVGPoint();
const nct2 = svgCanvas.getSvgRoot().createSVGPoint();
let anglea = Math.atan2(y1, x1);
let angleb = Math.atan2(y2, x2);
if (anglea < 0) { anglea += 2 * Math.PI; }
if (angleb < 0) { angleb += 2 * Math.PI; }
r1 = Math.sqrt(x1 * x1 + y1 * y1)
const r2 = Math.sqrt(x2 * x2 + y2 * y2)
const nct1 = svgCanvas.getSvgRoot().createSVGPoint()
const nct2 = svgCanvas.getSvgRoot().createSVGPoint()
let anglea = Math.atan2(y1, x1)
let angleb = Math.atan2(y2, x2)
if (anglea < 0) { anglea += 2 * Math.PI }
if (angleb < 0) { angleb += 2 * Math.PI }
const angleBetween = Math.abs(anglea - angleb);
const angleDiff = Math.abs(Math.PI - angleBetween) / 2;
const angleBetween = Math.abs(anglea - angleb)
const angleDiff = Math.abs(Math.PI - angleBetween) / 2
let newAnglea; let newAngleb;
let newAnglea; let newAngleb
if (anglea - angleb > 0) {
newAnglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff);
newAngleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff);
newAnglea = angleBetween < Math.PI ? (anglea + angleDiff) : (anglea - angleDiff)
newAngleb = angleBetween < Math.PI ? (angleb - angleDiff) : (angleb + angleDiff)
} else {
newAnglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff);
newAngleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff);
newAnglea = angleBetween < Math.PI ? (anglea - angleDiff) : (anglea + angleDiff)
newAngleb = angleBetween < Math.PI ? (angleb + angleDiff) : (angleb - angleDiff)
}
// rotate the points
nct1.x = r1 * Math.cos(newAnglea) + pt.x;
nct1.y = r1 * Math.sin(newAnglea) + pt.y;
nct2.x = r2 * Math.cos(newAngleb) + pt.x;
nct2.y = r2 * Math.sin(newAngleb) + pt.y;
nct1.x = r1 * Math.cos(newAnglea) + pt.x
nct1.y = r1 * Math.sin(newAnglea) + pt.y
nct2.x = r2 * Math.cos(newAngleb) + pt.x
nct2.y = r2 * Math.sin(newAngleb) + pt.y
return [ nct1, nct2 ];
return [nct1, nct2]
}
return undefined;
};
return undefined
}
/**
* @function module:path.getPath_
@@ -415,12 +414,12 @@ export const smoothControlPoints = (ct1, ct2, pt) => {
* @returns {module:path.Path}
*/
export const getPath_ = (elem) => {
let p = pathData[elem.id];
let p = pathData[elem.id]
if (!p) {
p = pathData[elem.id] = new Path(elem);
p = pathData[elem.id] = new Path(elem)
}
return p;
};
return p
}
/**
* @function module:path.removePath_
@@ -428,34 +427,36 @@ export const getPath_ = (elem) => {
* @returns {void}
*/
export const removePath_ = (id) => {
if (id in pathData) { delete pathData[id]; }
};
if (id in pathData) { delete pathData[id] }
}
let newcx; let newcy; let oldcx; let oldcy; let angle;
let newcx; let newcy; let oldcx; let oldcy; let angle
const getRotVals = (x, y) => {
let dx = x - oldcx;
let dy = y - oldcy;
let dx = x - oldcx
let dy = y - oldcy
// rotate the point around the old center
let r = Math.sqrt(dx * dx + dy * dy);
let theta = Math.atan2(dy, dx) + angle;
dx = r * Math.cos(theta) + oldcx;
dy = r * Math.sin(theta) + oldcy;
let r = Math.sqrt(dx * dx + dy * dy)
let theta = Math.atan2(dy, dx) + angle
dx = r * Math.cos(theta) + oldcx
dy = r * Math.sin(theta) + oldcy
// dx,dy should now hold the actual coordinates of each
// point after being rotated
// now we want to rotate them around the new center in the reverse direction
dx -= newcx;
dy -= newcy;
dx -= newcx
dy -= newcy
r = Math.sqrt(dx * dx + dy * dy);
theta = Math.atan2(dy, dx) - angle;
r = Math.sqrt(dx * dx + dy * dy)
theta = Math.atan2(dy, dx) - angle
return { x: r * Math.cos(theta) + newcx,
y: r * Math.sin(theta) + newcy };
};
return {
x: r * Math.cos(theta) + newcx,
y: r * Math.sin(theta) + newcy
}
}
// If the path was rotated, we must now pay the piper:
// Every path point must be rotated into the rotated coordinate system of
@@ -469,55 +470,55 @@ const getRotVals = (x, y) => {
* @returns {void}
*/
export const recalcRotatedPath = () => {
const currentPath = path.elem;
angle = getRotationAngle(currentPath, true);
if (!angle) { return; }
const currentPath = path.elem
angle = getRotationAngle(currentPath, true)
if (!angle) { return }
// selectedBBoxes[0] = path.oldbbox;
const oldbox = path.oldbbox; // selectedBBoxes[0],
oldcx = oldbox.x + oldbox.width / 2;
oldcy = oldbox.y + oldbox.height / 2;
const box = getBBox(currentPath);
newcx = box.x + box.width / 2;
newcy = box.y + box.height / 2;
const oldbox = path.oldbbox // selectedBBoxes[0],
oldcx = oldbox.x + oldbox.width / 2
oldcy = oldbox.y + oldbox.height / 2
const box = getBBox(currentPath)
newcx = box.x + box.width / 2
newcy = box.y + box.height / 2
// un-rotate the new center to the proper position
const dx = newcx - oldcx;
const dy = newcy - oldcy;
const r = Math.sqrt(dx * dx + dy * dy);
const theta = Math.atan2(dy, dx) + angle;
const dx = newcx - oldcx
const dy = newcy - oldcy
const r = Math.sqrt(dx * dx + dy * dy)
const theta = Math.atan2(dy, dx) + angle
newcx = r * Math.cos(theta) + oldcx;
newcy = r * Math.sin(theta) + oldcy;
newcx = r * Math.cos(theta) + oldcx
newcy = r * Math.sin(theta) + oldcy
const list = currentPath.pathSegList;
const list = currentPath.pathSegList
let i = list.numberOfItems;
let i = list.numberOfItems
while (i) {
i -= 1;
const seg = list.getItem(i);
const type = seg.pathSegType;
if (type === 1) { continue; }
i -= 1
const seg = list.getItem(i)
const type = seg.pathSegType
if (type === 1) { continue }
const rvals = getRotVals(seg.x, seg.y);
const points = [ rvals.x, rvals.y ];
const rvals = getRotVals(seg.x, seg.y)
const points = [rvals.x, rvals.y]
if (!isNullish(seg.x1) && !isNullish(seg.x2)) {
const cVals1 = getRotVals(seg.x1, seg.y1);
const cVals2 = getRotVals(seg.x2, seg.y2);
points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y);
const cVals1 = getRotVals(seg.x1, seg.y1)
const cVals2 = getRotVals(seg.x2, seg.y2)
points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y)
}
replacePathSeg(type, i, points);
replacePathSeg(type, i, points)
} // loop for each point
/* box = */ getBBox(currentPath);
/* box = */ getBBox(currentPath)
// selectedBBoxes[0].x = box.x; selectedBBoxes[0].y = box.y;
// selectedBBoxes[0].width = box.width; selectedBBoxes[0].height = box.height;
// now we must set the new transform to be rotated around the new center
const Rnc = svgCanvas.getSvgRoot().createSVGTransform();
const tlist = currentPath.transform.baseVal;
Rnc.setRotate((angle * 180.0 / Math.PI), newcx, newcy);
tlist.replaceItem(Rnc, 0);
};
const Rnc = svgCanvas.getSvgRoot().createSVGTransform()
const tlist = currentPath.transform.baseVal
Rnc.setRotate((angle * 180.0 / Math.PI), newcx, newcy)
tlist.replaceItem(Rnc, 0)
}
// ====================================
// Public API starts here
@@ -527,8 +528,8 @@ export const recalcRotatedPath = () => {
* @returns {void}
*/
export const clearData = () => {
pathData = {};
};
pathData = {}
}
// Making public for mocking
/**
@@ -538,27 +539,27 @@ export const clearData = () => {
* @returns {void}
*/
export const reorientGrads = (elem, m) => {
const bb = utilsGetBBox(elem);
const bb = utilsGetBBox(elem)
for (let i = 0; i < 2; i++) {
const type = i === 0 ? 'fill' : 'stroke';
const attrVal = elem.getAttribute(type);
const type = i === 0 ? 'fill' : 'stroke'
const attrVal = elem.getAttribute(type)
if (attrVal && attrVal.startsWith('url(')) {
const grad = getRefElem(attrVal);
const grad = getRefElem(attrVal)
if (grad.tagName === 'linearGradient') {
let x1 = grad.getAttribute('x1') || 0;
let y1 = grad.getAttribute('y1') || 0;
let x2 = grad.getAttribute('x2') || 1;
let y2 = grad.getAttribute('y2') || 0;
let x1 = grad.getAttribute('x1') || 0
let y1 = grad.getAttribute('y1') || 0
let x2 = grad.getAttribute('x2') || 1
let y2 = grad.getAttribute('y2') || 0
// Convert to USOU points
x1 = (bb.width * x1) + bb.x;
y1 = (bb.height * y1) + bb.y;
x2 = (bb.width * x2) + bb.x;
y2 = (bb.height * y2) + bb.y;
x1 = (bb.width * x1) + bb.x
y1 = (bb.height * y1) + bb.y
x2 = (bb.width * x2) + bb.x
y2 = (bb.height * y2) + bb.y
// Transform those points
const pt1 = transformPoint(x1, y1, m);
const pt2 = transformPoint(x2, y2, m);
const pt1 = transformPoint(x1, y1, m)
const pt2 = transformPoint(x2, y2, m)
// Convert back to BB points
const gCoords = {
@@ -566,19 +567,19 @@ export const reorientGrads = (elem, m) => {
y1: (pt1.y - bb.y) / bb.height,
x2: (pt2.x - bb.x) / bb.width,
y2: (pt2.y - bb.y) / bb.height
};
const newgrad = grad.cloneNode(true);
for (const [ key, value ] of Object.entries(gCoords)) {
newgrad.setAttribute(key, value);
}
newgrad.id = svgCanvas.getNextId();
findDefs().append(newgrad);
elem.setAttribute(type, 'url(#' + newgrad.id + ')');
const newgrad = grad.cloneNode(true)
for (const [key, value] of Object.entries(gCoords)) {
newgrad.setAttribute(key, value)
}
newgrad.id = svgCanvas.getNextId()
findDefs().append(newgrad)
elem.setAttribute(type, 'url(#' + newgrad.id + ')')
}
}
}
};
}
/**
* This is how we map paths to our preferred relative segment types.
@@ -588,7 +589,7 @@ export const reorientGrads = (elem, m) => {
const pathMap = [
0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
];
]
/**
* Convert a path to one with only absolute or relative values.
@@ -599,159 +600,159 @@ const pathMap = [
* @returns {string}
*/
export const convertPath = (pth, toRel) => {
const { pathSegList } = pth;
const len = pathSegList.numberOfItems;
let curx = 0; let cury = 0;
let d = '';
let lastM = null;
const { pathSegList } = pth
const len = pathSegList.numberOfItems
let curx = 0; let cury = 0
let d = ''
let lastM = null
for (let i = 0; i < len; ++i) {
const seg = pathSegList.getItem(i);
const seg = pathSegList.getItem(i)
// if these properties are not in the segment, set them to zero
let x = seg.x || 0;
let y = seg.y || 0;
let x1 = seg.x1 || 0;
let y1 = seg.y1 || 0;
let x2 = seg.x2 || 0;
let y2 = seg.y2 || 0;
let x = seg.x || 0
let y = seg.y || 0
let x1 = seg.x1 || 0
let y1 = seg.y1 || 0
let x2 = seg.x2 || 0
let y2 = seg.y2 || 0
const type = seg.pathSegType;
let letter = pathMap[type][toRel ? 'toLowerCase' : 'toUpperCase']();
const type = seg.pathSegType
let letter = pathMap[type][toRel ? 'toLowerCase' : 'toUpperCase']()
switch (type) {
case 1: // z,Z closepath (Z/z)
d += 'z';
if (lastM && !toRel) {
curx = lastM[0];
cury = lastM[1];
}
break;
case 12: // absolute horizontal line (H)
x -= curx;
case 1: // z,Z closepath (Z/z)
d += 'z'
if (lastM && !toRel) {
curx = lastM[0]
cury = lastM[1]
}
break
case 12: // absolute horizontal line (H)
x -= curx
// Fallthrough
case 13: // relative horizontal line (h)
if (toRel) {
y = 0;
curx += x;
letter = 'l';
} else {
y = cury;
x += curx;
curx = x;
letter = 'L';
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [ [ x, y ] ]);
break;
case 14: // absolute vertical line (V)
y -= cury;
case 13: // relative horizontal line (h)
if (toRel) {
y = 0
curx += x
letter = 'l'
} else {
y = cury
x += curx
curx = x
letter = 'L'
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [[x, y]])
break
case 14: // absolute vertical line (V)
y -= cury
// Fallthrough
case 15: // relative vertical line (v)
if (toRel) {
x = 0;
cury += y;
letter = 'l';
} else {
x = curx;
y += cury;
cury = y;
letter = 'L';
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [ [ x, y ] ]);
break;
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;
case 15: // relative vertical line (v)
if (toRel) {
x = 0
cury += y
letter = 'l'
} else {
x = curx
y += cury
cury = y
letter = 'L'
}
// Convert to "line" for easier editing
d += pathDSegment(letter, [[x, y]])
break
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
case 5: // relative line (l)
case 3: // relative move (m)
case 19: // relative smooth quad (t)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
if (type === 2 || type === 3) { lastM = [ curx, cury ]; }
case 5: // relative line (l)
case 3: // relative move (m)
case 19: // relative smooth quad (t)
if (toRel) {
curx += x
cury += y
} else {
x += curx
y += cury
curx = x
cury = y
}
if (type === 2 || type === 3) { lastM = [curx, cury] }
d += pathDSegment(letter, [ [ x, y ] ]);
break;
case 6: // absolute cubic (C)
x -= curx; x1 -= curx; x2 -= curx;
y -= cury; y1 -= cury; y2 -= cury;
d += pathDSegment(letter, [[x, y]])
break
case 6: // absolute cubic (C)
x -= curx; x1 -= curx; x2 -= curx
y -= cury; y1 -= cury; y2 -= cury
// Fallthrough
case 7: // relative cubic (c)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx; x1 += curx; x2 += curx;
y += cury; y1 += cury; y2 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [ [ x1, y1 ], [ x2, y2 ], [ x, y ] ]);
break;
case 8: // absolute quad (Q)
x -= curx; x1 -= curx;
y -= cury; y1 -= cury;
case 7: // relative cubic (c)
if (toRel) {
curx += x
cury += y
} else {
x += curx; x1 += curx; x2 += curx
y += cury; y1 += cury; y2 += cury
curx = x
cury = y
}
d += pathDSegment(letter, [[x1, y1], [x2, y2], [x, y]])
break
case 8: // absolute quad (Q)
x -= curx; x1 -= curx
y -= cury; y1 -= cury
// Fallthrough
case 9: // relative quad (q)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx; x1 += curx;
y += cury; y1 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [ [ x1, y1 ], [ x, y ] ]);
break;
case 9: // relative quad (q)
if (toRel) {
curx += x
cury += y
} else {
x += curx; x1 += curx
y += cury; y1 += cury
curx = x
cury = y
}
d += pathDSegment(letter, [[x1, y1], [x, y]])
break
// Fallthrough
case 11: // relative elliptical arc (a)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx;
y += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [ [ seg.r1, seg.r2 ] ], [
seg.angle,
(seg.largeArcFlag ? 1 : 0),
(seg.sweepFlag ? 1 : 0)
], [ x, y ]);
break;
case 16: // absolute smooth cubic (S)
x -= curx; x2 -= curx;
y -= cury; y2 -= cury;
case 11: // relative elliptical arc (a)
if (toRel) {
curx += x
cury += y
} else {
x += curx
y += cury
curx = x
cury = y
}
d += pathDSegment(letter, [[seg.r1, seg.r2]], [
seg.angle,
(seg.largeArcFlag ? 1 : 0),
(seg.sweepFlag ? 1 : 0)
], [x, y])
break
case 16: // absolute smooth cubic (S)
x -= curx; x2 -= curx
y -= cury; y2 -= cury
// Fallthrough
case 17: // relative smooth cubic (s)
if (toRel) {
curx += x;
cury += y;
} else {
x += curx; x2 += curx;
y += cury; y2 += cury;
curx = x;
cury = y;
}
d += pathDSegment(letter, [ [ x2, y2 ], [ x, y ] ]);
break;
case 17: // relative smooth cubic (s)
if (toRel) {
curx += x
cury += y
} else {
x += curx; x2 += curx
y += cury; y2 += cury
curx = x
cury = y
}
d += pathDSegment(letter, [[x2, y2], [x, y]])
break
} // switch on path segment type
} // for each segment
return d;
};
return d
}
/**
* TODO: refactor callers in `convertPath` to use `getPathDFromSegments` instead of this function.
@@ -764,21 +765,21 @@ export const convertPath = (pth, toRel) => {
*/
const pathDSegment = (letter, points, morePoints, lastPoint) => {
points.forEach((pnt, i) => {
points[i] = shortFloat(pnt);
});
let segment = letter + points.join(' ');
points[i] = shortFloat(pnt)
})
let segment = letter + points.join(' ')
if (morePoints) {
segment += ' ' + morePoints.join(' ');
segment += ' ' + morePoints.join(' ')
}
if (lastPoint) {
segment += ' ' + shortFloat(lastPoint);
segment += ' ' + shortFloat(lastPoint)
}
return segment;
};
return segment
}
/**
* Group: Path edit functions.
* Functions relating to editing path elements.
*/
export const pathActions = pathActionsMethod;
export const pathActions = pathActionsMethod
// end pathActions

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import { getReverseNS, NS } from './namespaces.js';
import { getHref, setHref, getUrlFromAttr } from './utilities.js';
import { getReverseNS, NS } from './namespaces.js'
import { getHref, setHref, getUrlFromAttr } from './utilities.js'
const REVERSE_NS = getReverseNS();
const REVERSE_NS = getReverseNS()
// Todo: Split out into core attributes, presentation attributes, etc. so consistent
/**
@@ -18,102 +18,102 @@ const REVERSE_NS = getReverseNS();
* @type {PlainObject}
*/
/* eslint-disable max-len */
const svgGenericWhiteList = [ 'class', 'id', 'display', 'transform', 'style' ];
const svgGenericWhiteList = ['class', 'id', 'display', 'transform', 'style']
const svgWhiteList_ = {
// SVG Elements
a: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title' ],
circle: [ 'clip-path', 'clip-rule', 'cx', 'cy', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage' ],
clipPath: [ 'clipPathUnits' ],
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
circle: ['clip-path', 'clip-rule', 'cx', 'cy', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
clipPath: ['clipPathUnits'],
defs: [],
desc: [],
ellipse: [ 'clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage' ],
feBlend: [ 'in', 'in2' ],
feColorMatrix: [ 'in', 'type', 'value', 'result', 'values' ],
feComposite: [ 'in', 'operator', 'result', 'in2' ],
feFlood: [ 'flood-color', 'in', 'result', 'flood-opacity' ],
feGaussianBlur: [ 'color-interpolation-filters', 'in', 'requiredFeatures', 'stdDeviation', 'result' ],
ellipse: ['clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
feBlend: ['in', 'in2'],
feColorMatrix: ['in', 'type', 'value', 'result', 'values'],
feComposite: ['in', 'operator', 'result', 'in2'],
feFlood: ['flood-color', 'in', 'result', 'flood-opacity'],
feGaussianBlur: ['color-interpolation-filters', 'in', 'requiredFeatures', 'stdDeviation', 'result'],
feMerge: [],
feMergeNode: [ 'in' ],
feMorphology: [ 'in', 'operator', 'radius' ],
feOffset: [ 'dx', 'in', 'dy', 'result' ],
filter: [ 'color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y' ],
foreignObject: [ 'font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y' ],
g: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor' ],
image: [ 'clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'xlink:href', 'xlink:title', 'y' ],
line: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2' ],
linearGradient: [ 'gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2' ],
marker: [ 'markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox' ],
mask: [ 'height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y' ],
metadata: [ ],
path: [ 'clip-path', 'clip-rule', 'd', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage' ],
pattern: [ 'height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y' ],
polygon: [ 'clip-path', 'clip-rule', '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', 'systemLanguage', 'sides', 'shape', 'edge', 'point', 'starRadiusMultiplier', 'r', 'radialshift' ],
polyline: [ 'clip-path', 'clip-rule', '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', 'systemLanguage', 'se:connector' ],
radialGradient: [ 'cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href' ],
rect: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'width', 'x', 'y' ],
stop: [ 'offset', 'requiredFeatures', 'stop-opacity', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform' ],
style: [ 'type' ],
svg: [ 'clip-path', 'clip-rule', 'enable-background', 'filter', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'systemLanguage', 'version', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xmlns:oi', 'oi:animations', 'y', 'stroke-linejoin', 'fill-rule', 'aria-label', 'stroke-width', 'fill-rule', 'xml:space' ],
switch: [ 'requiredFeatures', 'systemLanguage' ],
symbol: [ 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'viewBox', 'width', 'height' ],
text: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'x', 'xml:space', 'y' ],
textPath: [ 'method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href' ],
feMergeNode: ['in'],
feMorphology: ['in', 'operator', 'radius'],
feOffset: ['dx', 'in', 'dy', 'result'],
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
foreignObject: ['font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y'],
g: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
image: ['clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'xlink:href', 'xlink:title', 'y'],
line: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2'],
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'],
marker: ['markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox'],
mask: ['height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'],
metadata: [],
path: ['clip-path', 'clip-rule', 'd', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'],
polygon: ['clip-path', 'clip-rule', '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', 'systemLanguage', 'sides', 'shape', 'edge', 'point', 'starRadiusMultiplier', 'r', 'radialshift'],
polyline: ['clip-path', 'clip-rule', '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', 'systemLanguage', 'se:connector'],
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'],
rect: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'width', 'x', 'y'],
stop: ['offset', 'requiredFeatures', 'stop-opacity', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform'],
style: ['type'],
svg: ['clip-path', 'clip-rule', 'enable-background', 'filter', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'systemLanguage', 'version', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xmlns:oi', 'oi:animations', 'y', 'stroke-linejoin', 'fill-rule', 'aria-label', 'stroke-width', 'fill-rule', 'xml:space'],
switch: ['requiredFeatures', 'systemLanguage'],
symbol: ['fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'viewBox', 'width', 'height'],
text: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'x', 'xml:space', 'y'],
textPath: ['method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
title: [],
tspan: [ 'clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'textLength', 'x', 'xml:space', 'y' ],
use: [ 'clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow' ],
tspan: ['clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'textLength', 'x', 'xml:space', 'y'],
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
// MathML Elements
annotation: [ 'encoding' ],
'annotation-xml': [ 'encoding' ],
maction: [ 'actiontype', 'other', 'selection' ],
math: [ 'xmlns' ],
menclose: [ 'notation' ],
annotation: ['encoding'],
'annotation-xml': ['encoding'],
maction: ['actiontype', 'other', 'selection'],
math: ['xmlns'],
menclose: ['notation'],
merror: [],
mfrac: [ 'linethickness' ],
mi: [ 'mathvariant' ],
mfrac: ['linethickness'],
mi: ['mathvariant'],
mmultiscripts: [],
mn: [],
mo: [ 'fence', 'lspace', 'maxsize', 'minsize', 'rspace', 'stretchy' ],
mo: ['fence', 'lspace', 'maxsize', 'minsize', 'rspace', 'stretchy'],
mover: [],
mpadded: [ 'lspace', 'width', 'height', 'depth', 'voffset' ],
mpadded: ['lspace', 'width', 'height', 'depth', 'voffset'],
mphantom: [],
mprescripts: [],
mroot: [],
mrow: [ 'xlink:href', 'xlink:type', 'xmlns:xlink' ],
mspace: [ 'depth', 'height', 'width' ],
mrow: ['xlink:href', 'xlink:type', 'xmlns:xlink'],
mspace: ['depth', 'height', 'width'],
msqrt: [],
mstyle: [ 'displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel' ],
mstyle: ['displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel'],
msub: [],
msubsup: [],
msup: [],
mtable: [ 'align', 'columnalign', 'columnlines', 'columnspacing', 'displaystyle', 'equalcolumns', 'equalrows', 'frame', 'rowalign', 'rowlines', 'rowspacing', 'width' ],
mtd: [ 'columnalign', 'columnspan', 'rowalign', 'rowspan' ],
mtable: ['align', 'columnalign', 'columnlines', 'columnspacing', 'displaystyle', 'equalcolumns', 'equalrows', 'frame', 'rowalign', 'rowlines', 'rowspacing', 'width'],
mtd: ['columnalign', 'columnspan', 'rowalign', 'rowspan'],
mtext: [],
mtr: [ 'columnalign', 'rowalign' ],
mtr: ['columnalign', 'rowalign'],
munder: [],
munderover: [],
none: [],
semantics: []
};
}
/* eslint-enable max-len */
// add generic attributes to all elements of the whitelist
Object.keys(svgWhiteList_).forEach((element) => svgWhiteList_[element] = [ ...svgWhiteList_[element], ...svgGenericWhiteList ]);
Object.keys(svgWhiteList_).forEach((element) => { svgWhiteList_[element] = [...svgWhiteList_[element], ...svgGenericWhiteList] })
// Produce a Namespace-aware version of svgWhitelist
const svgWhiteListNS_ = {};
Object.entries(svgWhiteList_).forEach(([ elt, atts ]) => {
const attNS = {};
Object.entries(atts).forEach(function ([ _i, att ]) {
const svgWhiteListNS_ = {}
Object.entries(svgWhiteList_).forEach(([elt, atts]) => {
const attNS = {}
Object.entries(atts).forEach(function ([_i, att]) {
if (att.includes(':')) {
const v = att.split(':');
attNS[v[1]] = NS[(v[0]).toUpperCase()];
const v = att.split(':')
attNS[v[1]] = NS[(v[0]).toUpperCase()]
} else {
attNS[att] = att === 'xmlns' ? NS.XMLNS : null;
attNS[att] = att === 'xmlns' ? NS.XMLNS : null
}
});
svgWhiteListNS_[elt] = attNS;
});
})
svgWhiteListNS_[elt] = attNS
})
/**
* Sanitizes the input node and its children.
@@ -126,127 +126,127 @@ export const sanitizeSvg = function (node) {
// Cleanup text nodes
if (node.nodeType === 3) { // 3 === TEXT_NODE
// Trim whitespace
node.nodeValue = node.nodeValue.trim();
node.nodeValue = node.nodeValue.trim()
// Remove if empty
if (!node.nodeValue.length) {
node.remove();
node.remove()
}
}
// We only care about element nodes.
// Automatically return for all non-element nodes, such as comments, etc.
if (node.nodeType !== 1) { // 1 == ELEMENT_NODE
return;
return
}
const doc = node.ownerDocument;
const parent = node.parentNode;
const doc = node.ownerDocument
const parent = node.parentNode
// can parent ever be null here? I think the root node's parent is the document...
if (!doc || !parent) {
return;
return
}
const allowedAttrs = svgWhiteList_[node.nodeName];
const allowedAttrsNS = svgWhiteListNS_[node.nodeName];
const allowedAttrs = svgWhiteList_[node.nodeName]
const allowedAttrsNS = svgWhiteListNS_[node.nodeName]
// if this element is supported, sanitize it
if (typeof allowedAttrs !== 'undefined') {
const seAttrs = [];
let i = node.attributes.length;
const seAttrs = []
let i = node.attributes.length
while (i--) {
// if the attribute is not in our whitelist, then remove it
const attr = node.attributes.item(i);
const attrName = attr.nodeName;
const attrLocalName = attr.localName;
const attrNsURI = attr.namespaceURI;
const attr = node.attributes.item(i)
const attrName = attr.nodeName
const attrLocalName = attr.localName
const attrNsURI = attr.namespaceURI
// Check that an attribute with the correct localName in the correct namespace is on
// our whitelist or is a namespace declaration for one of our allowed namespaces
if ( attrNsURI !== allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS
&& !(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value]) ) {
if (attrNsURI !== allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS &&
!(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) {
// Bypassing the whitelist to allow se: and oi: prefixes
// We can add specific namepaces on demand for now.
// Is there a more appropriate way to do this?
if (attrName.startsWith('se:') || attrName.startsWith('oi:')|| attrName.startsWith('data-')) {
if (attrName.startsWith('se:') || attrName.startsWith('oi:') || attrName.startsWith('data-')) {
// We should bypass the namespace aswell
const seAttrNS = (attrName.startsWith('se:')) ? NS.SE : ((attrName.startsWith('oi:')) ? NS.OI : null);
seAttrs.push([ attrName, attr.value, seAttrNS ]);
const seAttrNS = (attrName.startsWith('se:')) ? NS.SE : ((attrName.startsWith('oi:')) ? NS.OI : null)
seAttrs.push([attrName, attr.value, seAttrNS])
} else {
console.warn(`sanitizeSvg: attribute ${attrName} in element ${node.nodeName} not in whitelist is removed`);
node.removeAttributeNS(attrNsURI, attrLocalName);
console.warn(`sanitizeSvg: attribute ${attrName} in element ${node.nodeName} not in whitelist is removed`)
node.removeAttributeNS(attrNsURI, attrLocalName)
}
}
// For the style attribute, rewrite it in terms of XML presentational attributes
if (attrName === 'style') {
const props = attr.value.split(';');
let p = props.length;
const props = attr.value.split(';')
let p = props.length
while (p--) {
const [ name, val ] = props[p].split(':');
const styleAttrName = (name || '').trim();
const styleAttrVal = (val || '').trim();
const [name, val] = props[p].split(':')
const styleAttrName = (name || '').trim()
const styleAttrVal = (val || '').trim()
// Now check that this attribute is supported
if (allowedAttrs.includes(styleAttrName)) {
node.setAttribute(styleAttrName, styleAttrVal);
node.setAttribute(styleAttrName, styleAttrVal)
}
}
node.removeAttribute('style');
node.removeAttribute('style')
}
}
Object.values(seAttrs).forEach(([ att, val, ns ]) => {
node.setAttributeNS(ns, att, val);
});
Object.values(seAttrs).forEach(([att, val, ns]) => {
node.setAttributeNS(ns, att, val)
})
// for some elements that have a xlink:href, ensure the URI refers to a local element
// (but not for links)
const href = getHref(node);
const href = getHref(node)
if (href &&
[ 'filter', 'linearGradient', 'pattern',
'radialGradient', 'textPath', 'use' ].includes(node.nodeName) && href[0] !== '#') {
['filter', 'linearGradient', 'pattern',
'radialGradient', 'textPath', 'use'].includes(node.nodeName) && href[0] !== '#') {
// remove the attribute (but keep the element)
setHref(node, '');
console.warn(`sanitizeSvg: attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed`);
node.removeAttributeNS(NS.XLINK, 'href');
setHref(node, '')
console.warn(`sanitizeSvg: attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed`)
node.removeAttributeNS(NS.XLINK, 'href')
}
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
if (node.nodeName === 'use' && !getHref(node)) {
console.warn(`sanitizeSvg: element ${node.nodeName} without a xlink:href is removed`);
node.remove();
return;
console.warn(`sanitizeSvg: element ${node.nodeName} without a xlink:href is removed`)
node.remove()
return
}
// if the element has attributes pointing to a non-local reference,
// need to remove the attribute
Object.values([ 'clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' ], function (attr) {
let val = node.getAttribute(attr);
Object.values(['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'], function (attr) {
let val = node.getAttribute(attr)
if (val) {
val = getUrlFromAttr(val);
val = getUrlFromAttr(val)
// simply check for first character being a '#'
if (val && val[0] !== '#') {
node.setAttribute(attr, '');
console.warn(`sanitizeSvg: attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed`);
node.removeAttribute(attr);
node.setAttribute(attr, '')
console.warn(`sanitizeSvg: attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed`)
node.removeAttribute(attr)
}
}
});
})
// recurse to children
i = node.childNodes.length;
while (i--) { sanitizeSvg(node.childNodes.item(i)); }
i = node.childNodes.length
while (i--) { sanitizeSvg(node.childNodes.item(i)) }
// else (element not supported), remove it
} else {
// remove all children from this node and insert them before this node
// TODO: in the case of animation elements this will hardly ever be correct
console.warn(`sanitizeSvg: element ${node.nodeName} not supported is removed`);
const children = [];
console.warn(`sanitizeSvg: element ${node.nodeName} not supported is removed`)
const children = []
while (node.hasChildNodes()) {
children.push(parent.insertBefore(node.firstChild, node));
children.push(parent.insertBefore(node.firstChild, node))
}
// remove this node from the document altogether
node.remove();
node.remove()
// call sanitizeSvg on each of those children
let i = children.length;
while (i--) { sanitizeSvg(children[i]); }
let i = children.length
while (i--) { sanitizeSvg(children[i]) }
}
};
}

View File

@@ -6,13 +6,13 @@
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import { isTouch, isWebkit } from '../common/browser.js'; // , isOpera
import { getRotationAngle, getBBox, getStrokedBBox, isNullish } from './utilities.js';
import { transformListToTransform, transformBox, transformPoint } from './math.js';
import { isTouch, isWebkit } from '../common/browser.js' // , isOpera
import { getRotationAngle, getBBox, getStrokedBBox, isNullish } from './utilities.js'
import { transformListToTransform, transformBox, transformPoint } from './math.js'
let svgCanvas;
let selectorManager_; // A Singleton
const gripRadius = isTouch() ? 10 : 4;
let svgCanvas
let selectorManager_ // A Singleton
const gripRadius = isTouch() ? 10 : 4
/**
* Private class for DOM element selection boxes.
@@ -23,21 +23,21 @@ 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;
this.id = id
// this holds a reference to the element for which this selector is being used
this.selectedElement = elem;
this.selectedElement = elem
// this is a flag used internally to track whether the selector is being used or not
this.locked = true;
this.locked = true
// this holds a reference to the <g> element that holds all visual elements of the selector
this.selectorGroup = svgCanvas.createSVGElement({
element: 'g',
attr: { id: ('selectorGroup' + this.id) }
});
})
// this holds a reference to the path rect
this.selectorRect = svgCanvas.createSVGElement({
@@ -51,8 +51,8 @@ export class Selector {
// need to specify this so that the rect is not selectable
style: 'pointer-events:none'
}
});
this.selectorGroup.append(this.selectorRect);
})
this.selectorGroup.append(this.selectorRect)
// this holds a reference to the grip coordinates for this selector
this.gripCoords = {
@@ -64,9 +64,9 @@ export class Selector {
s: null,
sw: null,
w: null
};
}
this.reset(this.selectedElement, bbox);
this.reset(this.selectedElement, bbox)
}
/**
@@ -75,11 +75,11 @@ export class Selector {
* @param {module:utilities.BBoxObject} bbox - Optional bbox to use for reset (prevents duplicate getBBox call).
* @returns {void}
*/
reset(e, bbox) {
this.locked = true;
this.selectedElement = e;
this.resize(bbox);
this.selectorGroup.setAttribute('display', 'inline');
reset (e, bbox) {
this.locked = true
this.selectedElement = e
this.resize(bbox)
this.selectorGroup.setAttribute('display', 'inline')
}
/**
@@ -87,14 +87,14 @@ export class Selector {
* @param {boolean} show - Indicates whether grips should be shown or not
* @returns {void}
*/
showGrips(show) {
const bShow = show ? 'inline' : 'none';
selectorManager_.selectorGripsGroup.setAttribute('display', bShow);
const elem = this.selectedElement;
this.hasGrips = show;
showGrips (show) {
const bShow = show ? 'inline' : 'none'
selectorManager_.selectorGripsGroup.setAttribute('display', bShow)
const elem = this.selectedElement
this.hasGrips = show
if (elem && show) {
this.selectorGroup.append(selectorManager_.selectorGripsGroup);
Selector.updateGripCursors(getRotationAngle(elem));
this.selectorGroup.append(selectorManager_.selectorGripsGroup)
Selector.updateGripCursors(getRotationAngle(elem))
}
}
@@ -103,132 +103,132 @@ export class Selector {
* @param {module:utilities.BBoxObject} [bbox] - BBox to use for resize (prevents duplicate getBBox call).
* @returns {void}
*/
resize(bbox) {
const dataStorage = svgCanvas.getDataStorage();
const selectedBox = this.selectorRect;
const mgr = selectorManager_;
const selectedGrips = mgr.selectorGrips;
const selected = this.selectedElement;
const zoom = svgCanvas.getZoom();
let offset = 1 / zoom;
const sw = selected.getAttribute('stroke-width');
resize (bbox) {
const dataStorage = svgCanvas.getDataStorage()
const selectedBox = this.selectorRect
const mgr = selectorManager_
const selectedGrips = mgr.selectorGrips
const selected = this.selectedElement
const zoom = svgCanvas.getZoom()
let offset = 1 / zoom
const sw = selected.getAttribute('stroke-width')
if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
offset += (sw / 2);
offset += (sw / 2)
}
const { tagName } = selected;
const { tagName } = selected
if (tagName === 'text') {
offset += 2 / zoom;
offset += 2 / zoom
}
// loop and transform our bounding box until we reach our first rotation
const tlist = selected.transform.baseVal;
const m = transformListToTransform(tlist).matrix;
const tlist = selected.transform.baseVal
const m = transformListToTransform(tlist).matrix
// This should probably be handled somewhere else, but for now
// it keeps the selection box correctly positioned when zoomed
m.e *= zoom;
m.f *= zoom;
m.e *= zoom
m.f *= zoom
if (!bbox) {
bbox = getBBox(selected);
bbox = getBBox(selected)
}
// 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' && !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 ]);
const strokedBbox = getStrokedBBox([selected.childNodes])
if (strokedBbox) {
bbox = strokedBbox;
bbox = strokedBbox
}
}
// apply the transforms
const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height;
const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height
// bbox = {x: l, y: t, width: w, height: h}; // Not in use
// we need to handle temporary transforms too
// if skewed, get its transformed box, then find its axis-aligned bbox
// *
offset *= zoom;
offset *= zoom
const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m);
const { aabox } = nbox;
let nbax = aabox.x - offset;
let nbay = aabox.y - offset;
let nbaw = aabox.width + (offset * 2);
let nbah = aabox.height + (offset * 2);
const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m)
const { aabox } = nbox
let nbax = aabox.x - offset
let nbay = aabox.y - offset
let nbaw = aabox.width + (offset * 2)
let nbah = aabox.height + (offset * 2)
// now if the shape is rotated, un-rotate it
const cx = nbax + nbaw / 2;
const cy = nbay + nbah / 2;
const cx = nbax + nbaw / 2
const cy = nbay + nbah / 2
const angle = getRotationAngle(selected);
const angle = getRotationAngle(selected)
if (angle) {
const rot = svgCanvas.getSvgRoot().createSVGTransform();
rot.setRotate(-angle, cx, cy);
const rotm = rot.matrix;
nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm);
nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm);
nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm);
nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm);
const rot = svgCanvas.getSvgRoot().createSVGTransform()
rot.setRotate(-angle, cx, cy)
const rotm = rot.matrix
nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm)
nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm)
nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm)
nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm)
// calculate the axis-aligned bbox
const { tl } = nbox;
let minx = tl.x;
let miny = tl.y;
let maxx = tl.x;
let maxy = tl.y;
const { tl } = nbox
let minx = tl.x
let miny = tl.y
let maxx = tl.x
let 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;
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset;
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset;
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
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset
nbax = minx;
nbay = miny;
nbaw = (maxx - minx);
nbah = (maxy - miny);
nbax = minx
nbay = miny
nbaw = (maxx - minx)
nbah = (maxy - miny)
}
const dstr = 'M' + nbax + ',' + nbay +
' L' + (nbax + nbaw) + ',' + nbay +
' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
' ' + nbax + ',' + (nbay + nbah) + 'z';
' ' + nbax + ',' + (nbay + nbah) + 'z'
const xform = angle ? 'rotate(' + [ angle, cx, cy ].join(',') + ')' : '';
const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
// TODO(codedread): Is this needed?
// if (selected === selectedElements[0]) {
this.gripCoords = {
nw: [ nbax, nbay ],
ne: [ nbax + nbaw, nbay ],
sw: [ nbax, nbay + nbah ],
se: [ nbax + nbaw, nbay + nbah ],
n: [ nbax + (nbaw) / 2, nbay ],
w: [ nbax, nbay + (nbah) / 2 ],
e: [ nbax + nbaw, nbay + (nbah) / 2 ],
s: [ nbax + (nbaw) / 2, nbay + nbah ]
};
selectedBox.setAttribute('d', dstr);
this.selectorGroup.setAttribute('transform', xform);
Object.entries(this.gripCoords).forEach(([ dir, coords ]) => {
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
});
nw: [nbax, nbay],
ne: [nbax + nbaw, nbay],
sw: [nbax, nbay + nbah],
se: [nbax + nbaw, nbay + nbah],
n: [nbax + (nbaw) / 2, nbay],
w: [nbax, nbay + (nbah) / 2],
e: [nbax + nbaw, nbay + (nbah) / 2],
s: [nbax + (nbaw) / 2, nbay + nbah]
}
selectedBox.setAttribute('d', dstr)
this.selectorGroup.setAttribute('transform', xform)
Object.entries(this.gripCoords).forEach(([dir, coords]) => {
selectedGrips[dir].setAttribute('cx', coords[0])
selectedGrips[dir].setAttribute('cy', coords[1])
})
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2);
mgr.rotateGripConnector.setAttribute('y1', nbay);
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2);
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5));
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
mgr.rotateGripConnector.setAttribute('y1', nbay)
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2);
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5));
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
// }
}
@@ -238,17 +238,17 @@ export class Selector {
* @param {Float} angle - Current rotation angle in degrees
* @returns {void}
*/
static updateGripCursors(angle) {
const dirArr = Object.keys(selectorManager_.selectorGrips);
let steps = Math.round(angle / 45);
if (steps < 0) { steps += 8; }
static updateGripCursors (angle) {
const dirArr = Object.keys(selectorManager_.selectorGrips)
let steps = Math.round(angle / 45)
if (steps < 0) { steps += 8 }
while (steps > 0) {
dirArr.push(dirArr.shift());
steps--;
dirArr.push(dirArr.shift())
steps--
}
Object.values(selectorManager_.selectorGrips).forEach((gripElement, i) => {
gripElement.setAttribute('style', ('cursor:' + dirArr[i] + '-resize'));
});
gripElement.setAttribute('style', ('cursor:' + dirArr[i] + '-resize'))
})
}
}
@@ -259,18 +259,18 @@ 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;
this.selectorParentGroup = null
// this is a special rect that is used for multi-select
this.rubberBandBox = null;
this.rubberBandBox = null
// this will hold objects of type Selector (see above)
this.selectors = [];
this.selectors = []
// this holds a map of SVG elements to their Selector object
this.selectorMap = {};
this.selectorMap = {}
// this holds a reference to the grip elements
this.selectorGrips = {
@@ -282,41 +282,41 @@ export class SelectorManager {
s: null,
sw: null,
w: null
};
}
this.selectorGripsGroup = null;
this.rotateGripConnector = null;
this.rotateGrip = null;
this.selectorGripsGroup = null
this.rotateGripConnector = null
this.rotateGrip = null
this.initGroup();
this.initGroup()
}
/**
* Resets the parent selector group element.
* @returns {void}
*/
initGroup() {
const dataStorage = svgCanvas.getDataStorage();
initGroup () {
const dataStorage = svgCanvas.getDataStorage()
// remove old selector parent group if it existed
if (this.selectorParentGroup && this.selectorParentGroup.parentNode) {
this.selectorParentGroup.remove();
this.selectorParentGroup.remove()
}
// create parent selector group and add it to svgroot
this.selectorParentGroup = svgCanvas.createSVGElement({
element: 'g',
attr: { id: 'selectorParentGroup' }
});
})
this.selectorGripsGroup = svgCanvas.createSVGElement({
element: 'g',
attr: { display: 'none' }
});
this.selectorParentGroup.append(this.selectorGripsGroup);
svgCanvas.getSvgRoot().append(this.selectorParentGroup);
})
this.selectorParentGroup.append(this.selectorGripsGroup)
svgCanvas.getSvgRoot().append(this.selectorParentGroup)
this.selectorMap = {};
this.selectors = [];
this.rubberBandBox = null;
this.selectorMap = {}
this.selectors = []
this.rubberBandBox = null
// add the corner grips
Object.keys(this.selectorGrips).forEach((dir) => {
@@ -334,13 +334,13 @@ export class SelectorManager {
'stroke-width': 2,
'pointer-events': 'all'
}
});
})
dataStorage.put(grip, 'dir', dir);
dataStorage.put(grip, 'type', 'resize');
this.selectorGrips[dir] = grip;
this.selectorGripsGroup.append(grip);
});
dataStorage.put(grip, 'dir', dir)
dataStorage.put(grip, 'type', 'resize')
this.selectorGrips[dir] = grip
this.selectorGripsGroup.append(grip)
})
// add rotator elems
this.rotateGripConnector =
@@ -351,8 +351,8 @@ export class SelectorManager {
stroke: '#22C',
'stroke-width': '1'
}
});
this.selectorGripsGroup.append(this.rotateGripConnector);
})
this.selectorGripsGroup.append(this.rotateGripConnector)
this.rotateGrip =
svgCanvas.createSVGElement({
@@ -365,13 +365,13 @@ export class SelectorManager {
'stroke-width': 2,
style: `cursor:url(${svgCanvas.curConfig.imgPath}/rotate.svg) 12 12, auto;`
}
});
this.selectorGripsGroup.append(this.rotateGrip);
dataStorage.put(this.rotateGrip, 'type', 'rotate');
})
this.selectorGripsGroup.append(this.rotateGrip)
dataStorage.put(this.rotateGrip, 'type', 'rotate')
if (document.getElementById('canvasBackground')) { return; }
if (document.getElementById('canvasBackground')) { return }
const [ width, height ] = svgCanvas.curConfig.dimensions;
const [width, height] = svgCanvas.curConfig.dimensions
const canvasbg = svgCanvas.createSVGElement({
element: 'svg',
attr: {
@@ -383,7 +383,7 @@ export class SelectorManager {
overflow: (isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out
style: 'pointer-events:none'
}
});
})
const rect = svgCanvas.createSVGElement({
element: 'rect',
@@ -397,9 +397,9 @@ export class SelectorManager {
fill: '#FFF',
style: 'pointer-events:none'
}
});
canvasbg.append(rect);
svgCanvas.getSvgRoot().insertBefore(canvasbg, svgCanvas.getSvgContent());
})
canvasbg.append(rect)
svgCanvas.getSvgRoot().insertBefore(canvasbg, svgCanvas.getSvgContent())
}
/**
@@ -408,28 +408,28 @@ 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) {
if (!elem) { return null; }
requestSelector (elem, bbox) {
if (!elem) { return null }
const N = this.selectors.length;
const N = this.selectors.length
// If we've already acquired one for this element, return it.
if (typeof this.selectorMap[elem.id] === 'object') {
this.selectorMap[elem.id].locked = true;
return this.selectorMap[elem.id];
this.selectorMap[elem.id].locked = true
return this.selectorMap[elem.id]
}
for (let i = 0; i < N; ++i) {
if (this.selectors[i] && !this.selectors[i].locked) {
this.selectors[i].locked = true;
this.selectors[i].reset(elem, bbox);
this.selectorMap[elem.id] = this.selectors[i];
return this.selectors[i];
this.selectors[i].locked = true
this.selectors[i].reset(elem, bbox)
this.selectorMap[elem.id] = this.selectors[i]
return this.selectors[i]
}
}
// if we reached here, no available selectors were found, we create one
this.selectors[N] = new Selector(N, elem, bbox);
this.selectorParentGroup.append(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
this.selectors[N] = new Selector(N, elem, bbox)
this.selectorParentGroup.append(this.selectors[N].selectorGroup)
this.selectorMap[elem.id] = this.selectors[N]
return this.selectors[N]
}
/**
@@ -438,27 +438,27 @@ export class SelectorManager {
* @param {Element} elem - DOM element to remove the selector for
* @returns {void}
*/
releaseSelector(elem) {
if (isNullish(elem)) { return; }
const N = this.selectors.length;
const sel = this.selectorMap[elem.id];
releaseSelector (elem) {
if (isNullish(elem)) { return }
const N = this.selectors.length
const sel = this.selectorMap[elem.id]
if (sel && !sel.locked) {
// TODO(codedread): Ensure this exists in this module.
console.warn('WARNING! selector was released but was already unlocked');
console.warn('WARNING! selector was released but was already unlocked')
}
for (let i = 0; i < N; ++i) {
if (this.selectors[i] && this.selectors[i] === sel) {
delete this.selectorMap[elem.id];
sel.locked = false;
sel.selectedElement = null;
sel.showGrips(false);
delete this.selectorMap[elem.id]
sel.locked = false
sel.selectedElement = null
sel.showGrips(false)
// remove from DOM and store reference in JS but only if it exists in the DOM
try {
sel.selectorGroup.setAttribute('display', 'none');
} catch (e) {/* empty fn */ }
sel.selectorGroup.setAttribute('display', 'none')
} catch (e) { /* empty fn */ }
break;
break
}
}
}
@@ -467,7 +467,7 @@ 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 =
svgCanvas.createSVGElement({
@@ -481,10 +481,10 @@ export class SelectorManager {
display: 'none',
style: 'pointer-events:none'
}
});
this.selectorParentGroup.append(this.rubberBandBox);
})
this.selectorParentGroup.append(this.rubberBandBox)
}
return this.rubberBandBox;
return this.rubberBandBox
}
}
@@ -531,12 +531,12 @@ export class SelectorManager {
* @returns {void}
*/
export const init = (canvas) => {
svgCanvas = canvas;
selectorManager_ = new SelectorManager();
};
svgCanvas = canvas
selectorManager_ = new SelectorManager()
}
/**
* @function module:select.getSelectorManager
* @returns {module:select.SelectorManager} The SelectorManager instance.
*/
export const getSelectorManager = () => selectorManager_;
export const getSelectorManager = () => selectorManager_

File diff suppressed because it is too large Load Diff

View File

@@ -5,21 +5,21 @@
* @copyright 2011 Jeff Schiller
*/
import { NS } from "./namespaces.js";
import { NS } from './namespaces.js'
import {
getBBox,
getStrokedBBoxDefaultVisible
} from "./utilities.js";
} from './utilities.js'
import {
transformPoint,
transformListToTransform,
rectsIntersect
} from "./math.js";
import * as hstry from "./history.js";
import { getClosest } from "../editor/components/jgraduate/Util.js";
} from './math.js'
import * as hstry from './history.js'
import { getClosest } from '../editor/components/jgraduate/Util.js'
const { BatchCommand } = hstry;
let svgCanvas = null;
const { BatchCommand } = hstry
let svgCanvas = null
/**
* @function module:selection.init
@@ -27,8 +27,8 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
};
svgCanvas = canvas
}
/**
* Clears the selection. The 'selected' handler is then optionally called.
@@ -38,20 +38,20 @@ export const init = function (canvas) {
* @fires module:selection.SvgCanvas#event:selected
*/
export const clearSelectionMethod = function (noCall) {
const selectedElements = svgCanvas.getSelectedElements();
const selectedElements = svgCanvas.getSelectedElements()
selectedElements.forEach((elem) => {
if (!elem) {
return;
return
}
svgCanvas.selectorManager.releaseSelector(elem);
});
svgCanvas?.setEmptySelectedElements();
svgCanvas.selectorManager.releaseSelector(elem)
})
svgCanvas?.setEmptySelectedElements()
if (!noCall) {
svgCanvas.call("selected", svgCanvas.getSelectedElements());
svgCanvas.call('selected', svgCanvas.getSelectedElements())
}
};
}
/**
* Adds a list of elements to the selection. The 'selected' handler is then called.
@@ -60,56 +60,56 @@ export const clearSelectionMethod = function (noCall) {
* @fires module:selection.SvgCanvas#event:selected
*/
export const addToSelectionMethod = function (elemsToAdd, showGrips) {
const selectedElements = svgCanvas.getSelectedElements();
const selectedElements = svgCanvas.getSelectedElements()
if (!elemsToAdd.length) {
return;
return
}
// find the first null in our selectedElements array
let firstNull = 0;
let firstNull = 0
while (firstNull < selectedElements.length) {
if (selectedElements[firstNull] === null) {
break;
break
}
++firstNull;
++firstNull
}
// now add each element consecutively
let i = elemsToAdd.length;
let i = elemsToAdd.length
while (i--) {
let elem = elemsToAdd[i];
let elem = elemsToAdd[i]
if (!elem || !elem.getBBox) {
continue;
continue
}
if (elem.tagName === "a" && elem.childNodes.length === 1) {
if (elem.tagName === 'a' && elem.childNodes.length === 1) {
// Make "a" element's child be the selected element
elem = elem.firstChild;
elem = elem.firstChild
}
// if it's not already there, add it
if (!selectedElements.includes(elem)) {
selectedElements[firstNull] = elem;
selectedElements[firstNull] = elem
// only the first selectedBBoxes element is ever used in the codebase these days
// if (j === 0) selectedBBoxes[0] = utilsGetBBox(elem);
firstNull++;
const sel = svgCanvas.selectorManager.requestSelector(elem);
firstNull++
const sel = svgCanvas.selectorManager.requestSelector(elem)
if (selectedElements.length > 1) {
sel.showGrips(false);
sel.showGrips(false)
}
}
}
if (!selectedElements.length) {
return;
return
}
svgCanvas.call("selected", selectedElements);
svgCanvas.call('selected', selectedElements)
if (selectedElements.length === 1) {
svgCanvas.selectorManager
.requestSelector(selectedElements[0])
.showGrips(showGrips);
.showGrips(showGrips)
}
// make sure the elements are in the correct order
@@ -117,63 +117,63 @@ export const addToSelectionMethod = function (elemsToAdd, showGrips) {
selectedElements.sort(function (a, b) {
if (a && b && a.compareDocumentPosition) {
return 3 - (b.compareDocumentPosition(a) & 6);
return 3 - (b.compareDocumentPosition(a) & 6)
}
if (!a) {
return 1;
return 1
}
return 0;
});
return 0
})
// Make sure first elements are not null
while (!selectedElements[0]) {
selectedElements.shift(0);
selectedElements.shift(0)
}
};
}
/**
* @name module:svgcanvas.SvgCanvas#getMouseTarget
* @type {module:path.EditorContext#getMouseTarget}
*/
export const getMouseTargetMethod = function (evt) {
if (!evt) {
return null;
return null
}
let mouseTarget = evt.target;
let mouseTarget = evt.target
// if it was a <use>, Opera and WebKit return the SVGElementInstance
if (mouseTarget.correspondingUseElement) {
mouseTarget = mouseTarget.correspondingUseElement;
mouseTarget = mouseTarget.correspondingUseElement
}
// 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"
[NS.MATH, NS.HTML].includes(mouseTarget.namespaceURI) &&
mouseTarget.id !== 'svgcanvas'
) {
while (mouseTarget.nodeName !== "foreignObject") {
mouseTarget = mouseTarget.parentNode;
while (mouseTarget.nodeName !== 'foreignObject') {
mouseTarget = mouseTarget.parentNode
if (!mouseTarget) {
return svgCanvas.getSvgRoot();
return svgCanvas.getSvgRoot()
}
}
}
// Get the desired mouseTarget with jQuery selector-fu
// If it's root-like, select the root
const currentLayer = svgCanvas.getCurrentDrawing().getCurrentLayer();
const svgRoot = svgCanvas.getSvgRoot();
const container = svgCanvas.getDOMContainer();
const content = svgCanvas.getSvgContent();
if ([ svgRoot, container, content, currentLayer ].includes(mouseTarget)) {
return svgCanvas.getSvgRoot();
const currentLayer = svgCanvas.getCurrentDrawing().getCurrentLayer()
const svgRoot = svgCanvas.getSvgRoot()
const container = svgCanvas.getDOMContainer()
const content = svgCanvas.getSvgContent()
if ([svgRoot, container, content, currentLayer].includes(mouseTarget)) {
return svgCanvas.getSvgRoot()
}
// If it's a selection grip, return the grip parent
if (getClosest(mouseTarget.parentNode, "#selectorParentGroup")) {
if (getClosest(mouseTarget.parentNode, '#selectorParentGroup')) {
// While we could instead have just returned mouseTarget,
// this makes it easier to indentify as being a selector grip
return svgCanvas.selectorManager.selectorParentGroup;
return svgCanvas.selectorManager.selectorParentGroup
}
while (
@@ -181,11 +181,11 @@ export const getMouseTargetMethod = function (evt) {
svgCanvas.getCurrentGroup() || currentLayer
)
) {
mouseTarget = mouseTarget.parentNode;
mouseTarget = mouseTarget.parentNode
}
return mouseTarget;
};
return mouseTarget
}
/**
* @typedef {module:svgcanvas.ExtensionMouseDownStatus|module:svgcanvas.ExtensionMouseUpStatus|module:svgcanvas.ExtensionIDsUpdatedStatus|module:locale.ExtensionLocaleData[]|void} module:svgcanvas.ExtensionStatus
* @tutorial ExtensionDocs
@@ -218,24 +218,24 @@ export const runExtensionsMethod = function (
returnArray,
nameFilter
) {
let result = returnArray ? [] : false;
for (const [ name, ext ] of Object.entries(svgCanvas.getExtensions())) {
let result = returnArray ? [] : false
for (const [name, ext] of Object.entries(svgCanvas.getExtensions())) {
if (nameFilter && !nameFilter(name)) {
return;
return
}
if (ext && action in ext) {
if (typeof vars === "function") {
vars = vars(name); // ext, action
if (typeof vars === 'function') {
vars = vars(name) // ext, action
}
if (returnArray) {
result.push(ext[action](vars));
result.push(ext[action](vars))
} else {
result = ext[action](vars);
result = ext[action](vars)
}
}
}
return result;
};
return result
}
/**
* Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc).
@@ -247,18 +247,18 @@ export const runExtensionsMethod = function (
*/
export const getVisibleElementsAndBBoxes = function (parent) {
if (!parent) {
const svgContent = svgCanvas.getSvgContent();
parent = svgContent.children; // Prevent layers from being included
const svgContent = svgCanvas.getSvgContent()
parent = svgContent.children // Prevent layers from being included
}
const contentElems = [];
const elements = parent.children;
const contentElems = []
const elements = parent.children
Array.from(elements).forEach((elem) => {
if (elem.getBBox) {
contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([ elem ]) });
contentElems.push({ elem, bbox: getStrokedBBoxDefaultVisible([elem]) })
}
});
return contentElems.reverse();
};
})
return contentElems.reverse()
}
/**
* This method sends back an array or a NodeList full of elements that
@@ -273,55 +273,55 @@ export const getVisibleElementsAndBBoxes = function (parent) {
* @returns {Element[]|NodeList} Bbox elements
*/
export const getIntersectionListMethod = function (rect) {
const zoom = svgCanvas.getZoom();
const zoom = svgCanvas.getZoom()
if (!svgCanvas.getRubberBox()) {
return null;
return null
}
const parent =
svgCanvas.getCurrentGroup() ||
svgCanvas.getCurrentDrawing().getCurrentLayer();
svgCanvas.getCurrentDrawing().getCurrentLayer()
let rubberBBox;
let rubberBBox
if (!rect) {
rubberBBox = getBBox(svgCanvas.getRubberBox());
rubberBBox = getBBox(svgCanvas.getRubberBox())
const bb = svgCanvas.getSvgContent().createSVGRect();
[ "x", "y", "width", "height", "top", "right", "bottom", "left" ].forEach(
['x', 'y', 'width', 'height', 'top', 'right', 'bottom', 'left'].forEach(
(o) => {
bb[o] = rubberBBox[o] / zoom;
bb[o] = rubberBBox[o] / zoom
}
);
rubberBBox = bb;
)
rubberBBox = bb
} else {
rubberBBox = svgCanvas.getSvgContent().createSVGRect();
rubberBBox.x = rect.x;
rubberBBox.y = rect.y;
rubberBBox.width = rect.width;
rubberBBox.height = rect.height;
rubberBBox = svgCanvas.getSvgContent().createSVGRect()
rubberBBox.x = rect.x
rubberBBox.y = rect.y
rubberBBox.width = rect.width
rubberBBox.height = rect.height
}
const resultList = [];
const resultList = []
if (svgCanvas.getCurBBoxes().length === 0) {
// Cache all bboxes
svgCanvas.setCurBBoxes(getVisibleElementsAndBBoxes(parent));
svgCanvas.setCurBBoxes(getVisibleElementsAndBBoxes(parent))
}
let i = svgCanvas.getCurBBoxes().length;
let i = svgCanvas.getCurBBoxes().length
while (i--) {
const curBBoxes = svgCanvas.getCurBBoxes();
const curBBoxes = svgCanvas.getCurBBoxes()
if (!rubberBBox.width) {
continue;
continue
}
if (rectsIntersect(rubberBBox, curBBoxes[i].bbox)) {
resultList.push(curBBoxes[i].elem);
resultList.push(curBBoxes[i].elem)
}
}
// addToSelection expects an array, but it's ok to pass a NodeList
// because using square-bracket notation is allowed:
// https://www.w3.org/TR/DOM-Level-2-Core/ecma-script-binding.html
return resultList;
};
return resultList
}
/**
* @typedef {PlainObject} ElementAndBBox
@@ -336,13 +336,13 @@ export const getIntersectionListMethod = function (rect) {
* @returns {void}
*/
export const groupSvgElem = function (elem) {
const dataStorage = svgCanvas.getDataStorage();
const g = document.createElementNS(NS.SVG, "g");
elem.replaceWith(g);
g.appendChild(elem);
dataStorage.put(g, "gsvg", elem);
g.id = svgCanvas.getNextId();
};
const dataStorage = svgCanvas.getDataStorage()
const g = document.createElementNS(NS.SVG, 'g')
elem.replaceWith(g)
g.appendChild(elem)
dataStorage.put(g, 'gsvg', elem)
g.id = svgCanvas.getNextId()
}
/**
* Runs the SVG Document through the sanitizer and then updates its paths.
@@ -351,16 +351,16 @@ export const groupSvgElem = function (elem) {
* @returns {void}
*/
export const prepareSvg = function (newDoc) {
svgCanvas.sanitizeSvg(newDoc.documentElement);
svgCanvas.sanitizeSvg(newDoc.documentElement)
// convert paths into absolute commands
const paths = [ ...newDoc.getElementsByTagNameNS(NS.SVG, "path") ];
const paths = [...newDoc.getElementsByTagNameNS(NS.SVG, 'path')]
paths.forEach((path) => {
const convertedPath = svgCanvas.pathActions.convertPath(path);
path.setAttribute("d", convertedPath);
svgCanvas.pathActions.fixEnd(path);
});
};
const convertedPath = svgCanvas.pathActions.convertPath(path)
path.setAttribute('d', convertedPath)
svgCanvas.pathActions.fixEnd(path)
})
}
/**
* Removes any old rotations if present, prepends a new rotation at the
@@ -372,21 +372,21 @@ export const prepareSvg = function (newDoc) {
* @returns {void}
*/
export const setRotationAngle = function (val, preventUndo) {
const selectedElements = svgCanvas.getSelectedElements();
const selectedElements = svgCanvas.getSelectedElements()
// ensure val is the proper type
val = Number.parseFloat(val);
const elem = selectedElements[0];
const oldTransform = elem.getAttribute("transform");
const bbox = getBBox(elem);
const cx = bbox.x + bbox.width / 2;
const cy = bbox.y + bbox.height / 2;
const tlist = elem.transform.baseVal;
val = Number.parseFloat(val)
const elem = selectedElements[0]
const oldTransform = elem.getAttribute('transform')
const bbox = getBBox(elem)
const cx = bbox.x + bbox.width / 2
const cy = bbox.y + bbox.height / 2
const tlist = elem.transform.baseVal
// only remove the real rotational transform if present (i.e. at index=0)
if (tlist.numberOfItems > 0) {
const xform = tlist.getItem(0);
const xform = tlist.getItem(0)
if (xform.type === 4) {
tlist.removeItem(0);
tlist.removeItem(0)
}
}
// find Rnc and insert it
@@ -395,33 +395,33 @@ export const setRotationAngle = function (val, preventUndo) {
cx,
cy,
transformListToTransform(tlist).matrix
);
const Rnc = svgCanvas.getSvgRoot().createSVGTransform();
Rnc.setRotate(val, center.x, center.y);
)
const Rnc = svgCanvas.getSvgRoot().createSVGTransform()
Rnc.setRotate(val, center.x, center.y)
if (tlist.numberOfItems) {
tlist.insertItemBefore(Rnc, 0);
tlist.insertItemBefore(Rnc, 0)
} else {
tlist.appendItem(Rnc);
tlist.appendItem(Rnc)
}
} else if (tlist.numberOfItems === 0) {
elem.removeAttribute("transform");
elem.removeAttribute('transform')
}
if (!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");
const newTransform = elem.getAttribute('transform')
if (oldTransform) {
elem.setAttribute("transform", oldTransform);
elem.setAttribute('transform', oldTransform)
} else {
elem.removeAttribute("transform");
elem.removeAttribute('transform')
}
svgCanvas.changeSelectedAttribute(
"transform",
'transform',
newTransform,
selectedElements
);
svgCanvas.call("changed", selectedElements);
)
svgCanvas.call('changed', selectedElements)
}
// const pointGripContainer = getElem('pathpointgrip_container');
// if (elem.nodeName === 'path' && pointGripContainer) {
@@ -429,10 +429,10 @@ export const setRotationAngle = function (val, preventUndo) {
// }
const selector = svgCanvas.selectorManager.requestSelector(
selectedElements[0]
);
selector.resize();
svgCanvas.getSelector().updateGripCursors(val);
};
)
selector.resize()
svgCanvas.getSelector().updateGripCursors(val)
}
/**
* Runs `recalculateDimensions` on the selected elements,
@@ -443,19 +443,19 @@ export const setRotationAngle = function (val, preventUndo) {
*/
export const recalculateAllSelectedDimensions = function () {
const text =
svgCanvas.getCurrentResizeMode() === "none" ? "position" : "size";
const batchCmd = new BatchCommand(text);
const selectedElements = svgCanvas.getSelectedElements();
svgCanvas.getCurrentResizeMode() === 'none' ? 'position' : 'size'
const batchCmd = new BatchCommand(text)
const selectedElements = svgCanvas.getSelectedElements()
selectedElements.forEach((elem) => {
const cmd = svgCanvas.recalculateDimensions(elem);
const cmd = svgCanvas.recalculateDimensions(elem)
if (cmd) {
batchCmd.addSubCommand(cmd);
batchCmd.addSubCommand(cmd)
}
});
})
if (!batchCmd.isEmpty()) {
svgCanvas.addCommandToHistory(batchCmd);
svgCanvas.call("changed", selectedElements);
svgCanvas.addCommandToHistory(batchCmd)
svgCanvas.call('changed', selectedElements)
}
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,8 @@
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import { NS } from './namespaces.js';
import { text2xml } from './utilities.js';
import { NS } from './namespaces.js'
import { text2xml } from './utilities.js'
/**
* @function module:svgcanvas.svgRootElement svgRootElement the svg node and its children.
@@ -32,5 +32,5 @@ export const svgRootElement = function (svgdoc, dimensions) {
</svg>`
).documentElement,
true
);
};
)
}

View File

@@ -5,19 +5,18 @@
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import { NS } from './namespaces.js';
import { NS } from './namespaces.js'
import {
transformPoint, getMatrix
} from './math.js';
} from './math.js'
import {
assignAttributes, getElem, getBBox as utilsGetBBox
} from './utilities.js';
} from './utilities.js'
import {
supportsGoodTextCharPos
} from '../common/browser.js';
} from '../common/browser.js'
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:text-actions.init
@@ -25,8 +24,8 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
};
svgCanvas = canvas
}
/**
* Group: Text edit functions
@@ -35,16 +34,16 @@ export const init = function (canvas) {
* @memberof module:svgcanvas.SvgCanvas#
*/
export const textActionsMethod = (function () {
let curtext;
let textinput;
let cursor;
let selblock;
let blinker;
let chardata = [];
let textbb; // , transbb;
let matrix;
let lastX; let lastY;
let allowDbl;
let curtext
let textinput
let cursor
let selblock
let blinker
let chardata = []
let textbb // , transbb;
let matrix
let lastX; let lastY
let allowDbl
/**
*
@@ -52,42 +51,42 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
function setCursor (index) {
const empty = (textinput.value === '');
textinput.focus();
const empty = (textinput.value === '')
textinput.focus()
if (!arguments.length) {
if (empty) {
index = 0;
index = 0
} else {
if (textinput.selectionEnd !== textinput.selectionStart) { return; }
index = textinput.selectionEnd;
if (textinput.selectionEnd !== textinput.selectionStart) { return }
index = textinput.selectionEnd
}
}
const charbb = chardata[index];
const charbb = chardata[index]
if (!empty) {
textinput.setSelectionRange(index, index);
textinput.setSelectionRange(index, index)
}
cursor = getElem('text_cursor');
cursor = getElem('text_cursor')
if (!cursor) {
cursor = document.createElementNS(NS.SVG, 'line');
cursor = document.createElementNS(NS.SVG, 'line')
assignAttributes(cursor, {
id: 'text_cursor',
stroke: '#333',
'stroke-width': 1
});
getElem('selectorParentGroup').append(cursor);
})
getElem('selectorParentGroup').append(cursor)
}
if (!blinker) {
blinker = setInterval(function () {
const show = (cursor.getAttribute('display') === 'none');
cursor.setAttribute('display', show ? 'inline' : 'none');
}, 600);
const show = (cursor.getAttribute('display') === 'none')
cursor.setAttribute('display', show ? 'inline' : 'none')
}, 600)
}
const startPt = ptToScreen(charbb.x, textbb.y);
const endPt = ptToScreen(charbb.x, (textbb.y + textbb.height));
const startPt = ptToScreen(charbb.x, textbb.y)
const endPt = ptToScreen(charbb.x, (textbb.y + textbb.height))
assignAttributes(cursor, {
x1: startPt.x,
@@ -96,9 +95,9 @@ export const textActionsMethod = (function () {
y2: endPt.y,
visibility: 'visible',
display: 'inline'
});
})
if (selblock) { selblock.setAttribute('d', ''); }
if (selblock) { selblock.setAttribute('d', '') }
}
/**
@@ -110,45 +109,45 @@ export const textActionsMethod = (function () {
*/
function setSelection (start, end, skipInput) {
if (start === end) {
setCursor(end);
return;
setCursor(end)
return
}
if (!skipInput) {
textinput.setSelectionRange(start, end);
textinput.setSelectionRange(start, end)
}
selblock = getElem('text_selectblock');
selblock = getElem('text_selectblock')
if (!selblock) {
selblock = document.createElementNS(NS.SVG, 'path');
selblock = document.createElementNS(NS.SVG, 'path')
assignAttributes(selblock, {
id: 'text_selectblock',
fill: 'green',
opacity: 0.5,
style: 'pointer-events:none'
});
getElem('selectorParentGroup').append(selblock);
})
getElem('selectorParentGroup').append(selblock)
}
const startbb = chardata[start];
const endbb = chardata[end];
const startbb = chardata[start]
const endbb = chardata[end]
cursor.setAttribute('visibility', 'hidden');
cursor.setAttribute('visibility', 'hidden')
const tl = ptToScreen(startbb.x, textbb.y);
const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y);
const bl = ptToScreen(startbb.x, textbb.y + textbb.height);
const br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height);
const tl = ptToScreen(startbb.x, textbb.y)
const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y)
const bl = ptToScreen(startbb.x, textbb.y + textbb.height)
const br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height)
const dstr = 'M' + tl.x + ',' + tl.y +
' L' + tr.x + ',' + tr.y +
' ' + br.x + ',' + br.y +
' ' + bl.x + ',' + bl.y + 'z';
' ' + bl.x + ',' + bl.y + 'z'
assignAttributes(selblock, {
d: dstr,
display: 'inline'
});
})
}
/**
@@ -159,29 +158,29 @@ export const textActionsMethod = (function () {
*/
function getIndexFromPoint (mouseX, mouseY) {
// Position cursor here
const pt = svgCanvas.getSvgRoot().createSVGPoint();
pt.x = mouseX;
pt.y = mouseY;
const pt = svgCanvas.getSvgRoot().createSVGPoint()
pt.x = mouseX
pt.y = mouseY
// No content, so return 0
if (chardata.length === 1) { return 0; }
if (chardata.length === 1) { return 0 }
// Determine if cursor should be on left or right of character
let charpos = curtext.getCharNumAtPosition(pt);
let charpos = curtext.getCharNumAtPosition(pt)
if (charpos < 0) {
// Out of text range, look at mouse coords
charpos = chardata.length - 2;
charpos = chardata.length - 2
if (mouseX <= chardata[0].x) {
charpos = 0;
charpos = 0
}
} else if (charpos >= chardata.length - 2) {
charpos = chardata.length - 2;
charpos = chardata.length - 2
}
const charbb = chardata[charpos];
const mid = charbb.x + (charbb.width / 2);
const charbb = chardata[charpos]
const mid = charbb.x + (charbb.width / 2)
if (mouseX > mid) {
charpos++;
charpos++
}
return charpos;
return charpos
}
/**
@@ -191,7 +190,7 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
function setCursorFromPoint (mouseX, mouseY) {
setCursor(getIndexFromPoint(mouseX, mouseY));
setCursor(getIndexFromPoint(mouseX, mouseY))
}
/**
@@ -202,12 +201,12 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
function setEndSelectionFromPoint (x, y, apply) {
const i1 = textinput.selectionStart;
const i2 = getIndexFromPoint(x, y);
const i1 = textinput.selectionStart
const i2 = getIndexFromPoint(x, y)
const start = Math.min(i1, i2);
const end = Math.max(i1, i2);
setSelection(start, end, !apply);
const start = Math.min(i1, i2)
const end = Math.max(i1, i2)
setSelection(start, end, !apply)
}
/**
@@ -220,18 +219,18 @@ export const textActionsMethod = (function () {
const out = {
x: xIn,
y: yIn
};
const zoom = svgCanvas.getZoom();
out.x /= zoom;
out.y /= zoom;
}
const zoom = svgCanvas.getZoom()
out.x /= zoom
out.y /= zoom
if (matrix) {
const pt = transformPoint(out.x, out.y, matrix.inverse());
out.x = pt.x;
out.y = pt.y;
const pt = transformPoint(out.x, out.y, matrix.inverse())
out.x = pt.x
out.y = pt.y
}
return out;
return out
}
/**
@@ -244,18 +243,18 @@ export const textActionsMethod = (function () {
const out = {
x: xIn,
y: yIn
};
}
if (matrix) {
const pt = transformPoint(out.x, out.y, matrix);
out.x = pt.x;
out.y = pt.y;
const pt = transformPoint(out.x, out.y, matrix)
out.x = pt.x
out.y = pt.y
}
const zoom = svgCanvas.getZoom();
out.x *= zoom;
out.y *= zoom;
const zoom = svgCanvas.getZoom()
out.x *= zoom
out.y *= zoom
return out;
return out
}
/**
@@ -264,8 +263,8 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
function selectAll (evt) {
setSelection(0, curtext.textContent.length);
evt.target.removeEventListener('click', selectAll);
setSelection(0, curtext.textContent.length)
evt.target.removeEventListener('click', selectAll)
}
/**
@@ -274,26 +273,26 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
function selectWord (evt) {
if (!allowDbl || !curtext) { return; }
const zoom = svgCanvas.getZoom();
const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm());
const mouseX = ept.x * zoom;
const mouseY = ept.y * zoom;
const pt = screenToPt(mouseX, mouseY);
if (!allowDbl || !curtext) { return }
const zoom = svgCanvas.getZoom()
const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm())
const mouseX = ept.x * zoom
const mouseY = ept.y * zoom
const pt = screenToPt(mouseX, mouseY)
const index = getIndexFromPoint(pt.x, pt.y);
const str = curtext.textContent;
const first = str.substr(0, index).replace(/[a-z\d]+$/i, '').length;
const m = str.substr(index).match(/^[a-z\d]+/i);
const last = (m ? m[0].length : 0) + index;
setSelection(first, last);
const index = getIndexFromPoint(pt.x, pt.y)
const str = curtext.textContent
const first = str.substr(0, index).replace(/[a-z\d]+$/i, '').length
const m = str.substr(index).match(/^[a-z\d]+/i)
const last = (m ? m[0].length : 0) + index
setSelection(first, last)
// Set tripleclick
evt.target.addEventListener('click', selectAll);
evt.target.addEventListener('click', selectAll)
setTimeout(function () {
evt.target.removeEventListener('click', selectAll);
}, 300);
evt.target.removeEventListener('click', selectAll)
}, 300)
}
return /** @lends module:svgcanvas.SvgCanvas#textActions */ {
@@ -304,16 +303,16 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
select (target, x, y) {
curtext = target;
svgCanvas.textActions.toEditMode(x, y);
curtext = target
svgCanvas.textActions.toEditMode(x, y)
},
/**
* @param {Element} elem
* @returns {void}
*/
start (elem) {
curtext = elem;
svgCanvas.textActions.toEditMode();
curtext = elem
svgCanvas.textActions.toEditMode()
},
/**
* @param {external:MouseEvent} evt
@@ -323,12 +322,12 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
mouseDown (evt, mouseTarget, startX, startY) {
const pt = screenToPt(startX, startY);
const pt = screenToPt(startX, startY)
textinput.focus();
setCursorFromPoint(pt.x, pt.y);
lastX = startX;
lastY = startY;
textinput.focus()
setCursorFromPoint(pt.x, pt.y)
lastX = startX
lastY = startY
// TODO: Find way to block native selection
},
@@ -338,8 +337,8 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
mouseMove (mouseX, mouseY) {
const pt = screenToPt(mouseX, mouseY);
setEndSelectionFromPoint(pt.x, pt.y);
const pt = screenToPt(mouseX, mouseY)
setEndSelectionFromPoint(pt.x, pt.y)
},
/**
* @param {external:MouseEvent} evt
@@ -348,9 +347,9 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
mouseUp (evt, mouseX, mouseY) {
const pt = screenToPt(mouseX, mouseY);
const pt = screenToPt(mouseX, mouseY)
setEndSelectionFromPoint(pt.x, pt.y, true);
setEndSelectionFromPoint(pt.x, pt.y, true)
// TODO: Find a way to make this work: Use transformed BBox instead of evt.target
// if (lastX === mouseX && lastY === mouseY
@@ -365,7 +364,7 @@ export const textActionsMethod = (function () {
mouseY < lastY + 2 &&
mouseY > lastY - 2
) {
svgCanvas.textActions.toSelectMode(true);
svgCanvas.textActions.toSelectMode(true)
}
},
/**
@@ -380,16 +379,16 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
toEditMode (x, y) {
allowDbl = false;
svgCanvas.setCurrentMode('textedit');
svgCanvas.selectorManager.requestSelector(curtext).showGrips(false);
allowDbl = false
svgCanvas.setCurrentMode('textedit')
svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
// Make selector group accept clicks
/* const selector = */ svgCanvas.selectorManager.requestSelector(curtext); // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
/* const selector = */ svgCanvas.selectorManager.requestSelector(curtext) // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
// const sel = selector.selectorRect;
svgCanvas.textActions.init();
svgCanvas.textActions.init()
curtext.style.cursor = 'text';
curtext.style.cursor = 'text'
// if (supportsEditableText()) {
// curtext.setAttribute('editable', 'simple');
@@ -397,15 +396,15 @@ export const textActionsMethod = (function () {
// }
if (!arguments.length) {
setCursor();
setCursor()
} else {
const pt = screenToPt(x, y);
setCursorFromPoint(pt.x, pt.y);
const pt = screenToPt(x, y)
setCursorFromPoint(pt.x, pt.y)
}
setTimeout(function () {
allowDbl = true;
}, 300);
allowDbl = true
}, 300)
},
/**
* @param {boolean|Element} selectElem
@@ -413,28 +412,28 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
toSelectMode (selectElem) {
svgCanvas.setCurrentMode('select');
clearInterval(blinker);
blinker = null;
if (selblock) { selblock.setAttribute('display', 'none'); }
if (cursor) { cursor.setAttribute('visibility', 'hidden'); }
curtext.style.cursor = 'move';
svgCanvas.setCurrentMode('select')
clearInterval(blinker)
blinker = null
if (selblock) { selblock.setAttribute('display', 'none') }
if (cursor) { cursor.setAttribute('visibility', 'hidden') }
curtext.style.cursor = 'move'
if (selectElem) {
svgCanvas.clearSelection();
curtext.style.cursor = 'move';
svgCanvas.clearSelection()
curtext.style.cursor = 'move'
svgCanvas.call('selected', [ curtext ]);
svgCanvas.addToSelection([ curtext ], true);
svgCanvas.call('selected', [curtext])
svgCanvas.addToSelection([curtext], true)
}
if (curtext && !curtext.textContent.length) {
// No content, so delete
svgCanvas.deleteSelectedElements();
svgCanvas.deleteSelectedElements()
}
textinput.blur();
textinput.blur()
curtext = false;
curtext = false
// if (supportsEditableText()) {
// curtext.removeAttribute('editable');
@@ -445,14 +444,14 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
setInputElem (elem) {
textinput = elem;
textinput = elem
},
/**
* @returns {void}
*/
clear () {
if (svgCanvas.getCurrentMode() === 'textedit') {
svgCanvas.textActions.toSelectMode();
svgCanvas.textActions.toSelectMode()
}
},
/**
@@ -460,8 +459,8 @@ export const textActionsMethod = (function () {
* @returns {void}
*/
init (_inputElem) {
if (!curtext) { return; }
let i; let end;
if (!curtext) { return }
let i; let end
// if (supportsEditableText()) {
// curtext.select();
// return;
@@ -469,43 +468,43 @@ export const textActionsMethod = (function () {
if (!curtext.parentNode) {
// Result of the ffClone, need to get correct element
const selectedElements = svgCanvas.getSelectedElements();
curtext = selectedElements[0];
svgCanvas.selectorManager.requestSelector(curtext).showGrips(false);
const selectedElements = svgCanvas.getSelectedElements()
curtext = selectedElements[0]
svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
}
const str = curtext.textContent;
const len = str.length;
const str = curtext.textContent
const len = str.length
const xform = curtext.getAttribute('transform');
const xform = curtext.getAttribute('transform')
textbb = utilsGetBBox(curtext);
textbb = utilsGetBBox(curtext)
matrix = xform ? getMatrix(curtext) : null;
matrix = xform ? getMatrix(curtext) : null
chardata = [];
chardata.length = len;
textinput.focus();
chardata = []
chardata.length = len
textinput.focus()
curtext.removeEventListener("dblclick", selectWord);
curtext.addEventListener("dblclick", selectWord);
curtext.removeEventListener('dblclick', selectWord)
curtext.addEventListener('dblclick', selectWord)
if (!len) {
end = { x: textbb.x + (textbb.width / 2), width: 0 };
end = { x: textbb.x + (textbb.width / 2), width: 0 }
}
for (i = 0; i < len; i++) {
const start = curtext.getStartPositionOfChar(i);
end = curtext.getEndPositionOfChar(i);
const start = curtext.getStartPositionOfChar(i)
end = curtext.getEndPositionOfChar(i)
if (!supportsGoodTextCharPos()) {
const zoom = svgCanvas.getZoom();
const offset = svgCanvas.contentW * zoom;
start.x -= offset;
end.x -= offset;
const zoom = svgCanvas.getZoom()
const offset = svgCanvas.contentW * zoom
start.x -= offset
end.x -= offset
start.x /= zoom;
end.x /= zoom;
start.x /= zoom
end.x /= zoom
}
// Get a "bbox" equivalent for each character. Uses the
@@ -517,15 +516,15 @@ export const textActionsMethod = (function () {
y: textbb.y, // start.y?
width: end.x - start.x,
height: textbb.height
};
}
}
// Add a last bbox for cursor at end of text
chardata.push({
x: end.x,
width: 0
});
setSelection(textinput.selectionStart, textinput.selectionEnd, true);
})
setSelection(textinput.selectionStart, textinput.selectionEnd, true)
}
};
}());
}
}())

View File

@@ -4,23 +4,23 @@
* @license MIT
* @copyright 2011 Jeff Schiller
*/
import * as draw from './draw.js';
import * as hstry from './history.js';
import * as draw from './draw.js'
import * as hstry from './history.js'
import {
getRotationAngle, getBBox as utilsGetBBox, isNullish, setHref, getStrokedBBoxDefaultVisible
} from './utilities.js';
} from './utilities.js'
import {
isGecko
} from '../common/browser.js';
} from '../common/browser.js'
import {
transformPoint, transformListToTransform
} from './math.js';
} from './math.js'
const {
UndoManager, HistoryEventTypes
} = hstry;
} = hstry
let svgCanvas = null;
let svgCanvas = null
/**
* @function module:undo.init
@@ -28,9 +28,9 @@ let svgCanvas = null;
* @returns {void}
*/
export const init = function (canvas) {
svgCanvas = canvas;
canvas.undoMgr = getUndoManager();
};
svgCanvas = canvas
canvas.undoMgr = getUndoManager()
}
export const getUndoManager = () => {
return new UndoManager({
@@ -41,70 +41,70 @@ export const getUndoManager = () => {
* @returns {void}
*/
handleHistoryEvent (eventType, cmd) {
const EventTypes = HistoryEventTypes;
const EventTypes = HistoryEventTypes
// TODO: handle setBlurOffsets.
if (eventType === EventTypes.BEFORE_UNAPPLY || eventType === EventTypes.BEFORE_APPLY) {
svgCanvas.clearSelection();
svgCanvas.clearSelection()
} else if (eventType === EventTypes.AFTER_APPLY || eventType === EventTypes.AFTER_UNAPPLY) {
const elems = cmd.elements();
svgCanvas.pathActions.clear();
svgCanvas.call('changed', elems);
const cmdType = cmd.type();
const isApply = (eventType === EventTypes.AFTER_APPLY);
const elems = cmd.elements()
svgCanvas.pathActions.clear()
svgCanvas.call('changed', elems)
const cmdType = cmd.type()
const isApply = (eventType === EventTypes.AFTER_APPLY)
if (cmdType === 'MoveElementCommand') {
const parent = isApply ? cmd.newParent : cmd.oldParent;
const parent = isApply ? cmd.newParent : cmd.oldParent
if (parent === svgCanvas.getSvgContent()) {
draw.identifyLayers();
draw.identifyLayers()
}
} else if (cmdType === 'InsertElementCommand' || cmdType === 'RemoveElementCommand') {
if (cmd.parent === svgCanvas.getSvgContent()) {
draw.identifyLayers();
draw.identifyLayers()
}
if (cmdType === 'InsertElementCommand') {
if (isApply) {
svgCanvas.restoreRefElements(cmd.elem);
svgCanvas.restoreRefElements(cmd.elem)
}
} else if (!isApply) {
svgCanvas.restoreRefElements(cmd.elem);
svgCanvas.restoreRefElements(cmd.elem)
}
if (cmd.elem && cmd.elem.tagName === 'use') {
svgCanvas.setUseData(cmd.elem);
svgCanvas.setUseData(cmd.elem)
}
} else if (cmdType === 'ChangeElementCommand') {
// if we are changing layer names, re-identify all layers
if (cmd.elem.tagName === 'title' &&
cmd.elem.parentNode.parentNode === svgCanvas.getSvgContent()
) {
draw.identifyLayers();
draw.identifyLayers()
}
const values = isApply ? cmd.newValues : cmd.oldValues;
const values = isApply ? cmd.newValues : cmd.oldValues
// If stdDeviation was changed, update the blur.
if (values.stdDeviation) {
svgCanvas.setBlurOffsets(cmd.elem.parentNode, values.stdDeviation);
svgCanvas.setBlurOffsets(cmd.elem.parentNode, values.stdDeviation)
}
if (cmd.elem.tagName === 'text'){
const [ dx, dy ] = [ cmd.newValues.x - cmd.oldValues.x,
cmd.newValues.y - cmd.oldValues.y ];
if (cmd.elem.tagName === 'text') {
const [dx, dy] = [cmd.newValues.x - cmd.oldValues.x,
cmd.newValues.y - cmd.oldValues.y]
const tspans = cmd.elem.children;
const tspans = cmd.elem.children
for (let i = 0; i < tspans.length; i++){
let x = Number(tspans[i].getAttribute('x'));
let y = Number(tspans[i].getAttribute('y'));
for (let i = 0; i < tspans.length; i++) {
let x = Number(tspans[i].getAttribute('x'))
let y = Number(tspans[i].getAttribute('y'))
const unapply = (eventType === EventTypes.AFTER_UNAPPLY);
x = unapply? x - dx: x + dx;
y = unapply? y - dy: y + dy;
const unapply = (eventType === EventTypes.AFTER_UNAPPLY)
x = unapply ? x - dx : x + dx
y = unapply ? y - dy : y + dy
tspans[i].setAttribute('x', x);
tspans[i].setAttribute('y', y);
tspans[i].setAttribute('x', x)
tspans[i].setAttribute('y', y)
}
}
}
}
}
});
};
})
}
/**
* Hack for Firefox bugs where text element features aren't updated or get
@@ -117,15 +117,15 @@ export const getUndoManager = () => {
* @returns {Element} Cloned element
*/
export const ffClone = function (elem) {
if (!isGecko()) { return elem; }
const clone = elem.cloneNode(true);
elem.before(clone);
elem.remove();
svgCanvas.selectorManager.releaseSelector(elem);
svgCanvas.setSelectedElements(0, clone);
svgCanvas.selectorManager.requestSelector(clone).showGrips(true);
return clone;
};
if (!isGecko()) { return elem }
const clone = elem.cloneNode(true)
elem.before(clone)
elem.remove()
svgCanvas.selectorManager.releaseSelector(elem)
svgCanvas.setSelectedElements(0, clone)
svgCanvas.selectorManager.requestSelector(clone).showGrips(true)
return clone
}
/**
* This function makes the changes to the elements. It does not add the change
@@ -136,43 +136,43 @@ export const ffClone = function (elem) {
* @returns {void}
*/
export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, elems) {
const selectedElements = svgCanvas.getSelectedElements();
const zoom = svgCanvas.getZoom();
const selectedElements = svgCanvas.getSelectedElements()
const zoom = svgCanvas.getZoom()
if (svgCanvas.getCurrentMode() === 'pathedit') {
// Editing node
svgCanvas.pathActions.moveNode(attr, newValue);
svgCanvas.pathActions.moveNode(attr, newValue)
}
elems = elems || selectedElements;
let i = elems.length;
const noXYElems = [ 'g', 'polyline', 'path' ];
elems = elems || selectedElements
let i = elems.length
const noXYElems = ['g', 'polyline', 'path']
// const goodGAttrs = ['transform', 'opacity', 'filter'];
while (i--) {
let elem = elems[i];
if (isNullish(elem)) { continue; }
let elem = elems[i]
if (isNullish(elem)) { continue }
// Set x,y vals on elements that don't have them
if ((attr === 'x' || attr === 'y') && noXYElems.includes(elem.tagName)) {
const bbox = getStrokedBBoxDefaultVisible([ elem ]);
const diffX = attr === 'x' ? newValue - bbox.x : 0;
const diffY = attr === 'y' ? newValue - bbox.y : 0;
svgCanvas.moveSelectedElements(diffX * zoom, diffY * zoom, true);
continue;
const bbox = getStrokedBBoxDefaultVisible([elem])
const diffX = attr === 'x' ? newValue - bbox.x : 0
const diffY = attr === 'y' ? newValue - bbox.y : 0
svgCanvas.moveSelectedElements(diffX * zoom, diffY * zoom, true)
continue
}
// only allow the transform/opacity/filter attribute to change on <g> elements, slightly hacky
// TODO: Missing statement body
// if (elem.tagName === 'g' && goodGAttrs.includes(attr)) {}
let oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr);
if (isNullish(oldval)) { oldval = ''; }
let oldval = attr === '#text' ? elem.textContent : elem.getAttribute(attr)
if (isNullish(oldval)) { oldval = '' }
if (oldval !== String(newValue)) {
if (attr === '#text') {
// const oldW = utilsGetBBox(elem).width;
elem.textContent = newValue;
elem.textContent = newValue
// FF bug occurs on on rotated elements
if ((/rotate/).test(elem.getAttribute('transform'))) {
elem = ffClone(elem);
elem = ffClone(elem)
}
// Hoped to solve the issue of moving text with text-anchor="start",
// but this doesn't actually fix it. Hopefully on the right track, though. -Fyrd
@@ -189,13 +189,13 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
// elem.setAttribute('y', elem.getAttribute('y') - dy);
// }
} else if (attr === '#href') {
setHref(elem, newValue);
setHref(elem, newValue)
} else if (newValue) {
elem.setAttribute(attr, newValue);
elem.setAttribute(attr, newValue)
} else if (typeof newValue === 'number') {
elem.setAttribute(attr, newValue);
elem.setAttribute(attr, newValue)
} else {
elem.removeAttribute(attr);
elem.removeAttribute(attr)
}
// Go into "select" mode for text changes
@@ -203,7 +203,7 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
// font-size can get reset to their old value, ultimately by svgEditor.updateContextPanel(),
// after calling textActions.toSelectMode() below
if (svgCanvas.getCurrentMode() === 'textedit' && attr !== '#text' && elem.textContent.length) {
svgCanvas.textActions.toSelectMode(elem);
svgCanvas.textActions.toSelectMode(elem)
}
// if (i === 0) {
@@ -215,8 +215,8 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
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);
(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
@@ -226,38 +226,38 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
setTimeout(function () {
// Due to element replacement, this element may no longer
// be part of the DOM
if (!elem.parentNode) { return; }
svgCanvas.selectorManager.requestSelector(elem).resize();
}, 0);
if (!elem.parentNode) { return }
svgCanvas.selectorManager.requestSelector(elem).resize()
}, 0)
}
// if this element was rotated, and we changed the position of this element
// we need to update the rotational transform attribute
const angle = getRotationAngle(elem);
const angle = getRotationAngle(elem)
if (angle !== 0 && attr !== 'transform') {
const tlist = elem.transform?.baseVal;
let n = tlist.numberOfItems;
const tlist = elem.transform?.baseVal
let n = tlist.numberOfItems
while (n--) {
const xform = tlist.getItem(n);
const xform = tlist.getItem(n)
if (xform.type === 4) {
// remove old rotate
tlist.removeItem(n);
tlist.removeItem(n)
const box = utilsGetBBox(elem);
const box = utilsGetBBox(elem)
const center = transformPoint(
box.x + box.width / 2, box.y + box.height / 2, transformListToTransform(tlist).matrix
);
const cx = center.x;
const cy = center.y;
const newrot = svgCanvas.getSvgRoot().createSVGTransform();
newrot.setRotate(angle, cx, cy);
tlist.insertItemBefore(newrot, n);
break;
)
const cx = center.x
const cy = center.y
const newrot = svgCanvas.getSvgRoot().createSVGTransform()
newrot.setRotate(angle, cx, cy)
tlist.insertItemBefore(newrot, n)
break
}
}
}
} // if oldValue != newValue
} // for each elem
};
}
/**
* Change the given/selected element and add the original value to the history stack.
@@ -271,16 +271,16 @@ export const changeSelectedAttributeNoUndoMethod = function (attr, newValue, ele
* @returns {void}
*/
export const changeSelectedAttributeMethod = function (attr, val, elems) {
const selectedElements = svgCanvas.getSelectedElements();
elems = elems || selectedElements;
svgCanvas.undoMgr.beginUndoableChange(attr, elems);
const selectedElements = svgCanvas.getSelectedElements()
elems = elems || selectedElements
svgCanvas.undoMgr.beginUndoableChange(attr, elems)
// const i = elems.length;
changeSelectedAttributeNoUndoMethod(attr, val, elems);
changeSelectedAttributeNoUndoMethod(attr, val, elems)
const batchCmd = svgCanvas.undoMgr.finishUndoableChange();
const batchCmd = svgCanvas.undoMgr.finishUndoableChange()
if (!batchCmd.isEmpty()) {
// svgCanvas.addCommandToHistory(batchCmd);
svgCanvas.undoMgr.addCommandToHistory(batchCmd);
svgCanvas.undoMgr.addCommandToHistory(batchCmd)
}
};
}

File diff suppressed because it is too large Load Diff