- Breaking change: Rename config file to svgedit-config-iife.js (or for the module version, svgedit-config-es.js);
also expect one directory higher; incorporates #207 (@iuyiuy) - Breaking change: Separate `extIconsPath` from `extPath` (not copying over icons) - Breaking change: Don't reference `custom.css` in HTML; can instead be referenced in JavaScript through the config file (provided in `svgedit-config-sample-iife.js`/`svgedit-config-sample-es.js` as `svgedit-custom.css` for better namespacing); incorporates #207 (@iuyiuy) - Breaking change: Remove minified jgraduate/spinbtn files (minified within Rollup routine) - Fix: Zoom when scrolled; incorporates #169 (@AndrolGenhald), adapting for conventions; also allow avoidance when shift key pressed - Fix: Update Atom feed reference in HTML - Fixes related to recent commits: Some path and method name fixes needed, function order, missing methods, variable scope declaration, no need for DOMContentLoaded listeners in modules, switch back to non-default export, avoid trimming nullish, deal with mock tests, fix `math.matrixMultiply`, use jquery-svg where needed for array/SVG attributes; add babel-polyfill and defer script to imagelib; other misc. fixes - Enhancement: Move config-sample.js out of `editor` directory - Enhancement: For `callback`-style extensions, also provide config object; add following to that object: buildCanvgCallback, canvg, decode64, encode64, executeAfterLoads, getTypeMap, isChrome, ieIE, NS, text2xml - Enhancement: Complete ES6 modules work (extensions, locales, tests), along with Babel; make Node build routine for converting modular source to non-modular, use `loadStylesheets` for modular stylehsheet defining (but parallel loading); - Enhancement: Add `stylesheets` config for modular but parallel stylesheet loading with `@default` option for simple inclusion/exclusion of defaults (if not going with default). - Refactoring: Clean up `svg-editor.html`: consistent indents; avoid extra lbs, avoid long lines - Refactoring: Avoid embedded API adding inline JavaScript listener - Refactoring: Move layers and context code to `draw.js` - Refactoring: Move `pathActions` from `svgcanvas.js` (though preserve aliases to these methods on `canvas`) and `convertPath` from `svgutils.js` to `path.js` - Refactoring: Move `getStrokedBBox` from `svgcanvas.js` (while keeping an alias) to `svgutils.js` (as `getStrokedBBoxDefaultVisible` to avoid conflict with existing) - Docs: Remove "dependencies" comments in code except where summarizing role of jQuery or a non-obvious dependency - Refactoring/Linting: Enfore `no-extra-semi` and `quote-props` rules - Refactoring: Further avoidance of quotes on properties (as possible) - Refactoring: Use `class` in place of functions where intended as classes - Refactoring: Consistency and granularity in extensions imports - Testing: Update QUnit to 2.6.1 (node_modules) and Sinon to 5.0.8 (and add sinon-test at 2.1.3) and enforce eslint-plugin-qunit linting rules; update custom extensions - Testing: Add node-static for automating (and accessing out-of-directory contents) - Testing: Avoid HTML attributes for styling - Testing: Add npm `test` script - Testing: Comment out unused jQuery SVG test - Testing: Add test1 and svgutils_performance_test to all tests page - Testing: Due apparently to Path having not been a formal class, the test was calling it without `new`; refactored now with sufficient mock data to take into account it is a class - npm: Update devDeps - npm: Add html modules and config build to test script
This commit is contained in:
@@ -10,9 +10,12 @@
|
||||
|
||||
import './pathseg.js';
|
||||
import RGBColor from './canvg/rgbcolor.js';
|
||||
import jqPluginSVG from './jquery-svg.js'; // Needed for SVG attribute setting and array form with `attr`
|
||||
import {importScript, importModule} from './external/dynamic-import-polyfill/importModule.js';
|
||||
import {NS} from './svgedit.js';
|
||||
import {getTransformList} from './svgtransformlist.js';
|
||||
import {shortFloat, setUnitAttr, getTypeMap} from './units.js';
|
||||
import {setUnitAttr, getTypeMap} from './units.js';
|
||||
import {convertPath} from './path.js';
|
||||
import {
|
||||
hasMatrixTransform, transformListToTransform, transformBox
|
||||
} from './math.js';
|
||||
@@ -22,7 +25,7 @@ import {
|
||||
} from './browser.js';
|
||||
|
||||
// Constants
|
||||
const $ = jQuery;
|
||||
const $ = jqPluginSVG(jQuery);
|
||||
|
||||
// String used to encode base64.
|
||||
const KEYSTR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
@@ -317,12 +320,12 @@ export const getUrlFromAttr = function (attrVal) {
|
||||
};
|
||||
|
||||
// Returns the given element's xlink:href value
|
||||
export const getHref = function (elem) {
|
||||
export let getHref = function (elem) {
|
||||
return elem.getAttributeNS(NS.XLINK, 'href');
|
||||
};
|
||||
|
||||
// Sets the given element's xlink:href value
|
||||
export const setHref = function (elem, val) {
|
||||
export let setHref = function (elem, val) {
|
||||
elem.setAttributeNS(NS.XLINK, 'xlink:href', val);
|
||||
};
|
||||
|
||||
@@ -585,7 +588,7 @@ export const getPathDFromSegments = function (pathSegments) {
|
||||
// elem - The element to be converted
|
||||
//
|
||||
// Returns:
|
||||
// The path d attribute or undefined if the element type is unknown.
|
||||
// The path d attribute or `undefined` if the element type is unknown.
|
||||
export const getPathDFromElement = function (elem) {
|
||||
// Possibly the cubed root of 6, but 1.81 works best
|
||||
let num = 1.81;
|
||||
@@ -692,8 +695,8 @@ export const getExtraAttributesForConvertToPath = function (elem) {
|
||||
// The resulting path's bounding box object.
|
||||
export const getBBoxOfElementAsPath = function (elem, addSvgElementFromJson, pathActions) {
|
||||
const path = addSvgElementFromJson({
|
||||
'element': 'path',
|
||||
'attr': getExtraAttributesForConvertToPath(elem)
|
||||
element: 'path',
|
||||
attr: getExtraAttributesForConvertToPath(elem)
|
||||
});
|
||||
|
||||
const eltrans = elem.getAttribute('transform');
|
||||
@@ -824,15 +827,13 @@ function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasMatrixTransform) {
|
||||
return hasMatrixTransform || !(closeTo0 || closeTo90);
|
||||
}
|
||||
|
||||
// Get bounding box that includes any transforms.
|
||||
//
|
||||
// Parameters:
|
||||
// elem - The DOM element to be converted
|
||||
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||
//
|
||||
// Returns:
|
||||
// A single bounding box object
|
||||
/**
|
||||
* Get bounding box that includes any transforms.
|
||||
* @param elem - The DOM element to be converted
|
||||
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||
* @param pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||
* @returns A single bounding box object
|
||||
*/
|
||||
export const getBBoxWithTransform = function (elem, addSvgElementFromJson, pathActions) {
|
||||
// TODO: Fix issue with rotated groups. Currently they work
|
||||
// fine in FF, but not in other browsers (same problem mentioned
|
||||
@@ -894,15 +895,13 @@ function getStrokeOffsetForBBox (elem) {
|
||||
return (!isNaN(sw) && elem.getAttribute('stroke') !== 'none') ? sw / 2 : 0;
|
||||
}
|
||||
|
||||
// Get the bounding box for one or more stroked and/or transformed elements
|
||||
//
|
||||
// Parameters:
|
||||
// elems - Array with DOM elements to check
|
||||
// addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||
// pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||
//
|
||||
// Returns:
|
||||
// A single bounding box object
|
||||
/**
|
||||
* Get the bounding box for one or more stroked and/or transformed elements
|
||||
* @param elems - Array with DOM elements to check
|
||||
* @param addSvgElementFromJson - Function to add the path element to the current layer. See canvas.addSvgElementFromJson
|
||||
* @param pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
|
||||
* @returns A single bounding box object
|
||||
*/
|
||||
export const getStrokedBBox = function (elems, addSvgElementFromJson, pathActions) {
|
||||
if (!elems || !elems.length) { return false; }
|
||||
|
||||
@@ -954,6 +953,41 @@ export const getStrokedBBox = function (elems, addSvgElementFromJson, pathAction
|
||||
return fullBb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all elements that have a BBox (excludes <defs>, <title>, etc).
|
||||
* Note that 0-opacity, off-screen etc elements are still considered "visible"
|
||||
* for this function
|
||||
* @param parent - The parent DOM element to search within
|
||||
* @returns {Array} All "visible" elements.
|
||||
*/
|
||||
export const getVisibleElements = function (parent) {
|
||||
if (!parent) {
|
||||
parent = $(editorContext_.getSVGContent()).children(); // Prevent layers from being included
|
||||
}
|
||||
|
||||
const contentElems = [];
|
||||
$(parent).children().each(function (i, elem) {
|
||||
if (elem.getBBox) {
|
||||
contentElems.push(elem);
|
||||
}
|
||||
});
|
||||
return contentElems.reverse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the bounding box for one or more stroked and/or transformed elements
|
||||
* @param elems - Array with DOM elements to check
|
||||
* @returns A single bounding box object
|
||||
*/
|
||||
export const getStrokedBBoxDefaultVisible = function (elems) {
|
||||
if (!elems) { elems = getVisibleElements(); }
|
||||
return getStrokedBBox(
|
||||
elems,
|
||||
editorContext_.addSvgElementFromJson,
|
||||
editorContext_.pathActions
|
||||
);
|
||||
};
|
||||
|
||||
// Get the rotation angle of the given transform list.
|
||||
//
|
||||
// Parameters:
|
||||
@@ -982,7 +1016,7 @@ export const getRotationAngleFromTransformList = function (tlist, toRad) {
|
||||
//
|
||||
// Returns:
|
||||
// Float with the angle in degrees or radians
|
||||
export const getRotationAngle = function (elem, toRad) {
|
||||
export let getRotationAngle = function (elem, toRad) {
|
||||
const selected = elem || editorContext_.getSelectedElements()[0];
|
||||
// find the rotation transform (if any) and set it
|
||||
const tlist = getTransformList(selected);
|
||||
@@ -1052,15 +1086,15 @@ export const cleanupElement = function (element) {
|
||||
const defaults = {
|
||||
'fill-opacity': 1,
|
||||
'stop-opacity': 1,
|
||||
'opacity': 1,
|
||||
'stroke': 'none',
|
||||
opacity: 1,
|
||||
stroke: 'none',
|
||||
'stroke-dasharray': 'none',
|
||||
'stroke-linejoin': 'miter',
|
||||
'stroke-linecap': 'butt',
|
||||
'stroke-opacity': 1,
|
||||
'stroke-width': 1,
|
||||
'rx': 0,
|
||||
'ry': 0
|
||||
rx: 0,
|
||||
ry: 0
|
||||
};
|
||||
|
||||
if (element.nodeName === 'ellipse') {
|
||||
@@ -1078,7 +1112,6 @@ export const cleanupElement = function (element) {
|
||||
};
|
||||
|
||||
// round value to for snapping
|
||||
// NOTE: This function did not move to svgutils.js since it depends on curConfig.
|
||||
export const snapToGrid = function (value) {
|
||||
const unit = editorContext_.getBaseUnit();
|
||||
let stepSize = editorContext_.getSnappingStep();
|
||||
@@ -1094,28 +1127,41 @@ export const regexEscape = function (str, delimiter) {
|
||||
return String(str).replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
|
||||
};
|
||||
|
||||
const loadedScripts = {};
|
||||
/**
|
||||
* @param {string} globalCheck A global which can be used to determine if the script is already loaded
|
||||
* @param {string} name A global which can be used to determine if the script is already loaded
|
||||
* @param {array} scripts An array of scripts to preload (in order)
|
||||
* @param {function} cb The callback to execute upon load.
|
||||
* @param {object} options Object with `globals` boolean property (if it is not a module)
|
||||
*/
|
||||
export const executeAfterLoads = function (globalCheck, scripts, cb) {
|
||||
export const executeAfterLoads = function (name, scripts, cb, options = {globals: false}) {
|
||||
return function () {
|
||||
const args = arguments;
|
||||
function endCallback () {
|
||||
cb.apply(null, args);
|
||||
}
|
||||
if (window[globalCheck]) {
|
||||
const modularVersion = !('svgEditor' in window) ||
|
||||
!window.svgEditor ||
|
||||
window.svgEditor.modules !== false;
|
||||
if (loadedScripts[name] === true) {
|
||||
endCallback();
|
||||
} else if (Array.isArray(loadedScripts[name])) { // Still loading
|
||||
loadedScripts[name].push(endCallback);
|
||||
} else {
|
||||
scripts.reduceRight(function (oldFunc, script) {
|
||||
return function () {
|
||||
// Todo: insert script with `s.type = 'module';` once modules
|
||||
// widely supported (or can hopefully refactor these function
|
||||
// and/or use `import()`)
|
||||
$.getScript(script, oldFunc);
|
||||
};
|
||||
}, endCallback)();
|
||||
loadedScripts[name] = [];
|
||||
const importer = modularVersion && !options.globals
|
||||
? importModule
|
||||
: importScript;
|
||||
scripts.reduce(function (oldProm, script) {
|
||||
// Todo: Once `import()` and modules widely supported, switch to it
|
||||
return oldProm.then(() => importer(script));
|
||||
}, Promise.resolve()).then(function () {
|
||||
loadedScripts[name] = true;
|
||||
endCallback();
|
||||
loadedScripts[name].forEach((cb) => {
|
||||
cb();
|
||||
});
|
||||
})();
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1125,12 +1171,17 @@ export const buildCanvgCallback = function (callCanvg) {
|
||||
};
|
||||
|
||||
export const buildJSPDFCallback = function (callJSPDF) {
|
||||
return executeAfterLoads('RGBColor', ['canvg/rgbcolor.js'], function () {
|
||||
return executeAfterLoads('RGBColor', ['canvg/rgbcolor.js'], () => {
|
||||
const arr = [];
|
||||
if (!RGBColor || RGBColor.ok === undefined) { // It's not our RGBColor, so we'll need to load it
|
||||
arr.push('canvg/rgbcolor.js');
|
||||
}
|
||||
executeAfterLoads('jsPDF', arr.concat('jspdf/underscore-min.js', 'jspdf/jspdf.min.js', 'jspdf/jspdf.plugin.svgToPdf.js'), callJSPDF)();
|
||||
executeAfterLoads('jsPDF', [
|
||||
...arr,
|
||||
'jspdf/underscore-min.js',
|
||||
'jspdf/jspdf.min.js',
|
||||
'jspdf/jspdf.plugin.svgToPdf.js'
|
||||
], callJSPDF, {globals: true})();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1193,189 +1244,11 @@ export const copyElem = function (el, getNextId) {
|
||||
return newEl;
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: refactor callers in convertPath to use getPathDFromSegments instead of this function.
|
||||
* Legacy code refactored from svgcanvas.pathActions.convertPath
|
||||
* @param letter - path segment command
|
||||
* @param {Array.<Array.<number>>} points - x,y points.
|
||||
* @param {Array.<Array.<number>>=} morePoints - x,y points
|
||||
* @param {Array.<number>=}lastPoint - x,y point
|
||||
* @returns {string}
|
||||
*/
|
||||
function pathDSegment (letter, points, morePoints, lastPoint) {
|
||||
$.each(points, function (i, pnt) {
|
||||
points[i] = shortFloat(pnt);
|
||||
});
|
||||
let segment = letter + points.join(' ');
|
||||
if (morePoints) {
|
||||
segment += ' ' + morePoints.join(' ');
|
||||
}
|
||||
if (lastPoint) {
|
||||
segment += ' ' + shortFloat(lastPoint);
|
||||
}
|
||||
return segment;
|
||||
}
|
||||
|
||||
// this is how we map paths to our preferred relative segment types
|
||||
const pathMap = [0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
|
||||
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'];
|
||||
|
||||
/**
|
||||
* TODO: move to pathActions.js when migrating rest of pathActions out of svgcanvas.js
|
||||
* Convert a path to one with only absolute or relative values
|
||||
* @param {Object} path - the path to convert
|
||||
* @param {boolean} toRel - true of convert to relative
|
||||
* @returns {string}
|
||||
*/
|
||||
export const convertPath = function (path, toRel) {
|
||||
const segList = path.pathSegList;
|
||||
const len = segList.numberOfItems;
|
||||
let curx = 0, cury = 0;
|
||||
let d = '';
|
||||
let lastM = null;
|
||||
|
||||
for (let i = 0; i < len; ++i) {
|
||||
const seg = segList.getItem(i);
|
||||
// if these properties are not in the segment, set them to zero
|
||||
let x = seg.x || 0,
|
||||
y = seg.y || 0,
|
||||
x1 = seg.x1 || 0,
|
||||
y1 = seg.y1 || 0,
|
||||
x2 = seg.x2 || 0,
|
||||
y2 = seg.y2 || 0;
|
||||
|
||||
const type = seg.pathSegType;
|
||||
let letter = pathMap[type]['to' + (toRel ? 'Lower' : 'Upper') + 'Case']();
|
||||
|
||||
switch (type) {
|
||||
case 1: // z,Z closepath (Z/z)
|
||||
d += 'z';
|
||||
if (lastM && !toRel) {
|
||||
curx = lastM[0];
|
||||
cury = lastM[1];
|
||||
}
|
||||
break;
|
||||
case 12: // absolute horizontal line (H)
|
||||
x -= curx;
|
||||
// Fallthrough
|
||||
case 13: // relative horizontal line (h)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
letter = 'l';
|
||||
} else {
|
||||
x += curx;
|
||||
curx = x;
|
||||
letter = 'L';
|
||||
}
|
||||
// Convert to "line" for easier editing
|
||||
d += pathDSegment(letter, [[x, cury]]);
|
||||
break;
|
||||
case 14: // absolute vertical line (V)
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
case 15: // relative vertical line (v)
|
||||
if (toRel) {
|
||||
cury += y;
|
||||
letter = 'l';
|
||||
} else {
|
||||
y += cury;
|
||||
cury = y;
|
||||
letter = 'L';
|
||||
}
|
||||
// Convert to "line" for easier editing
|
||||
d += pathDSegment(letter, [[curx, y]]);
|
||||
break;
|
||||
case 2: // absolute move (M)
|
||||
case 4: // absolute line (L)
|
||||
case 18: // absolute smooth quad (T)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
case 5: // relative line (l)
|
||||
case 3: // relative move (m)
|
||||
case 19: // relative smooth quad (t)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
cury += y;
|
||||
} else {
|
||||
x += curx;
|
||||
y += cury;
|
||||
curx = x;
|
||||
cury = y;
|
||||
}
|
||||
if (type === 2 || type === 3) { lastM = [curx, cury]; }
|
||||
|
||||
d += pathDSegment(letter, [[x, y]]);
|
||||
break;
|
||||
case 6: // absolute cubic (C)
|
||||
x -= curx; x1 -= curx; x2 -= curx;
|
||||
y -= cury; y1 -= cury; y2 -= cury;
|
||||
// Fallthrough
|
||||
case 7: // relative cubic (c)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
cury += y;
|
||||
} else {
|
||||
x += curx; x1 += curx; x2 += curx;
|
||||
y += cury; y1 += cury; y2 += cury;
|
||||
curx = x;
|
||||
cury = y;
|
||||
}
|
||||
d += pathDSegment(letter, [[x1, y1], [x2, y2], [x, y]]);
|
||||
break;
|
||||
case 8: // absolute quad (Q)
|
||||
x -= curx; x1 -= curx;
|
||||
y -= cury; y1 -= cury;
|
||||
// Fallthrough
|
||||
case 9: // relative quad (q)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
cury += y;
|
||||
} else {
|
||||
x += curx; x1 += curx;
|
||||
y += cury; y1 += cury;
|
||||
curx = x;
|
||||
cury = y;
|
||||
}
|
||||
d += pathDSegment(letter, [[x1, y1], [x, y]]);
|
||||
break;
|
||||
case 10: // absolute elliptical arc (A)
|
||||
x -= curx;
|
||||
y -= cury;
|
||||
// Fallthrough
|
||||
case 11: // relative elliptical arc (a)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
cury += y;
|
||||
} else {
|
||||
x += curx;
|
||||
y += cury;
|
||||
curx = x;
|
||||
cury = y;
|
||||
}
|
||||
d += pathDSegment(letter, [[seg.r1, seg.r2]], [
|
||||
seg.angle,
|
||||
(seg.largeArcFlag ? 1 : 0),
|
||||
(seg.sweepFlag ? 1 : 0)
|
||||
], [x, y]);
|
||||
break;
|
||||
case 16: // absolute smooth cubic (S)
|
||||
x -= curx; x2 -= curx;
|
||||
y -= cury; y2 -= cury;
|
||||
// Fallthrough
|
||||
case 17: // relative smooth cubic (s)
|
||||
if (toRel) {
|
||||
curx += x;
|
||||
cury += y;
|
||||
} else {
|
||||
x += curx; x2 += curx;
|
||||
y += cury; y2 += cury;
|
||||
curx = x;
|
||||
cury = y;
|
||||
}
|
||||
d += pathDSegment(letter, [[x2, y2], [x, y]]);
|
||||
break;
|
||||
} // switch on path segment type
|
||||
} // for each segment
|
||||
return d;
|
||||
// Unit testing
|
||||
export const mock = ({
|
||||
getHref: getHrefUser, setHref: setHrefUser, getRotationAngle: getRotationAngleUser
|
||||
}) => {
|
||||
getHref = getHrefUser;
|
||||
setHref = setHrefUser;
|
||||
getRotationAngle = getRotationAngleUser;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user