move to standard linter for simpler configuration
This commit is contained in:
@@ -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()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 $
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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]) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
};
|
||||
}());
|
||||
}
|
||||
}())
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user