2813 lines
94 KiB
JavaScript
2813 lines
94 KiB
JavaScript
/* globals jQuery */
|
|
/**
|
|
* Numerous tools for working with the editor's "canvas".
|
|
* @module svgcanvas
|
|
*
|
|
* @license MIT
|
|
*
|
|
* @copyright 2010 Alexis Deveria, 2010 Pavol Rusnak, 2010 Jeff Schiller
|
|
*
|
|
*/
|
|
|
|
import { Canvg as canvg } from 'canvg';
|
|
import 'pathseg';
|
|
|
|
import jQueryPluginSVG from './jQuery.attr.js'; // Needed for SVG attribute setting and array form with `attr`
|
|
|
|
import * as pathModule from './path.js';
|
|
import * as hstry from './history.js';
|
|
import * as draw from './draw.js';
|
|
import {
|
|
init as pasteInit, pasteElementsMethod
|
|
} from './paste-elem.js';
|
|
|
|
// eslint-disable-next-line no-duplicate-imports
|
|
import {
|
|
identifyLayers, createLayer, cloneLayer, deleteCurrentLayer,
|
|
setCurrentLayer, renameCurrentLayer, setCurrentLayerPosition,
|
|
setLayerVisibility, moveSelectedToLayer, mergeLayer, mergeAllLayers,
|
|
leaveContext, setContext
|
|
} from './draw.js';
|
|
import { svgRootElement } from './svgroot.js';
|
|
import {
|
|
init as undoInit, getUndoManager, changeSelectedAttributeNoUndoMethod,
|
|
changeSelectedAttributeMethod, ffClone
|
|
} from './undo.js';
|
|
import {
|
|
init as selectionInit, clearSelectionMethod, addToSelectionMethod, getMouseTargetMethod,
|
|
getIntersectionListMethod, runExtensionsMethod, groupSvgElem, prepareSvg,
|
|
recalculateAllSelectedDimensions, setRotationAngle
|
|
} from './selection.js';
|
|
import {
|
|
init as textActionsInit, textActionsMethod
|
|
} from './text-actions.js';
|
|
import {
|
|
init as eventInit, mouseMoveEvent, mouseUpEvent,
|
|
dblClickEvent, mouseDownEvent, DOMMouseScrollEvent
|
|
} from './event.js';
|
|
import { init as jsonInit, getJsonFromSvgElements, addSVGElementsFromJson } from './json.js';
|
|
import {
|
|
init as elemInit, getResolutionMethod, getTitleMethod, setGroupTitleMethod,
|
|
setDocumentTitleMethod, setResolutionMethod, getEditorNSMethod, setBBoxZoomMethod,
|
|
setZoomMethod, setColorMethod, setGradientMethod, findDuplicateGradient, setPaintMethod,
|
|
setStrokeWidthMethod, setStrokeAttrMethod, getBoldMethod, setBoldMethod, getItalicMethod,
|
|
setItalicMethod, setTextAnchorMethod, getFontFamilyMethod, setFontFamilyMethod, setFontColorMethod, getFontColorMethod,
|
|
getFontSizeMethod, setFontSizeMethod, getTextMethod, setTextContentMethod,
|
|
setImageURLMethod, setLinkURLMethod, setRectRadiusMethod, makeHyperlinkMethod,
|
|
removeHyperlinkMethod, setSegTypeMethod, setBackgroundMethod
|
|
} from './elem-get-set.js';
|
|
import {
|
|
init as selectedElemInit, moveToTopSelectedElem, moveToBottomSelectedElem,
|
|
moveUpDownSelected, moveSelectedElements, cloneSelectedElements, alignSelectedElements,
|
|
deleteSelectedElements, copySelectedElements, groupSelectedElements, pushGroupProperty,
|
|
ungroupSelectedElement, cycleElement, updateCanvas
|
|
} from './selected-elem.js';
|
|
import {
|
|
init as blurInit, setBlurNoUndo, setBlurOffsets, setBlur
|
|
} from './blur-event.js';
|
|
import { sanitizeSvg } from './sanitize.js';
|
|
import { getReverseNS, NS } from '../common/namespaces.js';
|
|
import {
|
|
text2xml, assignAttributes, cleanupElement, getElem, getUrlFromAttr,
|
|
findDefs, getHref, setHref, getRefElem, getRotationAngle, getPathBBox,
|
|
preventClickDefault, walkTree, getBBoxOfElementAsPath, convertToPath, encode64, decode64,
|
|
getVisibleElements, dropXMLInternalSubset, init as utilsInit,
|
|
getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible, isNullish, blankPageObjectURL,
|
|
$id, $qa, $qq
|
|
} from './utilities.js';
|
|
import {
|
|
transformPoint, matrixMultiply, hasMatrixTransform, transformListToTransform,
|
|
isIdentity, transformBox
|
|
} from './math.js';
|
|
import {
|
|
convertToNum, getTypeMap, init as unitsInit
|
|
} from '../common/units.js';
|
|
import {
|
|
svgCanvasToString, svgToString, setSvgString, save, exportPDF, setUseDataMethod,
|
|
init as svgInit, importSvgString, embedImage, rasterExport,
|
|
uniquifyElemsMethod, removeUnusedDefElemsMethod, convertGradientsMethod
|
|
} from './svg-exec.js';
|
|
import {
|
|
isChrome, isWebkit
|
|
} from '../common/browser.js'; // , supportsEditableText
|
|
import {
|
|
getTransformList, SVGTransformList as SVGEditTransformList
|
|
} from './svgtransformlist.js';
|
|
import {
|
|
remapElement,
|
|
init as coordsInit
|
|
} from './coords.js';
|
|
import {
|
|
recalculateDimensions,
|
|
init as recalculateInit
|
|
} from './recalculate.js';
|
|
import {
|
|
getSelectorManager,
|
|
Selector,
|
|
init as selectInit
|
|
} from './select.js';
|
|
import {
|
|
clearSvgContentElementInit,
|
|
init as clearInit
|
|
} from './clear.js';
|
|
import {
|
|
getClosest, getParents, mergeDeep
|
|
} from '../editor/components/jgraduate/Util.js';
|
|
|
|
const $ = jQueryPluginSVG(jQuery);
|
|
const {
|
|
MoveElementCommand, InsertElementCommand, RemoveElementCommand,
|
|
ChangeElementCommand, BatchCommand
|
|
} = hstry;
|
|
|
|
const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use';
|
|
const refAttrs = [ 'clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke' ];
|
|
|
|
if (!window.console) {
|
|
window.console = {};
|
|
window.console.log = function (_str) { /* empty fn */ };
|
|
window.console.dir = function (_str) { /* empty fn */ };
|
|
}
|
|
|
|
if (window.opera) {
|
|
window.console.log = function (str) { window.opera.postError(str); };
|
|
window.console.dir = function (_str) { /* empty fn */ };
|
|
}
|
|
|
|
// Reenable after fixing eslint-plugin-jsdoc to handle
|
|
/**
|
|
* The main SvgCanvas class that manages all SVG-related functions.
|
|
* @memberof module:svgcanvas
|
|
*
|
|
* @borrows module:coords.remapElement as #remapElement
|
|
* @borrows module:recalculate.recalculateDimensions as #recalculateDimensions
|
|
*
|
|
* @borrows module:utilities.cleanupElement as #cleanupElement
|
|
* @borrows module:utilities.getStrokedBBoxDefaultVisible as #getStrokedBBox
|
|
* @borrows module:utilities.getVisibleElements as #getVisibleElements
|
|
* @borrows module:utilities.findDefs as #findDefs
|
|
* @borrows module:utilities.getUrlFromAttr as #getUrlFromAttr
|
|
* @borrows module:utilities.getHref as #getHref
|
|
* @borrows module:utilities.setHref as #setHref
|
|
* @borrows module:utilities.getRotationAngle as #getRotationAngle
|
|
* @borrows module:utilities.getBBox as #getBBox
|
|
* @borrows module:utilities.getElem as #getElem
|
|
* @borrows module:utilities.getRefElem as #getRefElem
|
|
* @borrows module:utilities.assignAttributes as #assignAttributes
|
|
*
|
|
* @borrows module:SVGTransformList.getTransformList as #getTransformList
|
|
* @borrows module:math.matrixMultiply as #matrixMultiply
|
|
* @borrows module:math.hasMatrixTransform as #hasMatrixTransform
|
|
* @borrows module:math.transformListToTransform as #transformListToTransform
|
|
* @borrows module:units.convertToNum as #convertToNum
|
|
* @borrows module:sanitize.sanitizeSvg as #sanitizeSvg
|
|
* @borrows module:path.pathActions.linkControlPoints as #linkControlPoints
|
|
*/
|
|
class SvgCanvas {
|
|
/**
|
|
* @param {HTMLElement} container - The container HTML element that should hold the SVG root element
|
|
* @param {module:SVGeditor.configObj.curConfig} config - An object that contains configuration data
|
|
*/
|
|
constructor(container, config) {
|
|
// Alias Namespace constants
|
|
|
|
// Default configuration options
|
|
const curConfig = {
|
|
show_outside_canvas: true,
|
|
selectNew: true,
|
|
dimensions: [ 640, 480 ]
|
|
};
|
|
|
|
// Update config with new one if given
|
|
if (config) {
|
|
$.extend(curConfig, config);
|
|
}
|
|
|
|
// Array with width/height of canvas
|
|
const { dimensions } = curConfig;
|
|
|
|
const canvas = this;
|
|
|
|
this.$id = $id;
|
|
this.$qq = $qq;
|
|
this.$qa = $qa;
|
|
this.mergeDeep = mergeDeep;
|
|
this.getClosest = getClosest;
|
|
this.getParents = getParents;
|
|
/** A storage solution aimed at replacing jQuerys data function.
|
|
* Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap].
|
|
* This makes sure the data is garbage collected when the node is removed.
|
|
*/
|
|
this.dataStorage = {
|
|
_storage: new WeakMap(),
|
|
put: function (element, key, obj) {
|
|
if (!this._storage.has(element)) {
|
|
this._storage.set(element, new Map());
|
|
}
|
|
this._storage.get(element).set(key, obj);
|
|
},
|
|
get: function (element, key) {
|
|
return this._storage.get(element).get(key);
|
|
},
|
|
has: function (element, key) {
|
|
return this._storage.has(element) && this._storage.get(element).has(key);
|
|
},
|
|
remove: function (element, key) {
|
|
var ret = this._storage.get(element).delete(key);
|
|
if (!this._storage.get(element).size === 0) {
|
|
this._storage.delete(element);
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
const getDataStorage = this.getDataStorage = function () { return canvas.dataStorage; };
|
|
|
|
this.isLayer = draw.Layer.isLayer;
|
|
|
|
// "document" element associated with the container (same as window.document using default svg-editor.js)
|
|
// NOTE: This is not actually a SVG document, but an HTML document.
|
|
// JFH const svgdoc = container.ownerDocument;
|
|
const svgdoc = window.document;
|
|
|
|
// This is a container for the document being edited, not the document itself.
|
|
/**
|
|
* @name module:svgcanvas~svgroot
|
|
* @type {SVGSVGElement}
|
|
*/
|
|
const svgroot = svgRootElement(svgdoc, dimensions);
|
|
const getSVGRoot = () => svgroot;
|
|
container.append(svgroot);
|
|
|
|
/**
|
|
* The actual element that represents the final output SVG element.
|
|
* @name module:svgcanvas~svgcontent
|
|
* @type {SVGSVGElement}
|
|
*/
|
|
let svgcontent = svgdoc.createElementNS(NS.SVG, 'svg');
|
|
|
|
/**
|
|
* This should really be an intersection implementing all rather than a union.
|
|
* @type {module:draw.DrawCanvasInit#getSVGContent|module:utilities.EditorContext#getSVGContent}
|
|
*/
|
|
const getSVGContent = () => { return svgcontent; };
|
|
|
|
clearInit(
|
|
/**
|
|
* @implements {module:utilities.EditorContext}
|
|
*/
|
|
{
|
|
getSVGContent,
|
|
getDOMDocument() { return svgdoc; },
|
|
getDOMContainer() { return container; },
|
|
getSVGRoot,
|
|
getCurConfig() { return curConfig; }
|
|
}
|
|
);
|
|
/**
|
|
* This function resets the svgcontent element while keeping it in the DOM.
|
|
* @function module:svgcanvas.SvgCanvas#clearSvgContentElement
|
|
* @returns {void}
|
|
*/
|
|
const clearSvgContentElement = canvas.clearSvgContentElement = clearSvgContentElementInit;
|
|
clearSvgContentElement();
|
|
|
|
// Prefix string for element IDs
|
|
let idprefix = 'svg_';
|
|
|
|
/**
|
|
* Changes the ID prefix to the given value.
|
|
* @function module:svgcanvas.SvgCanvas#setIdPrefix
|
|
* @param {string} p - String with the new prefix
|
|
* @returns {void}
|
|
*/
|
|
canvas.setIdPrefix = function (p) {
|
|
idprefix = p;
|
|
};
|
|
|
|
/**
|
|
* Current `draw.Drawing` object.
|
|
* @type {module:draw.Drawing}
|
|
* @name module:svgcanvas.SvgCanvas#current_drawing_
|
|
*/
|
|
canvas.current_drawing_ = new draw.Drawing(svgcontent, idprefix);
|
|
|
|
/**
|
|
* Returns the current Drawing.
|
|
* @name module:svgcanvas.SvgCanvas#getCurrentDrawing
|
|
* @type {module:draw.DrawCanvasInit#getCurrentDrawing}
|
|
*/
|
|
const getCurrentDrawing = canvas.getCurrentDrawing = function () {
|
|
return canvas.current_drawing_;
|
|
};
|
|
|
|
/**
|
|
* Float displaying the current zoom level (1 = 100%, .5 = 50%, etc.).
|
|
* @type {Float}
|
|
*/
|
|
let currentZoom = 1;
|
|
|
|
// pointer to current group (for in-group editing)
|
|
let currentGroup = null;
|
|
|
|
// Object containing data for the currently selected styles
|
|
const allProperties = {
|
|
shape: {
|
|
fill: (curConfig.initFill.color === 'none' ? '' : '#') + curConfig.initFill.color,
|
|
fill_paint: null,
|
|
fill_opacity: curConfig.initFill.opacity,
|
|
stroke: '#' + curConfig.initStroke.color,
|
|
stroke_paint: null,
|
|
stroke_opacity: curConfig.initStroke.opacity,
|
|
stroke_width: curConfig.initStroke.width,
|
|
stroke_dasharray: 'none',
|
|
stroke_linejoin: 'miter',
|
|
stroke_linecap: 'butt',
|
|
opacity: curConfig.initOpacity
|
|
}
|
|
};
|
|
|
|
allProperties.text = $.extend(true, {}, allProperties.shape);
|
|
$.extend(allProperties.text, {
|
|
fill: '#000000',
|
|
stroke_width: curConfig.text && curConfig.text.stroke_width,
|
|
font_size: curConfig.text && curConfig.text.font_size,
|
|
font_family: curConfig.text && curConfig.text.font_family
|
|
});
|
|
|
|
// Current shape style properties
|
|
const curShape = allProperties.shape;
|
|
|
|
// Array with all the currently selected elements
|
|
// default size of 1 until it needs to grow bigger
|
|
let selectedElements = [];
|
|
|
|
jsonInit(
|
|
/**
|
|
* @implements {module:json.jsonContext}
|
|
*/
|
|
{
|
|
getDOMDocument() { return svgdoc; },
|
|
getDrawing() { return getCurrentDrawing(); },
|
|
getCurShape() { return curShape; },
|
|
getCurrentGroup() { return currentGroup; }
|
|
}
|
|
);
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.SVGAsJSON
|
|
* @property {string} element
|
|
* @property {PlainObject<string, string>} attr
|
|
* @property {module:svgcanvas.SVGAsJSON[]} children
|
|
*/
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getContentElem
|
|
* @param {Text|Element} data
|
|
* @returns {module:svgcanvas.SVGAsJSON}
|
|
*/
|
|
const getJsonFromSvgElement = this.getJsonFromSvgElement = getJsonFromSvgElements;
|
|
|
|
/**
|
|
* This should really be an intersection implementing all rather than a union.
|
|
* @name module:svgcanvas.SvgCanvas#addSVGElementFromJson
|
|
* @type {module:utilities.EditorContext#addSVGElementFromJson|module:path.EditorContext#addSVGElementFromJson}
|
|
*/
|
|
const addSVGElementFromJson = this.addSVGElementFromJson = addSVGElementsFromJson;
|
|
|
|
canvas.getTransformList = getTransformList;
|
|
canvas.matrixMultiply = matrixMultiply;
|
|
canvas.hasMatrixTransform = hasMatrixTransform;
|
|
canvas.transformListToTransform = transformListToTransform;
|
|
|
|
/**
|
|
* @type {module:utilities.EditorContext#getBaseUnit}
|
|
*/
|
|
const getBaseUnit = () => { return curConfig.baseUnit; };
|
|
|
|
/**
|
|
* Initialize from units.js.
|
|
* Send in an object implementing the ElementContainer interface (see units.js).
|
|
*/
|
|
unitsInit(
|
|
/**
|
|
* @implements {module:units.ElementContainer}
|
|
*/
|
|
{
|
|
getBaseUnit,
|
|
getElement: getElem,
|
|
getHeight() { return svgcontent.getAttribute('height') / currentZoom; },
|
|
getWidth() { return svgcontent.getAttribute('width') / currentZoom; },
|
|
getRoundDigits() { return saveOptions.round_digits; }
|
|
}
|
|
);
|
|
|
|
canvas.convertToNum = convertToNum;
|
|
|
|
/**
|
|
* Should really be an intersection with all needing to apply rather than a union.
|
|
* @name module:svgcanvas.SvgCanvas#getSelectedElements
|
|
* @type {module:utilities.EditorContext#getSelectedElements|module:draw.DrawCanvasInit#getSelectedElements|module:path.EditorContext#getSelectedElements}
|
|
*/
|
|
const getSelectedElements = this.getSelectedElems = function () {
|
|
return selectedElements;
|
|
};
|
|
|
|
this.setSelectedElements = function (key, value) {
|
|
selectedElements[key] = value;
|
|
};
|
|
this.setEmptySelectedElements = function () {
|
|
selectedElements = [];
|
|
};
|
|
|
|
const { pathActions } = pathModule;
|
|
|
|
/**
|
|
* This should actually be an intersection as all interfaces should be met.
|
|
* @type {module:utilities.EditorContext#getSVGRoot|module:recalculate.EditorContext#getSVGRoot|module:coords.EditorContext#getSVGRoot|module:path.EditorContext#getSVGRoot}
|
|
*/
|
|
|
|
utilsInit(
|
|
/**
|
|
* @implements {module:utilities.EditorContext}
|
|
*/
|
|
{
|
|
pathActions, // Ok since not modifying
|
|
getSVGContent,
|
|
addSVGElementFromJson,
|
|
getSelectedElements,
|
|
getDOMDocument() { return svgdoc; },
|
|
getDOMContainer() { return container; },
|
|
getSVGRoot,
|
|
// TODO: replace this mostly with a way to get the current drawing.
|
|
getBaseUnit,
|
|
getSnappingStep() { return curConfig.snappingStep; },
|
|
getDataStorage
|
|
}
|
|
);
|
|
|
|
canvas.findDefs = findDefs;
|
|
canvas.getUrlFromAttr = getUrlFromAttr;
|
|
canvas.getHref = getHref;
|
|
canvas.setHref = setHref;
|
|
/* const getBBox = */ canvas.getBBox = utilsGetBBox;
|
|
canvas.getRotationAngle = getRotationAngle;
|
|
canvas.getElem = getElem;
|
|
canvas.getRefElem = getRefElem;
|
|
canvas.assignAttributes = assignAttributes;
|
|
|
|
this.cleanupElement = cleanupElement;
|
|
|
|
/**
|
|
* This should actually be an intersection not a union as all should apply.
|
|
* @type {module:coords.EditorContext#getGridSnapping|module:path.EditorContext#getGridSnapping}
|
|
*/
|
|
const getGridSnapping = () => { return curConfig.gridSnapping; };
|
|
|
|
coordsInit(
|
|
/**
|
|
* @implements {module:coords.EditorContext}
|
|
*/
|
|
{
|
|
getDrawing() { return getCurrentDrawing(); },
|
|
getDataStorage,
|
|
getSVGRoot,
|
|
getGridSnapping
|
|
}
|
|
);
|
|
this.remapElement = remapElement;
|
|
|
|
recalculateInit(
|
|
/**
|
|
* @implements {module:recalculate.EditorContext}
|
|
*/
|
|
{
|
|
getSVGRoot,
|
|
getStartTransform() { return startTransform; },
|
|
setStartTransform(transform) { startTransform = transform; },
|
|
getDataStorage
|
|
}
|
|
);
|
|
this.recalculateDimensions = recalculateDimensions;
|
|
|
|
// import from sanitize.js
|
|
const nsMap = getReverseNS();
|
|
canvas.sanitizeSvg = sanitizeSvg;
|
|
|
|
/**
|
|
* This should really be an intersection applying to all types rather than a union.
|
|
* @name module:svgcanvas.SvgCanvas#getZoom
|
|
* @type {module:path.EditorContext#getCurrentZoom|module:select.SVGFactory#getCurrentZoom}
|
|
*/
|
|
const getCurrentZoom = this.getZoom = function () { return currentZoom; };
|
|
|
|
/**
|
|
* This method rounds the incoming value to the nearest value based on the `currentZoom`
|
|
* @name module:svgcanvas.SvgCanvas#round
|
|
* @type {module:path.EditorContext#round}
|
|
*/
|
|
const round = this.round = function (val) {
|
|
return Number.parseInt(val * currentZoom) / currentZoom;
|
|
};
|
|
|
|
selectInit(
|
|
curConfig,
|
|
/**
|
|
* Export to select.js.
|
|
* @implements {module:select.SVGFactory}
|
|
*/
|
|
{
|
|
createSVGElement(jsonMap) { return canvas.addSVGElementFromJson(jsonMap); },
|
|
svgRoot() { return svgroot; },
|
|
svgContent() { return svgcontent; },
|
|
getDataStorage,
|
|
getCurrentZoom
|
|
}
|
|
);
|
|
/**
|
|
* This object manages selectors for us.
|
|
* @name module:svgcanvas.SvgCanvas#selectorManager
|
|
* @type {module:select.SelectorManager}
|
|
*/
|
|
const selectorManager = this.selectorManager = getSelectorManager();
|
|
|
|
/**
|
|
* @name module:svgcanvas.SvgCanvas#getNextId
|
|
* @type {module:path.EditorContext#getNextId}
|
|
*/
|
|
const getNextId = canvas.getNextId = function () {
|
|
return getCurrentDrawing().getNextId();
|
|
};
|
|
|
|
/**
|
|
* @name module:svgcanvas.SvgCanvas#getId
|
|
* @type {module:path.EditorContext#getId}
|
|
*/
|
|
const getId = canvas.getId = function () {
|
|
return getCurrentDrawing().getId();
|
|
};
|
|
|
|
/**
|
|
* The "implements" should really be an intersection applying to all types rather than a union.
|
|
* @name module:svgcanvas.SvgCanvas#call
|
|
* @type {module:draw.DrawCanvasInit#call|module:path.EditorContext#call}
|
|
*/
|
|
const call = function (ev, arg) {
|
|
if (events[ev]) {
|
|
return events[ev](window, arg);
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const restoreRefElems = function (elem) {
|
|
// Look for missing reference elements, restore any found
|
|
let attrs = {};
|
|
refAttrs.forEach(function (item, _) {
|
|
attrs[item] = elem.getAttribute(item);
|
|
});
|
|
Object.values(attrs).forEach((val) => {
|
|
if (val && val.startsWith('url(')) {
|
|
const id = getUrlFromAttr(val).substr(1);
|
|
const ref = getElem(id);
|
|
if (!ref) {
|
|
findDefs().append(removedElements[id]);
|
|
delete removedElements[id];
|
|
}
|
|
}
|
|
});
|
|
|
|
const childs = elem.getElementsByTagName('*');
|
|
|
|
if (childs.length) {
|
|
for (let i = 0, l = childs.length; i < l; i++) {
|
|
restoreRefElems(childs[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
undoInit(
|
|
/**
|
|
* @implements {module:undo.undoContext}
|
|
*/
|
|
{
|
|
call,
|
|
restoreRefElems,
|
|
getSVGContent,
|
|
getCanvas() { return canvas; },
|
|
getCurrentMode() { return currentMode; },
|
|
getCurrentZoom,
|
|
getSVGRoot,
|
|
getSelectedElements
|
|
}
|
|
);
|
|
|
|
/**
|
|
* @name undoMgr
|
|
* @memberof module:svgcanvas.SvgCanvas#
|
|
* @type {module:history.HistoryEventHandler}
|
|
*/
|
|
const undoMgr = canvas.undoMgr = getUndoManager();
|
|
|
|
/**
|
|
* This should really be an intersection applying to all types rather than a union.
|
|
* @name module:svgcanvas~addCommandToHistory
|
|
* @type {module:path.EditorContext#addCommandToHistory|module:draw.DrawCanvasInit#addCommandToHistory}
|
|
*/
|
|
const addCommandToHistory = function (cmd) {
|
|
canvas.undoMgr.addCommandToHistory(cmd);
|
|
};
|
|
selectionInit(
|
|
/**
|
|
* @implements {module:selection.selectionContext}
|
|
*/
|
|
{
|
|
getCanvas() { return canvas; },
|
|
getDataStorage,
|
|
getCurrentGroup() { return currentGroup; },
|
|
getSelectedElements,
|
|
getSVGRoot,
|
|
getSVGContent,
|
|
getDOMContainer() { return container; },
|
|
getExtensions() { return extensions; },
|
|
setExtensions(key, value) { extensions[key] = value; },
|
|
getCurrentZoom,
|
|
getRubberBox() { return rubberBox; },
|
|
setCurBBoxes(value) { curBBoxes = value; },
|
|
getCurBBoxes(_value) { return curBBoxes; },
|
|
getCurrentResizeMode() { return currentResizeMode; },
|
|
addCommandToHistory,
|
|
getSelector() { return Selector; }
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Clears the selection. The 'selected' handler is then optionally called.
|
|
* This should really be an intersection applying to all types rather than a union.
|
|
* @name module:svgcanvas.SvgCanvas#clearSelection
|
|
* @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection}
|
|
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
*/
|
|
const clearSelection = this.clearSelection = clearSelectionMethod;
|
|
|
|
/**
|
|
* Adds a list of elements to the selection. The 'selected' handler is then called.
|
|
* @name module:svgcanvas.SvgCanvas#addToSelection
|
|
* @type {module:path.EditorContext#addToSelection}
|
|
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
*/
|
|
const addToSelection = this.addToSelection = addToSelectionMethod;
|
|
|
|
/**
|
|
* @type {module:path.EditorContext#getOpacity}
|
|
*/
|
|
const getOpacity = function () {
|
|
return curShape.opacity;
|
|
};
|
|
|
|
/**
|
|
* @name module:svgcanvas.SvgCanvas#getMouseTarget
|
|
* @type {module:path.EditorContext#getMouseTarget}
|
|
*/
|
|
const getMouseTarget = this.getMouseTarget = getMouseTargetMethod;
|
|
|
|
/**
|
|
* @namespace {module:path.pathActions} pathActions
|
|
* @memberof module:svgcanvas.SvgCanvas#
|
|
* @see module:path.pathActions
|
|
*/
|
|
canvas.pathActions = pathActions;
|
|
/**
|
|
* @type {module:path.EditorContext#resetD}
|
|
*/
|
|
function resetD(p) {
|
|
if (typeof pathActions.convertPath === 'function') {
|
|
p.setAttribute('d', pathActions.convertPath(p));
|
|
} else if (typeof pathActions.convertPaths === 'function') {
|
|
p.setAttribute('d', pathActions.convertPaths(p));
|
|
}
|
|
}
|
|
pathModule.init(
|
|
/**
|
|
* @implements {module:path.EditorContext}
|
|
*/
|
|
{
|
|
selectorManager, // Ok since not changing
|
|
canvas, // Ok since not changing
|
|
call,
|
|
resetD,
|
|
round,
|
|
clearSelection,
|
|
addToSelection,
|
|
addCommandToHistory,
|
|
remapElement,
|
|
addSVGElementFromJson,
|
|
getGridSnapping,
|
|
getOpacity,
|
|
getSelectedElements,
|
|
getContainer() {
|
|
return container;
|
|
},
|
|
setStarted(s) {
|
|
started = s;
|
|
},
|
|
getRubberBox() {
|
|
return rubberBox;
|
|
},
|
|
setRubberBox(rb) {
|
|
rubberBox = rb;
|
|
return rubberBox;
|
|
},
|
|
/**
|
|
* @param {PlainObject} ptsInfo
|
|
* @param {boolean} ptsInfo.closedSubpath
|
|
* @param {SVGCircleElement[]} ptsInfo.grips
|
|
* @fires module:svgcanvas.SvgCanvas#event:pointsAdded
|
|
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
* @returns {void}
|
|
*/
|
|
addPtsToSelection({ closedSubpath, grips }) {
|
|
// TODO: Correct this:
|
|
pathActions.canDeleteNodes = true;
|
|
pathActions.closed_subpath = closedSubpath;
|
|
call('pointsAdded', { closedSubpath, grips });
|
|
call('selected', grips);
|
|
},
|
|
/**
|
|
* @param {PlainObject} changes
|
|
* @param {ChangeElementCommand} changes.cmd
|
|
* @param {SVGPathElement} changes.elem
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
endChanges({ cmd, elem }) {
|
|
addCommandToHistory(cmd);
|
|
call('changed', [ elem ]);
|
|
},
|
|
getCurrentZoom,
|
|
getId,
|
|
getNextId,
|
|
getMouseTarget,
|
|
getCurrentMode() {
|
|
return currentMode;
|
|
},
|
|
setCurrentMode(cm) {
|
|
currentMode = cm;
|
|
return currentMode;
|
|
},
|
|
getDrawnPath() {
|
|
return drawnPath;
|
|
},
|
|
setDrawnPath(dp) {
|
|
drawnPath = dp;
|
|
return drawnPath;
|
|
},
|
|
getSVGRoot
|
|
}
|
|
);
|
|
|
|
// Interface strings, usually for title elements
|
|
const uiStrings = {};
|
|
|
|
// Animation element to change the opacity of any newly created element
|
|
const opacAni = document.createElementNS(NS.SVG, 'animate');
|
|
opacAni.setAttribute('attributeName', 'opacity');
|
|
opacAni.setAttribute('begin', 'indefinite');
|
|
opacAni.setAttribute('dur', 1);
|
|
opacAni.setAttribute('fill', 'freeze');
|
|
svgroot.appendChild(opacAni);
|
|
|
|
// (function () {
|
|
// TODO For Issue 208: this is a start on a thumbnail
|
|
// const svgthumb = svgdoc.createElementNS(NS.SVG, 'use');
|
|
// svgthumb.setAttribute('width', '100');
|
|
// svgthumb.setAttribute('height', '100');
|
|
// setHref(svgthumb, '#svgcontent');
|
|
// svgroot.append(svgthumb);
|
|
// }());
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.SaveOptions
|
|
* @property {boolean} apply
|
|
* @property {"embed"} [image]
|
|
* @property {Integer} round_digits
|
|
*/
|
|
|
|
// Object to contain image data for raster images that were found encodable
|
|
const encodableImages = {},
|
|
|
|
// Object with save options
|
|
/**
|
|
* @type {module:svgcanvas.SaveOptions}
|
|
*/
|
|
saveOptions = { round_digits: 5 },
|
|
|
|
// Object with IDs for imported files, to see if one was already added
|
|
importIds = {},
|
|
|
|
// Current text style properties
|
|
curText = allProperties.text,
|
|
|
|
// Object to contain all included extensions
|
|
extensions = {},
|
|
|
|
// Map of deleted reference elements
|
|
removedElements = {};
|
|
|
|
let
|
|
// String with image URL of last loadable image
|
|
lastGoodImgUrl = curConfig.imgPath + 'logo.svg',
|
|
|
|
// Boolean indicating whether or not a draw action has been started
|
|
started = false,
|
|
|
|
// String with an element's initial transform attribute value
|
|
startTransform = null,
|
|
|
|
// String indicating the current editor mode
|
|
currentMode = 'select',
|
|
|
|
// String with the current direction in which an element is being resized
|
|
currentResizeMode = 'none',
|
|
|
|
// Current general properties
|
|
curProperties = curShape,
|
|
|
|
// Array with selected elements' Bounding box object
|
|
// selectedBBoxes = new Array(1),
|
|
|
|
// The DOM element that was just selected
|
|
justSelected = null,
|
|
|
|
// DOM element for selection rectangle drawn by the user
|
|
rubberBox = null,
|
|
|
|
// Array of current BBoxes, used in getIntersectionList().
|
|
curBBoxes = [],
|
|
|
|
// Canvas point for the most recent right click
|
|
lastClickPoint = null;
|
|
|
|
this.runExtension = function (name, action, vars) {
|
|
return this.runExtensions(action, vars, false, (n) => n === name);
|
|
};
|
|
/* eslint-disable max-len */
|
|
/**
|
|
* @todo Consider: Should this return an array by default, so extension results aren't overwritten?
|
|
* @todo Would be easier to document if passing in object with key of action and vars as value; could then define an interface which tied both together
|
|
* @function module:svgcanvas.SvgCanvas#runExtensions
|
|
* @param {"mouseDown"|"mouseMove"|"mouseUp"|"zoomChanged"|"IDsUpdated"|"canvasUpdated"|"toolButtonStateUpdate"|"selectedChanged"|"elementTransition"|"elementChanged"|"langReady"|"langChanged"|"addLangData"|"onNewDocument"|"workareaResized"} action
|
|
* @param {module:svgcanvas.SvgCanvas#event:ext_mouseDown|module:svgcanvas.SvgCanvas#event:ext_mouseMove|module:svgcanvas.SvgCanvas#event:ext_mouseUp|module:svgcanvas.SvgCanvas#event:ext_zoomChanged|module:svgcanvas.SvgCanvas#event:ext_IDsUpdated|module:svgcanvas.SvgCanvas#event:ext_canvasUpdated|module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate|module:svgcanvas.SvgCanvas#event:ext_selectedChanged|module:svgcanvas.SvgCanvas#event:ext_elementTransition|module:svgcanvas.SvgCanvas#event:ext_elementChanged|module:svgcanvas.SvgCanvas#event:ext_langReady|module:svgcanvas.SvgCanvas#event:ext_langChanged|module:svgcanvas.SvgCanvas#event:ext_addLangData|module:svgcanvas.SvgCanvas#event:ext_onNewDocument|module:svgcanvas.SvgCanvas#event:ext_workareaResized|module:svgcanvas.ExtensionVarBuilder} [vars]
|
|
* @param {boolean} [returnArray]
|
|
* @param {module:svgcanvas.ExtensionNameFilter} nameFilter
|
|
* @returns {GenericArray<module:svgcanvas.ExtensionStatus>|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus.
|
|
*/
|
|
/* eslint-enable max-len */
|
|
|
|
this.runExtensions = runExtensionsMethod;
|
|
|
|
/**
|
|
* Add an extension to the editor.
|
|
* @function module:svgcanvas.SvgCanvas#addExtension
|
|
* @param {string} name - String with the ID of the extension. Used internally; no need for i18n.
|
|
* @param {module:svgcanvas.ExtensionInitCallback} [extInitFunc] - Function supplied by the extension with its data
|
|
* @param {module:svgcanvas.ExtensionInitArgs} initArgs
|
|
* @fires module:svgcanvas.SvgCanvas#event:extension_added
|
|
* @throws {TypeError|Error} `TypeError` if `extInitFunc` is not a function, `Error`
|
|
* if extension of supplied name already exists
|
|
* @returns {Promise<void>} Resolves to `undefined`
|
|
*/
|
|
this.addExtension = async function (name, extInitFunc, { $: jq, importLocale }) {
|
|
if (typeof extInitFunc !== 'function') {
|
|
throw new TypeError('Function argument expected for `svgcanvas.addExtension`');
|
|
}
|
|
if (name in extensions) {
|
|
throw new Error('Cannot add extension "' + name + '", an extension by that name already exists.');
|
|
}
|
|
// Provide private vars/funcs here. Is there a better way to do this?
|
|
/**
|
|
* @typedef {module:svgcanvas.PrivateMethods} module:svgcanvas.ExtensionArgumentObject
|
|
* @property {SVGSVGElement} svgroot See {@link module:svgcanvas~svgroot}
|
|
* @property {SVGSVGElement} svgcontent See {@link module:svgcanvas~svgcontent}
|
|
* @property {!(string|Integer)} nonce See {@link module:draw.Drawing#getNonce}
|
|
* @property {module:select.SelectorManager} selectorManager
|
|
* @property {module:SVGEditor~ImportLocale} importLocale
|
|
*/
|
|
/**
|
|
* @type {module:svgcanvas.ExtensionArgumentObject}
|
|
* @see {@link module:svgcanvas.PrivateMethods} source for the other methods/properties
|
|
*/
|
|
const argObj = $.extend(canvas.getPrivateMethods(), {
|
|
$: jq,
|
|
importLocale,
|
|
svgroot,
|
|
svgcontent,
|
|
nonce: getCurrentDrawing().getNonce(),
|
|
selectorManager
|
|
});
|
|
const extObj = await extInitFunc(argObj);
|
|
if (extObj) {
|
|
extObj.name = name;
|
|
}
|
|
|
|
extensions[name] = extObj;
|
|
return call('extension_added', extObj);
|
|
};
|
|
|
|
/**
|
|
* This method sends back an array or a NodeList full of elements that
|
|
* intersect the multi-select rubber-band-box on the currentLayer only.
|
|
*
|
|
* We brute-force `getIntersectionList` for browsers that do not support it (Firefox).
|
|
*
|
|
* Reference:
|
|
* Firefox does not implement `getIntersectionList()`, see {@link https://bugzilla.mozilla.org/show_bug.cgi?id=501421}.
|
|
* @function module:svgcanvas.SvgCanvas#getIntersectionList
|
|
* @param {SVGRect} rect
|
|
* @returns {Element[]|NodeList} Bbox elements
|
|
*/
|
|
const getIntersectionList = this.getIntersectionList = getIntersectionListMethod;
|
|
|
|
this.getStrokedBBox = getStrokedBBoxDefaultVisible;
|
|
|
|
this.getVisibleElements = getVisibleElements;
|
|
|
|
/**
|
|
* Wrap an SVG element into a group element, mark the group as 'gsvg'.
|
|
* @function module:svgcanvas.SvgCanvas#groupSvgElem
|
|
* @param {Element} elem - SVG element to wrap
|
|
* @returns {void}
|
|
*/
|
|
this.groupSvgElem = groupSvgElem;
|
|
|
|
// Set scope for these functions
|
|
|
|
// Object to contain editor event names and callback functions
|
|
const events = {};
|
|
|
|
canvas.call = call;
|
|
/**
|
|
* Array of what was changed (elements, layers).
|
|
* @event module:svgcanvas.SvgCanvas#event:changed
|
|
* @type {Element[]}
|
|
*/
|
|
/**
|
|
* Array of selected elements.
|
|
* @event module:svgcanvas.SvgCanvas#event:selected
|
|
* @type {Element[]}
|
|
*/
|
|
/**
|
|
* Array of selected elements.
|
|
* @event module:svgcanvas.SvgCanvas#event:transition
|
|
* @type {Element[]}
|
|
*/
|
|
/**
|
|
* The Element is always `SVGGElement`?
|
|
* If not `null`, will be the set current group element.
|
|
* @event module:svgcanvas.SvgCanvas#event:contextset
|
|
* @type {null|Element}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:pointsAdded
|
|
* @type {PlainObject}
|
|
* @property {boolean} closedSubpath
|
|
* @property {SVGCircleElement[]} grips Grips elements
|
|
*/
|
|
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:zoomed
|
|
* @type {PlainObject}
|
|
* @property {Float} x
|
|
* @property {Float} y
|
|
* @property {Float} width
|
|
* @property {Float} height
|
|
* @property {0.5|2} factor
|
|
* @see module:SVGEditor.BBoxObjectWithFactor
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:updateCanvas
|
|
* @type {PlainObject}
|
|
* @property {false} center
|
|
* @property {module:math.XYObject} newCtr
|
|
*/
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.ExtensionInitResponsePlusName
|
|
* @implements {module:svgcanvas.ExtensionInitResponse}
|
|
* @property {string} name The extension's resolved ID (whether explicit or based on file name)
|
|
*/
|
|
/**
|
|
* Generalized extension object response of
|
|
* [`init()`]{@link module:svgcanvas.ExtensionInitCallback}
|
|
* along with the name of the extension.
|
|
* @event module:svgcanvas.SvgCanvas#event:extension_added
|
|
* @type {module:svgcanvas.ExtensionInitResponsePlusName|void}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:extensions_added
|
|
* @type {void}
|
|
*/
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.Message
|
|
* @property {any} data The data
|
|
* @property {string} origin The origin
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:message
|
|
* @type {module:svgcanvas.Message}
|
|
*/
|
|
/**
|
|
* SVG canvas converted to string.
|
|
* @event module:svgcanvas.SvgCanvas#event:saved
|
|
* @type {string}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:setnonce
|
|
* @type {!(string|Integer)}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:unsetnonce
|
|
* @type {void}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:zoomDone
|
|
* @type {void}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:cleared
|
|
* @type {void}
|
|
*/
|
|
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:exported
|
|
* @type {module:svgcanvas.ImageExportedResults}
|
|
*/
|
|
/**
|
|
* @event module:svgcanvas.SvgCanvas#event:exportedPDF
|
|
* @type {module:svgcanvas.PDFExportedResults}
|
|
*/
|
|
/* eslint-disable max-len */
|
|
/**
|
|
* Creating a cover-all class until {@link https://github.com/jsdoc3/jsdoc/issues/1545} may be supported.
|
|
* `undefined` may be returned by {@link module:svgcanvas.SvgCanvas#event:extension_added} if the extension's `init` returns `undefined` It is also the type for the following events "zoomDone", "unsetnonce", "cleared", and "extensions_added".
|
|
* @event module:svgcanvas.SvgCanvas#event:GenericCanvasEvent
|
|
* @type {module:svgcanvas.SvgCanvas#event:selected|module:svgcanvas.SvgCanvas#event:changed|module:svgcanvas.SvgCanvas#event:contextset|module:svgcanvas.SvgCanvas#event:pointsAdded|module:svgcanvas.SvgCanvas#event:extension_added|module:svgcanvas.SvgCanvas#event:extensions_added|module:svgcanvas.SvgCanvas#event:message|module:svgcanvas.SvgCanvas#event:transition|module:svgcanvas.SvgCanvas#event:zoomed|module:svgcanvas.SvgCanvas#event:updateCanvas|module:svgcanvas.SvgCanvas#event:saved|module:svgcanvas.SvgCanvas#event:exported|module:svgcanvas.SvgCanvas#event:exportedPDF|module:svgcanvas.SvgCanvas#event:setnonce|module:svgcanvas.SvgCanvas#event:unsetnonce|void}
|
|
*/
|
|
/* eslint-enable max-len */
|
|
|
|
/**
|
|
* The promise return, if present, resolves to `undefined`
|
|
* (`extension_added`, `exported`, `saved`).
|
|
* @typedef {Promise<void>|void} module:svgcanvas.EventHandlerReturn
|
|
*/
|
|
|
|
/**
|
|
* @callback module:svgcanvas.EventHandler
|
|
* @param {external:Window} win
|
|
* @param {module:svgcanvas.SvgCanvas#event:GenericCanvasEvent} arg
|
|
* @listens module:svgcanvas.SvgCanvas#event:GenericCanvasEvent
|
|
* @returns {module:svgcanvas.EventHandlerReturn}
|
|
*/
|
|
/* eslint-disable max-len */
|
|
/**
|
|
* Attaches a callback function to an event.
|
|
* @function module:svgcanvas.SvgCanvas#bind
|
|
* @param {"changed"|"contextset"|"selected"|"pointsAdded"|"extension_added"|"extensions_added"|"message"|"transition"|"zoomed"|"updateCanvas"|"zoomDone"|"saved"|"exported"|"exportedPDF"|"setnonce"|"unsetnonce"|"cleared"} ev - String indicating the name of the event
|
|
* @param {module:svgcanvas.EventHandler} f - The callback function to bind to the event
|
|
* @returns {module:svgcanvas.EventHandler} The previous event
|
|
*/
|
|
/* eslint-enable max-len */
|
|
canvas.bind = function (ev, f) {
|
|
const old = events[ev];
|
|
events[ev] = f;
|
|
return old;
|
|
};
|
|
|
|
/**
|
|
* Runs the SVG Document through the sanitizer and then updates its paths.
|
|
* @function module:svgcanvas.SvgCanvas#prepareSvg
|
|
* @param {XMLDocument} newDoc - The SVG DOM document
|
|
* @returns {void}
|
|
*/
|
|
this.prepareSvg = prepareSvg;
|
|
|
|
/**
|
|
* Removes any old rotations if present, prepends a new rotation at the
|
|
* transformed center.
|
|
* @function module:svgcanvas.SvgCanvas#setRotationAngle
|
|
* @param {string|Float} val - The new rotation angle in degrees
|
|
* @param {boolean} preventUndo - Indicates whether the action should be undoable or not
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setRotationAngle = setRotationAngle;
|
|
|
|
/**
|
|
* Runs `recalculateDimensions` on the selected elements,
|
|
* adding the changes to a single batch command.
|
|
* @function module:svgcanvas.SvgCanvas#recalculateAllSelectedDimensions
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.recalculateAllSelectedDimensions = recalculateAllSelectedDimensions;
|
|
|
|
/**
|
|
* Debug tool to easily see the current matrix in the browser's console.
|
|
* @function module:svgcanvas~logMatrix
|
|
* @param {SVGMatrix} m The matrix
|
|
* @returns {void}
|
|
*/
|
|
const logMatrix = function (m) {
|
|
console.info([ m.a, m.b, m.c, m.d, m.e, m.f ]);
|
|
};
|
|
|
|
// Root Current Transformation Matrix in user units
|
|
let rootSctm = null;
|
|
|
|
/**
|
|
* Group: Selection.
|
|
*/
|
|
|
|
// TODO: do we need to worry about selectedBBoxes here?
|
|
|
|
/**
|
|
* Selects only the given elements, shortcut for `clearSelection(); addToSelection()`.
|
|
* @function module:svgcanvas.SvgCanvas#selectOnly
|
|
* @param {Element[]} elems - an array of DOM elements to be selected
|
|
* @param {boolean} showGrips - Indicates whether the resize grips should be shown
|
|
* @returns {void}
|
|
*/
|
|
const selectOnly = this.selectOnly = function (elems, showGrips) {
|
|
clearSelection(true);
|
|
addToSelection(elems, showGrips);
|
|
};
|
|
|
|
// TODO: could use slice here to make this faster?
|
|
// TODO: should the 'selected' handler
|
|
|
|
/**
|
|
* Removes elements from the selection.
|
|
* @function module:svgcanvas.SvgCanvas#removeFromSelection
|
|
* @param {Element[]} elemsToRemove - An array of elements to remove from selection
|
|
* @returns {void}
|
|
*/
|
|
/* const removeFromSelection = */ this.removeFromSelection = function (elemsToRemove) {
|
|
if (isNullish(selectedElements[0])) { return; }
|
|
if (!elemsToRemove.length) { return; }
|
|
|
|
// find every element and remove it from our array copy
|
|
const newSelectedItems = [],
|
|
len = selectedElements.length;
|
|
for (let i = 0; i < len; ++i) {
|
|
const elem = selectedElements[i];
|
|
if (elem) {
|
|
// keep the item
|
|
if (!elemsToRemove.includes(elem)) {
|
|
newSelectedItems.push(elem);
|
|
} else { // remove the item and its selector
|
|
selectorManager.releaseSelector(elem);
|
|
}
|
|
}
|
|
}
|
|
// the copy becomes the master now
|
|
selectedElements = newSelectedItems;
|
|
};
|
|
|
|
/**
|
|
* Clears the selection, then adds all elements in the current layer to the selection.
|
|
* @function module:svgcanvas.SvgCanvas#selectAllInCurrentLayer
|
|
* @returns {void}
|
|
*/
|
|
this.selectAllInCurrentLayer = function () {
|
|
const currentLayer = getCurrentDrawing().getCurrentLayer();
|
|
if (currentLayer) {
|
|
currentMode = 'select';
|
|
if (currentGroup) {
|
|
selectOnly(currentGroup.children);
|
|
} else {
|
|
selectOnly(currentLayer.children);
|
|
}
|
|
}
|
|
};
|
|
|
|
let drawnPath = null;
|
|
|
|
// Mouse events
|
|
(function () {
|
|
const freehand = {
|
|
minx: null,
|
|
miny: null,
|
|
maxx: null,
|
|
maxy: null
|
|
};
|
|
const THRESHOLD_DIST = 0.8,
|
|
STEP_COUNT = 10;
|
|
let dAttr = null;
|
|
let startX = null;
|
|
let startY = null;
|
|
let rStartX = null;
|
|
let rStartY = null;
|
|
let initBbox = {};
|
|
let sumDistance = 0;
|
|
const controllPoint2 = { x: 0, y: 0 };
|
|
const controllPoint1 = { x: 0, y: 0 };
|
|
let start = { x: 0, y: 0 };
|
|
const end = { x: 0, y: 0 };
|
|
let bSpline = { x: 0, y: 0 };
|
|
let nextPos = { x: 0, y: 0 };
|
|
let parameter;
|
|
let nextParameter;
|
|
|
|
/**
|
|
* @function eventInit Initialize from event.js
|
|
* @returns {void}
|
|
*/
|
|
eventInit(
|
|
/**
|
|
* @implements {module:event.eventContext_}
|
|
*/
|
|
{
|
|
getStarted() { return started; },
|
|
getCanvas() { return canvas; },
|
|
getDataStorage,
|
|
getCurConfig() { return curConfig; },
|
|
getCurrentMode() { return currentMode; },
|
|
getrootSctm() { return rootSctm; },
|
|
getStartX() { return startX; },
|
|
setStartX(value) { startX = value; },
|
|
getStartY() { return startY; },
|
|
setStartY(value) { startY = value; },
|
|
getRStartX() { return rStartX; },
|
|
getRStartY() { return rStartY; },
|
|
getRubberBox() { return rubberBox; },
|
|
getInitBbox() { return initBbox; },
|
|
getCurrentResizeMode() { return currentResizeMode; },
|
|
getCurrentGroup() { return currentGroup; },
|
|
getDrawnPath() { return drawnPath; },
|
|
getJustSelected() { return justSelected; },
|
|
getOpacAni() { return opacAni; },
|
|
getParameter() { return parameter; },
|
|
getNextParameter() { return nextParameter; },
|
|
getStepCount() { return STEP_COUNT; },
|
|
getThreSholdDist() { return THRESHOLD_DIST; },
|
|
getSumDistance() { return sumDistance; },
|
|
getStart(key) { return start[key]; },
|
|
getEnd(key) { return end[key]; },
|
|
getbSpline(key) { return bSpline[key]; },
|
|
getNextPos(key) { return nextPos[key]; },
|
|
getControllPoint1(key) { return controllPoint1[key]; },
|
|
getControllPoint2(key) { return controllPoint2[key]; },
|
|
getFreehand(key) { return freehand[key]; },
|
|
getDrawing() { return getCurrentDrawing(); },
|
|
getCurShape() { return curShape; },
|
|
getDAttr() { return dAttr; },
|
|
getLastGoodImgUrl() { return lastGoodImgUrl; },
|
|
getCurText(key) { return curText[key]; },
|
|
setDAttr(value) { dAttr = value; },
|
|
setEnd(key, value) { end[key] = value; },
|
|
setControllPoint1(key, value) { controllPoint1[key] = value; },
|
|
setControllPoint2(key, value) { controllPoint2[key] = value; },
|
|
setJustSelected(value) { justSelected = value; },
|
|
setParameter(value) { parameter = value; },
|
|
setStart(value) { start = value; },
|
|
setRStartX(value) { rStartX = value; },
|
|
setRStartY(value) { rStartY = value; },
|
|
setSumDistance(value) { sumDistance = value; },
|
|
setbSpline(value) { bSpline = value; },
|
|
setNextPos(value) { nextPos = value; },
|
|
setNextParameter(value) { nextParameter = value; },
|
|
setCurProperties(key, value) { curProperties[key] = value; },
|
|
setCurText(key, value) { curText[key] = value; },
|
|
setStarted(s) { started = s; },
|
|
setStartTransform(transform) { startTransform = transform; },
|
|
setCurrentMode(cm) {
|
|
currentMode = cm;
|
|
return currentMode;
|
|
},
|
|
setFreehand(key, value) { freehand[key] = value; },
|
|
setCurBBoxes(value) { curBBoxes = value; },
|
|
setRubberBox(value) { rubberBox = value; },
|
|
setInitBbox(value) { initBbox = value; },
|
|
setRootSctm(value) { rootSctm = value; },
|
|
setCurrentResizeMode(value) { currentResizeMode = value; },
|
|
setLastClickPoint(value) { lastClickPoint = value; },
|
|
getSelectedElements,
|
|
getCurrentZoom,
|
|
getId,
|
|
addCommandToHistory,
|
|
getSVGRoot,
|
|
getSVGContent,
|
|
call,
|
|
getIntersectionList
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Follows these conditions:
|
|
* - When we are in a create mode, the element is added to the canvas but the
|
|
* action is not recorded until mousing up.
|
|
* - When we are in select mode, select the element, remember the position
|
|
* and do nothing else.
|
|
* @param {MouseEvent} evt
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseDown
|
|
* @returns {void}
|
|
*/
|
|
const mouseDown = mouseDownEvent;
|
|
|
|
// in this function we do not record any state changes yet (but we do update
|
|
// any elements that are still being created, moved or resized on the canvas)
|
|
/**
|
|
*
|
|
* @param {MouseEvent} evt
|
|
* @fires module:svgcanvas.SvgCanvas#event:transition
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseMove
|
|
* @returns {void}
|
|
*/
|
|
const mouseMove = mouseMoveEvent;
|
|
|
|
// - in create mode, the element's opacity is set properly, we create an InsertElementCommand
|
|
// and store it on the Undo stack
|
|
// - in move/resize mode, the element's attributes which were affected by the move/resize are
|
|
// identified, a ChangeElementCommand is created and stored on the stack for those attrs
|
|
// this is done in when we recalculate the selected dimensions()
|
|
/**
|
|
*
|
|
* @param {MouseEvent} evt
|
|
* @fires module:svgcanvas.SvgCanvas#event:zoomed
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseUp
|
|
* @returns {void}
|
|
*/
|
|
const mouseUp = mouseUpEvent;
|
|
|
|
const dblClick = dblClickEvent;
|
|
|
|
// prevent links from being followed in the canvas
|
|
const handleLinkInCanvas = function (e) {
|
|
e.preventDefault();
|
|
return false;
|
|
};
|
|
|
|
// Added mouseup to the container here.
|
|
// TODO(codedread): Figure out why after the Closure compiler, the window mouseup is ignored.
|
|
container.addEventListener('mousedown', mouseDown);
|
|
container.addEventListener('mousemove', mouseMove);
|
|
container.addEventListener('click', handleLinkInCanvas);
|
|
container.addEventListener('dblclick', dblClick);
|
|
container.addEventListener('mouseup', mouseUp);
|
|
|
|
// TODO(rafaelcastrocouto): User preference for shift key and zoom factor
|
|
container.addEventListener('mousewheel', DOMMouseScrollEvent);
|
|
container.addEventListener('DOMMouseScroll', DOMMouseScrollEvent);
|
|
|
|
}());
|
|
|
|
textActionsInit(
|
|
/**
|
|
* @implements {module:text-actions.textActionsContext}
|
|
*/
|
|
{
|
|
getCanvas() { return canvas; },
|
|
getrootSctm() { return rootSctm; },
|
|
getSelectedElements,
|
|
getCurrentZoom,
|
|
getCurrentMode() {
|
|
return currentMode;
|
|
},
|
|
setCurrentMode(cm) {
|
|
currentMode = cm;
|
|
return currentMode;
|
|
},
|
|
getSVGRoot,
|
|
call
|
|
}
|
|
);
|
|
|
|
const textActions = canvas.textActions = textActionsMethod;
|
|
|
|
/**
|
|
* Group: Serialization.
|
|
*/
|
|
|
|
svgInit(
|
|
/**
|
|
* @implements {module:elem-get-set.elemInit}
|
|
*/
|
|
{
|
|
getCanvas() { return canvas; },
|
|
getDataStorage,
|
|
getSVGContent,
|
|
getSVGRoot,
|
|
getUIStrings() { return uiStrings; },
|
|
getCurrentGroup() { return currentGroup; },
|
|
getCurConfig() { return curConfig; },
|
|
getNsMap() { return nsMap; },
|
|
getSvgOption() { return saveOptions; },
|
|
setSvgOption(key, value) { saveOptions[key] = value; },
|
|
getSvgOptionApply() { return saveOptions.apply; },
|
|
getSvgOptionImages() { return saveOptions.images; },
|
|
getEncodableImages(key) { return encodableImages[key]; },
|
|
setEncodableImages(key, value) { encodableImages[key] = value; },
|
|
call,
|
|
getDOMDocument() { return svgdoc; },
|
|
getVisElems() { return visElems; },
|
|
getIdPrefix() { return idprefix; },
|
|
setCurrentZoom(value) { currentZoom = value; },
|
|
getImportIds(key) { return importIds[key]; },
|
|
setImportIds(key, value) { importIds[key] = value; },
|
|
setRemovedElements(key, value) { removedElements[key] = value; },
|
|
setSVGContent(value) { svgcontent = value; },
|
|
getrefAttrs() { return refAttrs; },
|
|
getcanvg() { return canvg; },
|
|
addCommandToHistory
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Looks at DOM elements inside the `<defs>` to see if they are referred to,
|
|
* removes them from the DOM if they are not.
|
|
* @function module:svgcanvas.SvgCanvas#removeUnusedDefElems
|
|
* @returns {Integer} The number of elements that were removed
|
|
*/
|
|
this.removeUnusedDefElems = removeUnusedDefElemsMethod;
|
|
|
|
/**
|
|
* Main function to set up the SVG content for output.
|
|
* @function module:svgcanvas.SvgCanvas#svgCanvasToString
|
|
* @returns {string} The SVG image for output
|
|
*/
|
|
this.svgCanvasToString = svgCanvasToString;
|
|
|
|
/**
|
|
* Sub function ran on each SVG element to convert it to a string as desired.
|
|
* @function module:svgcanvas.SvgCanvas#svgToString
|
|
* @param {Element} elem - The SVG element to convert
|
|
* @param {Integer} indent - Number of spaces to indent this tag
|
|
* @returns {string} The given element as an SVG tag
|
|
*/
|
|
this.svgToString = svgToString;
|
|
|
|
/**
|
|
* Function to run when image data is found.
|
|
* @callback module:svgcanvas.ImageEmbeddedCallback
|
|
* @param {string|false} result Data URL
|
|
* @returns {void}
|
|
*/
|
|
/**
|
|
* Converts a given image file to a data URL when possible, then runs a given callback.
|
|
* @function module:svgcanvas.SvgCanvas#embedImage
|
|
* @param {string} src - The path/URL of the image
|
|
* @returns {Promise<string|false>} Resolves to a Data URL (string|false)
|
|
*/
|
|
this.embedImage = embedImage;
|
|
|
|
/**
|
|
* Sets a given URL to be a "last good image" URL.
|
|
* @function module:svgcanvas.SvgCanvas#setGoodImage
|
|
* @param {string} val
|
|
* @returns {void}
|
|
*/
|
|
this.setGoodImage = function (val) {
|
|
lastGoodImgUrl = val;
|
|
};
|
|
|
|
/**
|
|
* Does nothing by default, handled by optional widget/extension.
|
|
* @function module:svgcanvas.SvgCanvas#open
|
|
* @returns {void}
|
|
*/
|
|
this.open = function () {
|
|
/* empty fn */
|
|
};
|
|
|
|
/**
|
|
* Serializes the current drawing into SVG XML text and passes it to the 'saved' handler.
|
|
* This function also includes the XML prolog. Clients of the `SvgCanvas` bind their save
|
|
* function to the 'saved' event.
|
|
* @function module:svgcanvas.SvgCanvas#save
|
|
* @param {module:svgcanvas.SaveOptions} opts
|
|
* @fires module:svgcanvas.SvgCanvas#event:saved
|
|
* @returns {void}
|
|
*/
|
|
this.save = save;
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.IssuesAndCodes
|
|
* @property {string[]} issueCodes The locale-independent code names
|
|
* @property {string[]} issues The localized descriptions
|
|
*/
|
|
|
|
/**
|
|
* @typedef {"feGaussianBlur"|"foreignObject"|"[stroke-dasharray]"|"text"} module:svgcanvas.IssueCode
|
|
*/
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.ImageExportedResults
|
|
* @property {string} datauri Contents as a Data URL
|
|
* @property {string} bloburl May be the empty string
|
|
* @property {string} svg The SVG contents as a string
|
|
* @property {string[]} issues The localization messages of `issueCodes`
|
|
* @property {module:svgcanvas.IssueCode[]} issueCodes CanVG issues found with the SVG
|
|
* @property {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} type The chosen image type
|
|
* @property {"image/png"|"image/jpeg"|"image/bmp"|"image/webp"} mimeType The image MIME type
|
|
* @property {Float} quality A decimal between 0 and 1 (for use with JPEG or WEBP)
|
|
* @property {string} exportWindowName A convenience for passing along a `window.name` to target a window on which the export could be added
|
|
*/
|
|
|
|
/**
|
|
* Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image,
|
|
* then calls "exported" with an object including the string, image
|
|
* information, and any issues found.
|
|
* @function module:svgcanvas.SvgCanvas#rasterExport
|
|
* @param {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} [imgType="PNG"]
|
|
* @param {Float} [quality] Between 0 and 1
|
|
* @param {string} [exportWindowName]
|
|
* @param {PlainObject} [opts]
|
|
* @param {boolean} [opts.avoidEvent]
|
|
* @fires module:svgcanvas.SvgCanvas#event:exported
|
|
* @todo Confirm/fix ICO type
|
|
* @returns {Promise<module:svgcanvas.ImageExportedResults>} Resolves to {@link module:svgcanvas.ImageExportedResults}
|
|
*/
|
|
this.rasterExport = rasterExport;
|
|
|
|
/**
|
|
* @typedef {void|"save"|"arraybuffer"|"blob"|"datauristring"|"dataurlstring"|"dataurlnewwindow"|"datauri"|"dataurl"} external:jsPDF.OutputType
|
|
* @todo Newer version to add also allows these `outputType` values "bloburi"|"bloburl" which return strings, so document here and for `outputType` of `module:svgcanvas.PDFExportedResults` below if added
|
|
*/
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.PDFExportedResults
|
|
* @property {string} svg The SVG PDF output
|
|
* @property {string|ArrayBuffer|Blob|window} output The output based on the `outputType`;
|
|
* if `undefined`, "datauristring", "dataurlstring", "datauri",
|
|
* or "dataurl", will be a string (`undefined` gives a document, while the others
|
|
* build as Data URLs; "datauri" and "dataurl" change the location of the current page); if
|
|
* "arraybuffer", will return `ArrayBuffer`; if "blob", returns a `Blob`;
|
|
* if "dataurlnewwindow", will change the current page's location and return a string
|
|
* if in Safari and no window object is found; otherwise opens in, and returns, a new `window`
|
|
* object; if "save", will have the same return as "dataurlnewwindow" if
|
|
* `navigator.getUserMedia` support is found without `URL.createObjectURL` support; otherwise
|
|
* returns `undefined` but attempts to save
|
|
* @property {external:jsPDF.OutputType} outputType
|
|
* @property {string[]} issues The human-readable localization messages of corresponding `issueCodes`
|
|
* @property {module:svgcanvas.IssueCode[]} issueCodes
|
|
* @property {string} exportWindowName
|
|
*/
|
|
|
|
/**
|
|
* Generates a PDF based on the current image, then calls "exportedPDF" with
|
|
* an object including the string, the data URL, and any issues found.
|
|
* @function module:svgcanvas.SvgCanvas#exportPDF
|
|
* @param {string} [exportWindowName] Will also be used for the download file name here
|
|
* @param {external:jsPDF.OutputType} [outputType="dataurlstring"]
|
|
* @fires module:svgcanvas.SvgCanvas#event:exportedPDF
|
|
* @returns {Promise<module:svgcanvas.PDFExportedResults>} Resolves to {@link module:svgcanvas.PDFExportedResults}
|
|
*/
|
|
this.exportPDF = exportPDF;
|
|
|
|
/**
|
|
* Returns the current drawing as raw SVG XML text.
|
|
* @function module:svgcanvas.SvgCanvas#getSvgString
|
|
* @returns {string} The current drawing as raw SVG XML text.
|
|
*/
|
|
this.getSvgString = function () {
|
|
saveOptions.apply = false;
|
|
return this.svgCanvasToString();
|
|
};
|
|
|
|
/**
|
|
* This function determines whether to use a nonce in the prefix, when
|
|
* generating IDs for future documents in SVG-Edit.
|
|
* If you're controlling SVG-Edit externally, and want randomized IDs, call
|
|
* this BEFORE calling `svgCanvas.setSvgString`.
|
|
* @function module:svgcanvas.SvgCanvas#randomizeIds
|
|
* @param {boolean} [enableRandomization] If true, adds a nonce to the prefix. Thus
|
|
* `svgCanvas.randomizeIds() <==> svgCanvas.randomizeIds(true)`
|
|
* @returns {void}
|
|
*/
|
|
this.randomizeIds = function (enableRandomization) {
|
|
if (arguments.length > 0 && enableRandomization === false) {
|
|
draw.randomizeIds(false, getCurrentDrawing());
|
|
} else {
|
|
draw.randomizeIds(true, getCurrentDrawing());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Ensure each element has a unique ID.
|
|
* @function module:svgcanvas.SvgCanvas#uniquifyElems
|
|
* @param {Element} g - The parent element of the tree to give unique IDs
|
|
* @returns {void}
|
|
*/
|
|
const uniquifyElems = this.uniquifyElems = uniquifyElemsMethod;
|
|
|
|
/**
|
|
* Assigns reference data for each use element.
|
|
* @function module:svgcanvas.SvgCanvas#setUseData
|
|
* @param {Element} parent
|
|
* @returns {void}
|
|
*/
|
|
const setUseData = this.setUseData = setUseDataMethod;
|
|
|
|
/**
|
|
* Converts gradients from userSpaceOnUse to objectBoundingBox.
|
|
* @function module:svgcanvas.SvgCanvas#convertGradients
|
|
* @param {Element} elem
|
|
* @returns {void}
|
|
*/
|
|
const convertGradients = this.convertGradients = convertGradientsMethod;
|
|
|
|
/**
|
|
* This function sets the current drawing as the input SVG XML.
|
|
* @function module:svgcanvas.SvgCanvas#setSvgString
|
|
* @param {string} xmlString - The SVG as XML text.
|
|
* @param {boolean} [preventUndo=false] - Indicates if we want to do the
|
|
* changes without adding them to the undo stack - e.g. for initializing a
|
|
* drawing on page load.
|
|
* @fires module:svgcanvas.SvgCanvas#event:setnonce
|
|
* @fires module:svgcanvas.SvgCanvas#event:unsetnonce
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {boolean} This function returns `false` if the set was
|
|
* unsuccessful, `true` otherwise.
|
|
*/
|
|
this.setSvgString = setSvgString;
|
|
|
|
/**
|
|
* This function imports the input SVG XML as a `<symbol>` in the `<defs>`, then adds a
|
|
* `<use>` to the current layer.
|
|
* @function module:svgcanvas.SvgCanvas#importSvgString
|
|
* @param {string} xmlString - The SVG as XML text.
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {null|Element} This function returns null if the import was unsuccessful, or the element otherwise.
|
|
* @todo
|
|
* - properly handle if namespace is introduced by imported content (must add to svgcontent
|
|
* and update all prefixes in the imported node)
|
|
* - properly handle recalculating dimensions, `recalculateDimensions()` doesn't handle
|
|
* arbitrary transform lists, but makes some assumptions about how the transform list
|
|
* was obtained
|
|
*/
|
|
this.importSvgString = importSvgString;
|
|
|
|
// Could deprecate, but besides external uses, their usage makes clear that
|
|
// canvas is a dependency for all of these
|
|
const dr = {
|
|
identifyLayers, createLayer, cloneLayer, deleteCurrentLayer,
|
|
setCurrentLayer, renameCurrentLayer, setCurrentLayerPosition,
|
|
setLayerVisibility, moveSelectedToLayer, mergeLayer, mergeAllLayers,
|
|
leaveContext, setContext
|
|
};
|
|
Object.entries(dr).forEach(([ prop, propVal ]) => {
|
|
canvas[prop] = propVal;
|
|
});
|
|
draw.init(
|
|
/**
|
|
* @implements {module:draw.DrawCanvasInit}
|
|
*/
|
|
{
|
|
pathActions,
|
|
getDataStorage,
|
|
getCurrentGroup() {
|
|
return currentGroup;
|
|
},
|
|
setCurrentGroup(cg) {
|
|
currentGroup = cg;
|
|
},
|
|
getSelectedElements,
|
|
getSVGContent,
|
|
undoMgr,
|
|
getCurrentDrawing,
|
|
clearSelection,
|
|
call,
|
|
addCommandToHistory,
|
|
/**
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
changeSVGContent() {
|
|
call('changed', [ svgcontent ]);
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Group: Document functions.
|
|
*/
|
|
|
|
/**
|
|
* Clears the current document. This is not an undoable action.
|
|
* @function module:svgcanvas.SvgCanvas#clear
|
|
* @fires module:svgcanvas.SvgCanvas#event:cleared
|
|
* @returns {void}
|
|
*/
|
|
this.clear = function () {
|
|
pathActions.clear();
|
|
|
|
clearSelection();
|
|
|
|
// clear the svgcontent node
|
|
canvas.clearSvgContentElement();
|
|
|
|
// create new document
|
|
canvas.current_drawing_ = new draw.Drawing(svgcontent);
|
|
|
|
// create empty first layer
|
|
canvas.createLayer('Layer 1');
|
|
|
|
// clear the undo stack
|
|
canvas.undoMgr.resetUndoStack();
|
|
|
|
// reset the selector manager
|
|
selectorManager.initGroup();
|
|
|
|
// reset the rubber band box
|
|
rubberBox = selectorManager.getRubberBandBox();
|
|
|
|
call('cleared');
|
|
};
|
|
|
|
// Alias function
|
|
this.linkControlPoints = pathActions.linkControlPoints;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getContentElem
|
|
* @returns {Element} The content DOM element
|
|
*/
|
|
this.getContentElem = function () { return svgcontent; };
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getRootElem
|
|
* @returns {SVGSVGElement} The root DOM element
|
|
*/
|
|
this.getRootElem = function () { return svgroot; };
|
|
|
|
elemInit(
|
|
/**
|
|
* @implements {module:elem-get-set.elemInit}
|
|
*/
|
|
{
|
|
addCommandToHistory,
|
|
getCurrentZoom,
|
|
getSVGContent,
|
|
getSelectedElements,
|
|
call,
|
|
changeSelectedAttributeNoUndoMethod,
|
|
getDOMDocument() { return svgdoc; },
|
|
getCanvas() { return canvas; },
|
|
getDataStorage,
|
|
setCanvas(key, value) { canvas[key] = value; },
|
|
setCurrentZoom(value) { currentZoom = value; },
|
|
setCurProperties(key, value) { curProperties[key] = value; },
|
|
getCurProperties(key) { return curProperties[key]; },
|
|
setCurShape(key, value) { curShape[key] = value; },
|
|
getCurText(key) { return curText[key]; },
|
|
setCurText(key, value) { curText[key] = value; }
|
|
}
|
|
);
|
|
|
|
/**
|
|
* @typedef {PlainObject} DimensionsAndZoom
|
|
* @property {Float} w Width
|
|
* @property {Float} h Height
|
|
* @property {Float} zoom Zoom
|
|
*/
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getResolution
|
|
* @returns {DimensionsAndZoom} The current dimensions and zoom level in an object
|
|
*/
|
|
const getResolution = this.getResolution = getResolutionMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getSnapToGrid
|
|
* @returns {boolean} The current snap to grid setting
|
|
*/
|
|
this.getSnapToGrid = function () { return curConfig.gridSnapping; };
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getVersion
|
|
* @returns {string} A string which describes the revision number of SvgCanvas.
|
|
*/
|
|
this.getVersion = function () {
|
|
return 'svgcanvas.js ($Rev$)';
|
|
};
|
|
|
|
/**
|
|
* Update interface strings with given values.
|
|
* @function module:svgcanvas.SvgCanvas#setUiStrings
|
|
* @param {module:path.uiStrings} strs - Object with strings (see the [locales API]{@link module:locale.LocaleStrings} and the [tutorial]{@tutorial LocaleDocs})
|
|
* @returns {void}
|
|
*/
|
|
this.setUiStrings = function (strs) {
|
|
Object.assign(uiStrings, strs.notification);
|
|
pathModule.setUiStrings(strs);
|
|
};
|
|
|
|
/**
|
|
* Update configuration options with given values.
|
|
* @function module:svgcanvas.SvgCanvas#setConfig
|
|
* @param {module:SVGEditor.Config} opts - Object with options
|
|
* @returns {void}
|
|
*/
|
|
this.setConfig = function (opts) {
|
|
Object.assign(curConfig, opts);
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getTitle
|
|
* @param {Element} [elem]
|
|
* @returns {string|void} the current group/SVG's title contents or
|
|
* `undefined` if no element is passed nd there are no selected elements.
|
|
*/
|
|
this.getTitle = getTitleMethod;
|
|
|
|
/**
|
|
* Sets the group/SVG's title content.
|
|
* @function module:svgcanvas.SvgCanvas#setGroupTitle
|
|
* @param {string} val
|
|
* @todo Combine this with `setDocumentTitle`
|
|
* @returns {void}
|
|
*/
|
|
this.setGroupTitle = setGroupTitleMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getDocumentTitle
|
|
* @returns {string|void} The current document title or an empty string if not found
|
|
*/
|
|
this.getDocumentTitle = function () {
|
|
return canvas.getTitle(svgcontent);
|
|
};
|
|
|
|
/**
|
|
* Adds/updates a title element for the document with the given name.
|
|
* This is an undoable action.
|
|
* @function module:svgcanvas.SvgCanvas#setDocumentTitle
|
|
* @param {string} newTitle - String with the new title
|
|
* @returns {void}
|
|
*/
|
|
this.setDocumentTitle = setDocumentTitleMethod;
|
|
|
|
/**
|
|
* Returns the editor's namespace URL, optionally adding it to the root element.
|
|
* @function module:svgcanvas.SvgCanvas#getEditorNS
|
|
* @param {boolean} [add] - Indicates whether or not to add the namespace value
|
|
* @returns {string} The editor's namespace URL
|
|
*/
|
|
this.getEditorNS = getEditorNSMethod;
|
|
|
|
/**
|
|
* Changes the document's dimensions to the given size.
|
|
* @function module:svgcanvas.SvgCanvas#setResolution
|
|
* @param {Float|"fit"} x - Number with the width of the new dimensions in user units.
|
|
* Can also be the string "fit" to indicate "fit to content".
|
|
* @param {Float} y - Number with the height of the new dimensions in user units.
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {boolean} Indicates if resolution change was successful.
|
|
* It will fail on "fit to content" option with no content to fit to.
|
|
*/
|
|
this.setResolution = setResolutionMethod;
|
|
|
|
/**
|
|
* @typedef {module:jQueryAttr.Attributes} module:svgcanvas.ElementPositionInCanvas
|
|
* @property {Float} x
|
|
* @property {Float} y
|
|
*/
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getOffset
|
|
* @returns {module:svgcanvas.ElementPositionInCanvas} An object with `x`, `y` values indicating the svgcontent element's
|
|
* position in the editor's canvas.
|
|
*/
|
|
this.getOffset = function () {
|
|
return { x: svgcontent.getAttribute('x'), y: svgcontent.getAttribute('y') };
|
|
};
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.ZoomAndBBox
|
|
* @property {Float} zoom
|
|
* @property {module:utilities.BBoxObject} bbox
|
|
*/
|
|
/**
|
|
* Sets the zoom level on the canvas-side based on the given value.
|
|
* @function module:svgcanvas.SvgCanvas#setBBoxZoom
|
|
* @param {"selection"|"canvas"|"content"|"layer"|module:SVGEditor.BBoxObjectWithFactor} val - Bounding box object to zoom to or string indicating zoom option. Note: the object value type is defined in `svg-editor.js`
|
|
* @param {Integer} editorW - The editor's workarea box's width
|
|
* @param {Integer} editorH - The editor's workarea box's height
|
|
* @returns {module:svgcanvas.ZoomAndBBox|void}
|
|
*/
|
|
this.setBBoxZoom = setBBoxZoomMethod;
|
|
|
|
/**
|
|
* The zoom level has changed. Supplies the new zoom level as a number (not percentage).
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_zoomChanged
|
|
* @type {Float}
|
|
*/
|
|
/**
|
|
* The bottom panel was updated.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate
|
|
* @type {PlainObject}
|
|
* @property {boolean} nofill Indicates fill is disabled
|
|
* @property {boolean} nostroke Indicates stroke is disabled
|
|
*/
|
|
/**
|
|
* The element selection has changed (elements were added/removed from selection).
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_selectedChanged
|
|
* @type {PlainObject}
|
|
* @property {Element[]} elems Array of the newly selected elements
|
|
* @property {Element|null} selectedElement The single selected element
|
|
* @property {boolean} multiselected Indicates whether one or more elements were selected
|
|
*/
|
|
/**
|
|
* Called when part of element is in process of changing, generally on
|
|
* mousemove actions like rotate, move, etc.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_elementTransition
|
|
* @type {PlainObject}
|
|
* @property {Element[]} elems Array of transitioning elements
|
|
*/
|
|
/**
|
|
* One or more elements were changed.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_elementChanged
|
|
* @type {PlainObject}
|
|
* @property {Element[]} elems Array of the affected elements
|
|
*/
|
|
/**
|
|
* Invoked as soon as the locale is ready.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_langReady
|
|
* @type {PlainObject}
|
|
* @property {string} lang The two-letter language code
|
|
* @property {module:SVGEditor.uiStrings} uiStrings
|
|
* @property {module:SVGEditor~ImportLocale} importLocale
|
|
*/
|
|
/**
|
|
* The language was changed. Two-letter code of the new language.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_langChanged
|
|
* @type {string}
|
|
*/
|
|
/**
|
|
* Means for an extension to add locale data. The two-letter language code.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_addLangData
|
|
* @type {PlainObject}
|
|
* @property {string} lang
|
|
* @property {module:SVGEditor~ImportLocale} importLocale
|
|
*/
|
|
/**
|
|
* Called when new image is created.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_onNewDocument
|
|
* @type {void}
|
|
*/
|
|
/**
|
|
* Called when sidepanel is resized or toggled.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_workareaResized
|
|
* @type {void}
|
|
*/
|
|
/**
|
|
* Called upon addition of the extension, or, if svgicons are set,
|
|
* after the icons are ready when extension SVG icons have loaded.
|
|
* @event module:svgcanvas.SvgCanvas#event:ext_callback
|
|
* @type {void}
|
|
*/
|
|
|
|
/**
|
|
* Sets the zoom to the given level.
|
|
* @function module:svgcanvas.SvgCanvas#setZoom
|
|
* @param {Float} zoomLevel - Float indicating the zoom level to change to
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_zoomChanged
|
|
* @returns {void}
|
|
*/
|
|
this.setZoom = setZoomMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getMode
|
|
* @returns {string} The current editor mode string
|
|
*/
|
|
this.getMode = function () {
|
|
return currentMode;
|
|
};
|
|
|
|
/**
|
|
* Sets the editor's mode to the given string.
|
|
* @function module:svgcanvas.SvgCanvas#setMode
|
|
* @param {string} name - String with the new mode to change to
|
|
* @returns {void}
|
|
*/
|
|
this.setMode = function (name) {
|
|
pathActions.clear(true);
|
|
textActions.clear();
|
|
curProperties = (selectedElements[0] && selectedElements[0].nodeName === 'text') ? curText : curShape;
|
|
currentMode = name;
|
|
};
|
|
|
|
/**
|
|
* Group: Element Styling.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.PaintOptions
|
|
* @property {"solidColor"} type
|
|
*/
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getColor
|
|
* @param {string} type
|
|
* @returns {string|module:svgcanvas.PaintOptions|Float|module:jGraduate~Paint} The current fill/stroke option
|
|
*/
|
|
this.getColor = function (type) {
|
|
return curProperties[type];
|
|
};
|
|
|
|
/**
|
|
* Change the current stroke/fill color/gradient value.
|
|
* @function module:svgcanvas.SvgCanvas#setColor
|
|
* @param {string} type - String indicating fill or stroke
|
|
* @param {string} val - The value to set the stroke attribute to
|
|
* @param {boolean} preventUndo - Boolean indicating whether or not this should be an undoable option
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setColor = setColorMethod;
|
|
|
|
/**
|
|
* Apply the current gradient to selected element's fill or stroke.
|
|
* @function module:svgcanvas.SvgCanvas#setGradient
|
|
* @param {"fill"|"stroke"} type - String indicating "fill" or "stroke" to apply to an element
|
|
* @returns {void}
|
|
*/
|
|
this.setGradient = setGradientMethod;
|
|
|
|
/**
|
|
* Set a color/gradient to a fill/stroke.
|
|
* @function module:svgcanvas.SvgCanvas#setPaint
|
|
* @param {"fill"|"stroke"} type - String with "fill" or "stroke"
|
|
* @param {module:jGraduate.jGraduatePaintOptions} paint - The jGraduate paint object to apply
|
|
* @returns {void}
|
|
*/
|
|
this.setPaint = setPaintMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#setStrokePaint
|
|
* @param {module:jGraduate~Paint} paint
|
|
* @returns {void}
|
|
*/
|
|
this.setStrokePaint = function (paint) {
|
|
this.setPaint('stroke', paint);
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#setFillPaint
|
|
* @param {module:jGraduate~Paint} paint
|
|
* @returns {void}
|
|
*/
|
|
this.setFillPaint = function (paint) {
|
|
this.setPaint('fill', paint);
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getStrokeWidth
|
|
* @returns {Float|string} The current stroke-width value
|
|
*/
|
|
this.getStrokeWidth = function () {
|
|
return curProperties.stroke_width;
|
|
};
|
|
|
|
/**
|
|
* Sets the stroke width for the current selected elements.
|
|
* When attempting to set a line's width to 0, this changes it to 1 instead.
|
|
* @function module:svgcanvas.SvgCanvas#setStrokeWidth
|
|
* @param {Float} val - A Float indicating the new stroke width value
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setStrokeWidth = setStrokeWidthMethod;
|
|
|
|
/**
|
|
* Set the given stroke-related attribute the given value for selected elements.
|
|
* @function module:svgcanvas.SvgCanvas#setStrokeAttr
|
|
* @param {string} attr - String with the attribute name
|
|
* @param {string|Float} val - String or number with the attribute value
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setStrokeAttr = setStrokeAttrMethod;
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.StyleOptions
|
|
* @property {string} fill
|
|
* @property {Float} fill_opacity
|
|
* @property {string} stroke
|
|
* @property {Float} stroke_width
|
|
* @property {string} stroke_dasharray
|
|
* @property {string} stroke_linejoin
|
|
* @property {string} stroke_linecap
|
|
* @property {Float} stroke_opacity
|
|
* @property {Float} opacity
|
|
*/
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getStyle
|
|
* @returns {module:svgcanvas.StyleOptions} current style options
|
|
*/
|
|
this.getStyle = function () {
|
|
return curShape;
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getOpacity
|
|
* @returns {Float} the current opacity
|
|
*/
|
|
this.getOpacity = getOpacity;
|
|
|
|
/**
|
|
* Sets the given opacity on the current selected elements.
|
|
* @function module:svgcanvas.SvgCanvas#setOpacity
|
|
* @param {string} val
|
|
* @returns {void}
|
|
*/
|
|
this.setOpacity = function (val) {
|
|
curShape.opacity = val;
|
|
changeSelectedAttribute('opacity', val);
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getFillOpacity
|
|
* @returns {Float} the current fill opacity
|
|
*/
|
|
this.getFillOpacity = function () {
|
|
return curShape.fill_opacity;
|
|
};
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getStrokeOpacity
|
|
* @returns {string} the current stroke opacity
|
|
*/
|
|
this.getStrokeOpacity = function () {
|
|
return curShape.stroke_opacity;
|
|
};
|
|
|
|
/**
|
|
* Sets the current fill/stroke opacity.
|
|
* @function module:svgcanvas.SvgCanvas#setPaintOpacity
|
|
* @param {string} type - String with "fill" or "stroke"
|
|
* @param {Float} val - Float with the new opacity value
|
|
* @param {boolean} preventUndo - Indicates whether or not this should be an undoable action
|
|
* @returns {void}
|
|
*/
|
|
this.setPaintOpacity = function (type, val, preventUndo) {
|
|
curShape[type + '_opacity'] = val;
|
|
if (!preventUndo) {
|
|
changeSelectedAttribute(type + '-opacity', val);
|
|
} else {
|
|
changeSelectedAttributeNoUndo(type + '-opacity', val);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Gets the current fill/stroke opacity.
|
|
* @function module:svgcanvas.SvgCanvas#getPaintOpacity
|
|
* @param {"fill"|"stroke"} type - String with "fill" or "stroke"
|
|
* @returns {Float} Fill/stroke opacity
|
|
*/
|
|
this.getPaintOpacity = function (type) {
|
|
return type === 'fill' ? this.getFillOpacity() : this.getStrokeOpacity();
|
|
};
|
|
|
|
/**
|
|
* Gets the `stdDeviation` blur value of the given element.
|
|
* @function module:svgcanvas.SvgCanvas#getBlur
|
|
* @param {Element} elem - The element to check the blur value for
|
|
* @returns {string} stdDeviation blur attribute value
|
|
*/
|
|
this.getBlur = function (elem) {
|
|
let val = 0;
|
|
// const elem = selectedElements[0];
|
|
|
|
if (elem) {
|
|
const filterUrl = elem.getAttribute('filter');
|
|
if (filterUrl) {
|
|
const blur = getElem(elem.id + '_blur');
|
|
if (blur) {
|
|
val = blur.firstChild.getAttribute('stdDeviation');
|
|
}
|
|
}
|
|
}
|
|
return val;
|
|
};
|
|
|
|
(function () {
|
|
let curCommand = null;
|
|
let filter = null;
|
|
let filterHidden = false;
|
|
|
|
blurInit(
|
|
/**
|
|
* @implements {module:elem-get-set.elemInit}
|
|
*/
|
|
{
|
|
getCanvas() { return canvas; },
|
|
getCurCommand() { return curCommand; },
|
|
setCurCommand(value) { curCommand = value; },
|
|
getFilter() { return filter; },
|
|
setFilter(value) { filter = value; },
|
|
getFilterHidden() { return filterHidden; },
|
|
setFilterHidden(value) { filterHidden = value; },
|
|
changeSelectedAttributeNoUndoMethod,
|
|
changeSelectedAttributeMethod,
|
|
isWebkit,
|
|
addCommandToHistory,
|
|
getSelectedElements
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Sets the `stdDeviation` blur value on the selected element without being undoable.
|
|
* @function module:svgcanvas.SvgCanvas#setBlurNoUndo
|
|
* @param {Float} val - The new `stdDeviation` value
|
|
* @returns {void}
|
|
*/
|
|
canvas.setBlurNoUndo = setBlurNoUndo;
|
|
|
|
/**
|
|
* Sets the `x`, `y`, `width`, `height` values of the filter element in order to
|
|
* make the blur not be clipped. Removes them if not neeeded.
|
|
* @function module:svgcanvas.SvgCanvas#setBlurOffsets
|
|
* @param {Element} filterElem - The filter DOM element to update
|
|
* @param {Float} stdDev - The standard deviation value on which to base the offset size
|
|
* @returns {void}
|
|
*/
|
|
canvas.setBlurOffsets = setBlurOffsets;
|
|
|
|
/**
|
|
* Adds/updates the blur filter to the selected element.
|
|
* @function module:svgcanvas.SvgCanvas#setBlur
|
|
* @param {Float} val - Float with the new `stdDeviation` blur value
|
|
* @param {boolean} complete - Whether or not the action should be completed (to add to the undo manager)
|
|
* @returns {void}
|
|
*/
|
|
canvas.setBlur = setBlur;
|
|
}());
|
|
|
|
/**
|
|
* Check whether selected element is bold or not.
|
|
* @function module:svgcanvas.SvgCanvas#getBold
|
|
* @returns {boolean} Indicates whether or not element is bold
|
|
*/
|
|
this.getBold = getBoldMethod;
|
|
|
|
/**
|
|
* Make the selected element bold or normal.
|
|
* @function module:svgcanvas.SvgCanvas#setBold
|
|
* @param {boolean} b - Indicates bold (`true`) or normal (`false`)
|
|
* @returns {void}
|
|
*/
|
|
this.setBold = setBoldMethod;
|
|
|
|
/**
|
|
* Check whether selected element is in italics or not.
|
|
* @function module:svgcanvas.SvgCanvas#getItalic
|
|
* @returns {boolean} Indicates whether or not element is italic
|
|
*/
|
|
this.getItalic = getItalicMethod;
|
|
|
|
/**
|
|
* Make the selected element italic or normal.
|
|
* @function module:svgcanvas.SvgCanvas#setItalic
|
|
* @param {boolean} i - Indicates italic (`true`) or normal (`false`)
|
|
* @returns {void}
|
|
*/
|
|
this.setItalic = setItalicMethod;
|
|
|
|
/**
|
|
* Set the new text anchor.
|
|
* @function module:svgcanvas.SvgCanvas#setTextAnchor
|
|
* @param {string} textAnchor - The value of the text anchor (start, middle or end)
|
|
* @returns {void}
|
|
*/
|
|
this.setTextAnchor = setTextAnchorMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getFontFamily
|
|
* @returns {string} The current font family
|
|
*/
|
|
this.getFontFamily = getFontFamilyMethod;
|
|
|
|
/**
|
|
* Set the new font family.
|
|
* @function module:svgcanvas.SvgCanvas#setFontFamily
|
|
* @param {string} val - String with the new font family
|
|
* @returns {void}
|
|
*/
|
|
this.setFontFamily = setFontFamilyMethod;
|
|
|
|
/**
|
|
* Set the new font color.
|
|
* @function module:svgcanvas.SvgCanvas#setFontColor
|
|
* @param {string} val - String with the new font color
|
|
* @returns {void}
|
|
*/
|
|
this.setFontColor = setFontColorMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getFontColor
|
|
* @returns {string} The current font color
|
|
*/
|
|
this.getFontColor = getFontColorMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getFontSize
|
|
* @returns {Float} The current font size
|
|
*/
|
|
this.getFontSize = getFontSizeMethod;
|
|
|
|
/**
|
|
* Applies the given font size to the selected element.
|
|
* @function module:svgcanvas.SvgCanvas#setFontSize
|
|
* @param {Float} val - Float with the new font size
|
|
* @returns {void}
|
|
*/
|
|
this.setFontSize = setFontSizeMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#getText
|
|
* @returns {string} The current text (`textContent`) of the selected element
|
|
*/
|
|
this.getText = getTextMethod;
|
|
|
|
/**
|
|
* Updates the text element with the given string.
|
|
* @function module:svgcanvas.SvgCanvas#setTextContent
|
|
* @param {string} val - String with the new text
|
|
* @returns {void}
|
|
*/
|
|
this.setTextContent = setTextContentMethod;
|
|
|
|
/**
|
|
* Sets the new image URL for the selected image element. Updates its size if
|
|
* a new URL is given.
|
|
* @function module:svgcanvas.SvgCanvas#setImageURL
|
|
* @param {string} val - String with the image URL/path
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setImageURL = setImageURLMethod;
|
|
|
|
/**
|
|
* Sets the new link URL for the selected anchor element.
|
|
* @function module:svgcanvas.SvgCanvas#setLinkURL
|
|
* @param {string} val - String with the link URL/path
|
|
* @returns {void}
|
|
*/
|
|
this.setLinkURL = setLinkURLMethod;
|
|
|
|
/**
|
|
* Sets the `rx` and `ry` values to the selected `rect` element
|
|
* to change its corner radius.
|
|
* @function module:svgcanvas.SvgCanvas#setRectRadius
|
|
* @param {string|Float} val - The new radius
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.setRectRadius = setRectRadiusMethod;
|
|
|
|
/**
|
|
* Wraps the selected element(s) in an anchor element or converts group to one.
|
|
* @function module:svgcanvas.SvgCanvas#makeHyperlink
|
|
* @param {string} url
|
|
* @returns {void}
|
|
*/
|
|
this.makeHyperlink = makeHyperlinkMethod;
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#removeHyperlink
|
|
* @returns {void}
|
|
*/
|
|
this.removeHyperlink = removeHyperlinkMethod;
|
|
|
|
/**
|
|
* Group: Element manipulation.
|
|
*/
|
|
|
|
/**
|
|
* Sets the new segment type to the selected segment(s).
|
|
* @function module:svgcanvas.SvgCanvas#setSegType
|
|
* @param {Integer} newType - New segment type. See {@link https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg} for list
|
|
* @returns {void}
|
|
*/
|
|
this.setSegType = setSegTypeMethod;
|
|
|
|
/**
|
|
* Convert selected element to a path, or get the BBox of an element-as-path.
|
|
* @function module:svgcanvas.SvgCanvas#convertToPath
|
|
* @todo (codedread): Remove the getBBox argument and split this function into two.
|
|
* @param {Element} elem - The DOM element to be converted
|
|
* @param {boolean} getBBox - Boolean on whether or not to only return the path's BBox
|
|
* @returns {void|DOMRect|false|SVGPathElement|null} If the getBBox flag is true, the resulting path's bounding box object.
|
|
* Otherwise the resulting path element is returned.
|
|
*/
|
|
this.convertToPath = function (elem, getBBox) {
|
|
if (isNullish(elem)) {
|
|
const elems = selectedElements;
|
|
elems.forEach(function(el){
|
|
if (el) { canvas.convertToPath(el); }
|
|
});
|
|
return undefined;
|
|
}
|
|
if (getBBox) {
|
|
return getBBoxOfElementAsPath(elem, addSVGElementFromJson, pathActions);
|
|
}
|
|
// TODO: Why is this applying attributes from curShape, then inside utilities.convertToPath it's pulling addition attributes from elem?
|
|
// TODO: If convertToPath is called with one elem, curShape and elem are probably the same; but calling with multiple is a bug or cool feature.
|
|
const attrs = {
|
|
fill: curShape.fill,
|
|
'fill-opacity': curShape.fill_opacity,
|
|
stroke: curShape.stroke,
|
|
'stroke-width': curShape.stroke_width,
|
|
'stroke-dasharray': curShape.stroke_dasharray,
|
|
'stroke-linejoin': curShape.stroke_linejoin,
|
|
'stroke-linecap': curShape.stroke_linecap,
|
|
'stroke-opacity': curShape.stroke_opacity,
|
|
opacity: curShape.opacity,
|
|
visibility: 'hidden'
|
|
};
|
|
return convertToPath(
|
|
elem, attrs, addSVGElementFromJson, pathActions,
|
|
clearSelection, addToSelection, hstry, addCommandToHistory
|
|
);
|
|
};
|
|
|
|
/**
|
|
* This function makes the changes to the elements. It does not add the change
|
|
* to the history stack.
|
|
* @param {string} attr - Attribute name
|
|
* @param {string|Float} newValue - String or number with the new attribute value
|
|
* @param {Element[]} elems - The DOM elements to apply the change to
|
|
* @returns {void}
|
|
*/
|
|
const changeSelectedAttributeNoUndo = changeSelectedAttributeNoUndoMethod;
|
|
|
|
/**
|
|
* Change the given/selected element and add the original value to the history stack.
|
|
* If you want to change all `selectedElements`, ignore the `elems` argument.
|
|
* If you want to change only a subset of `selectedElements`, then send the
|
|
* subset to this function in the `elems` argument.
|
|
* @function module:svgcanvas.SvgCanvas#changeSelectedAttribute
|
|
* @param {string} attr - String with the attribute name
|
|
* @param {string|Float} val - String or number with the new attribute value
|
|
* @param {Element[]} elems - The DOM elements to apply the change to
|
|
* @returns {void}
|
|
*/
|
|
const changeSelectedAttribute = this.changeSelectedAttribute = changeSelectedAttributeMethod;
|
|
|
|
/**
|
|
* Initialize from select-elem.js.
|
|
* Send in an object implementing the ElementContainer interface (see select-elem.js).
|
|
*/
|
|
selectedElemInit(
|
|
/**
|
|
* @implements {module:selected-elem.elementContext}
|
|
*/
|
|
{
|
|
getSelectedElements,
|
|
addCommandToHistory,
|
|
getJsonFromSvgElement,
|
|
addSVGElementFromJson,
|
|
changeSelectedAttribute,
|
|
flashStorage,
|
|
call,
|
|
getIntersectionList,
|
|
setCurBBoxes(value) { curBBoxes = value; },
|
|
getSVGRoot,
|
|
gettingSelectorManager() { return selectorManager; },
|
|
getCurrentZoom,
|
|
getDrawing() { return getCurrentDrawing(); },
|
|
getCurrentGroup() { return currentGroup; },
|
|
addToSelection,
|
|
getContentW() { return canvas.contentW; },
|
|
getContentH() { return canvas.contentH; },
|
|
getClipboardID() { return CLIPBOARD_ID; },
|
|
getDOMDocument() { return svgdoc; },
|
|
clearSelection,
|
|
getNextId,
|
|
selectOnly,
|
|
uniquifyElems,
|
|
setUseData,
|
|
convertGradients,
|
|
getSVGContent,
|
|
getCanvas() { return canvas; },
|
|
getDataStorage,
|
|
getVisElems() { return visElems; }
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Removes all selected elements from the DOM and adds the change to the
|
|
* history stack.
|
|
* @function module:svgcanvas.SvgCanvas#deleteSelectedElements
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.deleteSelectedElements = deleteSelectedElements;
|
|
|
|
/**
|
|
* Removes all selected elements from the DOM and adds the change to the
|
|
* history stack. Remembers removed elements on the clipboard.
|
|
* @function module:svgcanvas.SvgCanvas#cutSelectedElements
|
|
* @returns {void}
|
|
*/
|
|
this.cutSelectedElements = function () {
|
|
canvas.copySelectedElements();
|
|
canvas.deleteSelectedElements();
|
|
};
|
|
|
|
const CLIPBOARD_ID = 'svgedit_clipboard';
|
|
|
|
/**
|
|
* Flash the clipboard data momentarily on localStorage so all tabs can see.
|
|
* @returns {void}
|
|
*/
|
|
function flashStorage() {
|
|
const data = sessionStorage.getItem(CLIPBOARD_ID);
|
|
localStorage.setItem(CLIPBOARD_ID, data);
|
|
setTimeout(function () {
|
|
localStorage.removeItem(CLIPBOARD_ID);
|
|
}, 1);
|
|
}
|
|
|
|
/**
|
|
* Transfers sessionStorage from one tab to another.
|
|
* @param {!Event} ev Storage event.
|
|
* @returns {void}
|
|
*/
|
|
function storageChange(ev) {
|
|
if (!ev.newValue) return; // This is a call from removeItem.
|
|
if (ev.key === CLIPBOARD_ID + '_startup') {
|
|
// Another tab asked for our sessionStorage.
|
|
localStorage.removeItem(CLIPBOARD_ID + '_startup');
|
|
flashStorage();
|
|
} else if (ev.key === CLIPBOARD_ID) {
|
|
// Another tab sent data.
|
|
sessionStorage.setItem(CLIPBOARD_ID, ev.newValue);
|
|
}
|
|
}
|
|
|
|
// Listen for changes to localStorage.
|
|
window.addEventListener('storage', storageChange, false);
|
|
// Ask other tabs for sessionStorage (this is ONLY to trigger event).
|
|
localStorage.setItem(CLIPBOARD_ID + '_startup', Math.random());
|
|
/**
|
|
* Remembers the current selected elements on the clipboard.
|
|
* @function module:svgcanvas.SvgCanvas#copySelectedElements
|
|
* @returns {void}
|
|
*/
|
|
this.copySelectedElements = copySelectedElements;
|
|
|
|
/**
|
|
* @function pasteInit Initialize from paste-elem.js.
|
|
* @returns {void}
|
|
* paste element functionality
|
|
*/
|
|
pasteInit(
|
|
/**
|
|
* @implements {module:event.eventContext_}
|
|
*/
|
|
{
|
|
getCanvas() { return canvas; },
|
|
getClipBoardID() { return CLIPBOARD_ID; },
|
|
getLastClickPoint(key) { return lastClickPoint[key]; },
|
|
addCommandToHistory,
|
|
restoreRefElems
|
|
}
|
|
);
|
|
|
|
/**
|
|
* @function module:svgcanvas.SvgCanvas#pasteElements
|
|
* @param {"in_place"|"point"|void} type
|
|
* @param {Integer|void} x Expected if type is "point"
|
|
* @param {Integer|void} y Expected if type is "point"
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_IDsUpdated
|
|
* @returns {void}
|
|
*/
|
|
this.pasteElements = pasteElementsMethod;
|
|
|
|
/**
|
|
* Wraps all the selected elements in a group (`g`) element.
|
|
* @function module:svgcanvas.SvgCanvas#groupSelectedElements
|
|
* @param {"a"|"g"} [type="g"] - type of element to group into, defaults to `<g>`
|
|
* @param {string} [urlArg]
|
|
* @returns {void}
|
|
*/
|
|
this.groupSelectedElements = groupSelectedElements;
|
|
|
|
/**
|
|
* Pushes all appropriate parent group properties down to its children, then
|
|
* removes them from the group.
|
|
* @function module:svgcanvas.SvgCanvas#pushGroupProperties
|
|
* @param {SVGAElement|SVGGElement} g
|
|
* @param {boolean} undoable
|
|
* @returns {BatchCommand|void}
|
|
*/
|
|
this.pushGroupProperties = pushGroupProperty;
|
|
|
|
/**
|
|
* Unwraps all the elements in a selected group (`g`) element. This requires
|
|
* significant recalculations to apply group's transforms, etc. to its children.
|
|
* @function module:svgcanvas.SvgCanvas#ungroupSelectedElement
|
|
* @returns {void}
|
|
*/
|
|
this.ungroupSelectedElement = ungroupSelectedElement;
|
|
|
|
/**
|
|
* Repositions the selected element to the bottom in the DOM to appear on top of
|
|
* other elements.
|
|
* @function module:svgcanvas.SvgCanvas#moveToTopSelectedElement
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.moveToTopSelectedElement = moveToTopSelectedElem;
|
|
|
|
/**
|
|
* Repositions the selected element to the top in the DOM to appear under
|
|
* other elements.
|
|
* @function module:svgcanvas.SvgCanvas#moveToBottomSelectedElement
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.moveToBottomSelectedElement = moveToBottomSelectedElem;
|
|
|
|
/**
|
|
* Moves the select element up or down the stack, based on the visibly
|
|
* intersecting elements.
|
|
* @function module:svgcanvas.SvgCanvas#moveUpDownSelected
|
|
* @param {"Up"|"Down"} dir - String that's either 'Up' or 'Down'
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {void}
|
|
*/
|
|
this.moveUpDownSelected = moveUpDownSelected;
|
|
|
|
/**
|
|
* Moves selected elements on the X/Y axis.
|
|
* @function module:svgcanvas.SvgCanvas#moveSelectedElements
|
|
* @param {Float} dx - Float with the distance to move on the x-axis
|
|
* @param {Float} dy - Float with the distance to move on the y-axis
|
|
* @param {boolean} undoable - Boolean indicating whether or not the action should be undoable
|
|
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
* @returns {BatchCommand|void} Batch command for the move
|
|
*/
|
|
this.moveSelectedElements = moveSelectedElements;
|
|
|
|
/**
|
|
* Create deep DOM copies (clones) of all selected elements and move them slightly
|
|
* from their originals.
|
|
* @function module:svgcanvas.SvgCanvas#cloneSelectedElements
|
|
* @param {Float} x Float with the distance to move on the x-axis
|
|
* @param {Float} y Float with the distance to move on the y-axis
|
|
* @returns {void}
|
|
*/
|
|
this.cloneSelectedElements = cloneSelectedElements;
|
|
|
|
/**
|
|
* Aligns selected elements.
|
|
* @function module:svgcanvas.SvgCanvas#alignSelectedElements
|
|
* @param {string} type - String with single character indicating the alignment type
|
|
* @param {"selected"|"largest"|"smallest"|"page"} relativeTo
|
|
* @returns {void}
|
|
*/
|
|
this.alignSelectedElements = alignSelectedElements;
|
|
|
|
/**
|
|
* Group: Additional editor tools.
|
|
*/
|
|
|
|
/**
|
|
* @name module:svgcanvas.SvgCanvas#contentW
|
|
* @type {Float}
|
|
*/
|
|
this.contentW = getResolution().w;
|
|
/**
|
|
* @name module:svgcanvas.SvgCanvas#contentH
|
|
* @type {Float}
|
|
*/
|
|
this.contentH = getResolution().h;
|
|
|
|
/**
|
|
* @typedef {PlainObject} module:svgcanvas.CanvasInfo
|
|
* @property {Float} x - The canvas' new x coordinate
|
|
* @property {Float} y - The canvas' new y coordinate
|
|
* @property {string} oldX - The canvas' old x coordinate
|
|
* @property {string} oldY - The canvas' old y coordinate
|
|
* @property {Float} d_x - The x position difference
|
|
* @property {Float} d_y - The y position difference
|
|
*/
|
|
|
|
/**
|
|
* Updates the editor canvas width/height/position after a zoom has occurred.
|
|
* @function module:svgcanvas.SvgCanvas#updateCanvas
|
|
* @param {Float} w - Float with the new width
|
|
* @param {Float} h - Float with the new height
|
|
* @fires module:svgcanvas.SvgCanvas#event:ext_canvasUpdated
|
|
* @returns {module:svgcanvas.CanvasInfo}
|
|
*/
|
|
this.updateCanvas = updateCanvas;
|
|
|
|
/**
|
|
* Set the background of the editor (NOT the actual document).
|
|
* @function module:svgcanvas.SvgCanvas#setBackground
|
|
* @param {string} color - String with fill color to apply
|
|
* @param {string} url - URL or path to image to use
|
|
* @returns {void}
|
|
*/
|
|
this.setBackground = setBackgroundMethod;
|
|
|
|
/**
|
|
* Select the next/previous element within the current layer.
|
|
* @function module:svgcanvas.SvgCanvas#cycleElement
|
|
* @param {boolean} next - true = next and false = previous element
|
|
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
* @returns {void}
|
|
*/
|
|
this.cycleElement = cycleElement;
|
|
|
|
this.clear();
|
|
|
|
/**
|
|
* @interface module:svgcanvas.PrivateMethods
|
|
* @type {PlainObject}
|
|
* @property {module:svgcanvas~addCommandToHistory} addCommandToHistory
|
|
* @property {module:history.HistoryCommand} BatchCommand
|
|
* @property {module:history.HistoryCommand} ChangeElementCommand
|
|
* @property {module:utilities.decode64} decode64
|
|
* @property {module:utilities.dropXMLInternalSubset} dropXMLInternalSubset
|
|
* @property {module:utilities.encode64} encode64
|
|
* @property {module:svgcanvas~ffClone} ffClone
|
|
* @property {module:svgcanvas~findDuplicateGradient} findDuplicateGradient
|
|
* @property {module:utilities.getPathBBox} getPathBBox
|
|
* @property {module:units.getTypeMap} getTypeMap
|
|
* @property {module:draw.identifyLayers} identifyLayers
|
|
* @property {module:history.HistoryCommand} InsertElementCommand
|
|
* @property {module:browser.isChrome} isChrome
|
|
* @property {module:math.isIdentity} isIdentity
|
|
* @property {module:svgcanvas~logMatrix} logMatrix
|
|
* @property {module:history.HistoryCommand} MoveElementCommand
|
|
* @property {module:namespaces.NS} NS
|
|
* @property {module:utilities.preventClickDefault} preventClickDefault
|
|
* @property {module:history.HistoryCommand} RemoveElementCommand
|
|
* @property {module:SVGTransformList.SVGEditTransformList} SVGEditTransformList
|
|
* @property {module:utilities.text2xml} text2xml
|
|
* @property {module:math.transformBox} transformBox
|
|
* @property {module:math.transformPoint} transformPoint
|
|
* @property {module:utilities.walkTree} walkTree
|
|
*/
|
|
/**
|
|
* @deprecated getPrivateMethods
|
|
* Since all methods are/should be public somehow, this function should be removed;
|
|
* we might require `import` in place of this in the future once ES6 Modules
|
|
* widespread
|
|
|
|
* Being able to access private methods publicly seems wrong somehow,
|
|
* but currently appears to be the best way to allow testing and provide
|
|
* access to them to plugins.
|
|
* @function module:svgcanvas.SvgCanvas#getPrivateMethods
|
|
* @returns {module:svgcanvas.PrivateMethods}
|
|
*/
|
|
this.getPrivateMethods = function () {
|
|
return {
|
|
addCommandToHistory,
|
|
BatchCommand,
|
|
ChangeElementCommand,
|
|
decode64,
|
|
dropXMLInternalSubset,
|
|
encode64,
|
|
ffClone,
|
|
findDefs,
|
|
findDuplicateGradient,
|
|
getElem,
|
|
getPathBBox,
|
|
getTypeMap,
|
|
getUrlFromAttr,
|
|
identifyLayers: draw.identifyLayers,
|
|
InsertElementCommand,
|
|
isChrome,
|
|
isIdentity,
|
|
logMatrix,
|
|
MoveElementCommand,
|
|
NS,
|
|
preventClickDefault,
|
|
RemoveElementCommand,
|
|
SVGEditTransformList,
|
|
text2xml,
|
|
transformBox,
|
|
transformPoint,
|
|
walkTree
|
|
};
|
|
};
|
|
} // End constructor
|
|
} // End class
|
|
|
|
// attach utilities function to the class that are used by SvgEdit so
|
|
// we can avoid using the whole utilities.js file in svgEdit.js
|
|
SvgCanvas.isNullish = isNullish;
|
|
SvgCanvas.encode64 = encode64;
|
|
SvgCanvas.decode64 = decode64;
|
|
SvgCanvas.$id = $id;
|
|
SvgCanvas.$qq = $qq;
|
|
SvgCanvas.$qa = $qa;
|
|
SvgCanvas.mergeDeep = mergeDeep;
|
|
SvgCanvas.getClosest = getClosest;
|
|
SvgCanvas.getParents = getParents;
|
|
SvgCanvas.blankPageObjectURL = blankPageObjectURL;
|
|
|
|
export default SvgCanvas;
|