working but incomplete version with new build.

master
JFH 2020-08-06 21:09:16 +02:00
parent 31bca25b1c
commit 7f461f9c63
373 changed files with 88846 additions and 554 deletions

285
dist/common/browser.js vendored Normal file
View File

@ -0,0 +1,285 @@
/* globals jQuery */
/**
* Browser detection.
* @module browser
* @license MIT
*
* @copyright 2010 Jeff Schiller, 2010 Alexis Deveria
*/
// Dependencies:
// 1) jQuery (for $.alert())
import './svgpathseg.js';
import {NS} from './namespaces.js';
const $ = jQuery;
const supportsSVG_ = (function () {
return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg').createSVGRect);
}());
/**
* @function module:browser.supportsSvg
* @returns {boolean}
*/
export const supportsSvg = () => supportsSVG_;
const {userAgent} = navigator;
const svg = document.createElementNS(NS.SVG, 'svg');
// Note: Browser sniffing should only be used if no other detection method is possible
const isOpera_ = Boolean(window.opera);
const isWebkit_ = userAgent.includes('AppleWebKit');
const isGecko_ = userAgent.includes('Gecko/');
const isIE_ = userAgent.includes('MSIE');
const isChrome_ = userAgent.includes('Chrome/');
const isWindows_ = userAgent.includes('Windows');
const isMac_ = userAgent.includes('Macintosh');
const isTouch_ = 'ontouchstart' in window;
const supportsSelectors_ = (function () {
return Boolean(svg.querySelector);
}());
const supportsXpath_ = (function () {
return Boolean(document.evaluate);
}());
// segList functions (for FF1.5 and 2.0)
const supportsPathReplaceItem_ = (function () {
const path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,10');
const seglist = path.pathSegList;
const seg = path.createSVGPathSegLinetoAbs(5, 5);
try {
seglist.replaceItem(seg, 1);
return true;
} catch (err) {}
return false;
}());
const supportsPathInsertItemBefore_ = (function () {
const path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,10');
const seglist = path.pathSegList;
const seg = path.createSVGPathSegLinetoAbs(5, 5);
try {
seglist.insertItemBefore(seg, 1);
return true;
} catch (err) {}
return false;
}());
// text character positioning (for IE9 and now Chrome)
const supportsGoodTextCharPos_ = (function () {
const svgroot = document.createElementNS(NS.SVG, 'svg');
const svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgroot);
svgcontent.setAttribute('x', 5);
svgroot.append(svgcontent);
const text = document.createElementNS(NS.SVG, 'text');
text.textContent = 'a';
svgcontent.append(text);
try { // Chrome now fails here
const pos = text.getStartPositionOfChar(0).x;
return (pos === 0);
} catch (err) {
return false;
} finally {
svgroot.remove();
}
}());
const supportsPathBBox_ = (function () {
const svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgcontent);
const path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 C0,0 10,10 10,0');
svgcontent.append(path);
const bbox = path.getBBox();
svgcontent.remove();
return (bbox.height > 4 && bbox.height < 5);
}());
// Support for correct bbox sizing on groups with horizontal/vertical lines
const supportsHVLineContainerBBox_ = (function () {
const svgcontent = document.createElementNS(NS.SVG, 'svg');
document.documentElement.append(svgcontent);
const path = document.createElementNS(NS.SVG, 'path');
path.setAttribute('d', 'M0,0 10,0');
const path2 = document.createElementNS(NS.SVG, 'path');
path2.setAttribute('d', 'M5,0 15,0');
const g = document.createElementNS(NS.SVG, 'g');
g.append(path, path2);
svgcontent.append(g);
const bbox = g.getBBox();
svgcontent.remove();
// Webkit gives 0, FF gives 10, Opera (correctly) gives 15
return (bbox.width === 15);
}());
const supportsEditableText_ = (function () {
// TODO: Find better way to check support for this
return isOpera_;
}());
const supportsGoodDecimals_ = (function () {
// Correct decimals on clone attributes (Opera < 10.5/win/non-en)
const rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('x', 0.1);
const crect = rect.cloneNode(false);
const retValue = (!crect.getAttribute('x').includes(','));
if (!retValue) {
// Todo: i18nize or remove
$.alert(
'NOTE: This version of Opera is known to contain bugs in SVG-edit.\n' +
'Please upgrade to the <a href="http://opera.com">latest version</a> in which the problems have been fixed.'
);
}
return retValue;
}());
const supportsNonScalingStroke_ = (function () {
const rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('style', 'vector-effect:non-scaling-stroke');
return rect.style.vectorEffect === 'non-scaling-stroke';
}());
let supportsNativeSVGTransformLists_ = (function () {
const rect = document.createElementNS(NS.SVG, 'rect');
const rxform = rect.transform.baseVal;
const t1 = svg.createSVGTransform();
rxform.appendItem(t1);
const r1 = rxform.getItem(0);
const isSVGTransform = (o) => {
// https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform
return o && typeof o === 'object' && typeof o.setMatrix === 'function' && 'angle' in o;
};
return isSVGTransform(r1) && isSVGTransform(t1) &&
r1.type === t1.type && r1.angle === t1.angle &&
r1.matrix.a === t1.matrix.a &&
r1.matrix.b === t1.matrix.b &&
r1.matrix.c === t1.matrix.c &&
r1.matrix.d === t1.matrix.d &&
r1.matrix.e === t1.matrix.e &&
r1.matrix.f === t1.matrix.f;
}());
// Public API
/**
* @function module:browser.isOpera
* @returns {boolean}
*/
export const isOpera = () => isOpera_;
/**
* @function module:browser.isWebkit
* @returns {boolean}
*/
export const isWebkit = () => isWebkit_;
/**
* @function module:browser.isGecko
* @returns {boolean}
*/
export const isGecko = () => isGecko_;
/**
* @function module:browser.isIE
* @returns {boolean}
*/
export const isIE = () => isIE_;
/**
* @function module:browser.isChrome
* @returns {boolean}
*/
export const isChrome = () => isChrome_;
/**
* @function module:browser.isWindows
* @returns {boolean}
*/
export const isWindows = () => isWindows_;
/**
* @function module:browser.isMac
* @returns {boolean}
*/
export const isMac = () => isMac_;
/**
* @function module:browser.isTouch
* @returns {boolean}
*/
export const isTouch = () => isTouch_;
/**
* @function module:browser.supportsSelectors
* @returns {boolean}
*/
export const supportsSelectors = () => supportsSelectors_;
/**
* @function module:browser.supportsXpath
* @returns {boolean}
*/
export const supportsXpath = () => supportsXpath_;
/**
* @function module:browser.supportsPathReplaceItem
* @returns {boolean}
*/
export const supportsPathReplaceItem = () => supportsPathReplaceItem_;
/**
* @function module:browser.supportsPathInsertItemBefore
* @returns {boolean}
*/
export const supportsPathInsertItemBefore = () => supportsPathInsertItemBefore_;
/**
* @function module:browser.supportsPathBBox
* @returns {boolean}
*/
export const supportsPathBBox = () => supportsPathBBox_;
/**
* @function module:browser.supportsHVLineContainerBBox
* @returns {boolean}
*/
export const supportsHVLineContainerBBox = () => supportsHVLineContainerBBox_;
/**
* @function module:browser.supportsGoodTextCharPos
* @returns {boolean}
*/
export const supportsGoodTextCharPos = () => supportsGoodTextCharPos_;
/**
* @function module:browser.supportsEditableText
* @returns {boolean}
*/
export const supportsEditableText = () => supportsEditableText_;
/**
* @function module:browser.supportsGoodDecimals
* @returns {boolean}
*/
export const supportsGoodDecimals = () => supportsGoodDecimals_;
/**
* @function module:browser.supportsNonScalingStroke
* @returns {boolean}
*/
export const supportsNonScalingStroke = () => supportsNonScalingStroke_;
/**
* @function module:browser.supportsNativeTransformLists
* @returns {boolean}
*/
export const supportsNativeTransformLists = () => supportsNativeSVGTransformLists_;
/**
* Set `supportsNativeSVGTransformLists_` to `false` (for unit testing).
* @function module:browser.disableSupportsNativeTransformLists
* @returns {void}
*/
export const disableSupportsNativeTransformLists = () => {
supportsNativeSVGTransformLists_ = false;
};

79
dist/common/jQuery.attr.js vendored Normal file
View File

@ -0,0 +1,79 @@
/**
* A jQuery module to work with SVG attributes.
* @module jQueryAttr
* @license MIT
*/
/**
* This fixes `$(...).attr()` to work as expected with SVG elements.
* Does not currently use `*AttributeNS()` since we rarely need that.
* Adds {@link external:jQuery.fn.attr}.
* See {@link https://api.jquery.com/attr/} for basic documentation of `.attr()`.
*
* Additional functionality:
* - When getting attributes, a string that's a number is returned as type number.
* - If an array is supplied as the first parameter, multiple values are returned
* as an object with values for each given attribute.
* @function module:jQueryAttr.jQueryAttr
* @param {external:jQuery} $ The jQuery object to which to add the plug-in
* @returns {external:jQuery}
*/
export default function jQueryPluginSVG ($) {
const proxied = $.fn.attr,
svgns = 'http://www.w3.org/2000/svg';
/**
* @typedef {PlainObject<string, string|Float>} module:jQueryAttr.Attributes
*/
/**
* @function external:jQuery.fn.attr
* @param {string|string[]|PlainObject<string, string>} key
* @param {string} value
* @returns {external:jQuery|module:jQueryAttr.Attributes}
*/
$.fn.attr = function (key, value) {
const len = this.length;
if (!len) { return proxied.call(this, key, value); }
for (let i = 0; i < len; ++i) {
const elem = this[i];
// set/get SVG attribute
if (elem.namespaceURI === svgns) {
// Setting attribute
if (value !== undefined) {
elem.setAttribute(key, value);
} else if (Array.isArray(key)) {
// Getting attributes from array
const obj = {};
let j = key.length;
while (j--) {
const aname = key[j];
let attr = elem.getAttribute(aname);
// This returns a number when appropriate
if (attr || attr === '0') {
attr = isNaN(attr) ? attr : (attr - 0);
}
obj[aname] = attr;
}
return obj;
}
if (typeof key === 'object') {
// Setting attributes from object
for (const [name, val] of Object.entries(key)) {
elem.setAttribute(name, val);
}
// Getting attribute
} else {
let attr = elem.getAttribute(key);
if (attr || attr === '0') {
attr = isNaN(attr) ? attr : (attr - 0);
}
return attr;
}
} else {
return proxied.call(this, key, value);
}
}
return this;
};
return $;
}

222
dist/common/layer.js vendored Normal file
View File

@ -0,0 +1,222 @@
/* globals jQuery */
/**
* Provides tools for the layer concept.
* @module layer
* @license MIT
*
* @copyright 2011 Jeff Schiller, 2016 Flint O'Brien
*/
import {NS} from './namespaces.js';
import {toXml, walkTree, isNullish} from './utilities.js';
const $ = jQuery;
/**
* This class encapsulates the concept of a layer in the drawing. It can be constructed with
* an existing group element or, with three parameters, will create a new layer group element.
*
* @example
* const l1 = new Layer('name', group); // Use the existing group for this layer.
* const l2 = new Layer('name', group, svgElem); // Create a new group and add it to the DOM after group.
* const l3 = new Layer('name', null, svgElem); // Create a new group and add it to the DOM as the last layer.
* @memberof module:layer
*/
class Layer {
/**
* @param {string} name - Layer name
* @param {SVGGElement|null} group - An existing SVG group element or null.
* If group and no svgElem, use group for this layer.
* If group and svgElem, create a new group element and insert it in the DOM after group.
* If no group and svgElem, create a new group element and insert it in the DOM as the last layer.
* @param {SVGGElement} [svgElem] - The SVG DOM element. If defined, use this to add
* a new layer to the document.
*/
constructor (name, group, svgElem) {
this.name_ = name;
this.group_ = svgElem ? null : group;
if (svgElem) {
// Create a group element with title and add it to the DOM.
const svgdoc = svgElem.ownerDocument;
this.group_ = svgdoc.createElementNS(NS.SVG, 'g');
const layerTitle = svgdoc.createElementNS(NS.SVG, 'title');
layerTitle.textContent = name;
this.group_.append(layerTitle);
if (group) {
$(group).after(this.group_);
} else {
svgElem.append(this.group_);
}
}
addLayerClass(this.group_);
walkTree(this.group_, function (e) {
e.setAttribute('style', 'pointer-events:inherit');
});
this.group_.setAttribute('style', svgElem ? 'pointer-events:all' : 'pointer-events:none');
}
/**
* Get the layer's name.
* @returns {string} The layer name
*/
getName () {
return this.name_;
}
/**
* Get the group element for this layer.
* @returns {SVGGElement} The layer SVG group
*/
getGroup () {
return this.group_;
}
/**
* Active this layer so it takes pointer events.
* @returns {void}
*/
activate () {
this.group_.setAttribute('style', 'pointer-events:all');
}
/**
* Deactive this layer so it does NOT take pointer events.
* @returns {void}
*/
deactivate () {
this.group_.setAttribute('style', 'pointer-events:none');
}
/**
* Set this layer visible or hidden based on 'visible' parameter.
* @param {boolean} visible - If true, make visible; otherwise, hide it.
* @returns {void}
*/
setVisible (visible) {
const expected = visible === undefined || visible ? 'inline' : 'none';
const oldDisplay = this.group_.getAttribute('display');
if (oldDisplay !== expected) {
this.group_.setAttribute('display', expected);
}
}
/**
* Is this layer visible?
* @returns {boolean} True if visible.
*/
isVisible () {
return this.group_.getAttribute('display') !== 'none';
}
/**
* Get layer opacity.
* @returns {Float} Opacity value.
*/
getOpacity () {
const opacity = this.group_.getAttribute('opacity');
if (isNullish(opacity)) {
return 1;
}
return Number.parseFloat(opacity);
}
/**
* Sets the opacity of this layer. If opacity is not a value between 0.0 and 1.0,
* nothing happens.
* @param {Float} opacity - A float value in the range 0.0-1.0
* @returns {void}
*/
setOpacity (opacity) {
if (typeof opacity === 'number' && opacity >= 0.0 && opacity <= 1.0) {
this.group_.setAttribute('opacity', opacity);
}
}
/**
* Append children to this layer.
* @param {SVGGElement} children - The children to append to this layer.
* @returns {void}
*/
appendChildren (children) {
for (const child of children) {
this.group_.append(child);
}
}
/**
* @returns {SVGTitleElement|null}
*/
getTitleElement () {
const len = this.group_.childNodes.length;
for (let i = 0; i < len; ++i) {
const child = this.group_.childNodes.item(i);
if (child && child.tagName === 'title') {
return child;
}
}
return null;
}
/**
* Set the name of this layer.
* @param {string} name - The new name.
* @param {module:history.HistoryRecordingService} hrService - History recording service
* @returns {string|null} The new name if changed; otherwise, null.
*/
setName (name, hrService) {
const previousName = this.name_;
name = toXml(name);
// now change the underlying title element contents
const title = this.getTitleElement();
if (title) {
$(title).empty();
title.textContent = name;
this.name_ = name;
if (hrService) {
hrService.changeElement(title, {'#text': previousName});
}
return this.name_;
}
return null;
}
/**
* Remove this layer's group from the DOM. No more functions on group can be called after this.
* @returns {SVGGElement} The layer SVG group that was just removed.
*/
removeGroup () {
const group = this.group_;
this.group_.remove();
this.group_ = undefined;
return group;
}
}
/**
* @property {string} CLASS_NAME - class attribute assigned to all layer groups.
*/
Layer.CLASS_NAME = 'layer';
/**
* @property {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME
*/
Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)');
/**
* Add class `Layer.CLASS_NAME` to the element (usually `class='layer'`).
*
* @param {SVGGElement} elem - The SVG element to update
* @returns {void}
*/
function addLayerClass (elem) {
const classes = elem.getAttribute('class');
if (isNullish(classes) || !classes.length) {
elem.setAttribute('class', Layer.CLASS_NAME);
} else if (!Layer.CLASS_REGEX.test(classes)) {
elem.setAttribute('class', classes + ' ' + Layer.CLASS_NAME);
}
}
export default Layer;

222
dist/common/math.js vendored Normal file
View File

@ -0,0 +1,222 @@
/**
* Mathematical utilities.
* @module math
* @license MIT
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
/**
* @typedef {PlainObject} module:math.AngleCoord45
* @property {Float} x - The angle-snapped x value
* @property {Float} y - The angle-snapped y value
* @property {Integer} a - The angle at which to snap
*/
/**
* @typedef {PlainObject} module:math.XYObject
* @property {Float} x
* @property {Float} y
*/
import {NS} from './namespaces.js';
import {getTransformList} from './svgtransformlist.js';
// Constants
const NEAR_ZERO = 1e-14;
// Throw away SVGSVGElement used for creating matrices/transforms.
const svg = document.createElementNS(NS.SVG, 'svg');
/**
* A (hopefully) quicker function to transform a point by a matrix
* (this function avoids any DOM calls and just does the math).
* @function module:math.transformPoint
* @param {Float} x - Float representing the x coordinate
* @param {Float} y - Float representing the y coordinate
* @param {SVGMatrix} m - Matrix object to transform the point with
* @returns {module:math.XYObject} An x, y object representing the transformed point
*/
export const transformPoint = function (x, y, m) {
return {x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f};
};
/**
* Helper function to check if the matrix performs no actual transform
* (i.e. exists for identity purposes).
* @function module:math.isIdentity
* @param {SVGMatrix} m - The matrix object to check
* @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
*/
export const isIdentity = function (m) {
return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0);
};
/**
* This function tries to return a `SVGMatrix` that is the multiplication `m1 * m2`.
* We also round to zero when it's near zero.
* @function module:math.matrixMultiply
* @param {...SVGMatrix} args - Matrix objects to multiply
* @returns {SVGMatrix} The matrix object resulting from the calculation
*/
export const matrixMultiply = function (...args) {
const m = args.reduceRight((prev, m1) => {
return m1.multiply(prev);
});
if (Math.abs(m.a) < NEAR_ZERO) { m.a = 0; }
if (Math.abs(m.b) < NEAR_ZERO) { m.b = 0; }
if (Math.abs(m.c) < NEAR_ZERO) { m.c = 0; }
if (Math.abs(m.d) < NEAR_ZERO) { m.d = 0; }
if (Math.abs(m.e) < NEAR_ZERO) { m.e = 0; }
if (Math.abs(m.f) < NEAR_ZERO) { m.f = 0; }
return m;
};
/**
* See if the given transformlist includes a non-indentity matrix transform.
* @function module:math.hasMatrixTransform
* @param {SVGTransformList} [tlist] - The transformlist to check
* @returns {boolean} Whether or not a matrix transform was found
*/
export const hasMatrixTransform = function (tlist) {
if (!tlist) { return false; }
let num = tlist.numberOfItems;
while (num--) {
const xform = tlist.getItem(num);
if (xform.type === 1 && !isIdentity(xform.matrix)) { return true; }
}
return false;
};
/**
* @typedef {PlainObject} module:math.TransformedBox An object with the following values
* @property {module:math.XYObject} tl - The top left coordinate
* @property {module:math.XYObject} tr - The top right coordinate
* @property {module:math.XYObject} bl - The bottom left coordinate
* @property {module:math.XYObject} br - The bottom right coordinate
* @property {PlainObject} aabox - Object with the following values:
* @property {Float} aabox.x - Float with the axis-aligned x coordinate
* @property {Float} aabox.y - Float with the axis-aligned y coordinate
* @property {Float} aabox.width - Float with the axis-aligned width coordinate
* @property {Float} aabox.height - Float with the axis-aligned height coordinate
*/
/**
* Transforms a rectangle based on the given matrix.
* @function module:math.transformBox
* @param {Float} l - Float with the box's left coordinate
* @param {Float} t - Float with the box's top coordinate
* @param {Float} w - Float with the box width
* @param {Float} h - Float with the box height
* @param {SVGMatrix} m - Matrix object to transform the box by
* @returns {module:math.TransformedBox}
*/
export const transformBox = function (l, t, w, h, m) {
const tl = transformPoint(l, t, m),
tr = transformPoint((l + w), t, m),
bl = transformPoint(l, (t + h), m),
br = transformPoint((l + w), (t + h), m),
minx = Math.min(tl.x, tr.x, bl.x, br.x),
maxx = Math.max(tl.x, tr.x, bl.x, br.x),
miny = Math.min(tl.y, tr.y, bl.y, br.y),
maxy = Math.max(tl.y, tr.y, bl.y, br.y);
return {
tl,
tr,
bl,
br,
aabox: {
x: minx,
y: miny,
width: (maxx - minx),
height: (maxy - miny)
}
};
};
/**
* This returns a single matrix Transform for a given Transform List
* (this is the equivalent of `SVGTransformList.consolidate()` but unlike
* that method, this one does not modify the actual `SVGTransformList`).
* This function is very liberal with its `min`, `max` arguments.
* @function module:math.transformListToTransform
* @param {SVGTransformList} tlist - The transformlist object
* @param {Integer} [min=0] - Optional integer indicating start transform position
* @param {Integer} [max] - Optional integer indicating end transform position;
* defaults to one less than the tlist's `numberOfItems`
* @returns {SVGTransform} A single matrix transform object
*/
export const transformListToTransform = function (tlist, min, max) {
if (!tlist) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix());
}
min = min || 0;
max = max || (tlist.numberOfItems - 1);
min = Number.parseInt(min);
max = Number.parseInt(max);
if (min > max) { const temp = max; max = min; min = temp; }
let m = svg.createSVGMatrix();
for (let i = min; i <= max; ++i) {
// if our indices are out of range, just use a harmless identity matrix
const mtom = (i >= 0 && i < tlist.numberOfItems
? tlist.getItem(i).matrix
: svg.createSVGMatrix());
m = matrixMultiply(m, mtom);
}
return svg.createSVGTransformFromMatrix(m);
};
/**
* Get the matrix object for a given element.
* @function module:math.getMatrix
* @param {Element} elem - The DOM element to check
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
*/
export const getMatrix = function (elem) {
const tlist = getTransformList(elem);
return transformListToTransform(tlist).matrix;
};
/**
* Returns a 45 degree angle coordinate associated with the two given
* coordinates.
* @function module:math.snapToAngle
* @param {Integer} x1 - First coordinate's x value
* @param {Integer} y1 - First coordinate's y value
* @param {Integer} x2 - Second coordinate's x value
* @param {Integer} y2 - Second coordinate's y value
* @returns {module:math.AngleCoord45}
*/
export const snapToAngle = function (x1, y1, x2, y2) {
const snap = Math.PI / 4; // 45 degrees
const dx = x2 - x1;
const dy = y2 - y1;
const angle = Math.atan2(dy, dx);
const dist = Math.sqrt(dx * dx + dy * dy);
const snapangle = Math.round(angle / snap) * snap;
return {
x: x1 + dist * Math.cos(snapangle),
y: y1 + dist * Math.sin(snapangle),
a: snapangle
};
};
/**
* Check if two rectangles (BBoxes objects) intersect each other.
* @function module:math.rectsIntersect
* @param {SVGRect} r1 - The first BBox-like object
* @param {SVGRect} r2 - The second BBox-like object
* @returns {boolean} True if rectangles intersect
*/
export const rectsIntersect = function (r1, r2) {
return r2.x < (r1.x + r1.width) &&
(r2.x + r2.width) > r1.x &&
r2.y < (r1.y + r1.height) &&
(r2.y + r2.height) > r1.y;
};

33
dist/common/namespaces.js vendored Normal file
View File

@ -0,0 +1,33 @@
/**
* Namespaces or tools therefor.
* @module namespaces
* @license MIT
*/
/**
* Common namepaces constants in alpha order.
* @enum {string}
* @type {PlainObject}
* @memberof module:namespaces
*/
export const NS = {
HTML: 'http://www.w3.org/1999/xhtml',
MATH: 'http://www.w3.org/1998/Math/MathML',
SE: 'http://svg-edit.googlecode.com',
SVG: 'http://www.w3.org/2000/svg',
XLINK: 'http://www.w3.org/1999/xlink',
XML: 'http://www.w3.org/XML/1998/namespace',
XMLNS: 'http://www.w3.org/2000/xmlns/' // see http://www.w3.org/TR/REC-xml-names/#xmlReserved
};
/**
* @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;
};

972
dist/common/svgpathseg.js vendored Normal file
View File

@ -0,0 +1,972 @@
/* eslint-disable import/unambiguous, max-len */
/* globals SVGPathSeg, SVGPathSegMovetoRel, SVGPathSegMovetoAbs,
SVGPathSegMovetoRel, SVGPathSegLinetoRel, SVGPathSegLinetoAbs,
SVGPathSegLinetoHorizontalRel, SVGPathSegLinetoHorizontalAbs,
SVGPathSegLinetoVerticalRel, SVGPathSegLinetoVerticalAbs,
SVGPathSegClosePath, SVGPathSegCurvetoCubicRel,
SVGPathSegCurvetoCubicAbs, SVGPathSegCurvetoCubicSmoothRel,
SVGPathSegCurvetoCubicSmoothAbs, SVGPathSegCurvetoQuadraticRel,
SVGPathSegCurvetoQuadraticAbs, SVGPathSegCurvetoQuadraticSmoothRel,
SVGPathSegCurvetoQuadraticSmoothAbs, SVGPathSegArcRel, SVGPathSegArcAbs */
/**
* SVGPathSeg API polyfill
* https://github.com/progers/pathseg
*
* This is a drop-in replacement for the `SVGPathSeg` and `SVGPathSegList` APIs
* that were removed from SVG2 ({@link https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html}),
* including the latest spec changes which were implemented in Firefox 43 and
* Chrome 46.
*/
/* eslint-disable no-shadow, class-methods-use-this, jsdoc/require-jsdoc */
// Linting: We avoid `no-shadow` as ESLint thinks these are still available globals
// Linting: We avoid `class-methods-use-this` as this is a polyfill that must
// follow the conventions
(() => {
if (!('SVGPathSeg' in window)) {
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
class SVGPathSeg {
constructor (type, typeAsLetter, owningPathSegList) {
this.pathSegType = type;
this.pathSegTypeAsLetter = typeAsLetter;
this._owningPathSegList = owningPathSegList;
}
// Notify owning PathSegList on any changes so they can be synchronized back to the path element.
_segmentChanged () {
if (this._owningPathSegList) {
this._owningPathSegList.segmentChanged(this);
}
}
}
SVGPathSeg.prototype.classname = 'SVGPathSeg';
SVGPathSeg.PATHSEG_UNKNOWN = 0;
SVGPathSeg.PATHSEG_CLOSEPATH = 1;
SVGPathSeg.PATHSEG_MOVETO_ABS = 2;
SVGPathSeg.PATHSEG_MOVETO_REL = 3;
SVGPathSeg.PATHSEG_LINETO_ABS = 4;
SVGPathSeg.PATHSEG_LINETO_REL = 5;
SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6;
SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7;
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8;
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9;
SVGPathSeg.PATHSEG_ARC_ABS = 10;
SVGPathSeg.PATHSEG_ARC_REL = 11;
SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12;
SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13;
SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14;
SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15;
SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
class SVGPathSegClosePath extends SVGPathSeg {
constructor (owningPathSegList) {
super(SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
}
toString () { return '[object SVGPathSegClosePath]'; }
_asPathString () { return this.pathSegTypeAsLetter; }
clone () { return new SVGPathSegClosePath(undefined); }
}
class SVGPathSegMovetoAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_MOVETO_ABS, 'M', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegMovetoAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegMovetoAbs(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegMovetoAbs.prototype, {
x: {
get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true
},
y: {
get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true
}
});
class SVGPathSegMovetoRel extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_MOVETO_REL, 'm', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegMovetoRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegMovetoRel(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegMovetoRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegLinetoAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_LINETO_ABS, 'L', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegLinetoAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegLinetoAbs(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegLinetoAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegLinetoRel extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_LINETO_REL, 'l', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegLinetoRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegLinetoRel(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegLinetoRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoCubicAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y, x1, y1, x2, y2) {
super(SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, 'C', owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
this._x2 = x2;
this._y2 = y2;
}
toString () { return '[object SVGPathSegCurvetoCubicAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
}
Object.defineProperties(SVGPathSegCurvetoCubicAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x1: {get () { return this._x1; }, set (x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true},
y1: {get () { return this._y1; }, set (y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true},
x2: {get () { return this._x2; }, set (x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true},
y2: {get () { return this._y2; }, set (y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoCubicRel extends SVGPathSeg {
constructor (owningPathSegList, x, y, x1, y1, x2, y2) {
super(SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, 'c', owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
this._x2 = x2;
this._y2 = y2;
}
toString () { return '[object SVGPathSegCurvetoCubicRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
}
Object.defineProperties(SVGPathSegCurvetoCubicRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x1: {get () { return this._x1; }, set (x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true},
y1: {get () { return this._y1; }, set (y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true},
x2: {get () { return this._x2; }, set (x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true},
y2: {get () { return this._y2; }, set (y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoQuadraticAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y, x1, y1) {
super(SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, 'Q', owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
}
toString () { return '[object SVGPathSegCurvetoQuadraticAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); }
}
Object.defineProperties(SVGPathSegCurvetoQuadraticAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x1: {get () { return this._x1; }, set (x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true},
y1: {get () { return this._y1; }, set (y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoQuadraticRel extends SVGPathSeg {
constructor (owningPathSegList, x, y, x1, y1) {
super(SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, 'q', owningPathSegList);
this._x = x;
this._y = y;
this._x1 = x1;
this._y1 = y1;
}
toString () { return '[object SVGPathSegCurvetoQuadraticRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); }
}
Object.defineProperties(SVGPathSegCurvetoQuadraticRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x1: {get () { return this._x1; }, set (x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true},
y1: {get () { return this._y1; }, set (y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegArcAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
super(SVGPathSeg.PATHSEG_ARC_ABS, 'A', owningPathSegList);
this._x = x;
this._y = y;
this._r1 = r1;
this._r2 = r2;
this._angle = angle;
this._largeArcFlag = largeArcFlag;
this._sweepFlag = sweepFlag;
}
toString () { return '[object SVGPathSegArcAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
}
Object.defineProperties(SVGPathSegArcAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
r1: {get () { return this._r1; }, set (r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true},
r2: {get () { return this._r2; }, set (r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true},
angle: {get () { return this._angle; }, set (angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true},
largeArcFlag: {get () { return this._largeArcFlag; }, set (largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true},
sweepFlag: {get () { return this._sweepFlag; }, set (sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegArcRel extends SVGPathSeg {
constructor (owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
super(SVGPathSeg.PATHSEG_ARC_REL, 'a', owningPathSegList);
this._x = x;
this._y = y;
this._r1 = r1;
this._r2 = r2;
this._angle = angle;
this._largeArcFlag = largeArcFlag;
this._sweepFlag = sweepFlag;
}
toString () { return '[object SVGPathSegArcRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
}
Object.defineProperties(SVGPathSegArcRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
r1: {get () { return this._r1; }, set (r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true},
r2: {get () { return this._r2; }, set (r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true},
angle: {get () { return this._angle; }, set (angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true},
largeArcFlag: {get () { return this._largeArcFlag; }, set (largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true},
sweepFlag: {get () { return this._sweepFlag; }, set (sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegLinetoHorizontalAbs extends SVGPathSeg {
constructor (owningPathSegList, x) {
super(SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, 'H', owningPathSegList);
this._x = x;
}
toString () { return '[object SVGPathSegLinetoHorizontalAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x; }
clone () { return new SVGPathSegLinetoHorizontalAbs(undefined, this._x); }
}
Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype, 'x', {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true});
class SVGPathSegLinetoHorizontalRel extends SVGPathSeg {
constructor (owningPathSegList, x) {
super(SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, 'h', owningPathSegList);
this._x = x;
}
toString () { return '[object SVGPathSegLinetoHorizontalRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x; }
clone () { return new SVGPathSegLinetoHorizontalRel(undefined, this._x); }
}
Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype, 'x', {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true});
class SVGPathSegLinetoVerticalAbs extends SVGPathSeg {
constructor (owningPathSegList, y) {
super(SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, 'V', owningPathSegList);
this._y = y;
}
toString () { return '[object SVGPathSegLinetoVerticalAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._y; }
clone () { return new SVGPathSegLinetoVerticalAbs(undefined, this._y); }
}
Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype, 'y', {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true});
class SVGPathSegLinetoVerticalRel extends SVGPathSeg {
constructor (owningPathSegList, y) {
super(SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, 'v', owningPathSegList);
this._y = y;
}
toString () { return '[object SVGPathSegLinetoVerticalRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._y; }
clone () { return new SVGPathSegLinetoVerticalRel(undefined, this._y); }
}
Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype, 'y', {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true});
class SVGPathSegCurvetoCubicSmoothAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y, x2, y2) {
super(SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, 'S', owningPathSegList);
this._x = x;
this._y = y;
this._x2 = x2;
this._y2 = y2;
}
toString () { return '[object SVGPathSegCurvetoCubicSmoothAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); }
}
Object.defineProperties(SVGPathSegCurvetoCubicSmoothAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x2: {get () { return this._x2; }, set (x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true},
y2: {get () { return this._y2; }, set (y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoCubicSmoothRel extends SVGPathSeg {
constructor (owningPathSegList, x, y, x2, y2) {
super(SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, 's', owningPathSegList);
this._x = x;
this._y = y;
this._x2 = x2;
this._y2 = y2;
}
toString () { return '[object SVGPathSegCurvetoCubicSmoothRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); }
}
Object.defineProperties(SVGPathSegCurvetoCubicSmoothRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true},
x2: {get () { return this._x2; }, set (x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true},
y2: {get () { return this._y2; }, set (y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoQuadraticSmoothAbs extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, 'T', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegCurvetoQuadraticSmoothAbs]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegCurvetoQuadraticSmoothAbs.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true}
});
class SVGPathSegCurvetoQuadraticSmoothRel extends SVGPathSeg {
constructor (owningPathSegList, x, y) {
super(SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, 't', owningPathSegList);
this._x = x;
this._y = y;
}
toString () { return '[object SVGPathSegCurvetoQuadraticSmoothRel]'; }
_asPathString () { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
clone () { return new SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); }
}
Object.defineProperties(SVGPathSegCurvetoQuadraticSmoothRel.prototype, {
x: {get () { return this._x; }, set (x) { this._x = x; this._segmentChanged(); }, enumerable: true},
y: {get () { return this._y; }, set (y) { this._y = y; this._segmentChanged(); }, enumerable: true}
});
// Add createSVGPathSeg* functions to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathElement.
SVGPathElement.prototype.createSVGPathSegClosePath = function () { return new SVGPathSegClosePath(undefined); };
SVGPathElement.prototype.createSVGPathSegMovetoAbs = function (x, y) { return new SVGPathSegMovetoAbs(undefined, x, y); };
SVGPathElement.prototype.createSVGPathSegMovetoRel = function (x, y) { return new SVGPathSegMovetoRel(undefined, x, y); };
SVGPathElement.prototype.createSVGPathSegLinetoAbs = function (x, y) { return new SVGPathSegLinetoAbs(undefined, x, y); };
SVGPathElement.prototype.createSVGPathSegLinetoRel = function (x, y) { return new SVGPathSegLinetoRel(undefined, x, y); };
SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function (x, y, x1, y1, x2, y2) { return new SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2); };
SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function (x, y, x1, y1, x2, y2) { return new SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2); };
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function (x, y, x1, y1) { return new SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1); };
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function (x, y, x1, y1) { return new SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1); };
SVGPathElement.prototype.createSVGPathSegArcAbs = function (x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); };
SVGPathElement.prototype.createSVGPathSegArcRel = function (x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); };
SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function (x) { return new SVGPathSegLinetoHorizontalAbs(undefined, x); };
SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function (x) { return new SVGPathSegLinetoHorizontalRel(undefined, x); };
SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function (y) { return new SVGPathSegLinetoVerticalAbs(undefined, y); };
SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function (y) { return new SVGPathSegLinetoVerticalRel(undefined, y); };
SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function (x, y, x2, y2) { return new SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2); };
SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function (x, y, x2, y2) { return new SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2); };
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function (x, y) { return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y); };
SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function (x, y) { return new SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y); };
if (!('getPathSegAtLength' in SVGPathElement.prototype)) {
// Add getPathSegAtLength to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-__svg__SVGPathElement__getPathSegAtLength
// This polyfill requires SVGPathElement.getTotalLength to implement the distance-along-a-path algorithm.
SVGPathElement.prototype.getPathSegAtLength = function (distance) {
if (distance === undefined || !isFinite(distance)) {
throw new Error('Invalid arguments.');
}
const measurementElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
measurementElement.setAttribute('d', this.getAttribute('d'));
let lastPathSegment = measurementElement.pathSegList.numberOfItems - 1;
// If the path is empty, return 0.
if (lastPathSegment <= 0) {
return 0;
}
do {
measurementElement.pathSegList.removeItem(lastPathSegment);
if (distance > measurementElement.getTotalLength()) {
break;
}
lastPathSegment--;
} while (lastPathSegment > 0);
return lastPathSegment;
};
}
window.SVGPathSeg = SVGPathSeg;
window.SVGPathSegClosePath = SVGPathSegClosePath;
window.SVGPathSegMovetoAbs = SVGPathSegMovetoAbs;
window.SVGPathSegMovetoRel = SVGPathSegMovetoRel;
window.SVGPathSegLinetoAbs = SVGPathSegLinetoAbs;
window.SVGPathSegLinetoRel = SVGPathSegLinetoRel;
window.SVGPathSegCurvetoCubicAbs = SVGPathSegCurvetoCubicAbs;
window.SVGPathSegCurvetoCubicRel = SVGPathSegCurvetoCubicRel;
window.SVGPathSegCurvetoQuadraticAbs = SVGPathSegCurvetoQuadraticAbs;
window.SVGPathSegCurvetoQuadraticRel = SVGPathSegCurvetoQuadraticRel;
window.SVGPathSegArcAbs = SVGPathSegArcAbs;
window.SVGPathSegArcRel = SVGPathSegArcRel;
window.SVGPathSegLinetoHorizontalAbs = SVGPathSegLinetoHorizontalAbs;
window.SVGPathSegLinetoHorizontalRel = SVGPathSegLinetoHorizontalRel;
window.SVGPathSegLinetoVerticalAbs = SVGPathSegLinetoVerticalAbs;
window.SVGPathSegLinetoVerticalRel = SVGPathSegLinetoVerticalRel;
window.SVGPathSegCurvetoCubicSmoothAbs = SVGPathSegCurvetoCubicSmoothAbs;
window.SVGPathSegCurvetoCubicSmoothRel = SVGPathSegCurvetoCubicSmoothRel;
window.SVGPathSegCurvetoQuadraticSmoothAbs = SVGPathSegCurvetoQuadraticSmoothAbs;
window.SVGPathSegCurvetoQuadraticSmoothRel = SVGPathSegCurvetoQuadraticSmoothRel;
}
// Checking for SVGPathSegList in window checks for the case of an implementation without the
// SVGPathSegList API.
// The second check for appendItem is specific to Firefox 59+ which removed only parts of the
// SVGPathSegList API (e.g., appendItem). In this case we need to re-implement the entire API
// so the polyfill data (i.e., _list) is used throughout.
if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.prototype)) {
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
class SVGPathSegList {
constructor (pathElement) {
this._pathElement = pathElement;
this._list = this._parsePath(this._pathElement.getAttribute('d'));
// Use a MutationObserver to catch changes to the path's "d" attribute.
this._mutationObserverConfig = {attributes: true, attributeFilter: ['d']};
this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this));
this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
}
// Process any pending mutations to the path element and update the list as needed.
// This should be the first call of all public functions and is needed because
// MutationObservers are not synchronous so we can have pending asynchronous mutations.
_checkPathSynchronizedToList () {
this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
}
_updateListFromPathMutations (mutationRecords) {
if (!this._pathElement) {
return;
}
let hasPathMutations = false;
mutationRecords.forEach((record) => {
if (record.attributeName === 'd') {
hasPathMutations = true;
}
});
if (hasPathMutations) {
this._list = this._parsePath(this._pathElement.getAttribute('d'));
}
}
// Serialize the list and update the path's 'd' attribute.
_writeListToPath () {
this._pathElementMutationObserver.disconnect();
this._pathElement.setAttribute('d', SVGPathSegList._pathSegArrayAsString(this._list));
this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
}
// When a path segment changes the list needs to be synchronized back to the path element.
segmentChanged (pathSeg) {
this._writeListToPath();
}
clear () {
this._checkPathSynchronizedToList();
this._list.forEach((pathSeg) => {
pathSeg._owningPathSegList = null;
});
this._list = [];
this._writeListToPath();
}
initialize (newItem) {
this._checkPathSynchronizedToList();
this._list = [newItem];
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
_checkValidIndex (index) {
if (isNaN(index) || index < 0 || index >= this.numberOfItems) {
throw new Error('INDEX_SIZE_ERR');
}
}
getItem (index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
return this._list[index];
}
insertItemBefore (newItem, index) {
this._checkPathSynchronizedToList();
// Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
if (index > this.numberOfItems) {
index = this.numberOfItems;
}
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._list.splice(index, 0, newItem);
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
replaceItem (newItem, index) {
this._checkPathSynchronizedToList();
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._checkValidIndex(index);
this._list[index] = newItem;
newItem._owningPathSegList = this;
this._writeListToPath();
return newItem;
}
removeItem (index) {
this._checkPathSynchronizedToList();
this._checkValidIndex(index);
const item = this._list[index];
this._list.splice(index, 1);
this._writeListToPath();
return item;
}
appendItem (newItem) {
this._checkPathSynchronizedToList();
if (newItem._owningPathSegList) {
// SVG2 spec says to make a copy.
newItem = newItem.clone();
}
this._list.push(newItem);
newItem._owningPathSegList = this;
// TODO: Optimize this to just append to the existing attribute.
this._writeListToPath();
return newItem;
}
// This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
_parsePath (string) {
if (!string || !string.length) {
return [];
}
const owningPathSegList = this;
class Builder {
constructor () {
this.pathSegList = [];
}
appendSegment (pathSeg) {
this.pathSegList.push(pathSeg);
}
}
class Source {
constructor (string) {
this._string = string;
this._currentIndex = 0;
this._endIndex = this._string.length;
this._previousCommand = SVGPathSeg.PATHSEG_UNKNOWN;
this._skipOptionalSpaces();
}
_isCurrentSpace () {
const character = this._string[this._currentIndex];
return character <= ' ' && (character === ' ' || character === '\n' || character === '\t' || character === '\r' || character === '\f');
}
_skipOptionalSpaces () {
while (this._currentIndex < this._endIndex && this._isCurrentSpace()) {
this._currentIndex++;
}
return this._currentIndex < this._endIndex;
}
_skipOptionalSpacesOrDelimiter () {
if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) !== ',') {
return false;
}
if (this._skipOptionalSpaces()) {
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === ',') {
this._currentIndex++;
this._skipOptionalSpaces();
}
}
return this._currentIndex < this._endIndex;
}
hasMoreData () {
return this._currentIndex < this._endIndex;
}
peekSegmentType () {
const lookahead = this._string[this._currentIndex];
return this._pathSegTypeFromChar(lookahead);
}
_pathSegTypeFromChar (lookahead) {
switch (lookahead) {
case 'Z':
case 'z':
return SVGPathSeg.PATHSEG_CLOSEPATH;
case 'M':
return SVGPathSeg.PATHSEG_MOVETO_ABS;
case 'm':
return SVGPathSeg.PATHSEG_MOVETO_REL;
case 'L':
return SVGPathSeg.PATHSEG_LINETO_ABS;
case 'l':
return SVGPathSeg.PATHSEG_LINETO_REL;
case 'C':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
case 'c':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
case 'Q':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
case 'q':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
case 'A':
return SVGPathSeg.PATHSEG_ARC_ABS;
case 'a':
return SVGPathSeg.PATHSEG_ARC_REL;
case 'H':
return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
case 'h':
return SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
case 'V':
return SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
case 'v':
return SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
case 'S':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
case 's':
return SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
case 'T':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
case 't':
return SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
default:
return SVGPathSeg.PATHSEG_UNKNOWN;
}
}
_nextCommandHelper (lookahead, previousCommand) {
// Check for remaining coordinates in the current command.
if ((lookahead === '+' || lookahead === '-' || lookahead === '.' || (lookahead >= '0' && lookahead <= '9')) && previousCommand !== SVGPathSeg.PATHSEG_CLOSEPATH) {
if (previousCommand === SVGPathSeg.PATHSEG_MOVETO_ABS) {
return SVGPathSeg.PATHSEG_LINETO_ABS;
}
if (previousCommand === SVGPathSeg.PATHSEG_MOVETO_REL) {
return SVGPathSeg.PATHSEG_LINETO_REL;
}
return previousCommand;
}
return SVGPathSeg.PATHSEG_UNKNOWN;
}
initialCommandIsMoveTo () {
// If the path is empty it is still valid, so return true.
if (!this.hasMoreData()) {
return true;
}
const command = this.peekSegmentType();
// Path must start with moveTo.
return command === SVGPathSeg.PATHSEG_MOVETO_ABS || command === SVGPathSeg.PATHSEG_MOVETO_REL;
}
// Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
_parseNumber () {
let exponent = 0;
let integer = 0;
let frac = 1;
let decimal = 0;
let sign = 1;
let expsign = 1;
const startIndex = this._currentIndex;
this._skipOptionalSpaces();
// Read the sign.
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '+') {
this._currentIndex++;
} else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '-') {
this._currentIndex++;
sign = -1;
}
if (this._currentIndex === this._endIndex || ((this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') && this._string.charAt(this._currentIndex) !== '.')) {
// The first character of a number must be one of [0-9+-.].
return undefined;
}
// Read the integer part, build right-to-left.
const startIntPartIndex = this._currentIndex;
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
this._currentIndex++; // Advance to first non-digit.
}
if (this._currentIndex !== startIntPartIndex) {
let scanIntPartIndex = this._currentIndex - 1;
let multiplier = 1;
while (scanIntPartIndex >= startIntPartIndex) {
integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
multiplier *= 10;
}
}
// Read the decimals.
if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) === '.') {
this._currentIndex++;
// There must be a least one digit following the .
if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') {
return undefined;
}
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
frac *= 10;
decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
this._currentIndex += 1;
}
}
// Read the exponent part.
if (this._currentIndex !== startIndex && this._currentIndex + 1 < this._endIndex && (this._string.charAt(this._currentIndex) === 'e' || this._string.charAt(this._currentIndex) === 'E') && (this._string.charAt(this._currentIndex + 1) !== 'x' && this._string.charAt(this._currentIndex + 1) !== 'm')) {
this._currentIndex++;
// Read the sign of the exponent.
if (this._string.charAt(this._currentIndex) === '+') {
this._currentIndex++;
} else if (this._string.charAt(this._currentIndex) === '-') {
this._currentIndex++;
expsign = -1;
}
// There must be an exponent.
if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') {
return undefined;
}
while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
exponent *= 10;
exponent += (this._string.charAt(this._currentIndex) - '0');
this._currentIndex++;
}
}
let number = integer + decimal;
number *= sign;
if (exponent) {
number *= 10 ** (expsign * exponent);
}
if (startIndex === this._currentIndex) {
return undefined;
}
this._skipOptionalSpacesOrDelimiter();
return number;
}
_parseArcFlag () {
if (this._currentIndex >= this._endIndex) {
return undefined;
}
let flag = false;
const flagChar = this._string.charAt(this._currentIndex++);
if (flagChar === '0') {
flag = false;
} else if (flagChar === '1') {
flag = true;
} else {
return undefined;
}
this._skipOptionalSpacesOrDelimiter();
return flag;
}
parseSegment () {
const lookahead = this._string[this._currentIndex];
let command = this._pathSegTypeFromChar(lookahead);
if (command === SVGPathSeg.PATHSEG_UNKNOWN) {
// Possibly an implicit command. Not allowed if this is the first command.
if (this._previousCommand === SVGPathSeg.PATHSEG_UNKNOWN) {
return null;
}
command = this._nextCommandHelper(lookahead, this._previousCommand);
if (command === SVGPathSeg.PATHSEG_UNKNOWN) {
return null;
}
} else {
this._currentIndex++;
}
this._previousCommand = command;
switch (command) {
case SVGPathSeg.PATHSEG_MOVETO_REL:
return new SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_MOVETO_ABS:
return new SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_REL:
return new SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_ABS:
return new SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
return new SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
return new SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
return new SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
return new SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber());
case SVGPathSeg.PATHSEG_CLOSEPATH:
this._skipOptionalSpaces();
return new SVGPathSegClosePath(owningPathSegList);
case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
} case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
} case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL: {
const points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2);
} case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS: {
const points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2);
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1);
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
} case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
case SVGPathSeg.PATHSEG_ARC_REL: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegArcRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
} case SVGPathSeg.PATHSEG_ARC_ABS: {
const points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
return new SVGPathSegArcAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
} default:
throw new Error('Unknown path seg type.');
}
}
}
const builder = new Builder();
const source = new Source(string);
if (!source.initialCommandIsMoveTo()) {
return [];
}
while (source.hasMoreData()) {
const pathSeg = source.parseSegment();
if (!pathSeg) {
return [];
}
builder.appendSegment(pathSeg);
}
return builder.pathSegList;
}
// STATIC
static _pathSegArrayAsString (pathSegArray) {
let string = '';
let first = true;
pathSegArray.forEach((pathSeg) => {
if (first) {
first = false;
string += pathSeg._asPathString();
} else {
string += ' ' + pathSeg._asPathString();
}
});
return string;
}
}
SVGPathSegList.prototype.classname = 'SVGPathSegList';
Object.defineProperty(SVGPathSegList.prototype, 'numberOfItems', {
get () {
this._checkPathSynchronizedToList();
return this._list.length;
},
enumerable: true
});
// Add the pathSegList accessors to SVGPathElement.
// Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
Object.defineProperties(SVGPathElement.prototype, {
pathSegList: {
get () {
if (!this._pathSegList) {
this._pathSegList = new SVGPathSegList(this);
}
return this._pathSegList;
},
enumerable: true
},
// TODO: The following are not implemented and simply return SVGPathElement.pathSegList.
normalizedPathSegList: {get () { return this.pathSegList; }, enumerable: true},
animatedPathSegList: {get () { return this.pathSegList; }, enumerable: true},
animatedNormalizedPathSegList: {get () { return this.pathSegList; }, enumerable: true}
});
window.SVGPathSegList = SVGPathSegList;
}
})();

397
dist/common/svgtransformlist.js vendored Normal file
View File

@ -0,0 +1,397 @@
/**
* Partial polyfill of `SVGTransformList`
* @module SVGTransformList
*
* @license MIT
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import {NS} from './namespaces.js';
import {supportsNativeTransformLists} from './browser.js';
const svgroot = document.createElementNS(NS.SVG, 'svg');
/**
* Helper function to convert `SVGTransform` to a string.
* @param {SVGTransform} xform
* @returns {string}
*/
function transformToString (xform) {
const m = xform.matrix;
let text = '';
switch (xform.type) {
case 1: // MATRIX
text = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')';
break;
case 2: // TRANSLATE
text = 'translate(' + m.e + ',' + m.f + ')';
break;
case 3: // SCALE
if (m.a === m.d) {
text = 'scale(' + m.a + ')';
} else {
text = 'scale(' + m.a + ',' + m.d + ')';
}
break;
case 4: { // ROTATE
let cx = 0;
let cy = 0;
// this prevents divide by zero
if (xform.angle !== 0) {
const K = 1 - m.a;
cy = (K * m.f + m.b * m.e) / (K * K + m.b * m.b);
cx = (m.e - m.b * cy) / K;
}
text = 'rotate(' + xform.angle + ' ' + cx + ',' + cy + ')';
break;
}
}
return text;
}
/**
* Map of SVGTransformList objects.
*/
let listMap_ = {};
/**
* @interface module:SVGTransformList.SVGEditTransformList
* @property {Integer} numberOfItems unsigned long
*/
/**
* @function module:SVGTransformList.SVGEditTransformList#clear
* @returns {void}
*/
/**
* @function module:SVGTransformList.SVGEditTransformList#initialize
* @param {SVGTransform} newItem
* @returns {SVGTransform}
*/
/**
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
* @function module:SVGTransformList.SVGEditTransformList#getItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
/**
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
* @function module:SVGTransformList.SVGEditTransformList#insertItemBefore
* @param {SVGTransform} newItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
/**
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
* @function module:SVGTransformList.SVGEditTransformList#replaceItem
* @param {SVGTransform} newItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
/**
* DOES NOT THROW DOMException, INDEX_SIZE_ERR.
* @function module:SVGTransformList.SVGEditTransformList#removeItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
/**
* @function module:SVGTransformList.SVGEditTransformList#appendItem
* @param {SVGTransform} newItem
* @returns {SVGTransform}
*/
/**
* NOT IMPLEMENTED.
* @ignore
* @function module:SVGTransformList.SVGEditTransformList#createSVGTransformFromMatrix
* @param {SVGMatrix} matrix
* @returns {SVGTransform}
*/
/**
* NOT IMPLEMENTED.
* @ignore
* @function module:SVGTransformList.SVGEditTransformList#consolidate
* @returns {SVGTransform}
*/
/**
* SVGTransformList implementation for Webkit.
* These methods do not currently raise any exceptions.
* These methods also do not check that transforms are being inserted. This is basically
* implementing as much of SVGTransformList that we need to get the job done.
* @implements {module:SVGTransformList.SVGEditTransformList}
*/
export class SVGTransformList { // eslint-disable-line no-shadow
/**
* @param {Element} elem
* @returns {SVGTransformList}
*/
constructor (elem) {
this._elem = elem || null;
this._xforms = [];
// TODO: how do we capture the undo-ability in the changed transform list?
this._update = function () {
let tstr = '';
// /* const concatMatrix = */ svgroot.createSVGMatrix();
for (let i = 0; i < this.numberOfItems; ++i) {
const xform = this._list.getItem(i);
tstr += transformToString(xform) + ' ';
}
this._elem.setAttribute('transform', tstr);
};
this._list = this;
this._init = function () {
// Transform attribute parser
let str = this._elem.getAttribute('transform');
if (!str) { return; }
// TODO: Add skew support in future
const re = /\s*((scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
// const re = /\s*(?<xform>(?:scale|matrix|rotate|translate)\s*\(.*?\))\s*,?\s*/;
let m = true;
while (m) {
m = str.match(re);
str = str.replace(re, '');
if (m && m[1]) {
const x = m[1];
const bits = x.split(/\s*\(/);
const name = bits[0];
const valBits = bits[1].match(/\s*(.*?)\s*\)/);
valBits[1] = valBits[1].replace(/(\d)-/g, '$1 -');
const valArr = valBits[1].split(/[, ]+/);
const letters = 'abcdef'.split('');
/*
if (m && m.groups.xform) {
const x = m.groups.xform;
const [name, bits] = x.split(/\s*\(/);
const valBits = bits.match(/\s*(?<nonWhitespace>.*?)\s*\)/);
valBits.groups.nonWhitespace = valBits.groups.nonWhitespace.replace(
/(?<digit>\d)-/g, '$<digit> -'
);
const valArr = valBits.groups.nonWhitespace.split(/[, ]+/);
const letters = [...'abcdef'];
*/
const mtx = svgroot.createSVGMatrix();
Object.values(valArr).forEach(function (item, i) {
valArr[i] = Number.parseFloat(item);
if (name === 'matrix') {
mtx[letters[i]] = valArr[i];
}
});
const xform = svgroot.createSVGTransform();
const fname = 'set' + name.charAt(0).toUpperCase() + name.slice(1);
const values = name === 'matrix' ? [mtx] : valArr;
if (name === 'scale' && values.length === 1) {
values.push(values[0]);
} else if (name === 'translate' && values.length === 1) {
values.push(0);
} else if (name === 'rotate' && values.length === 1) {
values.push(0, 0);
}
xform[fname](...values);
this._list.appendItem(xform);
}
}
};
this._removeFromOtherLists = function (item) {
if (item) {
// Check if this transform is already in a transformlist, and
// remove it if so.
Object.values(listMap_).some((tl) => {
for (let i = 0, len = tl._xforms.length; i < len; ++i) {
if (tl._xforms[i] === item) {
tl.removeItem(i);
return true;
}
}
return false;
});
}
};
this.numberOfItems = 0;
}
/**
* @returns {void}
*/
clear () {
this.numberOfItems = 0;
this._xforms = [];
}
/**
* @param {SVGTransform} newItem
* @returns {void}
*/
initialize (newItem) {
this.numberOfItems = 1;
this._removeFromOtherLists(newItem);
this._xforms = [newItem];
}
/**
* @param {Integer} index unsigned long
* @throws {Error}
* @returns {SVGTransform}
*/
getItem (index) {
if (index < this.numberOfItems && index >= 0) {
return this._xforms[index];
}
const err = new Error('DOMException with code=INDEX_SIZE_ERR');
err.code = 1;
throw err;
}
/**
* @param {SVGTransform} newItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
insertItemBefore (newItem, index) {
let retValue = null;
if (index >= 0) {
if (index < this.numberOfItems) {
this._removeFromOtherLists(newItem);
const newxforms = new Array(this.numberOfItems + 1);
// TODO: use array copying and slicing
let i;
for (i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
newxforms[i] = newItem;
for (let j = i + 1; i < this.numberOfItems; ++j, ++i) {
newxforms[j] = this._xforms[i];
}
this.numberOfItems++;
this._xforms = newxforms;
retValue = newItem;
this._list._update();
} else {
retValue = this._list.appendItem(newItem);
}
}
return retValue;
}
/**
* @param {SVGTransform} newItem
* @param {Integer} index unsigned long
* @returns {SVGTransform}
*/
replaceItem (newItem, index) {
let retValue = null;
if (index < this.numberOfItems && index >= 0) {
this._removeFromOtherLists(newItem);
this._xforms[index] = newItem;
retValue = newItem;
this._list._update();
}
return retValue;
}
/**
* @param {Integer} index unsigned long
* @throws {Error}
* @returns {SVGTransform}
*/
removeItem (index) {
if (index < this.numberOfItems && index >= 0) {
const retValue = this._xforms[index];
const newxforms = new Array(this.numberOfItems - 1);
let i;
for (i = 0; i < index; ++i) {
newxforms[i] = this._xforms[i];
}
for (let j = i; j < this.numberOfItems - 1; ++j, ++i) {
newxforms[j] = this._xforms[i + 1];
}
this.numberOfItems--;
this._xforms = newxforms;
this._list._update();
return retValue;
}
const err = new Error('DOMException with code=INDEX_SIZE_ERR');
err.code = 1;
throw err;
}
/**
* @param {SVGTransform} newItem
* @returns {SVGTransform}
*/
appendItem (newItem) {
this._removeFromOtherLists(newItem);
this._xforms.push(newItem);
this.numberOfItems++;
this._list._update();
return newItem;
}
}
/**
* @function module:SVGTransformList.resetListMap
* @returns {void}
*/
export const resetListMap = function () {
listMap_ = {};
};
/**
* Removes transforms of the given element from the map.
* @function module:SVGTransformList.removeElementFromListMap
* @param {Element} elem - a DOM Element
* @returns {void}
*/
export let removeElementFromListMap = function (elem) { // eslint-disable-line import/no-mutable-exports
if (elem.id && listMap_[elem.id]) {
delete listMap_[elem.id];
}
};
/**
* Returns an object that behaves like a `SVGTransformList` for the given DOM element.
* @function module:SVGTransformList.getTransformList
* @param {Element} elem - DOM element to get a transformlist from
* @todo The polyfill should have `SVGAnimatedTransformList` and this should use it
* @returns {SVGAnimatedTransformList|SVGTransformList}
*/
export const getTransformList = function (elem) {
if (!supportsNativeTransformLists()) {
const id = elem.id || 'temp';
let t = listMap_[id];
if (!t || id === 'temp') {
listMap_[id] = new SVGTransformList(elem);
listMap_[id]._init();
t = listMap_[id];
}
return t;
}
if (elem.transform) {
return elem.transform.baseVal;
}
if (elem.gradientTransform) {
return elem.gradientTransform.baseVal;
}
if (elem.patternTransform) {
return elem.patternTransform.baseVal;
}
return null;
};
/**
* @callback module:SVGTransformList.removeElementFromListMap
* @param {Element} elem
* @returns {void}
*/
/**
* Replace `removeElementFromListMap` for unit-testing.
* @function module:SVGTransformList.changeRemoveElementFromListMap
* @param {module:SVGTransformList.removeElementFromListMap} cb Passed a single argument `elem`
* @returns {void}
*/
export const changeRemoveElementFromListMap = function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks
removeElementFromListMap = cb;
};

313
dist/common/units.js vendored Normal file
View File

@ -0,0 +1,313 @@
/**
* Tools for working with units.
* @module units
* @license MIT
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
*/
import {NS} from './namespaces.js';
const wAttrs = ['x', 'x1', 'cx', 'rx', 'width'];
const hAttrs = ['y', 'y1', 'cy', 'ry', 'height'];
const unitAttrs = ['r', 'radius', ...wAttrs, ...hAttrs];
// unused
/*
const unitNumMap = {
'%': 2,
em: 3,
ex: 4,
px: 5,
cm: 6,
mm: 7,
in: 8,
pt: 9,
pc: 10
};
*/
// Container of elements.
let elementContainer_;
// Stores mapping of unit type to user coordinates.
let typeMap_ = {};
/**
* @interface module:units.ElementContainer
*/
/**
* @function module:units.ElementContainer#getBaseUnit
* @returns {string} The base unit type of the container ('em')
*/
/**
* @function module:units.ElementContainer#getElement
* @returns {?Element} An element in the container given an id
*/
/**
* @function module:units.ElementContainer#getHeight
* @returns {Float} The container's height
*/
/**
* @function module:units.ElementContainer#getWidth
* @returns {Float} The container's width
*/
/**
* @function module:units.ElementContainer#getRoundDigits
* @returns {Integer} The number of digits number should be rounded to
*/
/* eslint-disable jsdoc/valid-types */
/**
* @typedef {PlainObject} module:units.TypeMap
* @property {Float} em
* @property {Float} ex
* @property {Float} in
* @property {Float} cm
* @property {Float} mm
* @property {Float} pt
* @property {Float} pc
* @property {Integer} px
* @property {0} %
*/
/* eslint-enable jsdoc/valid-types */
/**
* Initializes this module.
*
* @function module:units.init
* @param {module:units.ElementContainer} elementContainer - An object implementing the ElementContainer interface.
* @returns {void}
*/
export const init = function (elementContainer) {
elementContainer_ = elementContainer;
// Get correct em/ex values by creating a temporary SVG.
const svg = document.createElementNS(NS.SVG, 'svg');
document.body.append(svg);
const rect = document.createElementNS(NS.SVG, 'rect');
rect.setAttribute('width', '1em');
rect.setAttribute('height', '1ex');
rect.setAttribute('x', '1in');
svg.append(rect);
const bb = rect.getBBox();
svg.remove();
const inch = bb.x;
typeMap_ = {
em: bb.width,
ex: bb.height,
in: inch,
cm: inch / 2.54,
mm: inch / 25.4,
pt: inch / 72,
pc: inch / 6,
px: 1,
'%': 0
};
};
/**
* Group: Unit conversion functions.
*/
/**
* @function module:units.getTypeMap
* @returns {module:units.TypeMap} The unit object with values for each unit
*/
export const getTypeMap = function () {
return typeMap_;
};
/**
* @typedef {GenericArray} module:units.CompareNumbers
* @property {Integer} length 2
* @property {Float} 0
* @property {Float} 1
*/
/**
* Rounds a given value to a float with number of digits defined in
* `round_digits` of `saveOptions`
*
* @function module:units.shortFloat
* @param {string|Float|module:units.CompareNumbers} val - The value (or Array of two numbers) to be rounded
* @returns {Float|string} If a string/number was given, returns a Float. If an array, return a string
* with comma-separated floats
*/
export const shortFloat = function (val) {
const digits = elementContainer_.getRoundDigits();
if (!isNaN(val)) {
return Number(Number(val).toFixed(digits));
}
if (Array.isArray(val)) {
return shortFloat(val[0]) + ',' + shortFloat(val[1]);
}
return Number.parseFloat(val).toFixed(digits) - 0;
};
/**
* Converts the number to given unit or baseUnit.
* @function module:units.convertUnit
* @param {string|Float} val
* @param {"em"|"ex"|"in"|"cm"|"mm"|"pt"|"pc"|"px"|"%"} [unit]
* @returns {Float}
*/
export const convertUnit = function (val, unit) {
unit = unit || elementContainer_.getBaseUnit();
// baseVal.convertToSpecifiedUnits(unitNumMap[unit]);
// const val = baseVal.valueInSpecifiedUnits;
// baseVal.convertToSpecifiedUnits(1);
return shortFloat(val / typeMap_[unit]);
};
/**
* Sets an element's attribute based on the unit in its current value.
*
* @function module:units.setUnitAttr
* @param {Element} elem - DOM element to be changed
* @param {string} attr - Name of the attribute associated with the value
* @param {string} val - Attribute value to convert
* @returns {void}
*/
export const setUnitAttr = function (elem, attr, val) {
// if (!isNaN(val)) {
// New value is a number, so check currently used unit
// const oldVal = elem.getAttribute(attr);
// Enable this for alternate mode
// if (oldVal !== null && (isNaN(oldVal) || elementContainer_.getBaseUnit() !== 'px')) {
// // Old value was a number, so get unit, then convert
// let unit;
// if (oldVal.substr(-1) === '%') {
// const res = getResolution();
// unit = '%';
// val *= 100;
// if (wAttrs.includes(attr)) {
// val = val / res.w;
// } else if (hAttrs.includes(attr)) {
// val = val / res.h;
// } else {
// return val / Math.sqrt((res.w*res.w) + (res.h*res.h))/Math.sqrt(2);
// }
// } else {
// if (elementContainer_.getBaseUnit() !== 'px') {
// unit = elementContainer_.getBaseUnit();
// } else {
// unit = oldVal.substr(-2);
// }
// val = val / typeMap_[unit];
// }
//
// val += unit;
// }
// }
elem.setAttribute(attr, val);
};
const attrsToConvert = {
line: ['x1', 'x2', 'y1', 'y2'],
circle: ['cx', 'cy', 'r'],
ellipse: ['cx', 'cy', 'rx', 'ry'],
foreignObject: ['x', 'y', 'width', 'height'],
rect: ['x', 'y', 'width', 'height'],
image: ['x', 'y', 'width', 'height'],
use: ['x', 'y', 'width', 'height'],
text: ['x', 'y']
};
/**
* Converts all applicable attributes to the configured baseUnit.
* @function module:units.convertAttrs
* @param {Element} element - A DOM element whose attributes should be converted
* @returns {void}
*/
export const convertAttrs = function (element) {
const elName = element.tagName;
const unit = elementContainer_.getBaseUnit();
const attrs = attrsToConvert[elName];
if (!attrs) { return; }
const len = attrs.length;
for (let i = 0; i < len; i++) {
const attr = attrs[i];
const cur = element.getAttribute(attr);
if (cur) {
if (!isNaN(cur)) {
element.setAttribute(attr, (cur / typeMap_[unit]) + unit);
}
// else {
// Convert existing?
// }
}
}
};
/**
* Converts given values to numbers. Attributes must be supplied in
* case a percentage is given.
*
* @function module:units.convertToNum
* @param {string} attr - Name of the attribute associated with the value
* @param {string} val - Attribute value to convert
* @returns {Float} The converted number
*/
export const convertToNum = function (attr, val) {
// Return a number if that's what it already is
if (!isNaN(val)) { return val - 0; }
if (val.substr(-1) === '%') {
// Deal with percentage, depends on attribute
const num = val.substr(0, val.length - 1) / 100;
const width = elementContainer_.getWidth();
const height = elementContainer_.getHeight();
if (wAttrs.includes(attr)) {
return num * width;
}
if (hAttrs.includes(attr)) {
return num * height;
}
return num * Math.sqrt((width * width) + (height * height)) / Math.sqrt(2);
}
const unit = val.substr(-2);
const num = val.substr(0, val.length - 2);
// Note that this multiplication turns the string into a number
return num * typeMap_[unit];
};
/**
* Check if an attribute's value is in a valid format.
* @function module:units.isValidUnit
* @param {string} attr - The name of the attribute associated with the value
* @param {string} val - The attribute value to check
* @param {Element} selectedElement
* @returns {boolean} Whether the unit is valid
*/
export const isValidUnit = function (attr, val, selectedElement) {
if (unitAttrs.includes(attr)) {
// True if it's just a number
if (!isNaN(val)) {
return true;
}
// Not a number, check if it has a valid unit
val = val.toLowerCase();
return Object.keys(typeMap_).some((unit) => {
const re = new RegExp('^-?[\\d\\.]+' + unit + '$');
return re.test(val);
});
}
if (attr === 'id') {
// if we're trying to change the id, make sure it's not already present in the doc
// and the id value is valid.
let result = false;
// because getElem() can throw an exception in the case of an invalid id
// (according to https://www.w3.org/TR/xml-id/ IDs must be a NCName)
// we wrap it in an exception and only return true if the ID was valid and
// not already present
try {
const elem = elementContainer_.getElement(val);
result = (!elem || elem === selectedElement);
} catch (e) {}
return result;
}
return true;
};

1363
dist/common/utilities.js vendored Normal file

File diff suppressed because it is too large Load Diff

54
dist/editor/browser-not-supported.html vendored Normal file
View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
<link rel="icon" type="image/png" href="images/logo.png"/>
<link rel="stylesheet" href="svg-editor.css"/>
<title>Browser does not support SVG | SVG-edit</title>
<style>
body {
margin: 0;
overflow: hidden;
}
p {
font-size: 0.8em;
font-family: Verdana, Helvetica, Arial;
color: #000;
padding: 8px;
margin: 0;
}
#logo {
float: left;
padding: 10px;
}
#caniuse {
position: absolute;
top: 7em;
bottom: 0;
width: 100%;
}
#caniuse > iframe {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<img id="logo" src="images/logo.png" width="48" height="48" alt="SVG-edit logo" />
<p>Sorry, but your browser does not support SVG. Below is a list of
alternate browsers and versions that support SVG and SVG-edit
(from <a href="https://caniuse.com/#cats=SVG">caniuse.com</a>).
</p>
<p>Try the latest version of
<a href="https://www.getfirefox.com">Firefox</a>,
<a href="https://www.google.com/chrome/">Chrome</a>,
<a href="https://www.apple.com/safari/">Safari</a>,
<a href="https://www.opera.com/download">Opera</a> or
<a href="https://support.microsoft.com/en-us/help/17621/internet-explorer-downloads">Internet Explorer</a>.
</p>
<div id="caniuse">
<iframe src="https://caniuse.com/#cats=SVG"></iframe>
</div>
</body>
</html>

17
dist/editor/embedapi.html vendored Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Embed API</title>
<link rel="icon" type="image/png" href="images/logo.png"/>
<script src="jquery.min.js"></script>
<script type="module" src="embedapi-dom.js"></script>
</head>
<body>
<button id="load">Load example</button>
<button id="save">Save data</button>
<button id="exportPNG">Export data to PNG</button>
<button id="exportPDF">Export data to PDF</button>
<br/>
</body>
</html>

395
dist/editor/embedapi.js vendored Normal file
View File

@ -0,0 +1,395 @@
/**
* Handles underlying communication between the embedding window and the
* editor frame.
* @module EmbeddedSVGEdit
*/
let cbid = 0;
/**
* @callback module:EmbeddedSVGEdit.CallbackSetter
* @param {GenericCallback} newCallback Callback to be stored (signature dependent on function)
* @returns {void}
*/
/**
* @callback module:EmbeddedSVGEdit.CallbackSetGetter
* @param {...any} args Signature dependent on the function
* @returns {module:EmbeddedSVGEdit.CallbackSetter}
*/
/**
* @param {string} funcName
* @returns {module:EmbeddedSVGEdit.CallbackSetGetter}
*/
function getCallbackSetter (funcName) {
return function (...args) {
const that = this, // New callback
callbackID = this.send(funcName, args, function () { /* */ }); // The callback (currently it's nothing, but will be set later)
return function (newCallback) {
that.callbacks[callbackID] = newCallback; // Set callback
};
};
}
/**
* Having this separate from messageListener allows us to
* avoid using JSON parsing (and its limitations) in the case
* of same domain control.
* @param {module:EmbeddedSVGEdit.EmbeddedSVGEdit} t The `this` value
* @param {PlainObject} data
* @param {JSON} data.result
* @param {string} data.error
* @param {Integer} data.id
* @returns {void}
*/
function addCallback (t, {result, error, id: callbackID}) {
if (typeof callbackID === 'number' && t.callbacks[callbackID]) {
// These should be safe both because we check `cbid` is numeric and
// because the calls are from trusted origins
if (result) {
t.callbacks[callbackID](result); // lgtm [js/unvalidated-dynamic-method-call]
} else {
t.callbacks[callbackID](error, 'error'); // lgtm [js/unvalidated-dynamic-method-call]
}
}
}
/**
* @param {Event} e
* @returns {void}
*/
function messageListener (e) {
// We accept and post strings as opposed to objects for the sake of IE9 support; this
// will most likely be changed in the future
if (!e.data || !['string', 'object'].includes(typeof e.data)) {
return;
}
const {allowedOrigins} = this,
data = typeof e.data === 'object' ? e.data : JSON.parse(e.data);
if (!data || typeof data !== 'object' || data.namespace !== 'svg-edit' ||
e.source !== this.frame.contentWindow ||
(!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin))
) {
// eslint-disable-next-line no-console -- Info for developers
console.error(
`The origin ${e.origin} was not whitelisted as an origin from ` +
`which responses may be received by this ${window.origin} script.`
);
return;
}
addCallback(this, data);
}
/**
* @callback module:EmbeddedSVGEdit.MessageListener
* @param {MessageEvent} e
* @returns {void}
*/
/**
* @param {module:EmbeddedSVGEdit.EmbeddedSVGEdit} t The `this` value
* @returns {module:EmbeddedSVGEdit.MessageListener} Event listener
*/
function getMessageListener (t) {
return function (e) {
messageListener.call(t, e);
};
}
/**
* Embedded SVG-edit API.
* General usage:
* - Have an iframe somewhere pointing to a version of svg-edit > r1000.
* @example
// Initialize the magic with:
const svgCanvas = new EmbeddedSVGEdit(window.frames.svgedit);
// Pass functions in this format:
svgCanvas.setSvgString('string');
// Or if a callback is needed:
svgCanvas.setSvgString('string')(function (data, error) {
if (error) {
// There was an error
} else {
// Handle data
}
});
// Everything is done with the same API as the real svg-edit,
// and all documentation is unchanged.
// However, this file depends on the postMessage API which
// can only support JSON-serializable arguments and
// return values, so, for example, arguments whose value is
// 'undefined', a function, a non-finite number, or a built-in
// object like Date(), RegExp(), etc. will most likely not behave
// as expected. In such a case one may need to host
// the SVG editor on the same domain and reference the
// JavaScript methods on the frame itself.
// The only other difference is when handling returns:
// the callback notation is used instead.
const blah = new EmbeddedSVGEdit(window.frames.svgedit);
blah.clearSelection('woot', 'blah', 1337, [1, 2, 3, 4, 5, 'moo'], -42, {
a: 'tree', b: 6, c: 9
})(function () { console.log('GET DATA', args); });
*
* @memberof module:EmbeddedSVGEdit
*/
class EmbeddedSVGEdit {
/**
* @param {HTMLIFrameElement} frame
* @param {string[]} [allowedOrigins=[]] Array of origins from which incoming
* messages will be allowed when same origin is not used; defaults to none.
* If supplied, it should probably be the same as svgEditor's allowedOrigins
*/
constructor (frame, allowedOrigins) {
const that = this;
this.allowedOrigins = allowedOrigins || [];
// Initialize communication
this.frame = frame;
this.callbacks = {};
// List of functions extracted with this:
// Run in firebug on http://svg-edit.googlecode.com/svn/trunk/docs/files/svgcanvas-js.html
// for (const i=0,q=[],f = document.querySelectorAll('div.CFunction h3.CTitle a'); i < f.length; i++) { q.push(f[i].name); }; q
// const functions = ['clearSelection', 'addToSelection', 'removeFromSelection', 'open', 'save', 'getSvgString', 'setSvgString',
// 'createLayer', 'deleteCurrentLayer', 'setCurrentLayer', 'renameCurrentLayer', 'setCurrentLayerPosition', 'setLayerVisibility',
// 'moveSelectedToLayer', 'clear'];
// Newer, well, it extracts things that aren't documented as well. All functions accessible through the normal thingy can now be accessed though the API
// const {svgCanvas} = frame.contentWindow;
// const l = [];
// for (const i in svgCanvas) { if (typeof svgCanvas[i] === 'function') { l.push(i);} };
// alert("['" + l.join("', '") + "']");
// Run in svgedit itself
const functions = [
'addExtension',
'addSVGElementFromJson',
'addToSelection',
'alignSelectedElements',
'assignAttributes',
'bind',
'call',
'changeSelectedAttribute',
'cleanupElement',
'clear',
'clearSelection',
'clearSvgContentElement',
'cloneLayer',
'cloneSelectedElements',
'convertGradients',
'convertToGroup',
'convertToNum',
'convertToPath',
'copySelectedElements',
'createLayer',
'cutSelectedElements',
'cycleElement',
'deleteCurrentLayer',
'deleteSelectedElements',
'embedImage',
'exportPDF',
'findDefs',
'getBBox',
'getBlur',
'getBold',
'getColor',
'getContentElem',
'getCurrentDrawing',
'getDocumentTitle',
'getEditorNS',
'getElem',
'getFillOpacity',
'getFontColor',
'getFontFamily',
'getFontSize',
'getHref',
'getId',
'getIntersectionList',
'getItalic',
'getMode',
'getMouseTarget',
'getNextId',
'getOffset',
'getOpacity',
'getPaintOpacity',
'getPrivateMethods',
'getRefElem',
'getResolution',
'getRootElem',
'getRotationAngle',
'getSelectedElems',
'getStrokeOpacity',
'getStrokeWidth',
'getStrokedBBox',
'getStyle',
'getSvgString',
'getText',
'getTitle',
'getTransformList',
'getUIStrings',
'getUrlFromAttr',
'getVersion',
'getVisibleElements',
'getVisibleElementsAndBBoxes',
'getZoom',
'groupSelectedElements',
'groupSvgElem',
'hasMatrixTransform',
'identifyLayers',
'importSvgString',
'leaveContext',
'linkControlPoints',
'makeHyperlink',
'matrixMultiply',
'mergeAllLayers',
'mergeLayer',
'moveSelectedElements',
'moveSelectedToLayer',
'moveToBottomSelectedElement',
'moveToTopSelectedElement',
'moveUpDownSelected',
'open',
'pasteElements',
'prepareSvg',
'pushGroupProperties',
'randomizeIds',
'rasterExport',
'ready',
'recalculateAllSelectedDimensions',
'recalculateDimensions',
'remapElement',
'removeFromSelection',
'removeHyperlink',
'removeUnusedDefElems',
'renameCurrentLayer',
'round',
'runExtensions',
'sanitizeSvg',
'save',
'selectAllInCurrentLayer',
'selectOnly',
'setBBoxZoom',
'setBackground',
'setBlur',
'setBlurNoUndo',
'setBlurOffsets',
'setBold',
'setColor',
'setConfig',
'setContext',
'setCurrentLayer',
'setCurrentLayerPosition',
'setDocumentTitle',
'setFillPaint',
'setFontColor',
'setFontFamily',
'setFontSize',
'setGoodImage',
'setGradient',
'setGroupTitle',
'setHref',
'setIdPrefix',
'setImageURL',
'setItalic',
'setLayerVisibility',
'setLinkURL',
'setMode',
'setOpacity',
'setPaint',
'setPaintOpacity',
'setRectRadius',
'setResolution',
'setRotationAngle',
'setSegType',
'setStrokeAttr',
'setStrokePaint',
'setStrokeWidth',
'setSvgString',
'setTextContent',
'setUiStrings',
'setUseData',
'setZoom',
'svgCanvasToString',
'svgToString',
'transformListToTransform',
'ungroupSelectedElement',
'uniquifyElems',
'updateCanvas',
'zoomChanged'
];
// TODO: rewrite the following, it's pretty scary.
for (const func of functions) {
this[func] = getCallbackSetter(func);
}
// Older IE may need a polyfill for addEventListener, but so it would for SVG
window.addEventListener('message', getMessageListener(this));
window.addEventListener('keydown', (e) => {
const {type, key} = e;
if (key === 'Backspace') {
e.preventDefault();
const keyboardEvent = new KeyboardEvent(type, {key});
that.frame.contentDocument.dispatchEvent(keyboardEvent);
}
});
}
/**
* @param {string} name
* @param {ArgumentsArray} args Signature dependent on function
* @param {GenericCallback} callback (This may be better than a promise in case adding an event.)
* @returns {Integer}
*/
send (name, args, callback) { // eslint-disable-line promise/prefer-await-to-callbacks
const that = this;
cbid++;
this.callbacks[cbid] = callback;
setTimeout((function (callbackID) {
return function () { // Delay for the callback to be set in case its synchronous
/*
* Todo: Handle non-JSON arguments and return values (undefined,
* nonfinite numbers, functions, and built-in objects like Date,
* RegExp), etc.? Allow promises instead of callbacks? Review
* SVG-Edit functions for whether JSON-able parameters can be
* made compatile with all API functionality
*/
// We accept and post strings for the sake of IE9 support
let sameOriginWithGlobal = false;
try {
sameOriginWithGlobal = window.location.origin === that.frame.contentWindow.location.origin &&
that.frame.contentWindow.svgEditor.canvas;
} catch (err) {}
if (sameOriginWithGlobal) {
// Although we do not really need this API if we are working same
// domain, it could allow us to write in a way that would work
// cross-domain as well, assuming we stick to the argument limitations
// of the current JSON-based communication API (e.g., not passing
// callbacks). We might be able to address these shortcomings; see
// the todo elsewhere in this file.
const message = {id: callbackID},
{svgEditor: {canvas: svgCanvas}} = that.frame.contentWindow;
try {
message.result = svgCanvas[name](...args);
} catch (err) {
message.error = err.message;
}
addCallback(that, message);
} else { // Requires the ext-xdomain-messaging.js extension
that.frame.contentWindow.postMessage(JSON.stringify({
namespace: 'svgCanvas', id: callbackID, name, args
}), '*');
}
};
}(cbid)), 0);
return cbid;
}
}
export default EmbeddedSVGEdit;

View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 789 B

After

Width:  |  Height:  |  Size: 789 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 568 B

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View File

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More