svgedit
Version:
Powerful SVG-Editor for your browser
1,071 lines (954 loc) • 40.3 kB
HTML
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: svgcanvas/path-method.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: svgcanvas/path-method.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Path functionality.
* @module path
* @license MIT
*
* @copyright 2011 Alexis Deveria, 2011 Jeff Schiller
*/
import { NS } from './namespaces.js';
import { ChangeElementCommand } from './history.js';
import {
transformPoint, getMatrix
} from './math.js';
import {
assignAttributes, getRotationAngle, isNullish,
getElem
} from './utilities.js';
let pathMethodsContext_ = null;
let editorContext_ = null;
/**
* @function module:path-actions.init
* @param {module:path-actions.pathMethodsContext_} pathMethodsContext
* @returns {void}
*/
export const init = function (pathMethodsContext) {
pathMethodsContext_ = pathMethodsContext;
};
/* eslint-disable max-len */
/**
* @function module:path.ptObjToArr
* @todo See if this should just live in `replacePathSeg`
* @param {string} type
* @param {SVGPathSegMovetoAbs|SVGPathSegLinetoAbs|SVGPathSegCurvetoCubicAbs|SVGPathSegCurvetoQuadraticAbs|SVGPathSegArcAbs|SVGPathSegLinetoHorizontalAbs|SVGPathSegLinetoVerticalAbs|SVGPathSegCurvetoCubicSmoothAbs|SVGPathSegCurvetoQuadraticSmoothAbs} segItem
* @returns {ArgumentsArray}
*/
/* eslint-enable max-len */
export const ptObjToArrMethod = function (type, segItem) {
const segData = pathMethodsContext_.getSegData();
const props = segData[type];
return props.map((prop) => {
return segItem[prop];
});
};
/**
* @function module:path.getGripPt
* @param {Segment} seg
* @param {module:math.XYObject} altPt
* @returns {module:math.XYObject}
*/
export const getGripPtMethod = function (seg, altPt) {
const { path: pth } = seg;
let out = {
x: altPt ? altPt.x : seg.item.x,
y: altPt ? altPt.y : seg.item.y
};
if (pth.matrix) {
const pt = transformPoint(out.x, out.y, pth.matrix);
out = pt;
}
editorContext_ = pathMethodsContext_.getEditorContext();
const currentZoom = editorContext_.getCurrentZoom();
out.x *= currentZoom;
out.y *= currentZoom;
return out;
};
/**
* @function module:path.getPointFromGrip
* @param {module:math.XYObject} pt
* @param {module:path.Path} pth
* @returns {module:math.XYObject}
*/
export const getPointFromGripMethod = function (pt, pth) {
const out = {
x: pt.x,
y: pt.y
};
if (pth.matrix) {
pt = transformPoint(out.x, out.y, pth.imatrix);
out.x = pt.x;
out.y = pt.y;
}
editorContext_ = pathMethodsContext_.getEditorContext();
const currentZoom = editorContext_.getCurrentZoom();
out.x /= currentZoom;
out.y /= currentZoom;
return out;
};
/**
* @function module:path.getGripContainer
* @returns {Element}
*/
export const getGripContainerMethod = function () {
let c = getElem('pathpointgrip_container');
if (!c) {
const parentElement = getElem('selectorParentGroup');
c = document.createElementNS(NS.SVG, 'g');
parentElement.append(c);
c.id = 'pathpointgrip_container';
}
return c;
};
/**
* Requires prior call to `setUiStrings` if `xlink:title`
* to be set on the grip.
* @function module:path.addPointGrip
* @param {Integer} index
* @param {Integer} x
* @param {Integer} y
* @returns {SVGCircleElement}
*/
export const addPointGripMethod = function (index, x, y) {
// create the container of all the point grips
const pointGripContainer = getGripContainerMethod();
let pointGrip = getElem('pathpointgrip_' + index);
// create it
if (!pointGrip) {
pointGrip = document.createElementNS(NS.SVG, 'circle');
const atts = {
id: 'pathpointgrip_' + index,
display: 'none',
r: 4,
fill: '#0FF',
stroke: '#00F',
'stroke-width': 2,
cursor: 'move',
style: 'pointer-events:all'
};
const uiStrings = pathMethodsContext_.getUIStrings();
if ('pathNodeTooltip' in uiStrings) { // May be empty if running path.js without svg-editor
atts['xlink:title'] = uiStrings.pathNodeTooltip;
}
assignAttributes(pointGrip, atts);
pointGripContainer.append(pointGrip);
const grip = document.getElementById('pathpointgrip_' + index);
grip?.addEventListener("dblclick", () => {
const path = pathMethodsContext_.getPathObj();
if (path) {
path.setSegType();
}
});
}
if (x && y) {
// set up the point grip element and display it
assignAttributes(pointGrip, {
cx: x,
cy: y,
display: 'inline'
});
}
return pointGrip;
};
/**
* Requires prior call to `setUiStrings` if `xlink:title`
* to be set on the grip.
* @function module:path.addCtrlGrip
* @param {string} id
* @returns {SVGCircleElement}
*/
export const addCtrlGripMethod = function (id) {
let pointGrip = getElem('ctrlpointgrip_' + id);
if (pointGrip) { return pointGrip; }
pointGrip = document.createElementNS(NS.SVG, 'circle');
const atts = {
id: 'ctrlpointgrip_' + id,
display: 'none',
r: 4,
fill: '#0FF',
stroke: '#55F',
'stroke-width': 1,
cursor: 'move',
style: 'pointer-events:all'
};
const uiStrings = pathMethodsContext_.getUIStrings();
if ('pathCtrlPtTooltip' in uiStrings) { // May be empty if running path.js without svg-editor
atts['xlink:title'] = uiStrings.pathCtrlPtTooltip;
}
assignAttributes(pointGrip, atts);
getGripContainerMethod().append(pointGrip);
return pointGrip;
};
/**
* @function module:path.getCtrlLine
* @param {string} id
* @returns {SVGLineElement}
*/
export const getCtrlLineMethod = function (id) {
let ctrlLine = getElem('ctrlLine_' + id);
if (ctrlLine) { return ctrlLine; }
ctrlLine = document.createElementNS(NS.SVG, 'line');
assignAttributes(ctrlLine, {
id: 'ctrlLine_' + id,
stroke: '#555',
'stroke-width': 1,
style: 'pointer-events:none'
});
getGripContainerMethod().append(ctrlLine);
return ctrlLine;
};
/**
* @function module:path.getPointGrip
* @param {Segment} seg
* @param {boolean} update
* @returns {SVGCircleElement}
*/
export const getPointGripMethod = function (seg, update) {
const { index } = seg;
const pointGrip = addPointGripMethod(index);
if (update) {
const pt = getGripPtMethod(seg);
assignAttributes(pointGrip, {
cx: pt.x,
cy: pt.y,
display: 'inline'
});
}
return pointGrip;
};
/**
* @function module:path.getControlPoints
* @param {Segment} seg
* @returns {PlainObject<string, SVGLineElement|SVGCircleElement>}
*/
export const getControlPointsMethod = function (seg) {
const { item, index } = seg;
if (!('x1' in item) || !('x2' in item)) { return null; }
const cpt = {};
/* const pointGripContainer = */ getGripContainerMethod();
// Note that this is intentionally not seg.prev.item
const path = pathMethodsContext_.getPathObj();
const prev = path.segs[index - 1].item;
const segItems = [ prev, item ];
for (let i = 1; i < 3; i++) {
const id = index + 'c' + i;
const ctrlLine = cpt['c' + i + '_line'] = getCtrlLineMethod(id);
const pt = getGripPtMethod(seg, { x: item['x' + i], y: item['y' + i] });
const gpt = getGripPtMethod(seg, { x: segItems[i - 1].x, y: segItems[i - 1].y });
assignAttributes(ctrlLine, {
x1: pt.x,
y1: pt.y,
x2: gpt.x,
y2: gpt.y,
display: 'inline'
});
cpt['c' + i + '_line'] = ctrlLine;
// create it
const pointGrip = cpt['c' + i] = addCtrlGripMethod(id);
assignAttributes(pointGrip, {
cx: pt.x,
cy: pt.y,
display: 'inline'
});
cpt['c' + i] = pointGrip;
}
return cpt;
};
/**
* This replaces the segment at the given index. Type is given as number.
* @function module:path.replacePathSeg
* @param {Integer} type Possible values set during {@link module:path.init}
* @param {Integer} index
* @param {ArgumentsArray} pts
* @param {SVGPathElement} elem
* @returns {void}
*/
export const replacePathSegMethod = function (type, index, pts, elem) {
const path = pathMethodsContext_.getPathObj();
const pth = elem || path.elem;
const pathFuncs = pathMethodsContext_.getPathFuncs();
const func = 'createSVGPathSeg' + pathFuncs[type];
const seg = pth[func](...pts);
pth.pathSegList.replaceItem(seg, index);
};
/**
* @function module:path.getSegSelector
* @param {Segment} seg
* @param {boolean} update
* @returns {SVGPathElement}
*/
export const getSegSelectorMethod = function (seg, update) {
const { index } = seg;
let segLine = getElem('segline_' + index);
if (!segLine) {
const pointGripContainer = getGripContainerMethod();
// create segline
segLine = document.createElementNS(NS.SVG, 'path');
assignAttributes(segLine, {
id: 'segline_' + index,
display: 'none',
fill: 'none',
stroke: '#0FF',
'stroke-width': 2,
style: 'pointer-events:none',
d: 'M0,0 0,0'
});
pointGripContainer.append(segLine);
}
if (update) {
const { prev } = seg;
if (!prev) {
segLine.setAttribute('display', 'none');
return segLine;
}
const pt = getGripPtMethod(prev);
// Set start point
replacePathSegMethod(2, 0, [ pt.x, pt.y ], segLine);
const pts = ptObjToArrMethod(seg.type, seg.item); // , true);
for (let i = 0; i < pts.length; i += 2) {
const point = getGripPtMethod(seg, { x: pts[i], y: pts[i + 1] });
pts[i] = point.x;
pts[i + 1] = point.y;
}
replacePathSegMethod(seg.type, 1, pts, segLine);
}
return segLine;
};
/**
*
*/
export class Segment {
/**
* @param {Integer} index
* @param {SVGPathSeg} item
* @todo Is `item` be more constrained here?
*/
constructor (index, item) {
this.selected = false;
this.index = index;
this.item = item;
this.type = item.pathSegType;
this.ctrlpts = [];
this.ptgrip = null;
this.segsel = null;
}
/**
* @param {boolean} y
* @returns {void}
*/
showCtrlPts (y) {
for (const i in this.ctrlpts) {
if ({}.hasOwnProperty.call(this.ctrlpts, i)) {
this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none');
}
}
}
/**
* @param {boolean} y
* @returns {void}
*/
selectCtrls (y) {
document.getElementById('ctrlpointgrip_' + this.index + 'c1').setAttribute('fill', y ? '#0FF' : '#EEE');
document.getElementById('ctrlpointgrip_' + this.index + 'c2').setAttribute('fill', y ? '#0FF' : '#EEE');
}
/**
* @param {boolean} y
* @returns {void}
*/
show (y) {
if (this.ptgrip) {
this.ptgrip.setAttribute('display', y ? 'inline' : 'none');
this.segsel.setAttribute('display', y ? 'inline' : 'none');
// Show/hide all control points if available
this.showCtrlPts(y);
}
}
/**
* @param {boolean} y
* @returns {void}
*/
select (y) {
if (this.ptgrip) {
this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F');
this.segsel.setAttribute('display', y ? 'inline' : 'none');
if (this.ctrlpts) {
this.selectCtrls(y);
}
this.selected = y;
}
}
/**
* @returns {void}
*/
addGrip () {
this.ptgrip = getPointGripMethod(this, true);
this.ctrlpts = getControlPointsMethod(this); // , true);
this.segsel = getSegSelectorMethod(this, true);
}
/**
* @param {boolean} full
* @returns {void}
*/
update (full) {
if (this.ptgrip) {
const pt = getGripPtMethod(this);
assignAttributes(this.ptgrip, {
cx: pt.x,
cy: pt.y
});
getSegSelectorMethod(this, true);
if (this.ctrlpts) {
if (full) {
const path = pathMethodsContext_.getPathObj();
this.item = path.elem.pathSegList.getItem(this.index);
this.type = this.item.pathSegType;
}
getControlPointsMethod(this);
}
// this.segsel.setAttribute('display', y ? 'inline' : 'none');
}
}
/**
* @param {Integer} dx
* @param {Integer} dy
* @returns {void}
*/
move (dx, dy) {
const { item } = this;
const curPts = this.ctrlpts
? [
item.x += dx, item.y += dy,
item.x1, item.y1, item.x2 += dx, item.y2 += dy
]
: [ item.x += dx, item.y += dy ];
replacePathSegMethod(
this.type,
this.index,
// type 10 means ARC
this.type === 10 ? ptObjToArrMethod(this.type, item) : curPts
);
if (this.next && this.next.ctrlpts) {
const next = this.next.item;
const nextPts = [
next.x, next.y,
next.x1 += dx, next.y1 += dy, next.x2, next.y2
];
replacePathSegMethod(this.next.type, this.next.index, nextPts);
}
if (this.mate) {
// The last point of a closed subpath has a 'mate',
// which is the 'M' segment of the subpath
const { item: itm } = this.mate;
const pts = [ itm.x += dx, itm.y += dy ];
replacePathSegMethod(this.mate.type, this.mate.index, pts);
// Has no grip, so does not need 'updating'?
}
this.update(true);
if (this.next) { this.next.update(true); }
}
/**
* @param {Integer} num
* @returns {void}
*/
setLinked (num) {
let seg; let anum; let pt;
if (num === 2) {
anum = 1;
seg = this.next;
if (!seg) { return; }
pt = this.item;
} else {
anum = 2;
seg = this.prev;
if (!seg) { return; }
pt = seg.item;
}
const { item } = seg;
item['x' + anum] = pt.x + (pt.x - this.item['x' + num]);
item['y' + anum] = pt.y + (pt.y - this.item['y' + num]);
const pts = [
item.x, item.y,
item.x1, item.y1,
item.x2, item.y2
];
replacePathSegMethod(seg.type, seg.index, pts);
seg.update(true);
}
/**
* @param {Integer} num
* @param {Integer} dx
* @param {Integer} dy
* @returns {void}
*/
moveCtrl (num, dx, dy) {
const { item } = this;
item['x' + num] += dx;
item['y' + num] += dy;
const pts = [
item.x, item.y,
item.x1, item.y1, item.x2, item.y2
];
replacePathSegMethod(this.type, this.index, pts);
this.update(true);
}
/**
* @param {Integer} newType Possible values set during {@link module:path.init}
* @param {ArgumentsArray} pts
* @returns {void}
*/
setType (newType, pts) {
replacePathSegMethod(newType, this.index, pts);
this.type = newType;
const path = pathMethodsContext_.getPathObj();
this.item = path.elem.pathSegList.getItem(this.index);
this.showCtrlPts(newType === 6);
this.ctrlpts = getControlPointsMethod(this);
this.update(true);
}
}
/**
*
*/
export class Path {
/**
* @param {SVGPathElement} elem
* @throws {Error} If constructed without a path element
*/
constructor (elem) {
if (!elem || elem.tagName !== 'path') {
throw new Error('svgedit.path.Path constructed without a <path> element');
}
this.elem = elem;
this.segs = [];
this.selected_pts = [];
pathMethodsContext_.setPathObj(this);
// path = this;
this.init();
}
setPathContext() {
pathMethodsContext_.setPathObj(this);
}
/**
* Reset path data.
* @returns {module:path.Path}
*/
init () {
// Hide all grips, etc
// fixed, needed to work on all found elements, not just first
const pointGripContainer = getGripContainerMethod();
const elements = pointGripContainer.querySelectorAll('*');
Array.prototype.forEach.call(elements, function(el){
el.setAttribute('display', 'none');
});
const segList = this.elem.pathSegList;
const len = segList.numberOfItems;
this.segs = [];
this.selected_pts = [];
this.first_seg = null;
// Set up segs array
for (let i = 0; i < len; i++) {
const item = segList.getItem(i);
const segment = new Segment(i, item);
segment.path = this;
this.segs.push(segment);
}
const { segs } = this;
let startI = null;
for (let i = 0; i < len; i++) {
const seg = segs[i];
const nextSeg = (i + 1) >= len ? null : segs[i + 1];
const prevSeg = (i - 1) < 0 ? null : segs[i - 1];
if (seg.type === 2) {
if (prevSeg && prevSeg.type !== 1) {
// New sub-path, last one is open,
// so add a grip to last sub-path's first point
const startSeg = segs[startI];
startSeg.next = segs[startI + 1];
startSeg.next.prev = startSeg;
startSeg.addGrip();
}
// Remember that this is a starter seg
startI = i;
} else if (nextSeg && nextSeg.type === 1) {
// This is the last real segment of a closed sub-path
// Next is first seg after "M"
seg.next = segs[startI + 1];
// First seg after "M"'s prev is this
seg.next.prev = seg;
seg.mate = segs[startI];
seg.addGrip();
if (isNullish(this.first_seg)) {
this.first_seg = seg;
}
} else if (!nextSeg) {
if (seg.type !== 1) {
// Last seg, doesn't close so add a grip
// to last sub-path's first point
const startSeg = segs[startI];
startSeg.next = segs[startI + 1];
startSeg.next.prev = startSeg;
startSeg.addGrip();
seg.addGrip();
if (!this.first_seg) {
// Open path, so set first as real first and add grip
this.first_seg = segs[startI];
}
}
} else if (seg.type !== 1) {
// Regular segment, so add grip and its "next"
seg.addGrip();
// Don't set its "next" if it's an "M"
if (nextSeg && nextSeg.type !== 2) {
seg.next = nextSeg;
seg.next.prev = seg;
}
}
}
return this;
}
/**
* @callback module:path.PathEachSegCallback
* @this module:path.Segment
* @param {Integer} i The index of the seg being iterated
* @returns {boolean|void} Will stop execution of `eachSeg` if returns `false`
*/
/**
* @param {module:path.PathEachSegCallback} fn
* @returns {void}
*/
eachSeg (fn) {
const len = this.segs.length;
for (let i = 0; i < len; i++) {
const ret = fn.call(this.segs[i], i);
if (ret === false) { break; }
}
}
/**
* @param {Integer} index
* @returns {void}
*/
addSeg (index) {
// Adds a new segment
const seg = this.segs[index];
if (!seg.prev) { return; }
const { prev } = seg;
let newseg; let newX; let newY;
switch (seg.item.pathSegType) {
case 4: {
newX = (seg.item.x + prev.item.x) / 2;
newY = (seg.item.y + prev.item.y) / 2;
newseg = this.elem.createSVGPathSegLinetoAbs(newX, newY);
break;
} case 6: { // make it a curved segment to preserve the shape (WRS)
// https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation
const p0x = (prev.item.x + seg.item.x1) / 2;
const p1x = (seg.item.x1 + seg.item.x2) / 2;
const p2x = (seg.item.x2 + seg.item.x) / 2;
const p01x = (p0x + p1x) / 2;
const p12x = (p1x + p2x) / 2;
newX = (p01x + p12x) / 2;
const p0y = (prev.item.y + seg.item.y1) / 2;
const p1y = (seg.item.y1 + seg.item.y2) / 2;
const p2y = (seg.item.y2 + seg.item.y) / 2;
const p01y = (p0y + p1y) / 2;
const p12y = (p1y + p2y) / 2;
newY = (p01y + p12y) / 2;
newseg = this.elem.createSVGPathSegCurvetoCubicAbs(newX, newY, p0x, p0y, p01x, p01y);
const pts = [ seg.item.x, seg.item.y, p12x, p12y, p2x, p2y ];
replacePathSegMethod(seg.type, index, pts);
break;
}
}
const list = this.elem.pathSegList;
list.insertItemBefore(newseg, index);
}
/**
* @param {Integer} index
* @returns {void}
*/
deleteSeg (index) {
const seg = this.segs[index];
const list = this.elem.pathSegList;
seg.show(false);
const { next } = seg;
if (seg.mate) {
// Make the next point be the "M" point
const pt = [ next.item.x, next.item.y ];
replacePathSegMethod(2, next.index, pt);
// Reposition last node
replacePathSegMethod(4, seg.index, pt);
list.removeItem(seg.mate.index);
} else if (!seg.prev) {
// First node of open path, make next point the M
// const {item} = seg;
const pt = [ next.item.x, next.item.y ];
replacePathSegMethod(2, seg.next.index, pt);
list.removeItem(index);
} else {
list.removeItem(index);
}
}
/**
* @param {Integer} index
* @returns {void}
*/
removePtFromSelection (index) {
const pos = this.selected_pts.indexOf(index);
if (pos === -1) {
return;
}
this.segs[index].select(false);
this.selected_pts.splice(pos, 1);
}
/**
* @returns {void}
*/
clearSelection () {
this.eachSeg(function () {
// 'this' is the segment here
this.select(false);
});
this.selected_pts = [];
}
/**
* @returns {void}
*/
storeD () {
this.last_d = this.elem.getAttribute('d');
}
/**
* @param {Integer} y
* @returns {Path}
*/
show (y) {
// Shows this path's segment grips
this.eachSeg(function () {
// 'this' is the segment here
this.show(y);
});
if (y) {
this.selectPt(this.first_seg.index);
}
return this;
}
/**
* Move selected points.
* @param {Integer} dx
* @param {Integer} dy
* @returns {void}
*/
movePts (dx, dy) {
let i = this.selected_pts.length;
while (i--) {
const seg = this.segs[this.selected_pts[i]];
seg.move(dx, dy);
}
}
/**
* @param {Integer} dx
* @param {Integer} dy
* @returns {void}
*/
moveCtrl (dx, dy) {
const seg = this.segs[this.selected_pts[0]];
seg.moveCtrl(this.dragctrl, dx, dy);
if (pathMethodsContext_.getLinkControlPts()) {
seg.setLinked(this.dragctrl);
}
}
/**
* @param {?Integer} newType See {@link https://www.w3.org/TR/SVG/single-page.html#paths-InterfaceSVGPathSeg}
* @returns {void}
*/
setSegType (newType) {
this.storeD();
let i = this.selected_pts.length;
let text;
while (i--) {
const selPt = this.selected_pts[i];
// Selected seg
const cur = this.segs[selPt];
const { prev } = cur;
if (!prev) { continue; }
if (!newType) { // double-click, so just toggle
text = 'Toggle Path Segment Type';
// Toggle segment to curve/straight line
const oldType = cur.type;
newType = (oldType === 6) ? 4 : 6;
}
newType = Number(newType);
const curX = cur.item.x;
const curY = cur.item.y;
const prevX = prev.item.x;
const prevY = prev.item.y;
let points;
switch (newType) {
case 6: {
if (cur.olditem) {
const old = cur.olditem;
points = [ curX, curY, old.x1, old.y1, old.x2, old.y2 ];
} else {
const diffX = curX - prevX;
const diffY = curY - prevY;
// get control points from straight line segment
/*
const ct1x = (prevX + (diffY/2));
const ct1y = (prevY - (diffX/2));
const ct2x = (curX + (diffY/2));
const ct2y = (curY - (diffX/2));
*/
// create control points on the line to preserve the shape (WRS)
const ct1x = (prevX + (diffX / 3));
const ct1y = (prevY + (diffY / 3));
const ct2x = (curX - (diffX / 3));
const ct2y = (curY - (diffY / 3));
points = [ curX, curY, ct1x, ct1y, ct2x, ct2y ];
}
break;
} case 4: {
points = [ curX, curY ];
// Store original prevve segment nums
cur.olditem = cur.item;
break;
}
}
cur.setType(newType, points);
}
const path = pathMethodsContext_.getPathObj();
path.endChanges(text);
}
/**
* @param {Integer} pt
* @param {Integer} ctrlNum
* @returns {void}
*/
selectPt (pt, ctrlNum) {
this.clearSelection();
if (isNullish(pt)) {
this.eachSeg(function (i) {
// 'this' is the segment here.
if (this.prev) {
pt = i;
}
});
}
this.addPtsToSelection(pt);
if (ctrlNum) {
this.dragctrl = ctrlNum;
if (pathMethodsContext_.getLinkControlPts()) {
this.segs[pt].setLinked(ctrlNum);
}
}
}
/**
* Update position of all points.
* @returns {Path}
*/
update () {
const { elem } = this;
if (getRotationAngle(elem)) {
this.matrix = getMatrix(elem);
this.imatrix = this.matrix.inverse();
} else {
this.matrix = null;
this.imatrix = null;
}
this.eachSeg(function (i) {
this.item = elem.pathSegList.getItem(i);
this.update();
});
return this;
}
/**
* @param {string} text
* @returns {void}
*/
endChanges (text) {
const cmd = new ChangeElementCommand(this.elem, { d: this.last_d }, text);
editorContext_.endChanges({ cmd, elem: this.elem });
}
/**
* @param {Integer|Integer[]} indexes
* @returns {void}
*/
addPtsToSelection (indexes) {
if (!Array.isArray(indexes)) { indexes = [ indexes ]; }
indexes.forEach((index) => {
const seg = this.segs[index];
if (seg.ptgrip && !this.selected_pts.includes(index) && index >= 0) {
this.selected_pts.push(index);
}
});
this.selected_pts.sort();
let i = this.selected_pts.length;
const grips = [];
grips.length = i;
// Loop through points to be selected and highlight each
while (i--) {
const pt = this.selected_pts[i];
const seg = this.segs[pt];
seg.select(true);
grips[i] = seg.ptgrip;
}
const closedSubpath = Path.subpathIsClosed(this.selected_pts[0]);
editorContext_.addPtsToSelection({ grips, closedSubpath });
}
// STATIC
/**
* @param {Integer} index
* @returns {boolean}
*/
static subpathIsClosed (index) {
let clsd = false;
// Check if subpath is already open
const path = pathMethodsContext_.getPathObj();
path.eachSeg(function (i) {
if (i <= index) { return true; }
if (this.type === 2) {
// Found M first, so open
return false;
}
if (this.type === 1) {
// Found Z first, so closed
clsd = true;
return false;
}
return true;
});
return clsd;
}
}
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-blur.html">blur</a></li><li><a href="module-browser.html">browser</a></li><li><a href="module-clear.html">clear</a></li><li><a href="module-contextmenu.html">contextmenu</a></li><li><a href="module-coords.html">coords</a></li><li><a href="module-draw.html">draw</a></li><li><a href="module-elem-get-set%2520get%2520and%2520set%2520methods..html">elem-get-set get and set methods.</a></li><li><a href="module-event.html">event</a></li><li><a href="module-history.html">history</a></li><li><a href="module-jGraduate.html">jGraduate</a></li><li><a href="module-jPicker.html">jPicker</a></li><li><a href="module-jQueryAttr.html">jQueryAttr</a></li><li><a href="module-layer.html">layer</a></li><li><a href="module-locale.html">locale</a></li><li><a href="module-math.html">math</a></li><li><a href="module-namespaces.html">namespaces</a></li><li><a href="module-path.html">path</a></li><li><a href="module-recalculate.html">recalculate</a></li><li><a href="module-sanitize.html">sanitize</a></li><li><a href="module-select.html">select</a></li><li><a href="module-selected-elem.html">selected-elem</a></li><li><a href="module-selection.html">selection</a></li><li><a href="module-svg.html">svg</a></li><li><a href="module-svgcanvas.html">svgcanvas</a></li><li><a href="module-SVGEditor.html">SVGEditor</a></li><li><a href="module-text-actions%2520Tools%2520for%2520Text%2520edit%2520functions.html">text-actions Tools for Text edit functions</a></li><li><a href="module-undo.html">undo</a></li><li><a href="module-units.html">units</a></li><li><a href="module-utilities.html">utilities</a></li></ul><h3>Externals</h3><ul><li><a href="external-JamilihArray.html">JamilihArray</a></li><li><a href="external-jQuery.html">jQuery</a></li><li><a href="external-Math.html">Math</a></li><li><a href="external-MouseEvent.html">MouseEvent</a></li><li><a href="external-Window.html">Window</a></li></ul><h3>Namespaces</h3><ul><li><a href="external-jQuery.fn.html">fn</a></li><li><a href="external-jQuery.fn.$.fn.jPicker.defaults.html">defaults</a></li><li><a href="external-jQuery.fn.exports.jPickerMethod.html">exports.jPickerMethod</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.html">jGraduateDefaults</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.images.html">images</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.window.html">window</a></li><li><a href="external-jQuery.jGraduate.html">jGraduate</a></li><li><a href="external-jQuery.jPicker.html">jPicker</a></li><li><a href="external-jQuery.jPicker.ColorMethods.html">ColorMethods</a></li><li><a href="module-path.html#.pathActions">pathActions</a></li><li><a href="module-svgcanvas.SvgCanvas_pathActions.html">pathActions</a></li><li><a href="module-svgcanvas.SvgCanvas_textActions.html">textActions</a></li></ul><h3>Classes</h3><ul><li><a href="BottomPanel.html">BottomPanel</a></li><li><a href="configObj.html">configObj</a></li><li><a href="Dropdown.html">Dropdown</a></li><li><a href="EditorStartup.html">EditorStartup</a></li><li><a href="ElixMenuButton.html">ElixMenuButton</a></li><li><a href="ElixNumberSpinBox.html">ElixNumberSpinBox</a></li><li><a href="ExplorerButton.html">ExplorerButton</a></li><li><a href="external-jQuery.jGraduate.Paint.html">Paint</a></li><li><a href="external-jQuery.jPicker.Color.html">Color</a></li><li><a href="FlyingButton.html">FlyingButton</a></li><li><a href="LayersPanel.html">LayersPanel</a></li><li><a href="LeftPanel.html">LeftPanel</a></li><li><a href="MainMenu.html">MainMenu</a></li><li><a href="module.exports.html">exports</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="module-draw.Drawing.html">Drawing</a></li><li><a href="module-draw.Layer.html">Layer</a></li><li><a href="module-history.BatchCommand.html">BatchCommand</a></li><li><a href="module-history.ChangeElementCommand.html">ChangeElementCommand</a></li><li><a href="module-history.Command.html">Command</a></li><li><a href="module-history.HistoryRecordingService.html">HistoryRecordingService</a></li><li><a href="module-history.InsertElementCommand.html">InsertElementCommand</a></li><li><a href="module-history.MoveElementCommand.html">MoveElementCommand</a></li><li><a href="module-history.RemoveElementCommand.html">RemoveElementCommand</a></li><li><a href="module-history.UndoManager.html">UndoManager</a></li><li><a href="module-jPicker.module.exports.html">module.exports</a></li><li><a href="module-layer.Layer.html">Layer</a></li><li><a href="module-path.Path.html">Path</a></li><li><a href="module-path.Segment.html">Segment</a></li><li><a href="module-select.Selector.html">Selector</a></li><li><a href="module-select.SelectorManager.html">SelectorManager</a></li><li><a href="module-svgcanvas.SvgCanvas.html">SvgCanvas</a></li><li><a href="module-SVGEditor-Editor.html">Editor</a></li><li><a href="NumberSpinBox.html">NumberSpinBox</a></li><li><a href="PaintBox.html">PaintBox</a></li><li><a href="PlainNumberSpinBox.html">PlainNumberSpinBox</a></li><li><a href="Rulers.html">Rulers</a></li><li><a href="SeCMenuDialog.html">SeCMenuDialog</a></li><li><a href="SeCMenuLayerDialog.html">SeCMenuLayerDialog</a></li><li><a href="SeColorPicker.html">SeColorPicker</a></li><li><a href="SeEditPrefsDialog.html">SeEditPrefsDialog</a></li><li><a href="SeExportDialog.html">SeExportDialog</a></li><li><a href="SeImgPropDialog.html">SeImgPropDialog</a></li><li><a href="SEInput.html">SEInput</a></li><li><a href="SeList.html">SeList</a></li><li><a href="SeMenu.html">SeMenu</a></li><li><a href="SeMenuItem.html">SeMenuItem</a></li><li><a href="SEPalette.html">SEPalette</a></li><li><a href="SePlainAlertDialog.html">SePlainAlertDialog</a></li><li><a href="SePlainBorderButton.html">SePlainBorderButton</a></li><li><a href="SePromptDialog.html">SePromptDialog</a></li><li><a href="SESpinInput.html">SESpinInput</a></li><li><a href="SeStorageDialog.html">SeStorageDialog</a></li><li><a href="SeSvgSourceEditorDialog.html">SeSvgSourceEditorDialog</a></li><li><a href="SeText.html">SeText</a></li><li><a href="ToolButton.html">ToolButton</a></li><li><a href="TopPanel.html">TopPanel</a></li></ul><h3>Interfaces</h3><ul><li><a href="module-coords.EditorContext.html">EditorContext</a></li><li><a href="module-draw.DrawCanvasInit.html">DrawCanvasInit</a></li><li><a href="module-history.HistoryCommand.html">HistoryCommand</a></li><li><a href="module-history.HistoryEventHandler.html">HistoryEventHandler</a></li><li><a href="module-locale.LocaleEditorInit.html">LocaleEditorInit</a></li><li><a href="module-path.EditorContext.html">EditorContext</a></li><li><a href="module-recalculate.EditorContext.html">EditorContext</a></li><li><a href="module-select.SVGFactory.html">SVGFactory</a></li><li><a href="module-svgcanvas.PrivateMethods.html">PrivateMethods</a></li><li><a href="module-SVGEditor.Config.html">Config</a></li><li><a href="module-SVGEditor.Prefs.html">Prefs</a></li><li><a href="module-SVGthis.CustomHandler.html">CustomHandler</a></li><li><a href="module-units.ElementContainer.html">ElementContainer</a></li><li><a href="module-utilities.EditorContext.html">EditorContext</a></li></ul><h3>Events</h3><ul><li><a href="module-history-Command.html#event:event:history">history</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:changed">changed</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:cleared">cleared</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:contextset">contextset</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:exported">exported</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:exportedPDF">exportedPDF</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_addLangData">ext_addLangData</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_callback">ext_callback</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_canvasUpdated">ext_canvasUpdated</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_elementChanged">ext_elementChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_elementTransition">ext_elementTransition</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_IDsUpdated">ext_IDsUpdated</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_langChanged">ext_langChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_langReady">ext_langReady</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseDown">ext_mouseDown</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseMove">ext_mouseMove</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_mouseUp">ext_mouseUp</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_onNewDocument">ext_onNewDocument</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_selectedChanged">ext_selectedChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_toolButtonStateUpdate">ext_toolButtonStateUpdate</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_workareaResized">ext_workareaResized</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:ext_zoomChanged">ext_zoomChanged</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:extension_added">extension_added</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:extensions_added">extensions_added</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:GenericCanvasEvent">GenericCanvasEvent</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:message">message</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:pointsAdded">pointsAdded</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:saved">saved</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:selected">selected</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:setnonce">setnonce</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:transition">transition</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:unsetnonce">unsetnonce</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:updateCanvas">updateCanvas</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:zoomDone">zoomDone</a></li><li><a href="module-svgcanvas.SvgCanvas.html#event:event:zoomed">zoomed</a></li><li><a href="module-SVGEditor.html#event:event:svgEditorReadyEvent">svgEditorReadyEvent</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-CanvasAPI.html">CanvasAPI</a></li><li><a href="tutorial-Editor.html">Editor</a></li><li><a href="tutorial-EditorAPI.html">EditorAPI</a></li><li><a href="tutorial-Events.html">Events</a></li><li><a href="tutorial-FrequentlyAskedQuestions.html">Frequently Asked Questions (FAQ)</a></li></ul><h3>Global</h3><ul><li><a href="global.html#attributeChangedCallback">attributeChangedCallback</a></li><li><a href="global.html#connectedCallback">connectedCallback</a></li><li><a href="global.html#constructor">constructor</a></li><li><a href="global.html#expireCookie">expireCookie</a></li><li><a href="global.html#findPos">findPos</a></li><li><a href="global.html#formatValueFormatthenumericvalueasastring.Thisisusedafterincrementing/decrementingthevaluetoreformatthevalueasastring.">formatValue
Format the numeric value as a string.
This is used after incrementing/decrementing the value to reformat the
value as a string.</a></li><li><a href="global.html#get">get</a></li><li><a href="global.html#getClosest">getClosest</a></li><li><a href="global.html#getParents">getParents</a></li><li><a href="global.html#init">init</a></li><li><a href="global.html#inputsize">inputsize</a></li><li><a href="global.html#isNullish">isNullish</a></li><li><a href="global.html#loadloadConfig">load load Config</a></li><li><a href="global.html#loadFromURLLoadconfig/datafromURLifgiven">loadFromURL Load config/data from URL if given</a></li><li><a href="global.html#name">name</a></li><li><a href="global.html#observedAttributes">observedAttributes</a></li><li><a href="global.html#parseValue">parseValue</a></li><li><a href="global.html#pref">pref</a></li><li><a href="global.html#processResults">processResults</a></li><li><a href="global.html#readySignal">readySignal</a></li><li><a href="global.html#regexEscape">regexEscape</a></li><li><a href="global.html#removeStoragePrefCookie">removeStoragePrefCookie</a></li><li><a href="global.html#replaceStoragePrompt">replaceStoragePrompt</a></li><li><a href="global.html#set">set</a></li><li><a href="global.html#setupCurConfig">setupCurConfig</a></li><li><a href="global.html#setupCurPrefs">setupCurPrefs</a></li><li><a href="global.html#src">src</a></li><li><a href="global.html#stateEffects">stateEffects</a></li><li><a href="global.html#stepDown">stepDown</a></li><li><a href="global.html#stepUp">stepUp</a></li><li><a href="global.html#touchHandler">touchHandler</a></li><li><a href="global.html#updateLib">updateLib</a></li><li><a href="global.html#value">value</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.7</a> on Mon Nov 08 2021 09:47:00 GMT+0100 (Central European Standard Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>