From 77646cd14e53d22ecfb0b6b212f2fed541eb1561 Mon Sep 17 00:00:00 2001 From: JFH <20402845+jfhenon@users.noreply.github.com> Date: Sat, 22 Jan 2022 21:25:49 +0100 Subject: [PATCH] Refactor2 (#712) * review svg-exec * review selection.js elemgetset and selectedelems * Update svgcanvas.js --- src/svgcanvas/elem-get-set.js | 124 ++-- src/svgcanvas/selected-elem.js | 540 +++++++++------ src/svgcanvas/selection.js | 29 +- src/svgcanvas/svg-exec.js | 760 +++++++++++++-------- src/svgcanvas/svgcanvas.js | 1154 +++++++++++++++++++++----------- 5 files changed, 1680 insertions(+), 927 deletions(-) diff --git a/src/svgcanvas/elem-get-set.js b/src/svgcanvas/elem-get-set.js index 4a397d43..6600a648 100644 --- a/src/svgcanvas/elem-get-set.js +++ b/src/svgcanvas/elem-get-set.js @@ -24,13 +24,53 @@ let svgCanvas = null */ export const init = (canvas) => { svgCanvas = canvas + svgCanvas.getBold = getBoldMethod // Check whether selected element is bold or not. + svgCanvas.setBold = setBoldMethod // Make the selected element bold or normal. + svgCanvas.getItalic = getItalicMethod // Check whether selected element is in italics or not. + svgCanvas.setItalic = setItalicMethod // Make the selected element italic or normal. + svgCanvas.hasTextDecoration = hasTextDecorationMethod // Check whether the selected element has the given text decoration or not. + svgCanvas.addTextDecoration = addTextDecorationMethod // Adds the given value to the text decoration + svgCanvas.removeTextDecoration = removeTextDecorationMethod // Removes the given value from the text decoration + svgCanvas.setTextAnchor = setTextAnchorMethod // Set the new text anchor. + svgCanvas.setLetterSpacing = setLetterSpacingMethod // Set the new letter spacing. + svgCanvas.setWordSpacing = setWordSpacingMethod // Set the new word spacing. + svgCanvas.setTextLength = setTextLengthMethod // Set the new text length. + svgCanvas.setLengthAdjust = setLengthAdjustMethod // Set the new length adjust. + svgCanvas.getFontFamily = getFontFamilyMethod // The current font family + svgCanvas.setFontFamily = setFontFamilyMethod // Set the new font family. + svgCanvas.setFontColor = setFontColorMethod // Set the new font color. + svgCanvas.getFontColor = getFontColorMethod // The current font color + svgCanvas.getFontSize = getFontSizeMethod // The current font size + svgCanvas.setFontSize = setFontSizeMethod // Applies the given font size to the selected element. + svgCanvas.getText = getTextMethod // current text (`textContent`) of the selected element + svgCanvas.setTextContent = setTextContentMethod // Updates the text element with the given string. + svgCanvas.setImageURL = setImageURLMethod // Sets the new image URL for the selected image element + svgCanvas.setLinkURL = setLinkURLMethod // Sets the new link URL for the selected anchor element. + svgCanvas.setRectRadius = setRectRadiusMethod // Sets the `rx` and `ry` values to the selected `rect` element + svgCanvas.makeHyperlink = makeHyperlinkMethod // Wraps the selected element(s) in an anchor element or converts group to one. + svgCanvas.removeHyperlink = removeHyperlinkMethod + svgCanvas.setSegType = setSegTypeMethod // Sets the new segment type to the selected segment(s). + svgCanvas.setStrokeWidth = setStrokeWidthMethod // Sets the stroke width for the current selected elements. + svgCanvas.getResolution = getResolutionMethod // The current dimensions and zoom level in an object + svgCanvas.getTitle = getTitleMethod // the current group/SVG's title contents or `undefined` if no element + svgCanvas.setGroupTitle = setGroupTitleMethod // Sets the group/SVG's title content. + svgCanvas.setStrokeAttr = setStrokeAttrMethod // Set the given stroke-related attribute the given value for selected elements. + svgCanvas.setBackground = setBackgroundMethod // Set the background of the editor (NOT the actual document). + svgCanvas.setDocumentTitle = setDocumentTitleMethod // Adds/updates a title element for the document with the given name. + svgCanvas.getEditorNS = getEditorNSMethod // Returns the editor's namespace URL, optionally adding it to the root element. + svgCanvas.setResolution = setResolutionMethod // Changes the document's dimensions to the given size. + svgCanvas.setBBoxZoom = setBBoxZoomMethod // Sets the zoom level on the canvas-side based on the given value. + svgCanvas.setCurrentZoom = setZoomMethod // Sets the zoom to the given level. + svgCanvas.setColor = setColorMethod // Change the current stroke/fill color/gradien + svgCanvas.setGradient = setGradientMethod // Apply the current gradient to selected element's fill or stroke. + svgCanvas.setPaint = setPaintMethod // Set a color/gradient to a fill/stroke. } /** * @function module:elem-get-set.SvgCanvas#getResolution * @returns {DimensionsAndZoom} The current dimensions and zoom level in an object */ -export const getResolutionMethod = () => { +const getResolutionMethod = () => { const zoom = svgCanvas.getZoom() const w = svgCanvas.getSvgContent().getAttribute('width') / zoom const h = svgCanvas.getSvgContent().getAttribute('height') / zoom @@ -48,7 +88,7 @@ export const getResolutionMethod = () => { * @returns {string|void} the current group/SVG's title contents or * `undefined` if no element is passed nd there are no selected elements. */ -export const getTitleMethod = (elem) => { +const getTitleMethod = (elem) => { const selectedElements = svgCanvas.getSelectedElements() const dataStorage = svgCanvas.getDataStorage() elem = elem || selectedElements[0] @@ -74,7 +114,7 @@ export const getTitleMethod = (elem) => { * @todo Combine this with `setDocumentTitle` * @returns {void} */ -export const setGroupTitleMethod = (val) => { +const setGroupTitleMethod = (val) => { const { InsertElementCommand, RemoveElementCommand, ChangeElementCommand, BatchCommand @@ -119,7 +159,7 @@ export const setGroupTitleMethod = (val) => { * @param {string} newTitle - String with the new title * @returns {void} */ -export const setDocumentTitleMethod = (newTitle) => { +const setDocumentTitleMethod = (newTitle) => { const { ChangeElementCommand, BatchCommand } = svgCanvas.history const childs = svgCanvas.getSvgContent().childNodes let docTitle = false; let oldTitle = '' @@ -159,7 +199,7 @@ export const setDocumentTitleMethod = (newTitle) => { * @returns {boolean} Indicates if resolution change was successful. * It will fail on "fit to content" option with no content to fit to. */ -export const setResolutionMethod = (x, y) => { +const setResolutionMethod = (x, y) => { const { ChangeElementCommand, BatchCommand } = svgCanvas.history const zoom = svgCanvas.getZoom() const res = svgCanvas.getResolution() @@ -220,7 +260,7 @@ export const setResolutionMethod = (x, y) => { * @param {boolean} [add] - Indicates whether or not to add the namespace value * @returns {string} The editor's namespace URL */ -export const getEditorNSMethod = (add) => { +const getEditorNSMethod = (add) => { if (add) { svgCanvas.getSvgContent().setAttribute('xmlns:se', NS.SE) } @@ -240,7 +280,7 @@ export const getEditorNSMethod = (add) => { * @param {Integer} editorH - The editor's workarea box's height * @returns {module:elem-get-set.ZoomAndBBox|void} */ -export const setBBoxZoomMethod = (val, editorW, editorH) => { +const setBBoxZoomMethod = (val, editorW, editorH) => { const zoom = svgCanvas.getZoom() const selectedElements = svgCanvas.getSelectedElements() let spacer = 0.85 @@ -299,7 +339,7 @@ export const setBBoxZoomMethod = (val, editorW, editorH) => { * @fires module:elem-get-set.SvgCanvas#event:ext_zoomChanged * @returns {void} */ -export const setZoomMethod = (zoomLevel) => { +const setZoomMethod = (zoomLevel) => { const selectedElements = svgCanvas.getSelectedElements() const res = svgCanvas.getResolution() svgCanvas.getSvgContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel) @@ -317,11 +357,11 @@ export const setZoomMethod = (zoomLevel) => { * @function module:elem-get-set.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 +* @param {boolean} preventUndo - Boolean indicating whether or not svgCanvas should be an undoable option * @fires module:elem-get-set.SvgCanvas#event:changed * @returns {void} */ -export const setColorMethod = (type, val, preventUndo) => { +const setColorMethod = (type, val, preventUndo) => { const selectedElements = svgCanvas.getSelectedElements() svgCanvas.setCurShape(type, val) svgCanvas.setCurProperties(type + '_paint', { type: 'solidColor' }) @@ -367,7 +407,7 @@ export const setColorMethod = (type, val, preventUndo) => { * @param {"fill"|"stroke"} type - String indicating "fill" or "stroke" to apply to an element * @returns {void} */ -export const setGradientMethod = (type) => { +const setGradientMethod = (type) => { if (!svgCanvas.getCurProperties(type + '_paint') || svgCanvas.getCurProperties(type + '_paint').type === 'solidColor') { return } const canvas = svgCanvas @@ -394,7 +434,7 @@ export const setGradientMethod = (type) => { * @param {SVGGradientElement} grad - The gradient DOM element to compare to others * @returns {SVGGradientElement} The existing gradient if found, `null` if not */ -export const findDuplicateGradient = (grad) => { +const findDuplicateGradient = (grad) => { const defs = findDefs() const existingGrads = defs.querySelectorAll('linearGradient, radialGradient') let i = existingGrads.length @@ -468,7 +508,7 @@ export const findDuplicateGradient = (grad) => { * @param {module:jGraduate.jGraduatePaintOptions} paint - The jGraduate paint object to apply * @returns {void} */ -export const setPaintMethod = (type, paint) => { +const setPaintMethod = (type, paint) => { // make a copy const p = new jGraduate.Paint(paint) svgCanvas.setPaintOpacity(type, p.alpha / 100, true) @@ -494,7 +534,7 @@ export const setPaintMethod = (type, paint) => { * @fires module:elem-get-set.SvgCanvas#event:changed * @returns {void} */ -export const setStrokeWidthMethod = (val) => { +const setStrokeWidthMethod = (val) => { const selectedElements = svgCanvas.getSelectedElements() if (val === 0 && ['line', 'path'].includes(svgCanvas.getMode())) { svgCanvas.setStrokeWidth(1) @@ -538,7 +578,7 @@ export const setStrokeWidthMethod = (val) => { * @fires module:elem-get-set.SvgCanvas#event:changed * @returns {void} */ -export const setStrokeAttrMethod = (attr, val) => { +const setStrokeAttrMethod = (attr, val) => { const selectedElements = svgCanvas.getSelectedElements() svgCanvas.setCurShape(attr.replace('-', '_'), val) const elems = [] @@ -564,7 +604,7 @@ export const setStrokeAttrMethod = (attr, val) => { * @function module:svgcanvas.SvgCanvas#getBold * @returns {boolean} Indicates whether or not element is bold */ -export const getBoldMethod = () => { +const getBoldMethod = () => { const selectedElements = svgCanvas.getSelectedElements() // should only have one element selected const selected = selectedElements[0] @@ -581,7 +621,7 @@ export const getBoldMethod = () => { * @param {boolean} b - Indicates bold (`true`) or normal (`false`) * @returns {void} */ -export const setBoldMethod = (b) => { +const setBoldMethod = (b) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && @@ -597,7 +637,7 @@ export const setBoldMethod = (b) => { * Check whether selected element has the given text decoration value or not. * @returns {boolean} Indicates whether or not element has the text decoration value */ -export const hasTextDecorationMethod = (value) => { +const hasTextDecorationMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] @@ -614,7 +654,7 @@ export const hasTextDecorationMethod = (value) => { * @param value The text decoration value * @returns {void} */ -export const addTextDecorationMethod = (value) => { +const addTextDecorationMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -631,7 +671,7 @@ export const addTextDecorationMethod = (value) => { * @param value The text decoration value * @returns {void} */ -export const removeTextDecorationMethod = (value) => { +const removeTextDecorationMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -648,7 +688,7 @@ export const removeTextDecorationMethod = (value) => { * @function module:svgcanvas.SvgCanvas#getItalic * @returns {boolean} Indicates whether or not element is italic */ -export const getItalicMethod = () => { +const getItalicMethod = () => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -663,7 +703,7 @@ export const getItalicMethod = () => { * @param {boolean} i - Indicates italic (`true`) or normal (`false`) * @returns {void} */ -export const setItalicMethod = (i) => { +const setItalicMethod = (i) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -679,7 +719,7 @@ export const setItalicMethod = (i) => { * @param {string} value - The text anchor value (start, middle or end) * @returns {void} */ -export const setTextAnchorMethod = (value) => { +const setTextAnchorMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -695,7 +735,7 @@ export const setTextAnchorMethod = (value) => { * @param {string} value - The letter spacing value * @returns {void} */ -export const setLetterSpacingMethod = (value) => { +const setLetterSpacingMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -711,7 +751,7 @@ export const setLetterSpacingMethod = (value) => { * @param {string} value - The word spacing value * @returns {void} */ -export const setWordSpacingMethod = (value) => { +const setWordSpacingMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -727,7 +767,7 @@ export const setWordSpacingMethod = (value) => { * @param {string} value - The text length value * @returns {void} */ -export const setTextLengthMethod = (value) => { +const setTextLengthMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -743,7 +783,7 @@ export const setTextLengthMethod = (value) => { * @param {string} value - The length adjust value * @returns {void} */ -export const setLengthAdjustMethod = (value) => { +const setLengthAdjustMethod = (value) => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] if (selected?.tagName === 'text' && !selectedElements[1]) { @@ -758,7 +798,7 @@ export const setLengthAdjustMethod = (value) => { * @function module:svgcanvas.SvgCanvas#getFontFamily * @returns {string} The current font family */ -export const getFontFamilyMethod = () => { +const getFontFamilyMethod = () => { return svgCanvas.getCurText('font_family') } @@ -768,7 +808,7 @@ export const getFontFamilyMethod = () => { * @param {string} val - String with the new font family * @returns {void} */ -export const setFontFamilyMethod = (val) => { +const setFontFamilyMethod = (val) => { const selectedElements = svgCanvas.getSelectedElements() svgCanvas.setCurText('font_family', val) svgCanvas.changeSelectedAttribute('font-family', val) @@ -783,7 +823,7 @@ export const setFontFamilyMethod = (val) => { * @param {string} val - String with the new font color * @returns {void} */ -export const setFontColorMethod = (val) => { +const setFontColorMethod = (val) => { svgCanvas.setCurText('fill', val) svgCanvas.changeSelectedAttribute('fill', val) } @@ -792,7 +832,7 @@ export const setFontColorMethod = (val) => { * @function module:svgcanvas.SvgCanvas#getFontColor * @returns {string} The current font color */ -export const getFontColorMethod = () => { +const getFontColorMethod = () => { return svgCanvas.getCurText('fill') } @@ -800,7 +840,7 @@ export const getFontColorMethod = () => { * @function module:svgcanvas.SvgCanvas#getFontSize * @returns {Float} The current font size */ -export const getFontSizeMethod = () => { +const getFontSizeMethod = () => { return svgCanvas.getCurText('font_size') } @@ -810,7 +850,7 @@ export const getFontSizeMethod = () => { * @param {Float} val - Float with the new font size * @returns {void} */ -export const setFontSizeMethod = (val) => { +const setFontSizeMethod = (val) => { const selectedElements = svgCanvas.getSelectedElements() svgCanvas.setCurText('font_size', val) svgCanvas.changeSelectedAttribute('font-size', val) @@ -823,7 +863,7 @@ export const setFontSizeMethod = (val) => { * @function module:svgcanvas.SvgCanvas#getText * @returns {string} The current text (`textContent`) of the selected element */ -export const getTextMethod = () => { +const getTextMethod = () => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] return (selected) ? selected.textContent : '' @@ -835,7 +875,7 @@ export const getTextMethod = () => { * @param {string} val - String with the new text * @returns {void} */ -export const setTextContentMethod = (val) => { +const setTextContentMethod = (val) => { svgCanvas.changeSelectedAttribute('#text', val) svgCanvas.textActions.init(val) svgCanvas.textActions.setCursor() @@ -849,7 +889,7 @@ export const setTextContentMethod = (val) => { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ -export const setImageURLMethod = (val) => { +const setImageURLMethod = (val) => { const { ChangeElementCommand, BatchCommand } = svgCanvas.history const selectedElements = svgCanvas.getSelectedElements() const elem = selectedElements[0] @@ -898,7 +938,7 @@ export const setImageURLMethod = (val) => { * @param {string} val - String with the link URL/path * @returns {void} */ -export const setLinkURLMethod = (val) => { +const setLinkURLMethod = (val) => { const { ChangeElementCommand, BatchCommand } = svgCanvas.history const selectedElements = svgCanvas.getSelectedElements() let elem = selectedElements[0] @@ -935,7 +975,7 @@ export const setLinkURLMethod = (val) => { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ -export const setRectRadiusMethod = (val) => { +const setRectRadiusMethod = (val) => { const { ChangeElementCommand } = svgCanvas.history const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] @@ -956,7 +996,7 @@ export const setRectRadiusMethod = (val) => { * @param {string} url * @returns {void} */ -export const makeHyperlinkMethod = (url) => { +const makeHyperlinkMethod = (url) => { svgCanvas.groupSelectedElements('a', url) } @@ -964,7 +1004,7 @@ export const makeHyperlinkMethod = (url) => { * @function module:svgcanvas.SvgCanvas#removeHyperlink * @returns {void} */ -export const removeHyperlinkMethod = () => { +const removeHyperlinkMethod = () => { svgCanvas.ungroupSelectedElement() } @@ -978,7 +1018,7 @@ export const removeHyperlinkMethod = () => { * @param {Integer} newType - New segment type. See {@link https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg} for list * @returns {void} */ -export const setSegTypeMethod = (newType) => { +const setSegTypeMethod = (newType) => { svgCanvas.pathActions.setSegType(newType) } @@ -989,7 +1029,7 @@ export const setSegTypeMethod = (newType) => { * @param {string} url - URL or path to image to use * @returns {void} */ -export const setBackgroundMethod = (color, url) => { +const setBackgroundMethod = (color, url) => { const bg = getElement('canvasBackground') const border = bg.querySelector('rect') let bgImg = getElement('background_image') diff --git a/src/svgcanvas/selected-elem.js b/src/svgcanvas/selected-elem.js index b6c3c6d1..6070fc8e 100644 --- a/src/svgcanvas/selected-elem.js +++ b/src/svgcanvas/selected-elem.js @@ -10,32 +10,44 @@ import { NS } from './namespaces.js' import * as hstry from './history.js' import * as pathModule from './path.js' import { - getStrokedBBoxDefaultVisible, setHref, getElement, getHref, getVisibleElements, - findDefs, getRotationAngle, getRefElem, getBBox as utilsGetBBox, walkTreePost, assignAttributes, getFeGaussianBlur + getStrokedBBoxDefaultVisible, + setHref, + getElement, + getHref, + getVisibleElements, + findDefs, + getRotationAngle, + getRefElem, + getBBox as utilsGetBBox, + walkTreePost, + assignAttributes, + getFeGaussianBlur } from './utilities.js' import { - transformPoint, matrixMultiply, transformListToTransform + transformPoint, + matrixMultiply, + transformListToTransform } from './math.js' -import { - recalculateDimensions -} from './recalculate.js' -import { - isGecko -} from '../common/browser.js' // , supportsEditableText +import { recalculateDimensions } from './recalculate.js' +import { isGecko } from '../common/browser.js' // , supportsEditableText import { getParents } from '../editor/components/jgraduate/Util.js' const { - MoveElementCommand, BatchCommand, InsertElementCommand, RemoveElementCommand, ChangeElementCommand + MoveElementCommand, + BatchCommand, + InsertElementCommand, + RemoveElementCommand, + ChangeElementCommand } = hstry let svgCanvas = null /** -* @function module:selected-elem.init -* @param {module:selected-elem.elementContext} elementContext -* @returns {void} -*/ -export const init = (canvas) => { + * @function module:selected-elem.init + * @param {module:selected-elem.elementContext} elementContext + * @returns {void} + */ +export const init = canvas => { svgCanvas = canvas svgCanvas.copySelectedElements = copySelectedElements svgCanvas.groupSelectedElements = groupSelectedElements // Wraps all the selected elements in a group (`g`) element. @@ -53,12 +65,12 @@ export const init = (canvas) => { } /** -* Repositions the selected element to the bottom in the DOM to appear on top of -* other elements. -* @function module:selected-elem.SvgCanvas#moveToTopSelectedElem -* @fires module:selected-elem.SvgCanvas#event:changed -* @returns {void} -*/ + * Repositions the selected element to the bottom in the DOM to appear on top of + * other elements. + * @function module:selected-elem.SvgCanvas#moveToTopSelectedElem + * @fires module:selected-elem.SvgCanvas#event:changed + * @returns {void} + */ const moveToTopSelectedElem = () => { const [selected] = svgCanvas.getSelectedElements() if (selected) { @@ -69,19 +81,21 @@ const moveToTopSelectedElem = () => { // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { - svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'top')) + svgCanvas.addCommandToHistory( + new MoveElementCommand(t, oldNextSibling, oldParent, 'top') + ) svgCanvas.call('changed', [t]) } } } /** -* Repositions the selected element to the top in the DOM to appear under -* other elements. -* @function module:selected-elem.SvgCanvas#moveToBottomSelectedElement -* @fires module:selected-elem.SvgCanvas#event:changed -* @returns {void} -*/ + * Repositions the selected element to the top in the DOM to appear under + * other elements. + * @function module:selected-elem.SvgCanvas#moveToBottomSelectedElement + * @fires module:selected-elem.SvgCanvas#event:changed + * @returns {void} + */ const moveToBottomSelectedElem = () => { const [selected] = svgCanvas.getSelectedElements() if (selected) { @@ -101,32 +115,41 @@ const moveToBottomSelectedElem = () => { // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { - svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'bottom')) + svgCanvas.addCommandToHistory( + new MoveElementCommand(t, oldNextSibling, oldParent, 'bottom') + ) svgCanvas.call('changed', [t]) } } } /** -* Moves the select element up or down the stack, based on the visibly -* intersecting elements. -* @function module:selected-elem.SvgCanvas#moveUpDownSelected -* @param {"Up"|"Down"} dir - String that's either 'Up' or 'Down' -* @fires module:selected-elem.SvgCanvas#event:changed -* @returns {void} -*/ -const moveUpDownSelected = (dir) => { + * Moves the select element up or down the stack, based on the visibly + * intersecting elements. + * @function module:selected-elem.SvgCanvas#moveUpDownSelected + * @param {"Up"|"Down"} dir - String that's either 'Up' or 'Down' + * @fires module:selected-elem.SvgCanvas#event:changed + * @returns {void} + */ +const moveUpDownSelected = dir => { const selectedElements = svgCanvas.getSelectedElements() const selected = selectedElements[0] - if (!selected) { return } + if (!selected) { + return + } svgCanvas.setCurBBoxes([]) - let closest; let foundCur + let closest + let foundCur // jQuery sorts this list - const list = svgCanvas.getIntersectionList(getStrokedBBoxDefaultVisible([selected])) - if (dir === 'Down') { list.reverse() } + const list = svgCanvas.getIntersectionList( + getStrokedBBoxDefaultVisible([selected]) + ) + if (dir === 'Down') { + list.reverse() + } - Array.prototype.forEach.call(list, (el) => { + Array.prototype.forEach.call(list, el => { if (!foundCur) { if (el === selected) { foundCur = true @@ -136,7 +159,9 @@ const moveUpDownSelected = (dir) => { closest = el return false }) - if (!closest) { return } + if (!closest) { + return + } const t = selected const oldParent = t.parentNode @@ -149,20 +174,22 @@ const moveUpDownSelected = (dir) => { // If the element actually moved position, add the command and fire the changed // event handler. if (oldNextSibling !== t.nextSibling) { - svgCanvas.addCommandToHistory(new MoveElementCommand(t, oldNextSibling, oldParent, 'Move ' + dir)) + svgCanvas.addCommandToHistory( + new MoveElementCommand(t, oldNextSibling, oldParent, 'Move ' + dir) + ) svgCanvas.call('changed', [t]) } } /** -* Moves selected elements on the X/Y axis. -* @function module:selected-elem.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:selected-elem.SvgCanvas#event:changed -* @returns {BatchCommand|void} Batch command for the move -*/ + * Moves selected elements on the X/Y axis. + * @function module:selected-elem.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:selected-elem.SvgCanvas#event:changed + * @returns {BatchCommand|void} Batch command for the move + */ const moveSelectedElements = (dx, dy, undoable = true) => { const selectedElements = svgCanvas.getSelectedElements() @@ -198,7 +225,10 @@ const moveSelectedElements = (dx, dy, undoable = true) => { batchCmd.addSubCommand(cmd) } - svgCanvas.gettingSelectorManager().requestSelector(selected).resize() + svgCanvas + .gettingSelectorManager() + .requestSelector(selected) + .resize() } }) if (!batchCmd.isEmpty()) { @@ -212,22 +242,23 @@ const moveSelectedElements = (dx, dy, undoable = true) => { } /** -* Create deep DOM copies (clones) of all selected elements and move them slightly -* from their originals. -* @function module:selected-elem.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} -*/ + * Create deep DOM copies (clones) of all selected elements and move them slightly + * from their originals. + * @function module:selected-elem.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} + */ const cloneSelectedElements = (x, y) => { const selectedElements = svgCanvas.getSelectedElements() const currentGroup = svgCanvas.getCurrentGroup() - let i; let elem + let i + let elem const batchCmd = new BatchCommand('Clone Elements') // find all the elements selected (stop at first null) const len = selectedElements.length - const index = (el) => { + const index = el => { if (!el) return -1 let i = 0 do { @@ -237,18 +268,20 @@ const cloneSelectedElements = (x, y) => { } /** -* Sorts an array numerically and ascending. -* @param {Element} a -* @param {Element} b -* @returns {Integer} -*/ + * Sorts an array numerically and ascending. + * @param {Element} a + * @param {Element} b + * @returns {Integer} + */ const sortfunction = (a, b) => { - return (index(b) - index(a)) + return index(b) - index(a) } selectedElements.sort(sortfunction) for (i = 0; i < len; ++i) { elem = selectedElements[i] - if (!elem) { break } + if (!elem) { + break + } } // use slice to quickly get the subset of elements we need const copiedElements = selectedElements.slice(0, i) @@ -259,8 +292,8 @@ const cloneSelectedElements = (x, y) => { i = copiedElements.length while (i--) { // clone each element and replace it within copiedElements - elem = copiedElements[i] = drawing.copyElem(copiedElements[i]); - (currentGroup || drawing.getCurrentLayer()).append(elem) + elem = copiedElements[i] = drawing.copyElem(copiedElements[i]) + ;(currentGroup || drawing.getCurrentLayer()).append(elem) batchCmd.addSubCommand(new InsertElementCommand(elem)) } @@ -271,31 +304,49 @@ const cloneSelectedElements = (x, y) => { } } /** -* Aligns selected elements. -* @function module:selected-elem.SvgCanvas#alignSelectedElements -* @param {string} type - String with single character indicating the alignment type -* @param {"selected"|"largest"|"smallest"|"page"} relativeTo -* @returns {void} -*/ + * Aligns selected elements. + * @function module:selected-elem.SvgCanvas#alignSelectedElements + * @param {string} type - String with single character indicating the alignment type + * @param {"selected"|"largest"|"smallest"|"page"} relativeTo + * @returns {void} + */ const alignSelectedElements = (type, relativeTo) => { const selectedElements = svgCanvas.getSelectedElements() const bboxes = [] // angles = []; const len = selectedElements.length - if (!len) { return } - let minx = Number.MAX_VALUE; let maxx = Number.MIN_VALUE - let miny = Number.MAX_VALUE; let maxy = Number.MIN_VALUE - let curwidth = Number.MIN_VALUE; let curheight = Number.MIN_VALUE + if (!len) { + return + } + let minx = Number.MAX_VALUE + let maxx = Number.MIN_VALUE + let miny = Number.MAX_VALUE + let maxy = Number.MIN_VALUE + let curwidth = Number.MIN_VALUE + let curheight = Number.MIN_VALUE for (let i = 0; i < len; ++i) { - if (!selectedElements[i]) { break } + if (!selectedElements[i]) { + break + } const elem = selectedElements[i] bboxes[i] = getStrokedBBoxDefaultVisible([elem]) // now bbox is axis-aligned and handles rotation switch (relativeTo) { case 'smallest': - if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && - (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || - ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && + if ( + ((type === 'l' || + type === 'c' || + type === 'r' || + type === 'left' || + type === 'center' || + type === 'right') && + (curwidth === Number.MIN_VALUE || curwidth > bboxes[i].width)) || + ((type === 't' || + type === 'm' || + type === 'b' || + type === 'top' || + type === 'middle' || + type === 'bottom') && (curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) ) { minx = bboxes[i].x @@ -307,9 +358,20 @@ const alignSelectedElements = (type, relativeTo) => { } break case 'largest': - if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && - (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) || - ((type === 't' || type === 'm' || type === 'b' || type === 'top' || type === 'middle' || type === 'bottom') && + if ( + ((type === 'l' || + type === 'c' || + type === 'r' || + type === 'left' || + type === 'center' || + type === 'right') && + (curwidth === Number.MIN_VALUE || curwidth < bboxes[i].width)) || + ((type === 't' || + type === 'm' || + type === 'b' || + type === 'top' || + type === 'middle' || + type === 'bottom') && (curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) ) { minx = bboxes[i].x @@ -320,11 +382,20 @@ const alignSelectedElements = (type, relativeTo) => { curheight = bboxes[i].height } break - default: // 'selected' - if (bboxes[i].x < minx) { minx = bboxes[i].x } - if (bboxes[i].y < miny) { miny = bboxes[i].y } - if (bboxes[i].x + bboxes[i].width > maxx) { maxx = bboxes[i].x + bboxes[i].width } - if (bboxes[i].y + bboxes[i].height > maxy) { maxy = bboxes[i].y + bboxes[i].height } + default: + // 'selected' + if (bboxes[i].x < minx) { + minx = bboxes[i].x + } + if (bboxes[i].y < miny) { + miny = bboxes[i].y + } + if (bboxes[i].x + bboxes[i].width > maxx) { + maxx = bboxes[i].x + bboxes[i].width + } + if (bboxes[i].y + bboxes[i].height > maxy) { + maxy = bboxes[i].y + bboxes[i].height + } break } } // loop for each element to find the bbox and adjust min/max @@ -339,7 +410,9 @@ const alignSelectedElements = (type, relativeTo) => { const dx = new Array(len) const dy = new Array(len) for (let i = 0; i < len; ++i) { - if (!selectedElements[i]) { break } + if (!selectedElements[i]) { + break + } // const elem = selectedElements[i]; const bbox = bboxes[i] dx[i] = 0 @@ -375,12 +448,12 @@ const alignSelectedElements = (type, relativeTo) => { } /** -* Removes all selected elements from the DOM and adds the change to the -* history stack. -* @function module:selected-elem.SvgCanvas#deleteSelectedElements -* @fires module:selected-elem.SvgCanvas#event:changed -* @returns {void} -*/ + * Removes all selected elements from the DOM and adds the change to the + * history stack. + * @function module:selected-elem.SvgCanvas#deleteSelectedElements + * @fires module:selected-elem.SvgCanvas#event:changed + * @returns {void} + */ const deleteSelectedElements = () => { const selectedElements = svgCanvas.getSelectedElements() const batchCmd = new BatchCommand('Delete Elements') @@ -389,7 +462,9 @@ const deleteSelectedElements = () => { for (let i = 0; i < len; ++i) { const selected = selectedElements[i] - if (!selected) { break } + if (!selected) { + break + } let parent = selected.parentNode let t = selected @@ -414,20 +489,23 @@ const deleteSelectedElements = () => { } svgCanvas.setEmptySelectedElements() - if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) } + if (!batchCmd.isEmpty()) { + svgCanvas.addCommandToHistory(batchCmd) + } svgCanvas.call('changed', selectedCopy) svgCanvas.clearSelection() } /** -* Remembers the current selected elements on the clipboard. -* @function module:selected-elem.SvgCanvas#copySelectedElements -* @returns {void} -*/ + * Remembers the current selected elements on the clipboard. + * @function module:selected-elem.SvgCanvas#copySelectedElements + * @returns {void} + */ const copySelectedElements = () => { const selectedElements = svgCanvas.getSelectedElements() - const data = - JSON.stringify(selectedElements.map((x) => svgCanvas.getJsonFromSvgElements(x))) + const data = JSON.stringify( + selectedElements.map(x => svgCanvas.getJsonFromSvgElements(x)) + ) // Use sessionStorage for the clipboard data. sessionStorage.setItem(svgCanvas.getClipboardID(), data) svgCanvas.flashStorage() @@ -438,15 +516,17 @@ const copySelectedElements = () => { } /** -* Wraps all the selected elements in a group (`g`) element. -* @function module:selected-elem.SvgCanvas#groupSelectedElements -* @param {"a"|"g"} [type="g"] - type of element to group into, defaults to `` -* @param {string} [urlArg] -* @returns {void} -*/ + * Wraps all the selected elements in a group (`g`) element. + * @function module:selected-elem.SvgCanvas#groupSelectedElements + * @param {"a"|"g"} [type="g"] - type of element to group into, defaults to `` + * @param {string} [urlArg] + * @returns {void} + */ const groupSelectedElements = (type, urlArg) => { const selectedElements = svgCanvas.getSelectedElements() - if (!type) { type = 'g' } + if (!type) { + type = 'g' + } let cmdStr = '' let url @@ -455,7 +535,8 @@ const groupSelectedElements = (type, urlArg) => { cmdStr = 'Make hyperlink' url = urlArg || '' break - } default: { + } + default: { type = 'g' cmdStr = 'Group Elements' break @@ -480,31 +561,40 @@ const groupSelectedElements = (type, urlArg) => { let i = selectedElements.length while (i--) { let elem = selectedElements[i] - if (!elem) { continue } + if (!elem) { + continue + } - if (elem.parentNode.tagName === 'a' && elem.parentNode.childNodes.length === 1) { + if ( + elem.parentNode.tagName === 'a' && + elem.parentNode.childNodes.length === 1 + ) { elem = elem.parentNode } const oldNextSibling = elem.nextSibling const oldParent = elem.parentNode g.append(elem) - batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)) + batchCmd.addSubCommand( + new MoveElementCommand(elem, oldNextSibling, oldParent) + ) + } + if (!batchCmd.isEmpty()) { + svgCanvas.addCommandToHistory(batchCmd) } - if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) } // update selection svgCanvas.selectOnly([g], true) } /** -* Pushes all appropriate parent group properties down to its children, then -* removes them from the group. -* @function module:selected-elem.SvgCanvas#pushGroupProperty -* @param {SVGAElement|SVGGElement} g -* @param {boolean} undoable -* @returns {BatchCommand|void} -*/ + * Pushes all appropriate parent group properties down to its children, then + * removes them from the group. + * @function module:selected-elem.SvgCanvas#pushGroupProperty + * @param {SVGAElement|SVGGElement} g + * @param {boolean} undoable + * @returns {BatchCommand|void} + */ const pushGroupProperty = (g, undoable) => { const children = g.childNodes const len = children.length @@ -528,24 +618,32 @@ const pushGroupProperty = (g, undoable) => { filter: g.getAttribute('filter'), opacity: g.getAttribute('opacity') } - let gfilter; let gblur; let changes + let gfilter + let gblur + let changes const drawing = svgCanvas.getDrawing() for (let i = 0; i < len; i++) { const elem = children[i] - if (elem.nodeType !== 1) { continue } + if (elem.nodeType !== 1) { + continue + } if (gattrs.opacity !== null && gattrs.opacity !== 1) { // const c_opac = elem.getAttribute('opacity') || 1; - const newOpac = Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) / 100 + const newOpac = + Math.round((elem.getAttribute('opacity') || 1) * gattrs.opacity * 100) / + 100 svgCanvas.changeSelectedAttribute('opacity', newOpac, [elem]) } if (gattrs.filter) { let cblur = svgCanvas.getBlur(elem) const origCblur = cblur - if (!gblur) { gblur = svgCanvas.getBlur(g) } + if (!gblur) { + gblur = svgCanvas.getBlur(g) + } if (cblur) { // Is this formula correct? cblur = Number(gblur) + Number(cblur) @@ -566,9 +664,14 @@ const pushGroupProperty = (g, undoable) => { // const filterElem = getRefElem(gfilter); const blurElem = getFeGaussianBlur(gfilter) // Change this in future for different filters - const suffix = (blurElem?.tagName === 'feGaussianBlur') ? 'blur' : 'filter' + const suffix = + blurElem?.tagName === 'feGaussianBlur' ? 'blur' : 'filter' gfilter.id = elem.id + '_' + suffix - svgCanvas.changeSelectedAttribute('filter', 'url(#' + gfilter.id + ')', [elem]) + svgCanvas.changeSelectedAttribute( + 'filter', + 'url(#' + gfilter.id + ')', + [elem] + ) } } else { gfilter = getRefElem(elem.getAttribute('filter')) @@ -586,13 +689,19 @@ const pushGroupProperty = (g, undoable) => { let chtlist = elem.transform?.baseVal // Don't process gradient transforms - if (elem.tagName.includes('Gradient')) { chtlist = null } + if (elem.tagName.includes('Gradient')) { + chtlist = null + } // Hopefully not a problem to add this. Necessary for elements like - if (!chtlist) { continue } + if (!chtlist) { + continue + } // Apparently can get get a transformlist, but we don't want it to have one! - if (elem.tagName === 'defs') { continue } + if (elem.tagName === 'defs') { + continue + } if (glist.numberOfItems) { // TODO: if the group's transform is just a rotate, we can always transfer the @@ -621,7 +730,11 @@ const pushGroupProperty = (g, undoable) => { // get child's old center of rotation const cbox = utilsGetBBox(elem) const ceqm = transformListToTransform(chtlist).matrix - const coldc = transformPoint(cbox.x + cbox.width / 2, cbox.y + cbox.height / 2, ceqm) + const coldc = transformPoint( + cbox.x + cbox.width / 2, + cbox.y + cbox.height / 2, + ceqm + ) // sum group and child's angles const sangle = gangle + cangle @@ -655,7 +768,8 @@ const pushGroupProperty = (g, undoable) => { chtlist.appendItem(tr) } } - } else { // more complicated than just a rotate + } else { + // more complicated than just a rotate // transfer the group's transform down to each child and then // call recalculateDimensions() const oldxform = elem.getAttribute('transform') @@ -673,7 +787,9 @@ const pushGroupProperty = (g, undoable) => { chtlist.appendItem(newxform) } const cmd = recalculateDimensions(elem) - if (cmd) { batchCmd.addSubCommand(cmd) } + if (cmd) { + batchCmd.addSubCommand(cmd) + } } } @@ -693,13 +809,13 @@ const pushGroupProperty = (g, undoable) => { } /** -* Converts selected/given `` or child SVG element to a group. -* @function module:selected-elem.SvgCanvas#convertToGroup -* @param {Element} elem -* @fires module:selected-elem.SvgCanvas#event:selected -* @returns {void} -*/ -const convertToGroup = (elem) => { + * Converts selected/given `` or child SVG element to a group. + * @function module:selected-elem.SvgCanvas#convertToGroup + * @param {Element} elem + * @fires module:selected-elem.SvgCanvas#event:selected + * @returns {void} + */ +const convertToGroup = elem => { const selectedElements = svgCanvas.getSelectedElements() if (!elem) { elem = selectedElements[0] @@ -752,7 +868,13 @@ const convertToGroup = (elem) => { const prev = $elem.previousElementSibling // Remove element - batchCmd.addSubCommand(new RemoveElementCommand($elem, $elem.nextElementSibling, $elem.parentNode)) + batchCmd.addSubCommand( + new RemoveElementCommand( + $elem, + $elem.nextElementSibling, + $elem.parentNode + ) + ) $elem.remove() // See if other elements reference this symbol @@ -772,7 +894,9 @@ const convertToGroup = (elem) => { // Duplicate the gradients for Gecko, since they weren't included in the if (isGecko()) { const svgElement = findDefs() - const gradients = svgElement.querySelectorAll('linearGradient,radialGradient,pattern') + const gradients = svgElement.querySelectorAll( + 'linearGradient,radialGradient,pattern' + ) for (let i = 0, im = gradients.length; im > i; i++) { g.appendChild(gradients[i].cloneNode(true)) } @@ -789,7 +913,9 @@ const convertToGroup = (elem) => { // Put the dupe gradients back into (after uniquifying them) if (isGecko()) { const svgElement = findDefs() - const elements = g.querySelectorAll('linearGradient,radialGradient,pattern') + const elements = g.querySelectorAll( + 'linearGradient,radialGradient,pattern' + ) for (let i = 0, im = elements.length; im > i; i++) { svgElement.appendChild(elements[i]) } @@ -805,7 +931,9 @@ const convertToGroup = (elem) => { // remove symbol/svg element const { nextSibling } = elem elem.remove() - batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)) + batchCmd.addSubCommand( + new RemoveElementCommand(elem, nextSibling, parent) + ) } batchCmd.addSubCommand(new InsertElementCommand(g)) } @@ -820,7 +948,7 @@ const convertToGroup = (elem) => { // recalculate dimensions on the top-level children so that unnecessary transforms // are removed - walkTreePost(g, (n) => { + walkTreePost(g, n => { try { recalculateDimensions(n) } catch (e) { @@ -830,8 +958,10 @@ const convertToGroup = (elem) => { // Give ID for any visible element missing one const visElems = g.querySelectorAll(svgCanvas.getVisElems()) - Array.prototype.forEach.call(visElems, (el) => { - if (!el.id) { el.id = svgCanvas.getNextId() } + Array.prototype.forEach.call(visElems, el => { + if (!el.id) { + el.id = svgCanvas.getNextId() + } }) svgCanvas.selectOnly([g]) @@ -848,11 +978,11 @@ const convertToGroup = (elem) => { } /** -* 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:selected-elem.SvgCanvas#ungroupSelectedElement -* @returns {void} -*/ + * 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:selected-elem.SvgCanvas#ungroupSelectedElement + * @returns {void} + */ const ungroupSelectedElement = () => { const selectedElements = svgCanvas.getSelectedElements() const dataStorage = svgCanvas.getDataStorage() @@ -882,7 +1012,9 @@ const ungroupSelectedElement = () => { if (g.tagName === 'g' || g.tagName === 'a') { const batchCmd = new BatchCommand('Ungroup Elements') const cmd = pushGroupProperty(g, true) - if (cmd) { batchCmd.addSubCommand(cmd) } + if (cmd) { + batchCmd.addSubCommand(cmd) + } const parent = g.parentNode const anchor = g.nextSibling @@ -897,13 +1029,17 @@ const ungroupSelectedElement = () => { // Remove child title elements if (elem.tagName === 'title') { const { nextSibling } = elem - batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, oldParent)) + batchCmd.addSubCommand( + new RemoveElementCommand(elem, nextSibling, oldParent) + ) elem.remove() continue } children[i++] = parent.insertBefore(elem, anchor) - batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent)) + batchCmd.addSubCommand( + new MoveElementCommand(elem, oldNextSibling, oldParent) + ) } // remove the group from the selection @@ -914,20 +1050,22 @@ const ungroupSelectedElement = () => { g.remove() batchCmd.addSubCommand(new RemoveElementCommand(g, gNextSibling, parent)) - if (!batchCmd.isEmpty()) { svgCanvas.addCommandToHistory(batchCmd) } + if (!batchCmd.isEmpty()) { + svgCanvas.addCommandToHistory(batchCmd) + } // update selection svgCanvas.addToSelection(children) } } /** -* 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} -*/ + * 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} + */ const updateCanvas = (w, h) => { svgCanvas.getSvgRoot().setAttribute('width', w) svgCanvas.getSvgRoot().setAttribute('height', h) @@ -935,8 +1073,8 @@ const updateCanvas = (w, h) => { const bg = document.getElementById('canvasBackground') const oldX = Number(svgCanvas.getSvgContent().getAttribute('x')) const oldY = Number(svgCanvas.getSvgContent().getAttribute('y')) - const x = ((w - svgCanvas.contentW * zoom) / 2) - const y = ((h - svgCanvas.contentH * zoom) / 2) + const x = (w - svgCanvas.contentW * zoom) / 2 + const y = (h - svgCanvas.contentH * zoom) / 2 assignAttributes(svgCanvas.getSvgContent(), { width: svgCanvas.contentW * zoom, @@ -961,43 +1099,57 @@ const updateCanvas = (w, h) => { }) } - svgCanvas.selectorManager.selectorParentGroup.setAttribute('transform', 'translate(' + x + ',' + y + ')') + svgCanvas.selectorManager.selectorParentGroup.setAttribute( + 'transform', + 'translate(' + x + ',' + y + ')' + ) /** -* Invoked upon updates to the canvas. -* @event module:svgcanvas.SvgCanvas#event:ext_canvasUpdated -* @type {PlainObject} -* @property {Integer} new_x -* @property {Integer} new_y -* @property {string} old_x (Of Integer) -* @property {string} old_y (Of Integer) -* @property {Integer} d_x -* @property {Integer} d_y -*/ + * Invoked upon updates to the canvas. + * @event module:svgcanvas.SvgCanvas#event:ext_canvasUpdated + * @type {PlainObject} + * @property {Integer} new_x + * @property {Integer} new_y + * @property {string} old_x (Of Integer) + * @property {string} old_y (Of Integer) + * @property {Integer} d_x + * @property {Integer} d_y + */ svgCanvas.runExtensions( 'canvasUpdated', /** - * @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated} - */ - { new_x: x, new_y: y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY } + * @type {module:svgcanvas.SvgCanvas#event:ext_canvasUpdated} + */ + { + new_x: x, + new_y: y, + old_x: oldX, + old_y: oldY, + d_x: x - oldX, + d_y: y - oldY + } ) return { x, y, old_x: oldX, old_y: oldY, d_x: x - oldX, d_y: y - oldY } } /** -* 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} -*/ -const cycleElement = (next) => { + * 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} + */ +const cycleElement = next => { const selectedElements = svgCanvas.getSelectedElements() const currentGroup = svgCanvas.getCurrentGroup() let num const curElem = selectedElements[0] let elem = false - const allElems = getVisibleElements(currentGroup || svgCanvas.getCurrentDrawing().getCurrentLayer()) - if (!allElems.length) { return } + const allElems = getVisibleElements( + currentGroup || svgCanvas.getCurrentDrawing().getCurrentLayer() + ) + if (!allElems.length) { + return + } if (!curElem) { num = next ? allElems.length - 1 : 0 elem = allElems[num] diff --git a/src/svgcanvas/selection.js b/src/svgcanvas/selection.js index c7941016..61d57efe 100644 --- a/src/svgcanvas/selection.js +++ b/src/svgcanvas/selection.js @@ -28,6 +28,15 @@ let svgCanvas = null */ export const init = (canvas) => { svgCanvas = canvas + svgCanvas.getMouseTarget = getMouseTargetMethod + svgCanvas.clearSelection = clearSelectionMethod + svgCanvas.addToSelection = addToSelectionMethod + svgCanvas.getIntersectionList = getIntersectionListMethod + svgCanvas.runExtensions = runExtensionsMethod + svgCanvas.groupSvgElem = groupSvgElem + svgCanvas.prepareSvg = prepareSvg + svgCanvas.recalculateAllSelectedDimensions = recalculateAllSelectedDimensions + svgCanvas.setRotationAngle = setRotationAngle } /** @@ -37,7 +46,7 @@ export const init = (canvas) => { * @type {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} * @fires module:selection.SvgCanvas#event:selected */ -export const clearSelectionMethod = (noCall) => { +const clearSelectionMethod = (noCall) => { const selectedElements = svgCanvas.getSelectedElements() selectedElements.forEach((elem) => { if (!elem) { @@ -59,7 +68,7 @@ export const clearSelectionMethod = (noCall) => { * @type {module:path.EditorContext#addToSelection} * @fires module:selection.SvgCanvas#event:selected */ -export const addToSelectionMethod = (elemsToAdd, showGrips) => { +const addToSelectionMethod = (elemsToAdd, showGrips) => { const selectedElements = svgCanvas.getSelectedElements() if (!elemsToAdd.length) { return @@ -134,7 +143,7 @@ export const addToSelectionMethod = (elemsToAdd, showGrips) => { * @name module:svgcanvas.SvgCanvas#getMouseTarget * @type {module:path.EditorContext#getMouseTarget} */ -export const getMouseTargetMethod = (evt) => { +const getMouseTargetMethod = (evt) => { if (!evt) { return null } @@ -211,7 +220,7 @@ export const getMouseTargetMethod = (evt) => { * @returns {GenericArray|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus. */ /* eslint-enable max-len */ -export const runExtensionsMethod = ( +const runExtensionsMethod = ( action, vars, returnArray @@ -248,7 +257,7 @@ export const runExtensionsMethod = ( * @param {Element} parent - The parent DOM element to search within * @returns {ElementAndBBox[]} An array with objects that include: */ -export const getVisibleElementsAndBBoxes = (parent) => { +const getVisibleElementsAndBBoxes = (parent) => { if (!parent) { const svgContent = svgCanvas.getSvgContent() parent = svgContent.children // Prevent layers from being included @@ -275,7 +284,7 @@ export const getVisibleElementsAndBBoxes = (parent) => { * @param {SVGRect} rect * @returns {Element[]|NodeList} Bbox elements */ -export const getIntersectionListMethod = (rect) => { +const getIntersectionListMethod = (rect) => { const zoom = svgCanvas.getZoom() if (!svgCanvas.getRubberBox()) { return null @@ -338,7 +347,7 @@ export const getIntersectionListMethod = (rect) => { * @param {Element} elem - SVG element to wrap * @returns {void} */ -export const groupSvgElem = (elem) => { +const groupSvgElem = (elem) => { const dataStorage = svgCanvas.getDataStorage() const g = document.createElementNS(NS.SVG, 'g') elem.replaceWith(g) @@ -353,7 +362,7 @@ export const groupSvgElem = (elem) => { * @param {XMLDocument} newDoc - The SVG DOM document * @returns {void} */ -export const prepareSvg = (newDoc) => { +const prepareSvg = (newDoc) => { svgCanvas.sanitizeSvg(newDoc.documentElement) // convert paths into absolute commands @@ -374,7 +383,7 @@ export const prepareSvg = (newDoc) => { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ -export const setRotationAngle = (val, preventUndo) => { +const setRotationAngle = (val, preventUndo) => { const selectedElements = svgCanvas.getSelectedElements() // ensure val is the proper type val = Number.parseFloat(val) @@ -444,7 +453,7 @@ export const setRotationAngle = (val, preventUndo) => { * @fires module:svgcanvas.SvgCanvas#event:changed * @returns {void} */ -export const recalculateAllSelectedDimensions = () => { +const recalculateAllSelectedDimensions = () => { const text = svgCanvas.getCurrentResizeMode() === 'none' ? 'position' : 'size' const batchCmd = new BatchCommand(text) diff --git a/src/svgcanvas/svg-exec.js b/src/svgcanvas/svg-exec.js index 8901d5bf..b1c63c69 100644 --- a/src/svgcanvas/svg-exec.js +++ b/src/svgcanvas/svg-exec.js @@ -10,55 +10,71 @@ import 'svg2pdf.js/dist/svg2pdf.es.js' import html2canvas from 'html2canvas' import * as hstry from './history.js' import { - text2xml, cleanupElement, findDefs, getHref, preventClickDefault, - toXml, getStrokedBBoxDefaultVisible, encode64, createObjectURL, - dataURLToObjectURL, walkTree, getBBox as utilsGetBBox + text2xml, + cleanupElement, + findDefs, + getHref, + preventClickDefault, + toXml, + getStrokedBBoxDefaultVisible, + encode64, + createObjectURL, + dataURLToObjectURL, + walkTree, + getBBox as utilsGetBBox } from './utilities.js' -import { - transformPoint, transformListToTransform -} from './math.js' -import { - convertUnit, shortFloat, convertToNum -} from '../common/units.js' +import { transformPoint, transformListToTransform } from './math.js' +import { convertUnit, shortFloat, convertToNum } from '../common/units.js' import { isGecko, isChrome, isWebkit } from '../common/browser.js' import * as pathModule from './path.js' import { NS } from './namespaces.js' import * as draw from './draw.js' -import { - recalculateDimensions -} from './recalculate.js' +import { recalculateDimensions } from './recalculate.js' import { getParents, getClosest } from '../editor/components/jgraduate/Util.js' const { - InsertElementCommand, RemoveElementCommand, - ChangeElementCommand, BatchCommand + InsertElementCommand, + RemoveElementCommand, + ChangeElementCommand, + BatchCommand } = hstry let svgCanvas = null /** -* @function module:svg-exec.init -* @param {module:svg-exec.SvgCanvas#init} svgContext -* @returns {void} -*/ -export const init = (canvas) => { + * @function module:svg-exec.init + * @param {module:svg-exec.SvgCanvas#init} svgContext + * @returns {void} + */ +export const init = canvas => { svgCanvas = canvas + svgCanvas.setSvgString = setSvgString + svgCanvas.importSvgString = importSvgString + svgCanvas.uniquifyElems = uniquifyElemsMethod + svgCanvas.setUseData = setUseDataMethod + svgCanvas.convertGradients = convertGradientsMethod + svgCanvas.removeUnusedDefElems = removeUnusedDefElemsMethod // remove DOM elements inside the `` if they are notreferred to, + svgCanvas.svgCanvasToString = svgCanvasToString // Main function to set up the SVG content for output. + svgCanvas.svgToString = svgToString // Sub function ran on each SVG element to convert it to a string as desired. + svgCanvas.embedImage = embedImage // Converts a given image file to a data URL when possibl + svgCanvas.rasterExport = rasterExport // Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image + svgCanvas.exportPDF = exportPDF // Generates a PDF based on the current image, then calls "exportedPDF" } /** -* Main function to set up the SVG content for output. -* @function module:svgcanvas.SvgCanvas#svgCanvasToString -* @returns {string} The SVG image for output -*/ -export const svgCanvasToString = () => { + * Main function to set up the SVG content for output. + * @function module:svgcanvas.SvgCanvas#svgCanvasToString + * @returns {string} The SVG image for output + */ +const svgCanvasToString = () => { // keep calling it until there are none to remove - while (svgCanvas.removeUnusedDefElems() > 0) { } // eslint-disable-line no-empty + while (svgCanvas.removeUnusedDefElems() > 0) {} // eslint-disable-line no-empty svgCanvas.pathActions.clear(true) // Keep SVG-Edit comment on top const childNodesElems = svgCanvas.getSvgContent().childNodes - childNodesElems.forEach(function (node, i) { + childNodesElems.forEach((node, i) => { if (i && node.nodeType === 8 && node.data.includes('Created with')) { svgCanvas.getSvgContent().firstChild.before(node) } @@ -74,7 +90,7 @@ export const svgCanvasToString = () => { // Unwrap gsvg if it has no special attributes (only id and style) const gsvgElems = svgCanvas.getSvgContent().querySelectorAll('g[data-gsvg]') - Array.prototype.forEach.call(gsvgElems, function (element) { + Array.prototype.forEach.call(gsvgElems, (element) => { const attrs = element.attributes let len = attrs.length for (let i = 0; i < len; i++) { @@ -93,7 +109,7 @@ export const svgCanvasToString = () => { // Rewrap gsvg if (nakedSvgs.length) { - Array.prototype.forEach.call(nakedSvgs, function (el) { + Array.prototype.forEach.call(nakedSvgs, (el) => { svgCanvas.groupSvgElem(el) }) } @@ -102,13 +118,13 @@ export const 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 -*/ -export const svgToString = function (elem, indent) { + * 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 + */ +const svgToString = (elem, indent) => { const curConfig = svgCanvas.getCurConfig() const nsMap = svgCanvas.getNsMap() const out = [] @@ -123,8 +139,11 @@ export const svgToString = function (elem, indent) { return a.name > b.name ? -1 : 1 }) - for (let i = 0; i < indent; i++) { out.push(' ') } - out.push('<'); out.push(elem.nodeName) + for (let i = 0; i < indent; i++) { + out.push(' ') + } + out.push('<') + out.push(elem.nodeName) if (elem.id === 'svgcontent') { // Process root element separately const res = svgCanvas.getResolution() @@ -147,7 +166,17 @@ export const svgToString = function (elem, indent) { res.h = convertUnit(res.h, unit) + unit } - out.push(' width="' + res.w + '" height="' + res.h + '"' + vb + ' xmlns="' + NS.SVG + '"') + out.push( + ' width="' + + res.w + + '" height="' + + res.h + + '"' + + vb + + ' xmlns="' + + NS.SVG + + '"' + ) const nsuris = {} @@ -155,11 +184,17 @@ export const svgToString = function (elem, indent) { const csElements = elem.querySelectorAll('*') const cElements = Array.prototype.slice.call(csElements) cElements.push(elem) - Array.prototype.forEach.call(cElements, function (el) { + Array.prototype.forEach.call(cElements, (el) => { // const el = this; // for some elements have no attribute const uri = el.namespaceURI - if (uri && !nsuris[uri] && nsMap[uri] && nsMap[uri] !== 'xmlns' && nsMap[uri] !== 'xml') { + if ( + uri && + !nsuris[uri] && + nsMap[uri] && + nsMap[uri] !== 'xmlns' && + nsMap[uri] !== 'xml' + ) { nsuris[uri] = true out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"') } @@ -175,40 +210,71 @@ export const svgToString = function (elem, indent) { }) let i = attrs.length - const attrNames = ['width', 'height', 'xmlns', 'x', 'y', 'viewBox', 'id', 'overflow'] + const attrNames = [ + 'width', + 'height', + 'xmlns', + 'x', + 'y', + 'viewBox', + 'id', + 'overflow' + ] while (i--) { const attr = attrs[i] const attrVal = toXml(attr.value) // Namespaces have already been dealt with, so skip - if (attr.nodeName.startsWith('xmlns:')) { continue } + if (attr.nodeName.startsWith('xmlns:')) { + continue + } // only serialize attributes we don't use internally - if (attrVal !== '' && !attrNames.includes(attr.localName) && (!attr.namespaceURI || nsMap[attr.namespaceURI])) { + if ( + attrVal !== '' && + !attrNames.includes(attr.localName) && + (!attr.namespaceURI || nsMap[attr.namespaceURI]) + ) { out.push(' ') - out.push(attr.nodeName); out.push('="') - out.push(attrVal); out.push('"') + out.push(attr.nodeName) + out.push('="') + out.push(attrVal) + out.push('"') } } } else { // Skip empty defs - if (elem.nodeName === 'defs' && !elem.firstChild) { return '' } + if (elem.nodeName === 'defs' && !elem.firstChild) { + return '' + } const mozAttrs = ['-moz-math-font-style', '_moz-math-font-style'] for (let i = attrs.length - 1; i >= 0; i--) { const attr = attrs[i] let attrVal = toXml(attr.value) // remove bogus attributes added by Gecko - if (mozAttrs.includes(attr.localName)) { continue } + if (mozAttrs.includes(attr.localName)) { + continue + } if (attrVal === 'null') { - const styleName = attr.localName.replace(/-[a-z]/g, (s) => s[1].toUpperCase()) - if (Object.prototype.hasOwnProperty.call(elem.style, styleName)) { continue } + const styleName = attr.localName.replace(/-[a-z]/g, s => + s[1].toUpperCase() + ) + if (Object.prototype.hasOwnProperty.call(elem.style, styleName)) { + continue + } } if (attrVal !== '') { - if (attrVal.startsWith('pointer-events')) { continue } - if (attr.localName === 'class' && attrVal.startsWith('se_')) { continue } + if (attrVal.startsWith('pointer-events')) { + continue + } + if (attr.localName === 'class' && attrVal.startsWith('se_')) { + continue + } out.push(' ') - if (attr.localName === 'd') { attrVal = svgCanvas.pathActions.convertPath(elem, true) } + if (attr.localName === 'd') { + attrVal = svgCanvas.pathActions.convertPath(elem, true) + } if (!isNaN(attrVal)) { attrVal = shortFloat(attrVal) } else if (unitRe.test(attrVal)) { @@ -216,21 +282,30 @@ export const svgToString = function (elem, indent) { } // Embed images when saving - if (svgCanvas.getSvgOptionApply() && + if ( + svgCanvas.getSvgOptionApply() && elem.nodeName === 'image' && attr.localName === 'href' && svgCanvas.getSvgOptionImages() && svgCanvas.getSvgOptionImages() === 'embed' ) { const img = svgCanvas.getEncodableImages(attrVal) - if (img) { attrVal = img } + if (img) { + attrVal = img + } } // map various namespaces to our fixed namespace prefixes // (the default xmlns attribute itself does not get a prefix) - if (!attr.namespaceURI || attr.namespaceURI === NS.SVG || nsMap[attr.namespaceURI]) { - out.push(attr.nodeName); out.push('="') - out.push(attrVal); out.push('"') + if ( + !attr.namespaceURI || + attr.namespaceURI === NS.SVG || + nsMap[attr.namespaceURI] + ) { + out.push(attr.nodeName) + out.push('="') + out.push(attrVal) + out.push('"') } } } @@ -248,14 +323,16 @@ export const svgToString = function (elem, indent) { out.push('\n') out.push(svgCanvas.svgToString(child, indent)) break - case 3: { // text node + case 3: { + // text node const str = child.nodeValue.replace(/^\s+|\s+$/g, '') if (str !== '') { bOneLine = true out.push(String(toXml(str))) } break - } case 4: // cdata node + } + case 4: // cdata node out.push('\n') out.push(new Array(indent + 1).join(' ')) out.push('') + out.push('') } else { out.push('/>') } @@ -285,26 +366,28 @@ export const svgToString = function (elem, indent) { } // end svgToString() /** -* 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. -*/ -export const setSvgString = function (xmlString, preventUndo) { + * 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. + */ +const setSvgString = (xmlString, preventUndo) => { const curConfig = svgCanvas.getCurConfig() const dataStorage = svgCanvas.getDataStorage() try { // convert string into XML document const newDoc = text2xml(xmlString) - if (newDoc.firstElementChild && - newDoc.firstElementChild.namespaceURI !== NS.SVG) { + if ( + newDoc.firstElementChild && + newDoc.firstElementChild.namespaceURI !== NS.SVG + ) { return false } @@ -317,20 +400,29 @@ export const setSvgString = function (xmlString, preventUndo) { svgCanvas.getSvgContent().remove() const oldzoom = svgCanvas.getSvgContent() - batchCmd.addSubCommand(new RemoveElementCommand(oldzoom, nextSibling, svgCanvas.getSvgRoot())) + batchCmd.addSubCommand( + new RemoveElementCommand(oldzoom, nextSibling, svgCanvas.getSvgRoot()) + ) // set new svg document // If DOM3 adoptNode() available, use it. Otherwise fall back to DOM2 importNode() if (svgCanvas.getDOMDocument().adoptNode) { - svgCanvas.setSvgContent(svgCanvas.getDOMDocument().adoptNode(newDoc.documentElement)) + svgCanvas.setSvgContent( + svgCanvas.getDOMDocument().adoptNode(newDoc.documentElement) + ) } else { - svgCanvas.setSvgContent(svgCanvas.getDOMDocument().importNode(newDoc.documentElement, true)) + svgCanvas.setSvgContent( + svgCanvas.getDOMDocument().importNode(newDoc.documentElement, true) + ) } svgCanvas.getSvgRoot().append(svgCanvas.getSvgContent()) const content = svgCanvas.getSvgContent() - svgCanvas.current_drawing_ = new draw.Drawing(svgCanvas.getSvgContent(), svgCanvas.getIdPrefix()) + svgCanvas.current_drawing_ = new draw.Drawing( + svgCanvas.getSvgContent(), + svgCanvas.getIdPrefix() + ) // retrieve or set the nonce const nonce = svgCanvas.getCurrentDrawing().getNonce() @@ -342,7 +434,7 @@ export const setSvgString = function (xmlString, preventUndo) { // change image href vals if possible const elements = content.querySelectorAll('image') - Array.prototype.forEach.call(elements, function (image) { + Array.prototype.forEach.call(elements, (image) => { preventClickDefault(image) const val = svgCanvas.getHref(image) if (val) { @@ -388,9 +480,11 @@ export const setSvgString = function (xmlString, preventUndo) { // Wrap child SVGs in group elements const svgElements = content.querySelectorAll('svg') - Array.prototype.forEach.call(svgElements, function (element) { + Array.prototype.forEach.call(svgElements, (element) => { // Skip if it's in a - if (getClosest(element.parentNode, 'defs')) { return } + if (getClosest(element.parentNode, 'defs')) { + return + } svgCanvas.uniquifyElems(element) @@ -407,8 +501,10 @@ export const setSvgString = function (xmlString, preventUndo) { // For Firefox: Put all paint elems in defs if (isGecko()) { const svgDefs = findDefs() - const findElems = content.querySelectorAll('linearGradient, radialGradient, pattern') - Array.prototype.forEach.call(findElems, function (ele) { + const findElems = content.querySelectorAll( + 'linearGradient, radialGradient, pattern' + ) + Array.prototype.forEach.call(findElems, (ele) => { svgDefs.appendChild(ele) }) } @@ -435,7 +531,7 @@ export const setSvgString = function (xmlString, preventUndo) { attrs.height = vb[3] // handle content that doesn't have a viewBox } else { - ['width', 'height'].forEach(function (dim) { + ;['width', 'height'].forEach((dim) => { // Set to 100 if not given const val = content.getAttribute(dim) || '100%' if (String(val).substr(-1) === '%') { @@ -452,10 +548,12 @@ export const setSvgString = function (xmlString, preventUndo) { // Give ID for any visible layer children missing one const chiElems = content.children - Array.prototype.forEach.call(chiElems, function (chiElem) { + Array.prototype.forEach.call(chiElems, (chiElem) => { const visElems = chiElem.querySelectorAll(svgCanvas.getVisElems()) - Array.prototype.forEach.call(visElems, function (elem) { - if (!elem.id) { elem.id = svgCanvas.getNextId() } + Array.prototype.forEach.call(visElems, (elem) => { + if (!elem.id) { + elem.id = svgCanvas.getNextId() + } }) }) @@ -468,8 +566,12 @@ export const setSvgString = function (xmlString, preventUndo) { // Just in case negative numbers are given or // result from the percs calculation - if (attrs.width <= 0) { attrs.width = 100 } - if (attrs.height <= 0) { attrs.height = 100 } + if (attrs.width <= 0) { + attrs.width = 100 + } + if (attrs.height <= 0) { + attrs.height = 100 + } for (const [key, value] of Object.entries(attrs)) { content.setAttribute(key, value) @@ -482,7 +584,9 @@ export const setSvgString = function (xmlString, preventUndo) { const width = content.getAttribute('width') const height = content.getAttribute('height') const changes = { width: width, height: height } - batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgRoot(), changes)) + batchCmd.addSubCommand( + new ChangeElementCommand(svgCanvas.getSvgRoot(), changes) + ) // reset zoom svgCanvas.setZoom(1) @@ -502,22 +606,24 @@ export const setSvgString = function (xmlString, preventUndo) { } /** -* This function imports the input SVG XML as a `` in the ``, then adds a -* `` 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 -*/ -export const importSvgString = function (xmlString) { + * This function imports the input SVG XML as a `` in the ``, then adds a + * `` 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 + */ +const importSvgString = (xmlString) => { const dataStorage = svgCanvas.getDataStorage() - let j; let ts; let useEl + let j + let ts + let useEl try { // Get unique ID const uid = encode64(xmlString.length + xmlString).substr(0, 32) @@ -564,7 +670,10 @@ export const importSvgString = function (xmlString) { canvash = Number(svgCanvas.getSvgContent().getAttribute('height')) // imported content should be 1/3 of the canvas on its largest dimension - ts = innerh > innerw ? 'scale(' + (canvash / 3) / vb[3] + ')' : 'scale(' + (canvash / 3) / vb[2] + ')' + ts = + innerh > innerw + ? 'scale(' + canvash / 3 / vb[3] + ')' + : 'scale(' + canvash / 3 / vb[2] + ')' // Hack to make recalculateDimensions understand how to scale ts = 'translate(0) ' + ts + ' translate(0)' @@ -576,8 +685,10 @@ export const importSvgString = function (xmlString) { // Move all gradients into root for Firefox, workaround for this bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=353575 // TODO: Make this properly undo-able. - const elements = svg.querySelectorAll('linearGradient, radialGradient, pattern') - Array.prototype.forEach.call(elements, function (el) { + const elements = svg.querySelectorAll( + 'linearGradient, radialGradient, pattern' + ) + Array.prototype.forEach.call(elements, (el) => { defs.appendChild(el) }) } @@ -587,7 +698,8 @@ export const importSvgString = function (xmlString) { symbol.append(first) } const attrs = svg.attributes - for (const attr of attrs) { // Ok for `NamedNodeMap` + for (const attr of attrs) { + // Ok for `NamedNodeMap` symbol.setAttribute(attr.nodeName, attr.value) } symbol.id = svgCanvas.getNextId() @@ -604,9 +716,11 @@ export const importSvgString = function (xmlString) { useEl = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'use') useEl.id = svgCanvas.getNextId() - svgCanvas.setHref(useEl, '#' + symbol.id); - - (svgCanvas.getCurrentGroup() || svgCanvas.getCurrentDrawing().getCurrentLayer()).append(useEl) + svgCanvas.setHref(useEl, '#' + symbol.id) + ;( + svgCanvas.getCurrentGroup() || + svgCanvas.getCurrentDrawing().getCurrentLayer() + ).append(useEl) batchCmd.addSubCommand(new InsertElementCommand(useEl)) svgCanvas.clearSelection() @@ -637,17 +751,17 @@ export const importSvgString = function (xmlString) { * @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} Resolves to a Data URL (string|false) -*/ -export const embedImage = function (src) { + * 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} Resolves to a Data URL (string|false) + */ +const embedImage = (src) => { // Todo: Remove this Promise in favor of making an async/await `Image.load` utility - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { // load in the image and once it's loaded, get the dimensions const imgI = new Image() - imgI.addEventListener('load', (e) => { + imgI.addEventListener('load', e => { // create a canvas the same size as the raster image const cvs = document.createElement('canvas') cvs.width = e.currentTarget.width @@ -665,24 +779,28 @@ export const embedImage = function (src) { svgCanvas.setGoodImage(src) resolve(svgCanvas.getEncodableImages(src)) }) - imgI.addEventListener('error', (e) => { - reject(new Error(`error loading image: ${e.currentTarget.attributes.src.value}`)) + imgI.addEventListener('error', e => { + reject( + new Error( + `error loading image: ${e.currentTarget.attributes.src.value}` + ) + ) }) imgI.setAttribute('src', src) }) } /** -* @typedef {PlainObject} module:svgcanvas.IssuesAndCodes -* @property {string[]} issueCodes The locale-independent code names -* @property {string[]} issues The localized descriptions -*/ + * @typedef {PlainObject} module:svgcanvas.IssuesAndCodes + * @property {string[]} issueCodes The locale-independent code names + * @property {string[]} issues The localized descriptions + */ /** -* Codes only is useful for locale-independent detection. -* @returns {module:svgcanvas.IssuesAndCodes} -*/ -function getIssues () { + * Codes only is useful for locale-independent detection. + * @returns {module:svgcanvas.IssuesAndCodes} + */ +const getIssues = () => { const uiStrings = svgCanvas.getUIStrings() // remove the selected outline before serializing svgCanvas.clearSelection() @@ -693,15 +811,15 @@ function getIssues () { // Selector and notice const issueList = { - feGaussianBlur: uiStrings.exportNoBlur, - foreignObject: uiStrings.exportNoforeignObject, - '[stroke-dasharray]': uiStrings.exportNoDashArray + feGaussianBlur: uiStrings.NoBlur, + foreignObject: uiStrings.NoforeignObject, + '[stroke-dasharray]': uiStrings.NoDashArray } const content = svgCanvas.getSvgContent() // Add font/text check if Canvas Text API is not implemented if (!('font' in document.querySelector('CANVAS').getContext('2d'))) { - issueList.text = uiStrings.exportNoText + issueList.text = uiStrings.NoText } for (const [sel, descr] of Object.entries(issueList)) { @@ -713,166 +831,177 @@ function getIssues () { return { issues, issueCodes } } /** -* @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 -*/ + * @typedef {PlainObject} module:svgcanvas.ImageedResults + * @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} WindowName A convenience for passing along a `window.name` to target a window on which the 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} Resolves to {@link module:svgcanvas.ImageExportedResults} -*/ -export const rasterExport = async function (imgType, quality, exportWindowName, opts = {}) { - const type = imgType === 'ICO' ? 'BMP' : (imgType || 'PNG') + * Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image, + * then calls "ed" with an object including the string, image + * information, and any issues found. + * @function module:svgcanvas.SvgCanvas#raster + * @param {"PNG"|"JPEG"|"BMP"|"WEBP"|"ICO"} [imgType="PNG"] + * @param {Float} [quality] Between 0 and 1 + * @param {string} [WindowName] + * @param {PlainObject} [opts] + * @param {boolean} [opts.avoidEvent] + * @fires module:svgcanvas.SvgCanvas#event:ed + * @todo Confirm/fix ICO type + * @returns {Promise} Resolves to {@link module:svgcanvas.ImageedResults} + */ +const rasterExport = async (imgType, quality, WindowName, opts = {}) => { + const type = imgType === 'ICO' ? 'BMP' : imgType || 'PNG' const mimeType = 'image/' + type.toLowerCase() const { issues, issueCodes } = getIssues() const svg = svgCanvas.svgCanvasToString() const iframe = document.createElement('iframe') - iframe.onload = function () { + iframe.onload = () => { const iframedoc = iframe.contentDocument || iframe.contentWindow.document const ele = svgCanvas.getSvgContent() const cln = ele.cloneNode(true) iframedoc.body.appendChild(cln) - setTimeout(function () { + setTimeout(() => { // eslint-disable-next-line promise/catch-or-return - html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then((canvas) => { - return new Promise((resolve) => { - const dataURLType = type.toLowerCase() - const datauri = quality - ? canvas.toDataURL('image/' + dataURLType, quality) - : canvas.toDataURL('image/' + dataURLType) - iframe.parentNode.removeChild(iframe) - let bloburl + html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then( + canvas => { + return new Promise(resolve => { + const dataURLType = type.toLowerCase() + const datauri = quality + ? canvas.toDataURL('image/' + dataURLType, quality) + : canvas.toDataURL('image/' + dataURLType) + iframe.parentNode.removeChild(iframe) + let bloburl - function done () { - const obj = { - datauri, - bloburl, - svg, - issues, - issueCodes, - type: imgType, - mimeType, - quality, - exportWindowName + const done = () => { + const obj = { + datauri, + bloburl, + svg, + issues, + issueCodes, + type: imgType, + mimeType, + quality, + WindowName + } + if (!opts.avoidEvent) { + svgCanvas.call('ed', obj) + } + resolve(obj) } - if (!opts.avoidEvent) { - svgCanvas.call('exported', obj) + if (canvas.toBlob) { + canvas.toBlob( + blob => { + bloburl = createObjectURL(blob) + done() + }, + mimeType, + quality + ) + return } - resolve(obj) - } - if (canvas.toBlob) { - canvas.toBlob((blob) => { - bloburl = createObjectURL(blob) - done() - }, mimeType, quality) - return - } - bloburl = dataURLToObjectURL(datauri) - done() - }) - }) + bloburl = dataURLToObjectURL(datauri) + done() + }) + } + ) }, 1000) } document.body.appendChild(iframe) } /** -* @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 {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.PDFedResults` 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 -*/ + * @typedef {PlainObject} module:svgcanvas.PDFedResults + * @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} WindowName + */ /** -* 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} Resolves to {@link module:svgcanvas.PDFExportedResults} -*/ -export const exportPDF = async ( - exportWindowName, + * Generates a PDF based on the current image, then calls "edPDF" with + * an object including the string, the data URL, and any issues found. + * @function module:svgcanvas.SvgCanvas#PDF + * @param {string} [WindowName] Will also be used for the download file name here + * @param {external:jsPDF.OutputType} [outputType="dataurlstring"] + * @fires module:svgcanvas.SvgCanvas#event:edPDF + * @returns {Promise} Resolves to {@link module:svgcanvas.PDFedResults} + */ +const exportPDF = async ( + WindowName, outputType = isChrome() ? 'save' : undefined ) => { const res = svgCanvas.getResolution() const orientation = res.w > res.h ? 'landscape' : 'portrait' - const unit = 'pt' // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for export purposes + const unit = 'pt' // curConfig.baseUnit; // We could use baseUnit, but that is presumably not intended for purposes const iframe = document.createElement('iframe') - iframe.onload = function () { + iframe.onload = () => { const iframedoc = iframe.contentDocument || iframe.contentWindow.document const ele = svgCanvas.getSvgContent() const cln = ele.cloneNode(true) iframedoc.body.appendChild(cln) - setTimeout(function () { + setTimeout(() => { // eslint-disable-next-line promise/catch-or-return - html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then((canvas) => { - const imgData = canvas.toDataURL('image/png') - const doc = new JsPDF({ - orientation: orientation, - unit: unit, - format: [res.w, res.h] - }) - const docTitle = svgCanvas.getDocumentTitle() - doc.setProperties({ - title: docTitle - }) - doc.addImage(imgData, 'PNG', 0, 0, res.w, res.h) - iframe.parentNode.removeChild(iframe) - const { issues, issueCodes } = getIssues() - outputType = outputType || 'dataurlstring' - const obj = { issues, issueCodes, exportWindowName, outputType } - obj.output = doc.output(outputType, outputType === 'save' ? (exportWindowName || 'svg.pdf') : undefined) - svgCanvas.call('exportedPDF', obj) - return obj - }) + html2canvas(iframedoc.body, { useCORS: true, allowTaint: true }).then( + canvas => { + const imgData = canvas.toDataURL('image/png') + const doc = new JsPDF({ + orientation: orientation, + unit: unit, + format: [res.w, res.h] + }) + const docTitle = svgCanvas.getDocumentTitle() + doc.setProperties({ + title: docTitle + }) + doc.addImage(imgData, 'PNG', 0, 0, res.w, res.h) + iframe.parentNode.removeChild(iframe) + const { issues, issueCodes } = getIssues() + outputType = outputType || 'dataurlstring' + const obj = { issues, issueCodes, WindowName, outputType } + obj.output = doc.output( + outputType, + outputType === 'save' ? WindowName || 'svg.pdf' : undefined + ) + svgCanvas.call('edPDF', obj) + return obj + } + ) }, 1000) } document.body.appendChild(iframe) } /** -* 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} -*/ -export const uniquifyElemsMethod = function (g) { + * 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 uniquifyElemsMethod = (g) => { const ids = {} // TODO: Handle markers and connectors. These are not yet re-identified properly // as their referring elements do not get remapped. @@ -882,9 +1011,17 @@ export const uniquifyElemsMethod = function (g) { // // Problem #1: if svg_1 gets renamed, we do not update the polyline's se:connector attribute // Problem #2: if the polyline svg_7 gets renamed, we do not update the marker id nor the polyline's marker-end attribute - const refElems = ['filter', 'linearGradient', 'pattern', 'radialGradient', 'symbol', 'textPath', 'use'] + const refElems = [ + 'filter', + 'linearGradient', + 'pattern', + 'radialGradient', + 'symbol', + 'textPath', + 'use' + ] - walkTree(g, function (n) { + walkTree(g, (n) => { // if it's an element node if (n.nodeType === 1) { // and the element has an ID @@ -899,7 +1036,7 @@ export const uniquifyElemsMethod = function (g) { // now search for all attributes on this element that might refer // to other elements - svgCanvas.getrefAttrs().forEach(function (attr) { + svgCanvas.getrefAttrs().forEach((attr) => { const attrnode = n.getAttributeNode(attr) if (attrnode) { // the incoming file has been sanitized, so we should be able to safely just strip off the leading # @@ -933,7 +1070,9 @@ export const uniquifyElemsMethod = function (g) { // in ids, we now have a map of ids, elements and attributes, let's re-identify for (const oldid in ids) { - if (!oldid) { continue } + if (!oldid) { + continue + } const { elem } = ids[oldid] if (elem) { const newid = svgCanvas.getNextId() @@ -961,12 +1100,12 @@ export const uniquifyElemsMethod = function (g) { } /** -* Assigns reference data for each use element. -* @function module:svgcanvas.SvgCanvas#setUseData -* @param {Element} parent -* @returns {void} -*/ -export const setUseDataMethod = function (parent) { + * Assigns reference data for each use element. + * @function module:svgcanvas.SvgCanvas#setUseData + * @param {Element} parent + * @returns {void} + */ +const setUseDataMethod = (parent) => { let elems = parent if (parent.tagName !== 'use') { @@ -974,11 +1113,13 @@ export const setUseDataMethod = function (parent) { elems = elems.querySelectorAll('use') } - Array.prototype.forEach.call(elems, function (el, _) { + Array.prototype.forEach.call(elems, (el, _) => { const dataStorage = svgCanvas.getDataStorage() const id = svgCanvas.getHref(el).substr(1) const refElem = svgCanvas.getElement(id) - if (!refElem) { return } + if (!refElem) { + return + } dataStorage.put(el, 'ref', refElem) if (refElem.tagName === 'symbol' || refElem.tagName === 'svg') { dataStorage.put(el, 'symbol', refElem) @@ -988,26 +1129,36 @@ export const setUseDataMethod = function (parent) { } /** -* Looks at DOM elements inside the `` 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 -*/ -export const removeUnusedDefElemsMethod = function () { + * Looks at DOM elements inside the `` 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 + */ +const removeUnusedDefElemsMethod = () => { const defs = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, 'defs') - if (!defs || !defs.length) { return 0 } + if (!defs || !defs.length) { + return 0 + } // if (!defs.firstChild) { return; } const defelemUses = [] let numRemoved = 0 - const attrs = ['fill', 'stroke', 'filter', 'marker-start', 'marker-mid', 'marker-end'] + const attrs = [ + 'fill', + 'stroke', + 'filter', + 'marker-start', + 'marker-mid', + 'marker-end' + ] const alen = attrs.length const allEls = svgCanvas.getSvgContent().getElementsByTagNameNS(NS.SVG, '*') const allLen = allEls.length - let i; let j + let i + let j for (i = 0; i < allLen; i++) { const el = allEls[i] for (j = 0; j < alen; j++) { @@ -1024,8 +1175,10 @@ export const removeUnusedDefElemsMethod = function () { } } - Array.prototype.forEach.call(defs, function (def, i) { - const defelems = def.querySelectorAll('linearGradient, radialGradient, filter, marker, svg, symbol') + Array.prototype.forEach.call(defs, (def, i) => { + const defelems = def.querySelectorAll( + 'linearGradient, radialGradient, filter, marker, svg, symbol' + ) i = defelems.length while (i--) { const defelem = defelems[i] @@ -1042,31 +1195,48 @@ export const removeUnusedDefElemsMethod = function () { return numRemoved } /** -* Converts gradients from userSpaceOnUse to objectBoundingBox. -* @function module:svgcanvas.SvgCanvas#convertGradients -* @param {Element} elem -* @returns {void} -*/ -export const convertGradientsMethod = function (elem) { + * Converts gradients from userSpaceOnUse to objectBoundingBox. + * @function module:svgcanvas.SvgCanvas#convertGradients + * @param {Element} elem + * @returns {void} + */ +const convertGradientsMethod = (elem) => { let elems = elem.querySelectorAll('linearGradient, radialGradient') if (!elems.length && isWebkit()) { // Bug in webkit prevents regular *Gradient selector search - elems = Array.prototype.filter.call(elem.querySelectorAll('*'), function (curThis) { - return (curThis.tagName.includes('Gradient')) + elems = Array.prototype.filter.call(elem.querySelectorAll('*'), ( + curThis + ) => { + return curThis.tagName.includes('Gradient') }) } - Array.prototype.forEach.call(elems, function (grad) { + Array.prototype.forEach.call(elems, (grad) => { if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') { const svgContent = svgCanvas.getSvgContent() // TODO: Support more than one element with this ref by duplicating parent grad - let fillStrokeElems = svgContent.querySelectorAll('[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]') + let fillStrokeElems = svgContent.querySelectorAll( + '[fill="url(#' + grad.id + ')"],[stroke="url(#' + grad.id + ')"]' + ) if (!fillStrokeElems.length) { - const tmpFillStrokeElems = svgContent.querySelectorAll('[*|href="#' + grad.id + '"]') + const tmpFillStrokeElems = svgContent.querySelectorAll( + '[*|href="#' + grad.id + '"]' + ) if (!tmpFillStrokeElems.length) { return } else { - if ((tmpFillStrokeElems[0].tagName === 'linearGradient' || tmpFillStrokeElems[0].tagName === 'radialGradient') && tmpFillStrokeElems[0].getAttribute('gradientUnits') === 'userSpaceOnUse') { - fillStrokeElems = svgContent.querySelectorAll('[fill="url(#' + tmpFillStrokeElems[0].id + ')"],[stroke="url(#' + tmpFillStrokeElems[0].id + ')"]') + if ( + (tmpFillStrokeElems[0].tagName === 'linearGradient' || + tmpFillStrokeElems[0].tagName === 'radialGradient') && + tmpFillStrokeElems[0].getAttribute('gradientUnits') === + 'userSpaceOnUse' + ) { + fillStrokeElems = svgContent.querySelectorAll( + '[fill="url(#' + + tmpFillStrokeElems[0].id + + ')"],[stroke="url(#' + + tmpFillStrokeElems[0].id + + ')"]' + ) } else { return } @@ -1077,7 +1247,9 @@ export const convertGradientsMethod = function (elem) { // This will occur if the element is inside a or a , // in which we shouldn't need to convert anyway. - if (!bb) { return } + if (!bb) { + return + } if (grad.tagName === 'linearGradient') { const gCoords = { x1: grad.getAttribute('x1'), diff --git a/src/svgcanvas/svgcanvas.js b/src/svgcanvas/svgcanvas.js index 287c5693..9a5f5e5e 100644 --- a/src/svgcanvas/svgcanvas.js +++ b/src/svgcanvas/svgcanvas.js @@ -13,91 +13,108 @@ import 'pathseg' // SVGPathSeg Polyfill (see https://github.com/progers/pathseg) import * as pathModule from './path.js' import * as history from './history.js' import * as draw from './draw.js' -import { - init as pasteInit, pasteElementsMethod -} from './paste-elem.js' +import { init as pasteInit, pasteElementsMethod } from './paste-elem.js' import { init as touchInit } from './touch.js' import { svgRootElement } from './svgroot.js' import { - init as undoInit, changeSelectedAttributeNoUndoMethod, + init as undoInit, + changeSelectedAttributeNoUndoMethod, changeSelectedAttributeMethod } 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 selectionInit } from './selection.js' +import { init as textActionsInit, textActionsMethod } from './text-actions.js' import { init as eventInit } from './event.js' -import { init as jsonInit, getJsonFromSvgElements, addSVGElementsFromJson } from './json.js' +import { + init as jsonInit, + getJsonFromSvgElements, + addSVGElementsFromJson +} from './json.js' import * as elemGetSet from './elem-get-set.js' import { init as selectedElemInit } from './selected-elem.js' import { - init as blurInit, setBlurNoUndo, setBlurOffsets, setBlur + init as blurInit, + setBlurNoUndo, + setBlurOffsets, + setBlur } from './blur-event.js' import { sanitizeSvg } from './sanitize.js' import { getReverseNS, NS } from './namespaces.js' import { - assignAttributes, cleanupElement, getElement, getUrlFromAttr, - findDefs, getHref, setHref, getRefElem, getRotationAngle, - getBBoxOfElementAsPath, convertToPath, encode64, decode64, - getVisibleElements, init as utilsInit, - getBBox as utilsGetBBox, getStrokedBBoxDefaultVisible, blankPageObjectURL, - $id, $qa, $qq, $click, getFeGaussianBlur, stringToHTML, insertChildAtIndex + assignAttributes, + cleanupElement, + getElement, + getUrlFromAttr, + findDefs, + getHref, + setHref, + getRefElem, + getRotationAngle, + getBBoxOfElementAsPath, + convertToPath, + encode64, + decode64, + getVisibleElements, + init as utilsInit, + getBBox as utilsGetBBox, + getStrokedBBoxDefaultVisible, + blankPageObjectURL, + $id, + $qa, + $qq, + $click, + getFeGaussianBlur, + stringToHTML, + insertChildAtIndex } from './utilities.js' import { - matrixMultiply, hasMatrixTransform, transformListToTransform + matrixMultiply, + hasMatrixTransform, + transformListToTransform } from './math.js' -import { - convertToNum, init as unitsInit, getTypeMap -} from '../common/units.js' -import { - svgCanvasToString, svgToString, setSvgString, exportPDF, setUseDataMethod, - init as svgInit, importSvgString, embedImage, rasterExport, - uniquifyElemsMethod, removeUnusedDefElemsMethod, convertGradientsMethod -} from './svg-exec.js' -import { - remapElement, - init as coordsInit -} from './coords.js' +import { convertToNum, init as unitsInit, getTypeMap } from '../common/units.js' +import { init as svgInit } from './svg-exec.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 { - getSelectorManager, - Selector, - init as selectInit -} from './select.js' -import { - clearSvgContentElementInit, - init as clearInit -} from './clear.js' -import { - getClosest, getParents, mergeDeep + getClosest, + getParents, + mergeDeep } from '../editor/components/jgraduate/Util.js' import dataStorage from './dataStorage.js' -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'] +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' +] const THRESHOLD_DIST = 0.8 const STEP_COUNT = 10 const CLIPBOARD_ID = 'svgedit_clipboard' /** -* The main SvgCanvas class that manages all SVG-related functions. -* @memberof module:svgcanvas -* -*/ + * The main SvgCanvas class that manages all SVG-related functions. + * @memberof module:svgcanvas + * + */ 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 - */ + * @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) { // imported function made available as methods this.initializeSvgCanvasMethods() @@ -119,7 +136,8 @@ class SvgCanvas { this.events = {} // Object to contain editor event names and callback functions this.rootSctm = null // Root Current Transformation Matrix in user units this.drawnPath = null - this.freehand = { // Mouse events + this.freehand = { + // Mouse events minx: null, miny: null, maxx: null, @@ -141,7 +159,8 @@ class SvgCanvas { this.idprefix = 'svg_' // Prefix string for element IDs this.encodableImages = {} - this.curConfig = { // Default configuration options + this.curConfig = { + // Default configuration options show_outside_canvas: true, selectNew: true, dimensions: [640, 480] @@ -176,7 +195,9 @@ class SvgCanvas { // Object containing data for the currently selected styles const allProperties = { shape: { - fill: (this.curConfig.initFill.color === 'none' ? '' : '#') + this.curConfig.initFill.color, + fill: + (this.curConfig.initFill.color === 'none' ? '' : '#') + + this.curConfig.initFill.color, fill_paint: null, fill_opacity: this.curConfig.initFill.opacity, stroke: '#' + this.curConfig.initStroke.color, @@ -238,7 +259,7 @@ class SvgCanvas { elemGetSet.init(this) // prevent links from being followed in the canvas - const handleLinkInCanvas = (e) => { + const handleLinkInCanvas = e => { e.preventDefault() return false } @@ -261,11 +282,11 @@ class SvgCanvas { selectedElemInit(this) /** -* Transfers sessionStorage from one tab to another. -* @param {!Event} ev Storage event. -* @returns {void} -*/ - const storageChange = (ev) => { + * Transfers sessionStorage from one tab to another. + * @param {!Event} ev Storage event. + * @returns {void} + */ + const storageChange = ev => { if (!ev.newValue) return // This is a call from removeItem. if (ev.key === CLIPBOARD_ID + '_startup') { // Another tab asked for our sessionStorage. @@ -289,33 +310,114 @@ class SvgCanvas { this.clear() } // End constructor - getSvgOption () { return this.saveOptions } - setSvgOption (key, value) { this.saveOptions[key] = value } - getSelectedElements () { return this.selectedElements } - setSelectedElements (key, value) { this.selectedElements[key] = value } - setEmptySelectedElements () { this.selectedElements = [] } - getSvgRoot () { return this.svgroot } - getDOMDocument () { return this.svgdoc } - getDOMContainer () { return this.container } - getCurConfig () { return this.curConfig } - setIdPrefix (p) { this.idprefix = p } - getCurrentDrawing () { return this.current_drawing_ } - getCurShape () { return this.curShape } - getCurrentGroup () { return this.currentGroup } - getBaseUnit () { return this.curConfig.baseUnit } - getHeight () { return this.svgContent.getAttribute('height') / this.zoom } - getWidth () { return this.svgContent.getAttribute('width') / this.zoom } - getRoundDigits () { return this.saveOptions.round_digits } - getSnappingStep () { return this.curConfig.snappingStep } - getGridSnapping () { return this.curConfig.gridSnapping } - getStartTransform () { return this.startTransform } - setStartTransform (transform) { this.startTransform = transform } - getZoom () { return this.zoom } - round (val) { return Number.parseInt(val * this.zoom) / this.zoom } - createSVGElement (jsonMap) { return this.addSVGElementsFromJson(jsonMap) } - getContainer () { return this.container } - setStarted (s) { this.started = s } - getRubberBox () { return this.rubberBox } + getSvgOption () { + return this.saveOptions + } + + setSvgOption (key, value) { + this.saveOptions[key] = value + } + + getSelectedElements () { + return this.selectedElements + } + + setSelectedElements (key, value) { + this.selectedElements[key] = value + } + + setEmptySelectedElements () { + this.selectedElements = [] + } + + getSvgRoot () { + return this.svgroot + } + + getDOMDocument () { + return this.svgdoc + } + + getDOMContainer () { + return this.container + } + + getCurConfig () { + return this.curConfig + } + + setIdPrefix (p) { + this.idprefix = p + } + + getCurrentDrawing () { + return this.current_drawing_ + } + + getCurShape () { + return this.curShape + } + + getCurrentGroup () { + return this.currentGroup + } + + getBaseUnit () { + return this.curConfig.baseUnit + } + + getHeight () { + return this.svgContent.getAttribute('height') / this.zoom + } + + getWidth () { + return this.svgContent.getAttribute('width') / this.zoom + } + + getRoundDigits () { + return this.saveOptions.round_digits + } + + getSnappingStep () { + return this.curConfig.snappingStep + } + + getGridSnapping () { + return this.curConfig.gridSnapping + } + + getStartTransform () { + return this.startTransform + } + + setStartTransform (transform) { + this.startTransform = transform + } + + getZoom () { + return this.zoom + } + + round (val) { + return Number.parseInt(val * this.zoom) / this.zoom + } + + createSVGElement (jsonMap) { + return this.addSVGElementsFromJson(jsonMap) + } + + getContainer () { + return this.container + } + + setStarted (s) { + this.started = s + } + + getRubberBox () { + return this.rubberBox + } + setRubberBox (rb) { this.rubberBox = rb return this.rubberBox @@ -341,126 +443,402 @@ class SvgCanvas { this.call('changed', [elem]) } - getCurrentMode () { return this.currentMode } + getCurrentMode () { + return this.currentMode + } + setCurrentMode (cm) { this.currentMode = cm return this.currentMode } - getDrawnPath () { return this.drawnPath } + getDrawnPath () { + return this.drawnPath + } + setDrawnPath (dp) { this.drawnPath = dp return this.drawnPath } - setCurrentGroup (cg) { this.currentGroup = cg } - changeSvgContent () { this.call('changed', [this.svgContent]) } - getStarted () { return this.started } - getCanvas () { return this } - getrootSctm () { return this.rootSctm } - getStartX () { return this.startX } - setStartX (value) { this.startX = value } - getStartY () { return this.startY } - setStartY (value) { this.startY = value } - getRStartX () { return this.rStartX } - getRStartY () { return this.rStartY } - getInitBbox () { return this.initBbox } - getCurrentResizeMode () { return this.currentResizeMode } - getJustSelected () { return this.justSelected } - getOpacAni () { return this.opacAni } - getParameter () { return this.parameter } - getNextParameter () { return this.nextParameter } - getStepCount () { return STEP_COUNT } - getThreSholdDist () { return THRESHOLD_DIST } - getSumDistance () { return this.sumDistance } - getStart (key) { return this.start[key] } - getEnd (key) { return this.end[key] } - getbSpline (key) { return this.bSpline[key] } - getNextPos (key) { return this.nextPos[key] } - getControllPoint1 (key) { return this.controllPoint1[key] } - getControllPoint2 (key) { return this.controllPoint2[key] } - getFreehand (key) { return this.freehand[key] } - getDrawing () { return this.getCurrentDrawing() } - getDAttr () { return this.dAttr } - getLastGoodImgUrl () { return this.lastGoodImgUrl } - getCurText (key) { return this.curText[key] } - setDAttr (value) { this.dAttr = value } - setEnd (key, value) { this.end[key] = value } - setControllPoint1 (key, value) { this.controllPoint1[key] = value } - setControllPoint2 (key, value) { this.controllPoint2[key] = value } - setJustSelected (value) { this.justSelected = value } - setParameter (value) { this.parameter = value } - setStart (value) { this.start = value } - setRStartX (value) { this.rStartX = value } - setRStartY (value) { this.rStartY = value } - setSumDistance (value) { this.sumDistance = value } - setbSpline (value) { this.bSpline = value } - setNextPos (value) { this.nextPos = value } - setNextParameter (value) { this.nextParameter = value } - setCurText (key, value) { this.curText[key] = value } - setFreehand (key, value) { this.freehand[key] = value } - setCurBBoxes (value) { this.curBBoxes = value } - getCurBBoxes () { return this.curBBoxes } - setInitBbox (value) { this.initBbox = value } - setRootSctm (value) { this.rootSctm = value } - setCurrentResizeMode (value) { this.currentResizeMode = value } - getLastClickPoint (key) { return this.lastClickPoint[key] } - setLastClickPoint (value) { this.lastClickPoint = value } - getId () { return this.getCurrentDrawing().getId() } - getUIStrings () { return this.uiStrings } - getNsMap () { return this.nsMap } - getSvgOptionApply () { return this.saveOptions.apply } - getSvgOptionImages () { return this.saveOptions.images } - getEncodableImages (key) { return this.encodableImages[key] } - setEncodableImages (key, value) { this.encodableImages[key] = value } - getVisElems () { return visElems } - getIdPrefix () { return this.idprefix } - getDataStorage () { return dataStorage } - setZoom (value) { this.zoom = value } - getImportIds (key) { return this.importIds[key] } - setImportIds (key, value) { this.importIds[key] = value } - setRemovedElements (key, value) { this.removedElements[key] = value } - setSvgContent (value) { this.svgContent = value } - getrefAttrs () { return refAttrs } - getcanvg () { return canvg } - setCanvas (key, value) { this[key] = value } - setCurProperties (key, value) { this.curProperties[key] = value } - getCurProperties (key) { return this.curProperties[key] } - setCurShape (key, value) { this.curShape[key] = value } - gettingSelectorManager () { return this.selectorManager } - getContentW () { return this.contentW } - getContentH () { return this.contentH } - getClipboardID () { return CLIPBOARD_ID } - getSvgContent () { return this.svgContent } - getExtensions () { return this.extensions } - getSelector () { return Selector } - getMode () { return this.currentMode } // The current editor mode string - getNextId () { return this.getCurrentDrawing().getNextId() } - getCurCommand () { return this.curCommand } - setCurCommand (value) { this.curCommand = value } - getFilter () { return this.filter } - setFilter (value) { this.filter = value } - getFilterHidden () { return this.filterHidden } - setFilterHidden (value) { this.filterHidden = value } + setCurrentGroup (cg) { + this.currentGroup = cg + } + + changeSvgContent () { + this.call('changed', [this.svgContent]) + } + + getStarted () { + return this.started + } + + getCanvas () { + return this + } + + getrootSctm () { + return this.rootSctm + } + + getStartX () { + return this.startX + } + + setStartX (value) { + this.startX = value + } + + getStartY () { + return this.startY + } + + setStartY (value) { + this.startY = value + } + + getRStartX () { + return this.rStartX + } + + getRStartY () { + return this.rStartY + } + + getInitBbox () { + return this.initBbox + } + + getCurrentResizeMode () { + return this.currentResizeMode + } + + getJustSelected () { + return this.justSelected + } + + getOpacAni () { + return this.opacAni + } + + getParameter () { + return this.parameter + } + + getNextParameter () { + return this.nextParameter + } + + getStepCount () { + return STEP_COUNT + } + + getThreSholdDist () { + return THRESHOLD_DIST + } + + getSumDistance () { + return this.sumDistance + } + + getStart (key) { + return this.start[key] + } + + getEnd (key) { + return this.end[key] + } + + getbSpline (key) { + return this.bSpline[key] + } + + getNextPos (key) { + return this.nextPos[key] + } + + getControllPoint1 (key) { + return this.controllPoint1[key] + } + + getControllPoint2 (key) { + return this.controllPoint2[key] + } + + getFreehand (key) { + return this.freehand[key] + } + + getDrawing () { + return this.getCurrentDrawing() + } + + getDAttr () { + return this.dAttr + } + + getLastGoodImgUrl () { + return this.lastGoodImgUrl + } + + getCurText (key) { + return this.curText[key] + } + + setDAttr (value) { + this.dAttr = value + } + + setEnd (key, value) { + this.end[key] = value + } + + setControllPoint1 (key, value) { + this.controllPoint1[key] = value + } + + setControllPoint2 (key, value) { + this.controllPoint2[key] = value + } + + setJustSelected (value) { + this.justSelected = value + } + + setParameter (value) { + this.parameter = value + } + + setStart (value) { + this.start = value + } + + setRStartX (value) { + this.rStartX = value + } + + setRStartY (value) { + this.rStartY = value + } + + setSumDistance (value) { + this.sumDistance = value + } + + setbSpline (value) { + this.bSpline = value + } + + setNextPos (value) { + this.nextPos = value + } + + setNextParameter (value) { + this.nextParameter = value + } + + setCurText (key, value) { + this.curText[key] = value + } + + setFreehand (key, value) { + this.freehand[key] = value + } + + setCurBBoxes (value) { + this.curBBoxes = value + } + + getCurBBoxes () { + return this.curBBoxes + } + + setInitBbox (value) { + this.initBbox = value + } + + setRootSctm (value) { + this.rootSctm = value + } + + setCurrentResizeMode (value) { + this.currentResizeMode = value + } + + getLastClickPoint (key) { + return this.lastClickPoint[key] + } + + setLastClickPoint (value) { + this.lastClickPoint = value + } + + getId () { + return this.getCurrentDrawing().getId() + } + + getUIStrings () { + return this.uiStrings + } + + getNsMap () { + return this.nsMap + } + + getSvgOptionApply () { + return this.saveOptions.apply + } + + getSvgOptionImages () { + return this.saveOptions.images + } + + getEncodableImages (key) { + return this.encodableImages[key] + } + + setEncodableImages (key, value) { + this.encodableImages[key] = value + } + + getVisElems () { + return visElems + } + + getIdPrefix () { + return this.idprefix + } + + getDataStorage () { + return dataStorage + } + + setZoom (value) { + this.zoom = value + } + + getImportIds (key) { + return this.importIds[key] + } + + setImportIds (key, value) { + this.importIds[key] = value + } + + setRemovedElements (key, value) { + this.removedElements[key] = value + } + + setSvgContent (value) { + this.svgContent = value + } + + getrefAttrs () { + return refAttrs + } + + getcanvg () { + return canvg + } + + setCanvas (key, value) { + this[key] = value + } + + setCurProperties (key, value) { + this.curProperties[key] = value + } + + getCurProperties (key) { + return this.curProperties[key] + } + + setCurShape (key, value) { + this.curShape[key] = value + } + + gettingSelectorManager () { + return this.selectorManager + } + + getContentW () { + return this.contentW + } + + getContentH () { + return this.contentH + } + + getClipboardID () { + return CLIPBOARD_ID + } + + getSvgContent () { + return this.svgContent + } + + getExtensions () { + return this.extensions + } + + getSelector () { + return Selector + } + + getMode () { + return this.currentMode + } // The current editor mode string + + getNextId () { + return this.getCurrentDrawing().getNextId() + } + + getCurCommand () { + return this.curCommand + } + + setCurCommand (value) { + this.curCommand = value + } + + getFilter () { + return this.filter + } + + setFilter (value) { + this.filter = value + } + + getFilterHidden () { + return this.filterHidden + } + + setFilterHidden (value) { + this.filterHidden = value + } + /** - * 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} - */ + * 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} + */ setMode (name) { this.pathActions.clear(true) this.textActions.clear() - this.curProperties = (this.selectedElements[0]?.nodeName === 'text') ? this.curText : this.curShape + this.curProperties = + this.selectedElements[0]?.nodeName === 'text' + ? this.curText + : this.curShape this.currentMode = name } /** - * Clears the current document. This is not an undoable action. - * @function module:svgcanvas.SvgCanvas#clear - * @fires module:svgcanvas.SvgCanvas#event:cleared - * @returns {void} - */ + * Clears the current document. This is not an undoable action. + * @function module:svgcanvas.SvgCanvas#clear + * @fires module:svgcanvas.SvgCanvas#event:cleared + * @returns {void} + */ clear () { this.pathActions.clear() this.clearSelection() @@ -481,10 +859,16 @@ class SvgCanvas { async addExtension (name, extInitFunc, { importLocale }) { if (typeof extInitFunc !== 'function') { - throw new TypeError('Function argument expected for `svgcanvas.addExtension`') + throw new TypeError( + 'Function argument expected for `svgcanvas.addExtension`' + ) } if (name in this.extensions) { - throw new Error('Cannot add extension "' + name + '", an extension by that name already exists.') + throw new Error( + 'Cannot add extension "' + + name + + '", an extension by that name already exists.' + ) } const argObj = { importLocale, @@ -501,14 +885,17 @@ class SvgCanvas { return this.call('extension_added', extObj) } - addCommandToHistory (cmd) { this.undoMgr.addCommandToHistory(cmd) } + addCommandToHistory (cmd) { + this.undoMgr.addCommandToHistory(cmd) + } + restoreRefElements (elem) { // Look for missing reference elements, restore any found const attrs = {} refAttrs.forEach((item, _) => { attrs[item] = elem.getAttribute(item) }) - Object.values(attrs).forEach((val) => { + Object.values(attrs).forEach(val => { if (val?.startsWith('url(')) { const id = getUrlFromAttr(val).substr(1) const ref = getElement(id) @@ -535,12 +922,12 @@ class SvgCanvas { } /** - * Attaches a callback function to an event. - * @function module:svgcanvas.SvgCanvas#bind - * @param {string} 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 - */ + * Attaches a callback function to an event. + * @function module:svgcanvas.SvgCanvas#bind + * @param {string} 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 + */ bind (ev, f) { const old = this.events[ev] this.events[ev] = f @@ -548,9 +935,9 @@ class SvgCanvas { } /** -* Flash the clipboard data momentarily on localStorage so all tabs can see. -* @returns {void} -*/ + * Flash the clipboard data momentarily on localStorage so all tabs can see. + * @returns {void} + */ flashStorage () { const data = sessionStorage.getItem(CLIPBOARD_ID) localStorage.setItem(CLIPBOARD_ID, data) @@ -560,26 +947,30 @@ class SvgCanvas { } /** - * 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} - */ + * 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} + */ selectOnly (elems, showGrips) { this.clearSelection(true) this.addToSelection(elems, showGrips) } /** - * Removes elements from the selection. - * @function module:svgcanvas.SvgCanvas#removeFromSelection - * @param {Element[]} elemsToRemove - An array of elements to remove from selection - * @returns {void} - */ + * Removes elements from the selection. + * @function module:svgcanvas.SvgCanvas#removeFromSelection + * @param {Element[]} elemsToRemove - An array of elements to remove from selection + * @returns {void} + */ removeFromSelection (elemsToRemove) { - if (!this.selectedElements[0]) { return } - if (!elemsToRemove.length) { return } + if (!this.selectedElements[0]) { + return + } + if (!elemsToRemove.length) { + return + } // find every element and remove it from our array copy const newSelectedItems = [] @@ -590,7 +981,8 @@ class SvgCanvas { // keep the item if (!elemsToRemove.includes(elem)) { newSelectedItems.push(elem) - } else { // remove the item and its selector + } else { + // remove the item and its selector this.selectorManager.releaseSelector(elem) } } @@ -600,10 +992,10 @@ class SvgCanvas { } /** - * Clears the selection, then adds all elements in the current layer to the selection. - * @function module:svgcanvas.SvgCanvas#selectAllInCurrentLayer - * @returns {void} - */ + * Clears the selection, then adds all elements in the current layer to the selection. + * @function module:svgcanvas.SvgCanvas#selectAllInCurrentLayer + * @returns {void} + */ selectAllInCurrentLayer () { const currentLayer = this.getCurrentDrawing().getCurrentLayer() if (currentLayer) { @@ -621,89 +1013,125 @@ class SvgCanvas { } /** - * @function module:svgcanvas.SvgCanvas#getSnapToGrid - * @returns {boolean} The current snap to grid setting - */ - getSnapToGrid () { return this.curConfig.gridSnapping } + * @function module:svgcanvas.SvgCanvas#getSnapToGrid + * @returns {boolean} The current snap to grid setting + */ + getSnapToGrid () { + return this.curConfig.gridSnapping + } + /** - * @function module:svgcanvas.SvgCanvas#getVersion - * @returns {string} A string which describes the revision number of SvgCanvas. - */ - getVersion () { return 'svgcanvas.js ($Rev$)' } + * @function module:svgcanvas.SvgCanvas#getVersion + * @returns {string} A string which describes the revision number of SvgCanvas. + */ + getVersion () { + 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} - */ + * 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} + */ setUiStrings (strs) { Object.assign(this.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} - */ - setConfig (opts) { Object.assign(this.curConfig, opts) } - /** - * @function module:svgcanvas.SvgCanvas#getDocumentTitle - * @returns {string|void} The current document title or an empty string if not found - */ - getDocumentTitle () { return this.getTitle(this.svgContent) } - getOffset () { - return { x: Number(this.svgContent.getAttribute('x')), y: Number(this.svgContent.getAttribute('y')) } + * Update configuration options with given values. + * @function module:svgcanvas.SvgCanvas#setConfig + * @param {module:SVGEditor.Config} opts - Object with options + * @returns {void} + */ + setConfig (opts) { + Object.assign(this.curConfig, opts) } - getColor (type) { return this.curProperties[type] } - setStrokePaint (paint) { this.setPaint('stroke', paint) } /** - * @function module:svgcanvas.SvgCanvas#setFillPaint - * @param {module:jGraduate~Paint} paint - * @returns {void} - */ - setFillPaint (paint) { this.setPaint('fill', paint) } + * @function module:svgcanvas.SvgCanvas#getDocumentTitle + * @returns {string|void} The current document title or an empty string if not found + */ + getDocumentTitle () { + return this.getTitle(this.svgContent) + } + + getOffset () { + return { + x: Number(this.svgContent.getAttribute('x')), + y: Number(this.svgContent.getAttribute('y')) + } + } + + getColor (type) { + return this.curProperties[type] + } + + setStrokePaint (paint) { + this.setPaint('stroke', paint) + } + /** - * @function module:svgcanvas.SvgCanvas#getStrokeWidth - * @returns {Float|string} The current stroke-width value - */ - getStrokeWidth () { return this.curProperties.stroke_width } + * @function module:svgcanvas.SvgCanvas#setFillPaint + * @param {module:jGraduate~Paint} paint + * @returns {void} + */ + setFillPaint (paint) { + this.setPaint('fill', paint) + } + /** - * @function module:svgcanvas.SvgCanvas#getStyle - * @returns {module:svgcanvas.StyleOptions} current style options - */ - getStyle () { return this.curShape } + * @function module:svgcanvas.SvgCanvas#getStrokeWidth + * @returns {Float|string} The current stroke-width value + */ + getStrokeWidth () { + return this.curProperties.stroke_width + } + /** - * Sets the given opacity on the current selected elements. - * @function module:svgcanvas.SvgCanvas#setOpacity - * @param {string} val - * @returns {void} - */ + * @function module:svgcanvas.SvgCanvas#getStyle + * @returns {module:svgcanvas.StyleOptions} current style options + */ + getStyle () { + return this.curShape + } + + /** + * Sets the given opacity on the current selected elements. + * @function module:svgcanvas.SvgCanvas#setOpacity + * @param {string} val + * @returns {void} + */ setOpacity (val) { this.curShape.opacity = val this.changeSelectedAttribute('opacity', val) } /** - * @function module:svgcanvas.SvgCanvas#getFillOpacity - * @returns {Float} the current fill opacity - */ - getFillOpacity () { return this.curShape.fill_opacity } + * @function module:svgcanvas.SvgCanvas#getFillOpacity + * @returns {Float} the current fill opacity + */ + getFillOpacity () { + return this.curShape.fill_opacity + } + /** - * @function module:svgcanvas.SvgCanvas#getStrokeOpacity - * @returns {string} the current stroke opacity - */ - getStrokeOpacity () { return this.curShape.stroke_opacity } + * @function module:svgcanvas.SvgCanvas#getStrokeOpacity + * @returns {string} the current stroke opacity + */ + getStrokeOpacity () { + return this.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} - */ + * 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} + */ setPaintOpacity (type, val, preventUndo) { this.curShape[type + '_opacity'] = val if (!preventUndo) { @@ -714,21 +1142,21 @@ class SvgCanvas { } /** - * 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 - */ + * 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 + */ getPaintOpacity (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 - */ + * 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 + */ getBlur (elem) { let val = 0 if (elem) { @@ -750,32 +1178,35 @@ class SvgCanvas { } /** - * Sets a given URL to be a "last good image" URL. - * @function module:svgcanvas.SvgCanvas#setGoodImage - * @param {string} val - * @returns {void} - */ - setGoodImage (val) { this.lastGoodImgUrl = val } + * Sets a given URL to be a "last good image" URL. + * @function module:svgcanvas.SvgCanvas#setGoodImage + * @param {string} val + * @returns {void} + */ + setGoodImage (val) { + this.lastGoodImgUrl = val + } + /** - * Returns the current drawing as raw SVG XML text. - * @function module:svgcanvas.SvgCanvas#getSvgString - * @returns {string} The current drawing as raw SVG XML text. - */ + * Returns the current drawing as raw SVG XML text. + * @function module:svgcanvas.SvgCanvas#getSvgString + * @returns {string} The current drawing as raw SVG XML text. + */ getSvgString () { this.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 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} + */ randomizeIds (enableRandomization) { if (arguments.length > 0 && enableRandomization === false) { draw.randomizeIds(false, this.getCurrentDrawing()) @@ -785,25 +1216,31 @@ class SvgCanvas { } /** - * 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. - */ + * 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. + */ convertToPath (elem, getBBox) { // if elems not given, recursively call convertPath for all selected elements. if (!elem) { const elems = this.selectedElements - elems.forEach((el) => { - if (el) { this.convertToPath(el) } + elems.forEach(el => { + if (el) { + this.convertToPath(el) + } }) return undefined } if (getBBox) { - return getBBoxOfElementAsPath(elem, this.addSVGElementsFromJson, this.pathActions) + return getBBoxOfElementAsPath( + elem, + this.addSVGElementsFromJson, + this.pathActions + ) } // TODO: Why is this applying attributes from this.curShape, then inside utilities.convertToPath it's pulling addition attributes from elem? // TODO: If convertToPath is called with one elem, this.curShape and elem are probably the same; but calling with multiple is a bug or cool feature. @@ -823,11 +1260,11 @@ class SvgCanvas { } /** - * 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} - */ + * 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} + */ cutSelectedElements () { this.copySelectedElements() this.deleteSelectedElements() @@ -838,17 +1275,8 @@ class SvgCanvas { this.addSVGElementsFromJson = addSVGElementsFromJson this.clearSvgContentElement = clearSvgContentElementInit this.textActions = textActionsMethod - this.getIntersectionList = getIntersectionListMethod this.getStrokedBBox = getStrokedBBoxDefaultVisible this.getVisibleElements = getVisibleElements - this.uniquifyElems = uniquifyElemsMethod - this.setUseData = setUseDataMethod - this.convertGradients = convertGradientsMethod - this.setSvgString = setSvgString - this.importSvgString = importSvgString - this.runExtensions = runExtensionsMethod - this.clearSelection = clearSelectionMethod - this.addToSelection = addToSelectionMethod this.stringToHTML = stringToHTML this.insertChildAtIndex = insertChildAtIndex this.getClosest = getClosest @@ -871,18 +1299,7 @@ class SvgCanvas { this.remapElement = remapElement this.recalculateDimensions = recalculateDimensions this.sanitizeSvg = sanitizeSvg - this.groupSvgElem = groupSvgElem // Wrap an SVG element into a group element, mark the group as 'gsvg'. - this.prepareSvg = prepareSvg // Runs the SVG Document through the sanitizer and then updates its paths. - this.setRotationAngle = setRotationAngle // Removes any old rotations if present, prepends a new rotation at the transformed center. - this.recalculateAllSelectedDimensions = recalculateAllSelectedDimensions // Runs `recalculateDimensions` on selected elements,adding changes to a single batch command. this.pasteElements = pasteElementsMethod // Remembers the current selected elements on the clipboard. - this.getMouseTarget = getMouseTargetMethod - this.removeUnusedDefElems = removeUnusedDefElemsMethod // remove DOM elements inside the `` if they are notreferred to, - this.svgCanvasToString = svgCanvasToString // Main function to set up the SVG content for output. - this.svgToString = svgToString // Sub function ran on each SVG element to convert it to a string as desired. - this.embedImage = embedImage // Converts a given image file to a data URL when possibl - this.rasterExport = rasterExport // Generates a PNG (or JPG, BMP, WEBP) Data URL based on the current image - this.exportPDF = exportPDF // Generates a PDF based on the current image, then calls "exportedPDF" this.identifyLayers = draw.identifyLayers this.createLayer = draw.createLayer this.cloneLayer = draw.cloneLayer @@ -896,46 +1313,6 @@ class SvgCanvas { this.mergeAllLayers = draw.mergeAllLayers this.leaveContext = draw.leaveContext this.setContext = draw.setContext - this.getBold = elemGetSet.getBoldMethod // Check whether selected element is bold or not. - this.setBold = elemGetSet.setBoldMethod // Make the selected element bold or normal. - this.getItalic = elemGetSet.getItalicMethod // Check whether selected element is in italics or not. - this.setItalic = elemGetSet.setItalicMethod // Make the selected element italic or normal. - this.hasTextDecoration = elemGetSet.hasTextDecorationMethod // Check whether the selected element has the given text decoration or not. - this.addTextDecoration = elemGetSet.addTextDecorationMethod // Adds the given value to the text decoration - this.removeTextDecoration = elemGetSet.removeTextDecorationMethod // Removes the given value from the text decoration - this.setTextAnchor = elemGetSet.setTextAnchorMethod // Set the new text anchor. - this.setLetterSpacing = elemGetSet.setLetterSpacingMethod // Set the new letter spacing. - this.setWordSpacing = elemGetSet.setWordSpacingMethod // Set the new word spacing. - this.setTextLength = elemGetSet.setTextLengthMethod // Set the new text length. - this.setLengthAdjust = elemGetSet.setLengthAdjustMethod // Set the new length adjust. - this.getFontFamily = elemGetSet.getFontFamilyMethod // The current font family - this.setFontFamily = elemGetSet.setFontFamilyMethod // Set the new font family. - this.setFontColor = elemGetSet.setFontColorMethod // Set the new font color. - this.getFontColor = elemGetSet.getFontColorMethod // The current font color - this.getFontSize = elemGetSet.getFontSizeMethod // The current font size - this.setFontSize = elemGetSet.setFontSizeMethod // Applies the given font size to the selected element. - this.getText = elemGetSet.getTextMethod // current text (`textContent`) of the selected element - this.setTextContent = elemGetSet.setTextContentMethod // Updates the text element with the given string. - this.setImageURL = elemGetSet.setImageURLMethod // Sets the new image URL for the selected image element - this.setLinkURL = elemGetSet.setLinkURLMethod // Sets the new link URL for the selected anchor element. - this.setRectRadius = elemGetSet.setRectRadiusMethod // Sets the `rx` and `ry` values to the selected `rect` element - this.makeHyperlink = elemGetSet.makeHyperlinkMethod // Wraps the selected element(s) in an anchor element or converts group to one. - this.removeHyperlink = elemGetSet.removeHyperlinkMethod - this.setSegType = elemGetSet.setSegTypeMethod // Sets the new segment type to the selected segment(s). - this.setStrokeWidth = elemGetSet.setStrokeWidthMethod // Sets the stroke width for the current selected elements. - this.getResolution = elemGetSet.getResolutionMethod // The current dimensions and zoom level in an object - this.getTitle = elemGetSet.getTitleMethod // the current group/SVG's title contents or `undefined` if no element - this.setGroupTitle = elemGetSet.setGroupTitleMethod // Sets the group/SVG's title content. - this.setStrokeAttr = elemGetSet.setStrokeAttrMethod // Set the given stroke-related attribute the given value for selected elements. - this.setBackground = elemGetSet.setBackgroundMethod // Set the background of the editor (NOT the actual document). - this.setDocumentTitle = elemGetSet.setDocumentTitleMethod // Adds/updates a title element for the document with the given name. - this.getEditorNS = elemGetSet.getEditorNSMethod // Returns the editor's namespace URL, optionally adding it to the root element. - this.setResolution = elemGetSet.setResolutionMethod // Changes the document's dimensions to the given size. - this.setBBoxZoom = elemGetSet.setBBoxZoomMethod // Sets the zoom level on the canvas-side based on the given value. - this.setCurrentZoom = elemGetSet.setZoomMethod // Sets the zoom to the given level. - this.setColor = elemGetSet.setColorMethod // Change the current stroke/fill color/gradien - this.setGradient = elemGetSet.setGradientMethod // Apply the current gradient to selected element's fill or stroke. - this.setPaint = elemGetSet.setPaintMethod // Set a color/gradient to a fill/stroke. this.changeSelectedAttributeNoUndo = changeSelectedAttributeNoUndoMethod // This function makes the changes to the elements. It does not add the change to the history stack. this.changeSelectedAttribute = changeSelectedAttributeMethod // Change the given/selected element and add the original value to the history stack. this.setBlurNoUndo = setBlurNoUndo // Sets the `stdDeviation` blur value on the selected element without being undoable. @@ -949,6 +1326,9 @@ class SvgCanvas { this.$qq = $qq this.$qa = $qa this.$click = $click + this.encode64 = encode64 + this.decode64 = decode64 + this.mergeDeep = mergeDeep } } // End class