diff --git a/.eslintrc.js b/.eslintrc.js index ca0431ea..c0796c27 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,8 @@ module.exports = { rules: { /** @todo len should probably more 120-150 */ "max-len": [ "warn", { "code": 250 } ], + "indent": [ "error", 2 ], + "no-var": "error", /** @todo jsdoc should be made warn or error */ "valid-jsdoc": "off", /** @todo cognitive complexity should be much lower (25-50?) */ diff --git a/cypress/integration/unit/recalculate.js b/cypress/integration/unit/recalculate.js index 00272765..e81610f0 100644 --- a/cypress/integration/unit/recalculate.js +++ b/cypress/integration/unit/recalculate.js @@ -33,7 +33,7 @@ describe('recalculate', function () { return this._storage.has(element) && this._storage.get(element).has(key); }, remove: function (element, key) { - var ret = this._storage.get(element).delete(key); + let ret = this._storage.get(element).delete(key); if (!this._storage.get(element).size === 0) { this._storage.delete(element); } diff --git a/cypress/integration/unit/select.js b/cypress/integration/unit/select.js index 803549ba..0dba2bb0 100644 --- a/cypress/integration/unit/select.js +++ b/cypress/integration/unit/select.js @@ -27,7 +27,7 @@ describe('select', function () { return this._storage.has(element) && this._storage.get(element).has(key); }, remove: function (element, key) { - var ret = this._storage.get(element).delete(key); + let ret = this._storage.get(element).delete(key); if (!this._storage.get(element).size === 0) { this._storage.delete(element); } diff --git a/docs/tutorials/ExtensionDocs.md b/docs/tutorials/ExtensionDocs.md index b9581cea..9744d624 100644 --- a/docs/tutorials/ExtensionDocs.md +++ b/docs/tutorials/ExtensionDocs.md @@ -145,13 +145,13 @@ import { importSetGlobalDefault } from '../external/dynamic-import-polyfill/impo (async () => { -const url = `${svgEditor.curConfig.extPath}ext-locale//.js`; -const localeStrings = await importSetGlobalDefault(url, { - global: 'svgEditorExtensionLocale_imagelib_' + lang -}); + const url = `${svgEditor.curConfig.extPath}ext-locale//.js`; + const localeStrings = await importSetGlobalDefault(url, { + global: 'svgEditorExtensionLocale_imagelib_' + lang + }); -// Use `localeStrings` -console.info(localeStrings); + // Use `localeStrings` + console.info(localeStrings); })(); ``` diff --git a/src/common/browser.js b/src/common/browser.js index e83f1cc0..3470cb34 100644 --- a/src/common/browser.js +++ b/src/common/browser.js @@ -11,7 +11,7 @@ import 'pathseg'; import { NS } from './namespaces.js'; const supportsSVG_ = (function () { -return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg').createSVGRect); + return Boolean(document.createElementNS && document.createElementNS(NS.SVG, 'svg').createSVGRect); }()); /** @@ -33,108 +33,108 @@ const isMac_ = userAgent.includes('Macintosh'); const isTouch_ = 'ontouchstart' in window; const supportsSelectors_ = (function () { -return Boolean(svg.querySelector); + return Boolean(svg.querySelector); }()); const supportsXpath_ = (function () { -return Boolean(document.evaluate); + return Boolean(document.evaluate); }()); // segList functions (for FF1.5 and 2.0) const supportsPathReplaceItem_ = (function () { -const path = document.createElementNS(NS.SVG, 'path'); -path.setAttribute('d', 'M0,0 10,10'); -const seglist = path.pathSegList; -const seg = path.createSVGPathSegLinetoAbs(5, 5); -try { - seglist.replaceItem(seg, 1); - return true; -}catch (err) {/* empty */} -return false; + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 10,10'); + const seglist = path.pathSegList; + const seg = path.createSVGPathSegLinetoAbs(5, 5); + try { + seglist.replaceItem(seg, 1); + return true; + }catch (err) {/* empty */} + return false; }()); const supportsPathInsertItemBefore_ = (function () { -const path = document.createElementNS(NS.SVG, 'path'); -path.setAttribute('d', 'M0,0 10,10'); -const seglist = path.pathSegList; -const seg = path.createSVGPathSegLinetoAbs(5, 5); -try { - seglist.insertItemBefore(seg, 1); - return true; -}catch (err) {/* empty */} -return false; + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 10,10'); + const seglist = path.pathSegList; + const seg = path.createSVGPathSegLinetoAbs(5, 5); + try { + seglist.insertItemBefore(seg, 1); + return true; + }catch (err) {/* empty */} + return false; }()); // text character positioning (for IE9 and now Chrome) const supportsGoodTextCharPos_ = (function () { -const svgroot = document.createElementNS(NS.SVG, 'svg'); -const svgcontent = document.createElementNS(NS.SVG, 'svg'); -document.documentElement.append(svgroot); -svgcontent.setAttribute('x', 5); -svgroot.append(svgcontent); -const text = document.createElementNS(NS.SVG, 'text'); -text.textContent = 'a'; -svgcontent.append(text); -try { // Chrome now fails here - const pos = text.getStartPositionOfChar(0).x; - return (pos === 0); -} catch (err) { - return false; -} finally { - svgroot.remove(); -} + const svgroot = document.createElementNS(NS.SVG, 'svg'); + const svgcontent = document.createElementNS(NS.SVG, 'svg'); + document.documentElement.append(svgroot); + svgcontent.setAttribute('x', 5); + svgroot.append(svgcontent); + const text = document.createElementNS(NS.SVG, 'text'); + text.textContent = 'a'; + svgcontent.append(text); + try { // Chrome now fails here + const pos = text.getStartPositionOfChar(0).x; + return (pos === 0); + } catch (err) { + return false; + } finally { + svgroot.remove(); + } }()); const supportsPathBBox_ = (function () { -const svgcontent = document.createElementNS(NS.SVG, 'svg'); -document.documentElement.append(svgcontent); -const path = document.createElementNS(NS.SVG, 'path'); -path.setAttribute('d', 'M0,0 C0,0 10,10 10,0'); -svgcontent.append(path); -const bbox = path.getBBox(); -svgcontent.remove(); -return (bbox.height > 4 && bbox.height < 5); + const svgcontent = document.createElementNS(NS.SVG, 'svg'); + document.documentElement.append(svgcontent); + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 C0,0 10,10 10,0'); + svgcontent.append(path); + const bbox = path.getBBox(); + svgcontent.remove(); + return (bbox.height > 4 && bbox.height < 5); }()); // Support for correct bbox sizing on groups with horizontal/vertical lines const supportsHVLineContainerBBox_ = (function () { -const svgcontent = document.createElementNS(NS.SVG, 'svg'); -document.documentElement.append(svgcontent); -const path = document.createElementNS(NS.SVG, 'path'); -path.setAttribute('d', 'M0,0 10,0'); -const path2 = document.createElementNS(NS.SVG, 'path'); -path2.setAttribute('d', 'M5,0 15,0'); -const g = document.createElementNS(NS.SVG, 'g'); -g.append(path, path2); -svgcontent.append(g); -const bbox = g.getBBox(); -svgcontent.remove(); -// Webkit gives 0, FF gives 10, Opera (correctly) gives 15 -return (bbox.width === 15); + const svgcontent = document.createElementNS(NS.SVG, 'svg'); + document.documentElement.append(svgcontent); + const path = document.createElementNS(NS.SVG, 'path'); + path.setAttribute('d', 'M0,0 10,0'); + const path2 = document.createElementNS(NS.SVG, 'path'); + path2.setAttribute('d', 'M5,0 15,0'); + const g = document.createElementNS(NS.SVG, 'g'); + g.append(path, path2); + svgcontent.append(g); + const bbox = g.getBBox(); + svgcontent.remove(); + // Webkit gives 0, FF gives 10, Opera (correctly) gives 15 + return (bbox.width === 15); }()); const supportsEditableText_ = (function () { // TODO: Find better way to check support for this -return isOpera_; + return isOpera_; }()); const supportsNonScalingStroke_ = (function () { -const rect = document.createElementNS(NS.SVG, 'rect'); -rect.setAttribute('style', 'vector-effect:non-scaling-stroke'); -return rect.style.vectorEffect === 'non-scaling-stroke'; + const rect = document.createElementNS(NS.SVG, 'rect'); + rect.setAttribute('style', 'vector-effect:non-scaling-stroke'); + return rect.style.vectorEffect === 'non-scaling-stroke'; }()); let supportsNativeSVGTransformLists_ = (function () { -const rect = document.createElementNS(NS.SVG, 'rect'); -const rxform = rect.transform.baseVal; -const t1 = svg.createSVGTransform(); -rxform.appendItem(t1); -const r1 = rxform.getItem(0); -const isSVGTransform = (o) => { + const rect = document.createElementNS(NS.SVG, 'rect'); + const rxform = rect.transform.baseVal; + const t1 = svg.createSVGTransform(); + rxform.appendItem(t1); + const r1 = rxform.getItem(0); + const isSVGTransform = (o) => { // https://developer.mozilla.org/en-US/docs/Web/API/SVGTransform - return o && typeof o === 'object' && typeof o.setMatrix === 'function' && 'angle' in o; -}; -return isSVGTransform(r1) && isSVGTransform(t1) && + return o && typeof o === 'object' && typeof o.setMatrix === 'function' && 'angle' in o; + }; + return isSVGTransform(r1) && isSVGTransform(t1) && r1.type === t1.type && r1.angle === t1.angle && r1.matrix.a === t1.matrix.a && r1.matrix.b === t1.matrix.b && diff --git a/src/editor/Editor.js b/src/editor/Editor.js index d2e6d424..ab75ffc1 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -281,11 +281,11 @@ class Editor extends EditorStartup { parentSelector = document; } - var parents = []; - var p = el.parentNode; + let parents = []; + let p = el.parentNode; while (p !== parentSelector) { - var o = p; + let o = p; parents.push(o); p = o.parentNode; } @@ -609,12 +609,12 @@ class Editor extends EditorStartup { if (!this.multiselected) { // eslint-disable-next-line sonarjs/no-small-switch switch (mode) { - case 'rotate': { - const ang = this.svgCanvas.getRotationAngle(elem); - $id('angle').value = ang; - (ang === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled'); - break; - } + case 'rotate': { + const ang = this.svgCanvas.getRotationAngle(elem); + $id('angle').value = ang; + (ang === 0) ? $id('tool_reorient').classList.add('disabled') : $id('tool_reorient').classList.remove('disabled'); + break; + } } } this.svgCanvas.runExtensions('elementTransition', /** @type {module:svgcanvas.SvgCanvas#event:ext_elementTransition} */ { diff --git a/src/editor/EditorStartup.js b/src/editor/EditorStartup.js index f40d68d1..58b2f87e 100644 --- a/src/editor/EditorStartup.js +++ b/src/editor/EditorStartup.js @@ -255,8 +255,8 @@ class EditorStartup { }); function addListenerMulti(element, eventNames, listener) { - var events = eventNames.split(' '); - for (var i=0, iLen=events.length; i r.text()) + .then( (r) => r.text()) // eslint-disable-next-line promise/always-return - .then( (data) => { - post({ href, data }); - return data; - }) + .then( (data) => { + post({ href, data }); + return data; + }) // eslint-disable-next-line no-console - .catch( (error) => console.log(error)); + .catch( (error) => console.log(error)); } return false; }); diff --git a/src/editor/extensions/ext-mathjax/ext-mathjax.js b/src/editor/extensions/ext-mathjax/ext-mathjax.js index dc65204d..5b9175cb 100644 --- a/src/editor/extensions/ext-mathjax/ext-mathjax.js +++ b/src/editor/extensions/ext-mathjax/ext-mathjax.js @@ -232,7 +232,7 @@ export default { }, callback () { const head = document.head || document.getElementsByTagName('head')[0], - style = document.createElement('style'); + style = document.createElement('style'); style.textContent = '#mathjax fieldset{' + 'padding: 5px;' + 'margin: 5px;' + @@ -281,7 +281,7 @@ export default { 'display: block;' + 'height: 100px;' + '}'; - head.appendChild(style); + head.appendChild(style); } }; } diff --git a/src/editor/extensions/ext-overview_window/ext-overview_window.js b/src/editor/extensions/ext-overview_window/ext-overview_window.js index 7ae87b0f..582b7963 100644 --- a/src/editor/extensions/ext-overview_window/ext-overview_window.js +++ b/src/editor/extensions/ext-overview_window/ext-overview_window.js @@ -81,7 +81,7 @@ export default { // Compensate for changes in zoom and canvas size. const updateViewDimensions = function () { - const viewWidth = parseFloat(getComputedStyle($id("svgroot"), null).width.replace("px", "")); + const viewWidth = parseFloat(getComputedStyle($id("svgroot"), null).width.replace("px", "")); const viewHeight = parseFloat(getComputedStyle($id("svgroot"), null).height.replace("px", "")); const viewX = 640; diff --git a/src/editor/extensions/ext-placemark/ext-placemark.js b/src/editor/extensions/ext-placemark/ext-placemark.js index d2553640..a9b4da52 100644 --- a/src/editor/extensions/ext-placemark/ext-placemark.js +++ b/src/editor/extensions/ext-placemark/ext-placemark.js @@ -104,7 +104,7 @@ export default { const items = txt.split(';'); selElems.forEach((elem) => { if (elem && elem.getAttribute('class').includes('placemark')) { - var elements = elem.children; + let elements = elem.children; Array.prototype.forEach.call(elements, function(i, _){ const [ , , type, n ] = i.id.split('_'); if (type === 'txt') { @@ -125,7 +125,7 @@ export default { font = font.join(' '); selElems.forEach((elem) => { if (elem && elem.getAttribute('class').includes('placemark')) { - var elements = elem.children; + let elements = elem.children; Array.prototype.forEach.call(elements, function(i, _){ const [ , , type ] = i.id.split('_'); if (type === 'txt') { diff --git a/src/editor/extensions/ext-server_opensave/ext-php_savefile.js b/src/editor/extensions/ext-server_opensave/ext-php_savefile.js index b6a188ec..3314a0d0 100644 --- a/src/editor/extensions/ext-server_opensave/ext-php_savefile.js +++ b/src/editor/extensions/ext-server_opensave/ext-php_savefile.js @@ -30,7 +30,7 @@ export default { }).then( (res) => { return res; }) - .catch( (error) => { console.info('error =', error);}); + .catch( (error) => { console.info('error =', error);}); } }); } diff --git a/src/editor/extensions/ext-storage/storageDialog.js b/src/editor/extensions/ext-storage/storageDialog.js index 93333678..0ebe5d6a 100644 --- a/src/editor/extensions/ext-storage/storageDialog.js +++ b/src/editor/extensions/ext-storage/storageDialog.js @@ -94,7 +94,7 @@ export class SeStorageDialog extends HTMLElement { * @param {any} name * @returns {void} */ - init (i18next) { + init (i18next) { this.setAttribute('common-ok', i18next.t('common.ok')); this.setAttribute('common-cancel', i18next.t('common.cancel')); this.setAttribute('notify-editor_pref_msg', i18next.t('notification.editorPreferencesMsg')); diff --git a/src/editor/locale.js b/src/editor/locale.js index 9e482cd0..499cb35e 100644 --- a/src/editor/locale.js +++ b/src/editor/locale.js @@ -8,7 +8,7 @@ * */ - import i18next from 'i18next'; +import i18next from 'i18next'; /** * Used, for example, in the ImageLibs extension, to present libraries diff --git a/src/editor/panels/BottomPanel.js b/src/editor/panels/BottomPanel.js index ad7e521b..8ae363e2 100644 --- a/src/editor/panels/BottomPanel.js +++ b/src/editor/panels/BottomPanel.js @@ -16,132 +16,132 @@ class BottomPanel { constructor (editor) { this.editor = editor; } - /** + /** * @type {module} */ - get selectedElement () { - return this.editor.selectedElement; - } - /** + get selectedElement () { + return this.editor.selectedElement; + } + /** * @type {module} */ - get multiselected () { - return this.editor.multiselected; - } - /** + get multiselected () { + return this.editor.multiselected; + } + /** * @type {module} */ - changeStrokeWidth (e) { - let val = e.target.value; - if (val === 0 && this.editor.selectedElement && [ 'line', 'polyline' ].includes(this.editor.selectedElement.nodeName)) { - val = 1; - } - this.editor.svgCanvas.setStrokeWidth(val); + changeStrokeWidth (e) { + let val = e.target.value; + if (val === 0 && this.editor.selectedElement && [ 'line', 'polyline' ].includes(this.editor.selectedElement.nodeName)) { + val = 1; } - /** + this.editor.svgCanvas.setStrokeWidth(val); + } + /** * @type {module} */ - changeZoom (value) { - switch (value) { - case 'canvas': - case 'selection': - case 'layer': - case 'content': - this.editor.zoomChanged(window, value); - break; - default: - { - const zoomlevel = Number(value) / 100; - if (zoomlevel < 0.001) { - value = 0.1; - return; - } - const zoom = this.editor.svgCanvas.getZoom(); - const wArea = this.editor.workarea; - this.editor.zoomChanged(window, { - width: 0, - height: 0, - // center pt of scroll position - x: (wArea.scrollLeft + parseFloat(getComputedStyle(wArea, null).width.replace("px", "")) / 2) / zoom, - y: (wArea.scrollTop + parseFloat(getComputedStyle(wArea, null).height.replace("px", "")) / 2) / zoom, - zoom: zoomlevel - }, true); - } + changeZoom (value) { + switch (value) { + case 'canvas': + case 'selection': + case 'layer': + case 'content': + this.editor.zoomChanged(window, value); + break; + default: + { + const zoomlevel = Number(value) / 100; + if (zoomlevel < 0.001) { + value = 0.1; + return; } + const zoom = this.editor.svgCanvas.getZoom(); + const wArea = this.editor.workarea; + this.editor.zoomChanged(window, { + width: 0, + height: 0, + // center pt of scroll position + x: (wArea.scrollLeft + parseFloat(getComputedStyle(wArea, null).width.replace("px", "")) / 2) / zoom, + y: (wArea.scrollTop + parseFloat(getComputedStyle(wArea, null).height.replace("px", "")) / 2) / zoom, + zoom: zoomlevel + }, true); } - /** + } + } + /** * @fires module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate * @returns {void} */ - updateToolButtonState () { - const bNoFill = (this.editor.svgCanvas.getColor('fill') === 'none'); - const bNoStroke = (this.editor.svgCanvas.getColor('stroke') === 'none'); - const buttonsNeedingStroke = [ 'tool_fhpath', 'tool_line' ]; - const buttonsNeedingFillAndStroke = [ - 'tools_rect', 'tools_ellipse', - 'tool_text', 'tool_path' - ]; + updateToolButtonState () { + const bNoFill = (this.editor.svgCanvas.getColor('fill') === 'none'); + const bNoStroke = (this.editor.svgCanvas.getColor('stroke') === 'none'); + const buttonsNeedingStroke = [ 'tool_fhpath', 'tool_line' ]; + const buttonsNeedingFillAndStroke = [ + 'tools_rect', 'tools_ellipse', + 'tool_text', 'tool_path' + ]; - if (bNoStroke) { - buttonsNeedingStroke.forEach((btn) => { - // if btn is pressed, change to select button - if ($id(btn).pressed) { - this.editor.leftPanel.clickSelect(); - } - $id(btn).disabled = true; - }); - } else { - buttonsNeedingStroke.forEach((btn) => { - $id(btn).disabled = false; - }); - } - if (bNoStroke && bNoFill) { - // eslint-disable-next-line sonarjs/no-identical-functions - buttonsNeedingFillAndStroke.forEach((btn) => { - // if btn is pressed, change to select button - if ($id(btn).pressed) { - this.editor.leftPanel.clickSelect(); - } - $id(btn).disabled = true; - }); - } else { - buttonsNeedingFillAndStroke.forEach((btn) => { - $id(btn).disabled = false; - }); - } - this.editor.svgCanvas.runExtensions( - 'toolButtonStateUpdate', - /** @type {module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate} */ { - nofill: bNoFill, - nostroke: bNoStroke + if (bNoStroke) { + buttonsNeedingStroke.forEach((btn) => { + // if btn is pressed, change to select button + if ($id(btn).pressed) { + this.editor.leftPanel.clickSelect(); } - ); + $id(btn).disabled = true; + }); + } else { + buttonsNeedingStroke.forEach((btn) => { + $id(btn).disabled = false; + }); } - /** + if (bNoStroke && bNoFill) { + // eslint-disable-next-line sonarjs/no-identical-functions + buttonsNeedingFillAndStroke.forEach((btn) => { + // if btn is pressed, change to select button + if ($id(btn).pressed) { + this.editor.leftPanel.clickSelect(); + } + $id(btn).disabled = true; + }); + } else { + buttonsNeedingFillAndStroke.forEach((btn) => { + $id(btn).disabled = false; + }); + } + this.editor.svgCanvas.runExtensions( + 'toolButtonStateUpdate', + /** @type {module:svgcanvas.SvgCanvas#event:ext_toolButtonStateUpdate} */ { + nofill: bNoFill, + nostroke: bNoStroke + } + ); + } + /** * @type {module} */ - handleColorPicker (type, evt) { - const { paint } = evt.detail; - this.editor.svgCanvas.setPaint(type, paint); - this.updateToolButtonState(); - } - /** + handleColorPicker (type, evt) { + const { paint } = evt.detail; + this.editor.svgCanvas.setPaint(type, paint); + this.updateToolButtonState(); + } + /** * @type {module} */ - handleStrokeAttr (type, evt) { - this.editor.svgCanvas.setStrokeAttr(type, evt.detail.value); - } - /** + handleStrokeAttr (type, evt) { + this.editor.svgCanvas.setStrokeAttr(type, evt.detail.value); + } + /** * @type {module} */ - handleOpacity (evt) { - const val = Number.parseInt(evt.currentTarget.value.split('%')[0]); - this.editor.svgCanvas.setOpacity(val / 100); - } + handleOpacity (evt) { + const val = Number.parseInt(evt.currentTarget.value.split('%')[0]); + this.editor.svgCanvas.setOpacity(val / 100); + } /** * @type {module} */ - handlePalette (e) { + handlePalette (e) { e.preventDefault(); // shift key or right click for stroke const { picker, color } = e.detail; @@ -233,7 +233,7 @@ class BottomPanel { /** * @type {module} */ - updateColorpickers (apply) { + updateColorpickers (apply) { $id('fill_color').update(this.editor.svgCanvas, this.editor.selectedElement, apply); $id('stroke_color').update(this.editor.svgCanvas, this.editor.selectedElement, apply); } diff --git a/src/editor/panels/LayersPanel.js b/src/editor/panels/LayersPanel.js index 6e162ff7..7acff6a1 100644 --- a/src/editor/panels/LayersPanel.js +++ b/src/editor/panels/LayersPanel.js @@ -92,20 +92,20 @@ class LayersPanel { lmenuFunc(e) { const action = e?.detail?.trigger; switch (action) { - case "dupe": - this.cloneLayer(); - break; - case "delete": - this.deleteLayer(); - break; - case "merge_down": - this.mergeLayer(); - break; - case "merge_all": - this.editor.svgCanvas.mergeAllLayers(); - this.updateContextPanel(); - this.populateLayers(); - break; + case "dupe": + this.cloneLayer(); + break; + case "delete": + this.deleteLayer(); + break; + case "merge_down": + this.mergeLayer(); + break; + case "merge_all": + this.editor.svgCanvas.mergeAllLayers(); + this.updateContextPanel(); + this.populateLayers(); + break; } } /** @@ -283,7 +283,7 @@ class LayersPanel { index(el) { if (!el) return -1; - var i = 0; + let i = 0; do { i++; } while (el == el.previousElementSibling); diff --git a/src/editor/panels/TopPanel.js b/src/editor/panels/TopPanel.js index d9e5783b..edcedfde 100644 --- a/src/editor/panels/TopPanel.js +++ b/src/editor/panels/TopPanel.js @@ -68,48 +68,48 @@ class TopPanel { let i, len; if (!isNullish(this.selectedElement)) { switch (this.selectedElement.tagName) { - case "use": - case "image": - case "foreignObject": - break; - case "g": - case "a": { - // Look for common styles - const childs = this.selectedElement.getElementsByTagName("*"); - let gWidth = null; - for (i = 0, len = childs.length; i < len; i++) { - const swidth = childs[i].getAttribute("stroke-width"); + case "use": + case "image": + case "foreignObject": + break; + case "g": + case "a": { + // Look for common styles + const childs = this.selectedElement.getElementsByTagName("*"); + let gWidth = null; + for (i = 0, len = childs.length; i < len; i++) { + const swidth = childs[i].getAttribute("stroke-width"); - if (i === 0) { - gWidth = swidth; - } else if (gWidth !== swidth) { - gWidth = null; - } + if (i === 0) { + gWidth = swidth; + } else if (gWidth !== swidth) { + gWidth = null; } - - $id("stroke_width").value = (gWidth === null ? "" : gWidth); - this.editor.bottomPanel.updateColorpickers(true); - break; } - default: { - this.editor.bottomPanel.updateColorpickers(true); - $id("stroke_width").value = this.selectedElement.getAttribute("stroke-width") || 1; - $id("stroke_style").value = this.selectedElement.getAttribute("stroke-dasharray") || "none"; + $id("stroke_width").value = (gWidth === null ? "" : gWidth); + this.editor.bottomPanel.updateColorpickers(true); + break; + } + default: { + this.editor.bottomPanel.updateColorpickers(true); - let attr = + $id("stroke_width").value = this.selectedElement.getAttribute("stroke-width") || 1; + $id("stroke_style").value = this.selectedElement.getAttribute("stroke-dasharray") || "none"; + + let attr = this.selectedElement.getAttribute("stroke-linejoin") || "miter"; - if ($id("linejoin_" + attr).length) { - this.setStrokeOpt($id("linejoin_" + attr)); - } - - attr = this.selectedElement.getAttribute("stroke-linecap") || "butt"; - - if ($id("linecap_" + attr).length) { - this.setStrokeOpt($id("linecap_" + attr)); - } + if ($id("linejoin_" + attr).length) { + this.setStrokeOpt($id("linejoin_" + attr)); } + + attr = this.selectedElement.getAttribute("stroke-linecap") || "butt"; + + if ($id("linecap_" + attr).length) { + this.setStrokeOpt($id("linecap_" + attr)); + } + } } } @@ -332,21 +332,21 @@ class TopPanel { const textAnchorMiddle = $id("tool_text_anchor_middle"); const textAnchorEnd = $id("tool_text_anchor_end"); switch (elem.getAttribute("text-anchor")) { - case "start": - textAnchorStart.pressed = true; - textAnchorMiddle.pressed = false; - textAnchorEnd.pressed = false; - break; - case "middle": - textAnchorStart.pressed = false; - textAnchorMiddle.pressed = true; - textAnchorEnd.pressed = false; - break; - case "end": - textAnchorStart.pressed = false; - textAnchorMiddle.pressed = false; - textAnchorEnd.pressed = true; - break; + case "start": + textAnchorStart.pressed = true; + textAnchorMiddle.pressed = false; + textAnchorEnd.pressed = false; + break; + case "middle": + textAnchorStart.pressed = false; + textAnchorMiddle.pressed = true; + textAnchorEnd.pressed = false; + break; + case "end": + textAnchorStart.pressed = false; + textAnchorMiddle.pressed = false; + textAnchorEnd.pressed = true; + break; } if (this.editor.svgCanvas.addedNew) { // Timeout needed for IE9 diff --git a/src/svgcanvas/coords.js b/src/svgcanvas/coords.js index 87beca96..ad1d6f2b 100644 --- a/src/svgcanvas/coords.js +++ b/src/svgcanvas/coords.js @@ -115,192 +115,192 @@ export const remapElement = function (selected, changes, m) { // now we have a set of changes and an applied reduced transform list // we apply the changes directly to the DOM switch (elName) { - case 'foreignObject': - case 'rect': - case 'image': { - // Allow images to be inverted (give them matrix when flipped) - if (elName === 'image' && (m.a < 0 || m.d < 0)) { - // Convert to matrix - const chlist = getTransformList(selected); - const mt = editorContext_.getSVGRoot().createSVGTransform(); - mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m)); - chlist.clear(); - chlist.appendItem(mt); - } else { - const pt1 = remap(changes.x, changes.y); - changes.width = scalew(changes.width); - changes.height = scaleh(changes.height); - changes.x = pt1.x + Math.min(0, changes.width); - changes.y = pt1.y + Math.min(0, changes.height); - changes.width = Math.abs(changes.width); - changes.height = Math.abs(changes.height); - } - finishUp(); - break; - } case 'ellipse': { - const c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - changes.rx = scalew(changes.rx); - changes.ry = scaleh(changes.ry); - changes.rx = Math.abs(changes.rx); - changes.ry = Math.abs(changes.ry); - finishUp(); - break; - } case 'circle': { - const c = remap(changes.cx, changes.cy); - changes.cx = c.x; - changes.cy = c.y; - // take the minimum of the new selected box's dimensions for the new circle radius - const tbox = transformBox(box.x, box.y, box.width, box.height, m); - const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; - changes.r = Math.min(w / 2, h / 2); - - if (changes.r) { changes.r = Math.abs(changes.r); } - finishUp(); - break; - } case 'line': { - const pt1 = remap(changes.x1, changes.y1); - const pt2 = remap(changes.x2, changes.y2); - changes.x1 = pt1.x; - changes.y1 = pt1.y; - changes.x2 = pt2.x; - changes.y2 = pt2.y; - } // Fallthrough - case 'text': - case 'tspan': - case 'use': { - finishUp(); - break; - } case 'g': { - const dataStorage = editorContext_.getDataStorage(); - const gsvg = dataStorage.get(selected, 'gsvg'); - if (gsvg) { - assignAttributes(gsvg, changes, 1000, true); - } - break; - } case 'polyline': - case 'polygon': { - const len = changes.points.length; - for (let i = 0; i < len; ++i) { - const pt = changes.points[i]; - const { x, y } = remap(pt.x, pt.y); - changes.points[i].x = x; - changes.points[i].y = y; - } - - // const len = changes.points.length; - let pstr = ''; - for (let i = 0; i < len; ++i) { - const pt = changes.points[i]; - pstr += pt.x + ',' + pt.y + ' '; - } - selected.setAttribute('points', pstr); - break; - } case 'path': { - const segList = selected.pathSegList; - let len = segList.numberOfItems; - changes.d = []; - for (let i = 0; i < len; ++i) { - const seg = segList.getItem(i); - changes.d[i] = { - type: seg.pathSegType, - x: seg.x, - y: seg.y, - x1: seg.x1, - y1: seg.y1, - x2: seg.x2, - y2: seg.y2, - r1: seg.r1, - r2: seg.r2, - angle: seg.angle, - largeArcFlag: seg.largeArcFlag, - sweepFlag: seg.sweepFlag - }; - } - - len = changes.d.length; - const firstseg = changes.d[0], - currentpt = remap(firstseg.x, firstseg.y); - changes.d[0].x = currentpt.x; - changes.d[0].y = currentpt.y; - for (let i = 1; i < len; ++i) { - const seg = changes.d[i]; - const { type } = seg; - // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 - // if relative, we want to scalew, scaleh - if (type % 2 === 0) { // absolute - const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands - thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands - const pt = remap(thisx, thisy); - const pt1 = remap(seg.x1, seg.y1); - const pt2 = remap(seg.x2, seg.y2); - seg.x = pt.x; - seg.y = pt.y; - seg.x1 = pt1.x; - seg.y1 = pt1.y; - seg.x2 = pt2.x; - seg.y2 = pt2.y; - seg.r1 = scalew(seg.r1); - seg.r2 = scaleh(seg.r2); - } else { // relative - seg.x = scalew(seg.x); - seg.y = scaleh(seg.y); - seg.x1 = scalew(seg.x1); - seg.y1 = scaleh(seg.y1); - seg.x2 = scalew(seg.x2); - seg.y2 = scaleh(seg.y2); - seg.r1 = scalew(seg.r1); - seg.r2 = scaleh(seg.r2); - } - } // for each segment - - let dstr = ''; - len = changes.d.length; - for (let i = 0; i < len; ++i) { - const seg = changes.d[i]; - const { type } = seg; - dstr += pathMap[type]; - switch (type) { - case 13: // relative horizontal line (h) - case 12: // absolute horizontal line (H) - dstr += seg.x + ' '; - break; - case 15: // relative vertical line (v) - case 14: // absolute vertical line (V) - dstr += seg.y + ' '; - break; - case 3: // relative move (m) - case 5: // relative line (l) - case 19: // relative smooth quad (t) - case 2: // absolute move (M) - case 4: // absolute line (L) - case 18: // absolute smooth quad (T) - dstr += seg.x + ',' + seg.y + ' '; - break; - case 7: // relative cubic (c) - case 6: // absolute cubic (C) - dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' + - seg.x + ',' + seg.y + ' '; - break; - case 9: // relative quad (q) - case 8: // absolute quad (Q) - dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '; - break; - case 11: // relative elliptical arc (a) - case 10: // absolute elliptical arc (A) - dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) + - ' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; - break; - case 17: // relative smooth cubic (s) - case 16: // absolute smooth cubic (S) - dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '; - break; - } - } - - selected.setAttribute('d', dstr); - break; + case 'foreignObject': + case 'rect': + case 'image': { + // Allow images to be inverted (give them matrix when flipped) + if (elName === 'image' && (m.a < 0 || m.d < 0)) { + // Convert to matrix + const chlist = getTransformList(selected); + const mt = editorContext_.getSVGRoot().createSVGTransform(); + mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m)); + chlist.clear(); + chlist.appendItem(mt); + } else { + const pt1 = remap(changes.x, changes.y); + changes.width = scalew(changes.width); + changes.height = scaleh(changes.height); + changes.x = pt1.x + Math.min(0, changes.width); + changes.y = pt1.y + Math.min(0, changes.height); + changes.width = Math.abs(changes.width); + changes.height = Math.abs(changes.height); } + finishUp(); + break; + } case 'ellipse': { + const c = remap(changes.cx, changes.cy); + changes.cx = c.x; + changes.cy = c.y; + changes.rx = scalew(changes.rx); + changes.ry = scaleh(changes.ry); + changes.rx = Math.abs(changes.rx); + changes.ry = Math.abs(changes.ry); + finishUp(); + break; + } case 'circle': { + const c = remap(changes.cx, changes.cy); + changes.cx = c.x; + changes.cy = c.y; + // take the minimum of the new selected box's dimensions for the new circle radius + const tbox = transformBox(box.x, box.y, box.width, box.height, m); + const w = tbox.tr.x - tbox.tl.x, h = tbox.bl.y - tbox.tl.y; + changes.r = Math.min(w / 2, h / 2); + + if (changes.r) { changes.r = Math.abs(changes.r); } + finishUp(); + break; + } case 'line': { + const pt1 = remap(changes.x1, changes.y1); + const pt2 = remap(changes.x2, changes.y2); + changes.x1 = pt1.x; + changes.y1 = pt1.y; + changes.x2 = pt2.x; + changes.y2 = pt2.y; + } // Fallthrough + case 'text': + case 'tspan': + case 'use': { + finishUp(); + break; + } case 'g': { + const dataStorage = editorContext_.getDataStorage(); + const gsvg = dataStorage.get(selected, 'gsvg'); + if (gsvg) { + assignAttributes(gsvg, changes, 1000, true); + } + break; + } case 'polyline': + case 'polygon': { + const len = changes.points.length; + for (let i = 0; i < len; ++i) { + const pt = changes.points[i]; + const { x, y } = remap(pt.x, pt.y); + changes.points[i].x = x; + changes.points[i].y = y; + } + + // const len = changes.points.length; + let pstr = ''; + for (let i = 0; i < len; ++i) { + const pt = changes.points[i]; + pstr += pt.x + ',' + pt.y + ' '; + } + selected.setAttribute('points', pstr); + break; + } case 'path': { + const segList = selected.pathSegList; + let len = segList.numberOfItems; + changes.d = []; + for (let i = 0; i < len; ++i) { + const seg = segList.getItem(i); + changes.d[i] = { + type: seg.pathSegType, + x: seg.x, + y: seg.y, + x1: seg.x1, + y1: seg.y1, + x2: seg.x2, + y2: seg.y2, + r1: seg.r1, + r2: seg.r2, + angle: seg.angle, + largeArcFlag: seg.largeArcFlag, + sweepFlag: seg.sweepFlag + }; + } + + len = changes.d.length; + const firstseg = changes.d[0], + currentpt = remap(firstseg.x, firstseg.y); + changes.d[0].x = currentpt.x; + changes.d[0].y = currentpt.y; + for (let i = 1; i < len; ++i) { + const seg = changes.d[i]; + const { type } = seg; + // if absolute or first segment, we want to remap x, y, x1, y1, x2, y2 + // if relative, we want to scalew, scaleh + if (type % 2 === 0) { // absolute + const thisx = (seg.x !== undefined) ? seg.x : currentpt.x, // for V commands + thisy = (seg.y !== undefined) ? seg.y : currentpt.y; // for H commands + const pt = remap(thisx, thisy); + const pt1 = remap(seg.x1, seg.y1); + const pt2 = remap(seg.x2, seg.y2); + seg.x = pt.x; + seg.y = pt.y; + seg.x1 = pt1.x; + seg.y1 = pt1.y; + seg.x2 = pt2.x; + seg.y2 = pt2.y; + seg.r1 = scalew(seg.r1); + seg.r2 = scaleh(seg.r2); + } else { // relative + seg.x = scalew(seg.x); + seg.y = scaleh(seg.y); + seg.x1 = scalew(seg.x1); + seg.y1 = scaleh(seg.y1); + seg.x2 = scalew(seg.x2); + seg.y2 = scaleh(seg.y2); + seg.r1 = scalew(seg.r1); + seg.r2 = scaleh(seg.r2); + } + } // for each segment + + let dstr = ''; + len = changes.d.length; + for (let i = 0; i < len; ++i) { + const seg = changes.d[i]; + const { type } = seg; + dstr += pathMap[type]; + switch (type) { + case 13: // relative horizontal line (h) + case 12: // absolute horizontal line (H) + dstr += seg.x + ' '; + break; + case 15: // relative vertical line (v) + case 14: // absolute vertical line (V) + dstr += seg.y + ' '; + break; + case 3: // relative move (m) + case 5: // relative line (l) + case 19: // relative smooth quad (t) + case 2: // absolute move (M) + case 4: // absolute line (L) + case 18: // absolute smooth quad (T) + dstr += seg.x + ',' + seg.y + ' '; + break; + case 7: // relative cubic (c) + case 6: // absolute cubic (C) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' + + seg.x + ',' + seg.y + ' '; + break; + case 9: // relative quad (q) + case 8: // absolute quad (Q) + dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 11: // relative elliptical arc (a) + case 10: // absolute elliptical arc (A) + dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) + + ' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '; + break; + case 17: // relative smooth cubic (s) + case 16: // absolute smooth cubic (S) + dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '; + break; + } + } + + selected.setAttribute('d', dstr); + break; + } } }; diff --git a/src/svgcanvas/copy-elem.js b/src/svgcanvas/copy-elem.js index 7802c04c..b1eeacc4 100644 --- a/src/svgcanvas/copy-elem.js +++ b/src/svgcanvas/copy-elem.js @@ -13,7 +13,7 @@ export const copyElem = function (el, getNextId) { // manually create a copy of the element const newEl = document.createElementNS(el.namespaceURI, el.nodeName); Object.values(el.attributes).forEach((attr) => { - newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value); + newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value); }); // set the copied element's new id newEl.removeAttribute('id'); diff --git a/src/svgcanvas/elem-get-set.js b/src/svgcanvas/elem-get-set.js index a9671ed1..3140b186 100644 --- a/src/svgcanvas/elem-get-set.js +++ b/src/svgcanvas/elem-get-set.js @@ -265,29 +265,29 @@ export const setBBoxZoomMethod = function (val, editorW, editorH) { } switch (val) { - case 'selection': { - if (!selectedElements[0]) { return undefined; } - const selectedElems = selectedElements.map(function (n, _) { - if (n) { - return n; - } - return undefined; - }); - bb = getStrokedBBoxDefaultVisible(selectedElems); - break; - } case 'canvas': { - const res = elemContext_.getCanvas().getResolution(); - spacer = 0.95; - bb = { width: res.w, height: res.h, x: 0, y: 0 }; - break; - } case 'content': - bb = getStrokedBBoxDefaultVisible(); - break; - case 'layer': - bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer())); - break; - default: + case 'selection': { + if (!selectedElements[0]) { return undefined; } + const selectedElems = selectedElements.map(function (n, _) { + if (n) { + return n; + } return undefined; + }); + bb = getStrokedBBoxDefaultVisible(selectedElems); + break; + } case 'canvas': { + const res = elemContext_.getCanvas().getResolution(); + spacer = 0.95; + bb = { width: res.w, height: res.h, x: 0, y: 0 }; + break; + } case 'content': + bb = getStrokedBBoxDefaultVisible(); + break; + case 'layer': + bb = getStrokedBBoxDefaultVisible(getVisibleElements(elemContext_.getCanvas().getCurrentDrawing().getCurrentLayer())); + break; + default: + return undefined; } return calcZoom(bb); }; @@ -476,14 +476,14 @@ export const setPaintMethod = function (type, paint) { // now set the current paint object elemContext_.setCurProperties(type + '_paint', p); switch (p.type) { - case 'solidColor': - this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none'); - break; - case 'linearGradient': - case 'radialGradient': - elemContext_.setCanvas(type + 'Grad', p[p.type]); - elemContext_.getCanvas().setGradient(type); - break; + case 'solidColor': + this.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none'); + break; + case 'linearGradient': + case 'radialGradient': + elemContext_.setCanvas(type + 'Grad', p[p.type]); + elemContext_.getCanvas().setGradient(type); + break; } }; /** diff --git a/src/svgcanvas/event.js b/src/svgcanvas/event.js index 365a9a5f..5b0d4d6c 100644 --- a/src/svgcanvas/event.js +++ b/src/svgcanvas/event.js @@ -111,185 +111,348 @@ export const mouseMoveEvent = function (evt) { evt.preventDefault(); let tlist; switch (eventContext_.getCurrentMode()) { - case 'select': { - // we temporarily use a translate on the element(s) being dragged - // this transform is removed upon mousing up and the element is - // relocated to the new location - if (selectedElements[0] !== null) { - dx = x - eventContext_.getStartX(); - dy = y - eventContext_.getStartY(); - if (eventContext_.getCurConfig().gridSnapping) { - dx = snapToGrid(dx); - dy = snapToGrid(dy); - } - - if (dx !== 0 || dy !== 0) { - len = selectedElements.length; - for (i = 0; i < len; ++i) { - selected = selectedElements[i]; - if (isNullish(selected)) { break; } - // if (i === 0) { - // const box = utilsGetBBox(selected); - // selectedBBoxes[i].x = box.x + dx; - // selectedBBoxes[i].y = box.y + dy; - // } - - // update the dummy transform in our transform list - // to be a translate - const xform = eventContext_.getSVGRoot().createSVGTransform(); - tlist = getTransformList(selected); - // Note that if Webkit and there's no ID for this - // element, the dummy transform may have gotten lost. - // This results in unexpected behaviour - - xform.setTranslate(dx, dy); - if (tlist.numberOfItems) { - tlist.replaceItem(xform, 0); - } else { - tlist.appendItem(xform); - } - - // update our internal bbox that we're tracking while dragging - eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); - } - - eventContext_.getCanvas().call('transition', selectedElements); - } - } - break; - } case 'multiselect': { - realX *= currentZoom; - realY *= currentZoom; - assignAttributes(eventContext_.getRubberBox(), { - x: Math.min(eventContext_.getRStartX(), realX), - y: Math.min(eventContext_.getRStartY(), realY), - width: Math.abs(realX - eventContext_.getRStartX()), - height: Math.abs(realY - eventContext_.getRStartY()) - }, 100); - - // for each selected: - // - if newList contains selected, do nothing - // - if newList doesn't contain selected, remove it from selected - // - for any newList that was not in selectedElements, add it to selected - const elemsToRemove = selectedElements.slice(), elemsToAdd = [], - newList = eventContext_.getIntersectionList(); - - // For every element in the intersection, add if not present in selectedElements. - len = newList.length; - for (i = 0; i < len; ++i) { - const intElem = newList[i]; - // Found an element that was not selected before, so we should add it. - if (!selectedElements.includes(intElem)) { - elemsToAdd.push(intElem); - } - // Found an element that was already selected, so we shouldn't remove it. - const foundInd = elemsToRemove.indexOf(intElem); - if (foundInd !== -1) { - elemsToRemove.splice(foundInd, 1); - } - } - - if (elemsToRemove.length > 0) { - eventContext_.getCanvas().removeFromSelection(elemsToRemove); - } - - if (elemsToAdd.length > 0) { - eventContext_.getCanvas().addToSelection(elemsToAdd); - } - - break; - } case 'resize': { - // we track the resize bounding box and translate/scale the selected element - // while the mouse is down, when mouse goes up, we use this to recalculate - // the shape's coordinates - tlist = getTransformList(selected); - const hasMatrix = hasMatrixTransform(tlist); - box = hasMatrix ? eventContext_.getInitBbox() : utilsGetBBox(selected); - let left = box.x, - top = box.y, - { width, height } = box; - dx = (x - eventContext_.getStartX()); - dy = (y - eventContext_.getStartY()); - + case 'select': { + // we temporarily use a translate on the element(s) being dragged + // this transform is removed upon mousing up and the element is + // relocated to the new location + if (selectedElements[0] !== null) { + dx = x - eventContext_.getStartX(); + dy = y - eventContext_.getStartY(); if (eventContext_.getCurConfig().gridSnapping) { dx = snapToGrid(dx); dy = snapToGrid(dy); - height = snapToGrid(height); - width = snapToGrid(width); } - // if rotated, adjust the dx,dy values - angle = getRotationAngle(selected); - if (angle) { - const r = Math.sqrt(dx * dx + dy * dy), - theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0; - dx = r * Math.cos(theta); - dy = r * Math.sin(theta); - } + if (dx !== 0 || dy !== 0) { + len = selectedElements.length; + for (i = 0; i < len; ++i) { + selected = selectedElements[i]; + if (isNullish(selected)) { break; } + // if (i === 0) { + // const box = utilsGetBBox(selected); + // selectedBBoxes[i].x = box.x + dx; + // selectedBBoxes[i].y = box.y + dy; + // } - // if not stretching in y direction, set dy to 0 - // if not stretching in x direction, set dx to 0 - if (!eventContext_.getCurrentResizeMode().includes('n') && !eventContext_.getCurrentResizeMode().includes('s')) { - dy = 0; - } - if (!eventContext_.getCurrentResizeMode().includes('e') && !eventContext_.getCurrentResizeMode().includes('w')) { - dx = 0; - } + // update the dummy transform in our transform list + // to be a translate + const xform = eventContext_.getSVGRoot().createSVGTransform(); + tlist = getTransformList(selected); + // Note that if Webkit and there's no ID for this + // element, the dummy transform may have gotten lost. + // This results in unexpected behaviour - let // ts = null, - tx = 0, ty = 0, - sy = height ? (height + dy) / height : 1, - sx = width ? (width + dx) / width : 1; + xform.setTranslate(dx, dy); + if (tlist.numberOfItems) { + tlist.replaceItem(xform, 0); + } else { + tlist.appendItem(xform); + } + + // update our internal bbox that we're tracking while dragging + eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); + } + + eventContext_.getCanvas().call('transition', selectedElements); + } + } + break; + } case 'multiselect': { + realX *= currentZoom; + realY *= currentZoom; + assignAttributes(eventContext_.getRubberBox(), { + x: Math.min(eventContext_.getRStartX(), realX), + y: Math.min(eventContext_.getRStartY(), realY), + width: Math.abs(realX - eventContext_.getRStartX()), + height: Math.abs(realY - eventContext_.getRStartY()) + }, 100); + + // for each selected: + // - if newList contains selected, do nothing + // - if newList doesn't contain selected, remove it from selected + // - for any newList that was not in selectedElements, add it to selected + const elemsToRemove = selectedElements.slice(), elemsToAdd = [], + newList = eventContext_.getIntersectionList(); + + // For every element in the intersection, add if not present in selectedElements. + len = newList.length; + for (i = 0; i < len; ++i) { + const intElem = newList[i]; + // Found an element that was not selected before, so we should add it. + if (!selectedElements.includes(intElem)) { + elemsToAdd.push(intElem); + } + // Found an element that was already selected, so we shouldn't remove it. + const foundInd = elemsToRemove.indexOf(intElem); + if (foundInd !== -1) { + elemsToRemove.splice(foundInd, 1); + } + } + + if (elemsToRemove.length > 0) { + eventContext_.getCanvas().removeFromSelection(elemsToRemove); + } + + if (elemsToAdd.length > 0) { + eventContext_.getCanvas().addToSelection(elemsToAdd); + } + + break; + } case 'resize': { + // we track the resize bounding box and translate/scale the selected element + // while the mouse is down, when mouse goes up, we use this to recalculate + // the shape's coordinates + tlist = getTransformList(selected); + const hasMatrix = hasMatrixTransform(tlist); + box = hasMatrix ? eventContext_.getInitBbox() : utilsGetBBox(selected); + let left = box.x, + top = box.y, + { width, height } = box; + dx = (x - eventContext_.getStartX()); + dy = (y - eventContext_.getStartY()); + + if (eventContext_.getCurConfig().gridSnapping) { + dx = snapToGrid(dx); + dy = snapToGrid(dy); + height = snapToGrid(height); + width = snapToGrid(width); + } + + // if rotated, adjust the dx,dy values + angle = getRotationAngle(selected); + if (angle) { + const r = Math.sqrt(dx * dx + dy * dy), + theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0; + dx = r * Math.cos(theta); + dy = r * Math.sin(theta); + } + + // if not stretching in y direction, set dy to 0 + // if not stretching in x direction, set dx to 0 + if (!eventContext_.getCurrentResizeMode().includes('n') && !eventContext_.getCurrentResizeMode().includes('s')) { + dy = 0; + } + if (!eventContext_.getCurrentResizeMode().includes('e') && !eventContext_.getCurrentResizeMode().includes('w')) { + dx = 0; + } + + let // ts = null, + tx = 0, ty = 0, + sy = height ? (height + dy) / height : 1, + sx = width ? (width + dx) / width : 1; // if we are dragging on the north side, then adjust the scale factor and ty - if (eventContext_.getCurrentResizeMode().includes('n')) { - sy = height ? (height - dy) / height : 1; - ty = height; + if (eventContext_.getCurrentResizeMode().includes('n')) { + sy = height ? (height - dy) / height : 1; + ty = height; + } + + // if we dragging on the east side, then adjust the scale factor and tx + if (eventContext_.getCurrentResizeMode().includes('w')) { + sx = width ? (width - dx) / width : 1; + tx = width; + } + + // update the transform list with translate,scale,translate + const translateOrigin = eventContext_.getSVGRoot().createSVGTransform(), + scale = eventContext_.getSVGRoot().createSVGTransform(), + translateBack = eventContext_.getSVGRoot().createSVGTransform(); + + if (eventContext_.getCurConfig().gridSnapping) { + left = snapToGrid(left); + tx = snapToGrid(tx); + top = snapToGrid(top); + ty = snapToGrid(ty); + } + + translateOrigin.setTranslate(-(left + tx), -(top + ty)); + if (evt.shiftKey) { + if (sx === 1) { + sx = sy; + } else { sy = sx; } + } + scale.setScale(sx, sy); + + translateBack.setTranslate(left + tx, top + ty); + if (hasMatrix) { + const diff = angle ? 1 : 0; + tlist.replaceItem(translateOrigin, 2 + diff); + tlist.replaceItem(scale, 1 + diff); + tlist.replaceItem(translateBack, Number(diff)); + } else { + const N = tlist.numberOfItems; + tlist.replaceItem(translateBack, N - 3); + tlist.replaceItem(scale, N - 2); + tlist.replaceItem(translateOrigin, N - 1); + } + + eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); + eventContext_.getCanvas().call('transition', selectedElements); + + break; + } case 'zoom': { + realX *= currentZoom; + realY *= currentZoom; + assignAttributes(eventContext_.getRubberBox(), { + x: Math.min(eventContext_.getRStartX() * currentZoom, realX), + y: Math.min(eventContext_.getRStartY() * currentZoom, realY), + width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), + height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) + }, 100); + break; + } case 'text': { + assignAttributes(shape, { + x, + y + }, 1000); + break; + } case 'line': { + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + y = snapToGrid(y); + } + + let x2 = x; + let y2 = y; + + if (evt.shiftKey) { + xya = snapToAngle(eventContext_.getStartX(), eventContext_.getStartY(), x2, y2); + x2 = xya.x; + y2 = xya.y; + } + + shape.setAttribute('x2', x2); + shape.setAttribute('y2', y2); + break; + } case 'foreignObject': + // fall through + case 'square': + // fall through + case 'rect': + // fall through + case 'image': { + const square = (eventContext_.getCurrentMode() === 'square') || evt.shiftKey; + let + w = Math.abs(x - eventContext_.getStartX()), + h = Math.abs(y - eventContext_.getStartY()); + let newX, newY; + if (square) { + w = h = Math.max(w, h); + newX = eventContext_.getStartX() < x ? eventContext_.getStartX() : eventContext_.getStartX() - w; + newY = eventContext_.getStartY() < y ? eventContext_.getStartY() : eventContext_.getStartY() - h; + } else { + newX = Math.min(eventContext_.getStartX(), x); + newY = Math.min(eventContext_.getStartY(), y); + } + + if (eventContext_.getCurConfig().gridSnapping) { + w = snapToGrid(w); + h = snapToGrid(h); + newX = snapToGrid(newX); + newY = snapToGrid(newY); + } + + assignAttributes(shape, { + width: w, + height: h, + x: newX, + y: newY + }, 1000); + + break; + } case 'circle': { + cx = shape.getAttribute('cx'); + cy = shape.getAttribute('cy'); + let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); + if (eventContext_.getCurConfig().gridSnapping) { + rad = snapToGrid(rad); + } + shape.setAttribute('r', rad); + break; + } case 'ellipse': { + cx = shape.getAttribute('cx'); + cy = shape.getAttribute('cy'); + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + cx = snapToGrid(cx); + y = snapToGrid(y); + cy = snapToGrid(cy); + } + shape.setAttribute('rx', Math.abs(x - cx)); + const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy)); + shape.setAttribute('ry', ry); + break; + } + case 'fhellipse': + case 'fhrect': { + eventContext_.setFreehand('minx', Math.min(realX, eventContext_.getFreehand('minx'))); + eventContext_.setFreehand('maxx', Math.max(realX, eventContext_.getFreehand('maxx'))); + eventContext_.setFreehand('miny', Math.min(realY, eventContext_.getFreehand('miny'))); + eventContext_.setFreehand('maxy', Math.max(realY, eventContext_.getFreehand('maxy'))); + } + // Fallthrough + case 'fhpath': { + // dAttr += + realX + ',' + realY + ' '; + // shape.setAttribute('points', dAttr); + eventContext_.setEnd('x', realX); + eventContext_.setEnd('y', realY); + if (eventContext_.getControllPoint2('x') && eventContext_.getControllPoint2('y')) { + for (i = 0; i < eventContext_.getStepCount() - 1; i++) { + eventContext_.setParameter(i / eventContext_.getStepCount()); + eventContext_.setNextParameter((i + 1) / eventContext_.getStepCount()); + eventContext_.setbSpline(getBsplinePoint(eventContext_.getNextParameter())); + eventContext_.setNextPos({ x: eventContext_.getbSpline('x'), y: eventContext_.getbSpline('y') }); + eventContext_.setbSpline(getBsplinePoint(eventContext_.getParameter())); + eventContext_.setSumDistance( + eventContext_.getSumDistance() + Math.sqrt((eventContext_.getNextPos('x') - + eventContext_.getbSpline('x')) * (eventContext_.getNextPos('x') - + eventContext_.getbSpline('x')) + (eventContext_.getNextPos('y') - + eventContext_.getbSpline('y')) * (eventContext_.getNextPos('y') - eventContext_.getbSpline('y'))) + ); + if (eventContext_.getSumDistance() > eventContext_.getThreSholdDist()) { + eventContext_.setSumDistance(eventContext_.getSumDistance() - eventContext_.getThreSholdDist()); + + // Faster than completely re-writing the points attribute. + const point = eventContext_.getSVGContent().createSVGPoint(); + point.x = eventContext_.getbSpline('x'); + point.y = eventContext_.getbSpline('y'); + shape.points.appendItem(point); + } } + } + eventContext_.setControllPoint2('x', eventContext_.getControllPoint1('x')); + eventContext_.setControllPoint2('y', eventContext_.getControllPoint1('y')); + eventContext_.setControllPoint1('x', eventContext_.getStart('x')); + eventContext_.setControllPoint1('y', eventContext_.getStart('y')); + eventContext_.setStart({ x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y') }); + break; + // update path stretch line coordinates + } case 'path': + // fall through + case 'pathedit': { + x *= currentZoom; + y *= currentZoom; - // if we dragging on the east side, then adjust the scale factor and tx - if (eventContext_.getCurrentResizeMode().includes('w')) { - sx = width ? (width - dx) / width : 1; - tx = width; - } - - // update the transform list with translate,scale,translate - const translateOrigin = eventContext_.getSVGRoot().createSVGTransform(), - scale = eventContext_.getSVGRoot().createSVGTransform(), - translateBack = eventContext_.getSVGRoot().createSVGTransform(); - - if (eventContext_.getCurConfig().gridSnapping) { - left = snapToGrid(left); - tx = snapToGrid(tx); - top = snapToGrid(top); - ty = snapToGrid(ty); - } - - translateOrigin.setTranslate(-(left + tx), -(top + ty)); - if (evt.shiftKey) { - if (sx === 1) { - sx = sy; - } else { sy = sx; } - } - scale.setScale(sx, sy); - - translateBack.setTranslate(left + tx, top + ty); - if (hasMatrix) { - const diff = angle ? 1 : 0; - tlist.replaceItem(translateOrigin, 2 + diff); - tlist.replaceItem(scale, 1 + diff); - tlist.replaceItem(translateBack, Number(diff)); + if (eventContext_.getCurConfig().gridSnapping) { + x = snapToGrid(x); + y = snapToGrid(y); + eventContext_.setStartX(snapToGrid(eventContext_.getStartX())); + eventContext_.setStartY(snapToGrid(eventContext_.getStartY())); + } + if (evt.shiftKey) { + const { path } = pathModule; + let x1, y1; + if (path) { + x1 = path.dragging ? path.dragging[0] : eventContext_.getStartX(); + y1 = path.dragging ? path.dragging[1] : eventContext_.getStartY(); } else { - const N = tlist.numberOfItems; - tlist.replaceItem(translateBack, N - 3); - tlist.replaceItem(scale, N - 2); - tlist.replaceItem(translateOrigin, N - 1); + x1 = eventContext_.getStartX(); + y1 = eventContext_.getStartY(); } + xya = snapToAngle(x1, y1, x, y); + ({ x, y } = xya); + } - eventContext_.getCanvas().selectorManager.requestSelector(selected).resize(); - eventContext_.getCanvas().call('transition', selectedElements); - - break; - } case 'zoom': { + if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { realX *= currentZoom; realY *= currentZoom; assignAttributes(eventContext_.getRubberBox(), { @@ -298,210 +461,47 @@ export const mouseMoveEvent = function (evt) { width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) }, 100); - break; - } case 'text': { - assignAttributes(shape, { - x, - y - }, 1000); - break; - } case 'line': { - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - y = snapToGrid(y); - } - - let x2 = x; - let y2 = y; - - if (evt.shiftKey) { - xya = snapToAngle(eventContext_.getStartX(), eventContext_.getStartY(), x2, y2); - x2 = xya.x; - y2 = xya.y; - } - - shape.setAttribute('x2', x2); - shape.setAttribute('y2', y2); - break; - } case 'foreignObject': - // fall through - case 'square': - // fall through - case 'rect': - // fall through - case 'image': { - const square = (eventContext_.getCurrentMode() === 'square') || evt.shiftKey; - let - w = Math.abs(x - eventContext_.getStartX()), - h = Math.abs(y - eventContext_.getStartY()); - let newX, newY; - if (square) { - w = h = Math.max(w, h); - newX = eventContext_.getStartX() < x ? eventContext_.getStartX() : eventContext_.getStartX() - w; - newY = eventContext_.getStartY() < y ? eventContext_.getStartY() : eventContext_.getStartY() - h; - } else { - newX = Math.min(eventContext_.getStartX(), x); - newY = Math.min(eventContext_.getStartY(), y); - } - - if (eventContext_.getCurConfig().gridSnapping) { - w = snapToGrid(w); - h = snapToGrid(h); - newX = snapToGrid(newX); - newY = snapToGrid(newY); - } - - assignAttributes(shape, { - width: w, - height: h, - x: newX, - y: newY - }, 1000); - - break; - } case 'circle': { - cx = shape.getAttribute('cx'); - cy = shape.getAttribute('cy'); - let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy)); - if (eventContext_.getCurConfig().gridSnapping) { - rad = snapToGrid(rad); - } - shape.setAttribute('r', rad); - break; - } case 'ellipse': { - cx = shape.getAttribute('cx'); - cy = shape.getAttribute('cy'); - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - cx = snapToGrid(cx); - y = snapToGrid(y); - cy = snapToGrid(cy); - } - shape.setAttribute('rx', Math.abs(x - cx)); - const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy)); - shape.setAttribute('ry', ry); - break; } - case 'fhellipse': - case 'fhrect': { - eventContext_.setFreehand('minx', Math.min(realX, eventContext_.getFreehand('minx'))); - eventContext_.setFreehand('maxx', Math.max(realX, eventContext_.getFreehand('maxx'))); - eventContext_.setFreehand('miny', Math.min(realY, eventContext_.getFreehand('miny'))); - eventContext_.setFreehand('maxy', Math.max(realY, eventContext_.getFreehand('maxy'))); + eventContext_.getCanvas().pathActions.mouseMove(x, y); + + break; + } case 'textedit': { + x *= currentZoom; + y *= currentZoom; + // if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { + // assignAttributes(eventContext_.getRubberBox(), { + // x: Math.min(eventContext_.getStartX(), x), + // y: Math.min(eventContext_.getStartY(), y), + // width: Math.abs(x - eventContext_.getStartX()), + // height: Math.abs(y - eventContext_.getStartY()) + // }, 100); + // } + + eventContext_.getCanvas().textActions.mouseMove(mouseX, mouseY); + + break; + } case 'rotate': { + box = utilsGetBBox(selected); + cx = box.x + box.width / 2; + cy = box.y + box.height / 2; + const m = getMatrix(selected), + center = transformPoint(cx, cy, m); + cx = center.x; + cy = center.y; + angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360; + if (eventContext_.getCurConfig().gridSnapping) { + angle = snapToGrid(angle); + } + if (evt.shiftKey) { // restrict rotations to nice angles (WRS) + const snap = 45; + angle = Math.round(angle / snap) * snap; } - // Fallthrough - case 'fhpath': { - // dAttr += + realX + ',' + realY + ' '; - // shape.setAttribute('points', dAttr); - eventContext_.setEnd('x', realX); - eventContext_.setEnd('y', realY); - if (eventContext_.getControllPoint2('x') && eventContext_.getControllPoint2('y')) { - for (i = 0; i < eventContext_.getStepCount() - 1; i++) { - eventContext_.setParameter(i / eventContext_.getStepCount()); - eventContext_.setNextParameter((i + 1) / eventContext_.getStepCount()); - eventContext_.setbSpline(getBsplinePoint(eventContext_.getNextParameter())); - eventContext_.setNextPos({ x: eventContext_.getbSpline('x'), y: eventContext_.getbSpline('y') }); - eventContext_.setbSpline(getBsplinePoint(eventContext_.getParameter())); - eventContext_.setSumDistance( - eventContext_.getSumDistance() + Math.sqrt((eventContext_.getNextPos('x') - - eventContext_.getbSpline('x')) * (eventContext_.getNextPos('x') - - eventContext_.getbSpline('x')) + (eventContext_.getNextPos('y') - - eventContext_.getbSpline('y')) * (eventContext_.getNextPos('y') - eventContext_.getbSpline('y'))) - ); - if (eventContext_.getSumDistance() > eventContext_.getThreSholdDist()) { - eventContext_.setSumDistance(eventContext_.getSumDistance() - eventContext_.getThreSholdDist()); - // Faster than completely re-writing the points attribute. - const point = eventContext_.getSVGContent().createSVGPoint(); - point.x = eventContext_.getbSpline('x'); - point.y = eventContext_.getbSpline('y'); - shape.points.appendItem(point); - } - } - } - eventContext_.setControllPoint2('x', eventContext_.getControllPoint1('x')); - eventContext_.setControllPoint2('y', eventContext_.getControllPoint1('y')); - eventContext_.setControllPoint1('x', eventContext_.getStart('x')); - eventContext_.setControllPoint1('y', eventContext_.getStart('y')); - eventContext_.setStart({ x: eventContext_.getEnd('x'), y: eventContext_.getEnd('y') }); - break; - // update path stretch line coordinates - } case 'path': - // fall through - case 'pathedit': { - x *= currentZoom; - y *= currentZoom; - - if (eventContext_.getCurConfig().gridSnapping) { - x = snapToGrid(x); - y = snapToGrid(y); - eventContext_.setStartX(snapToGrid(eventContext_.getStartX())); - eventContext_.setStartY(snapToGrid(eventContext_.getStartY())); - } - if (evt.shiftKey) { - const { path } = pathModule; - let x1, y1; - if (path) { - x1 = path.dragging ? path.dragging[0] : eventContext_.getStartX(); - y1 = path.dragging ? path.dragging[1] : eventContext_.getStartY(); - } else { - x1 = eventContext_.getStartX(); - y1 = eventContext_.getStartY(); - } - xya = snapToAngle(x1, y1, x, y); - ({ x, y } = xya); - } - - if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { - realX *= currentZoom; - realY *= currentZoom; - assignAttributes(eventContext_.getRubberBox(), { - x: Math.min(eventContext_.getRStartX() * currentZoom, realX), - y: Math.min(eventContext_.getRStartY() * currentZoom, realY), - width: Math.abs(realX - eventContext_.getRStartX() * currentZoom), - height: Math.abs(realY - eventContext_.getRStartY() * currentZoom) - }, 100); - } - eventContext_.getCanvas().pathActions.mouseMove(x, y); - - break; - } case 'textedit': { - x *= currentZoom; - y *= currentZoom; - // if (eventContext_.getRubberBox() && eventContext_.getRubberBox().getAttribute('display') !== 'none') { - // assignAttributes(eventContext_.getRubberBox(), { - // x: Math.min(eventContext_.getStartX(), x), - // y: Math.min(eventContext_.getStartY(), y), - // width: Math.abs(x - eventContext_.getStartX()), - // height: Math.abs(y - eventContext_.getStartY()) - // }, 100); - // } - - eventContext_.getCanvas().textActions.mouseMove(mouseX, mouseY); - - break; - } case 'rotate': { - box = utilsGetBBox(selected); - cx = box.x + box.width / 2; - cy = box.y + box.height / 2; - const m = getMatrix(selected), - center = transformPoint(cx, cy, m); - cx = center.x; - cy = center.y; - angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360; - if (eventContext_.getCurConfig().gridSnapping) { - angle = snapToGrid(angle); - } - if (evt.shiftKey) { // restrict rotations to nice angles (WRS) - const snap = 45; - angle = Math.round(angle / snap) * snap; - } - - eventContext_.getCanvas().setRotationAngle(angle < -180 ? (360 + angle) : angle, true); - eventContext_.getCanvas().call('transition', selectedElements); - break; - } default: - break; + eventContext_.getCanvas().setRotationAngle(angle < -180 ? (360 + angle) : angle, true); + eventContext_.getCanvas().call('transition', selectedElements); + break; + } default: + break; } /** @@ -558,219 +558,219 @@ export const mouseUpEvent = function (evt) { eventContext_.setStarted(false); let t; switch (eventContext_.getCurrentMode()) { - // intentionally fall-through to select here - case 'resize': - case 'multiselect': - if (!isNullish(eventContext_.getRubberBox())) { - eventContext_.getRubberBox().setAttribute('display', 'none'); - eventContext_.setCurBBoxes([]); - } - eventContext_.setCurrentMode('select'); + // intentionally fall-through to select here + case 'resize': + case 'multiselect': + if (!isNullish(eventContext_.getRubberBox())) { + eventContext_.getRubberBox().setAttribute('display', 'none'); + eventContext_.setCurBBoxes([]); + } + eventContext_.setCurrentMode('select'); // Fallthrough - case 'select': - if (!isNullish(selectedElements[0])) { - // if we only have one selected element - if (isNullish(selectedElements[1])) { - // set our current stroke/fill properties to the element's - const selected = selectedElements[0]; - switch (selected.tagName) { - case 'g': - case 'use': - case 'image': - case 'foreignObject': - break; - default: - eventContext_.setCurProperties('fill', selected.getAttribute('fill')); - eventContext_.setCurProperties('fill_opacity', selected.getAttribute('fill-opacity')); - eventContext_.setCurProperties('stroke', selected.getAttribute('stroke')); - eventContext_.setCurProperties('stroke_opacity', selected.getAttribute('stroke-opacity')); - eventContext_.setCurProperties('stroke_width', selected.getAttribute('stroke-width')); - eventContext_.setCurProperties('stroke_dasharray', selected.getAttribute('stroke-dasharray')); - eventContext_.setCurProperties('stroke_linejoin', selected.getAttribute('stroke-linejoin')); - eventContext_.setCurProperties('stroke_linecap', selected.getAttribute('stroke-linecap')); - } - - if (selected.tagName === 'text') { - eventContext_.setCurText('font_size', selected.getAttribute('font-size')); - eventContext_.setCurText('font_family', selected.getAttribute('font-family')); - } - eventContext_.getCanvas().selectorManager.requestSelector(selected).showGrips(true); - - // This shouldn't be necessary as it was done on mouseDown... - // eventContext_.getCanvas().call('selected', [selected]); + case 'select': + if (!isNullish(selectedElements[0])) { + // if we only have one selected element + if (isNullish(selectedElements[1])) { + // set our current stroke/fill properties to the element's + const selected = selectedElements[0]; + switch (selected.tagName) { + case 'g': + case 'use': + case 'image': + case 'foreignObject': + break; + default: + eventContext_.setCurProperties('fill', selected.getAttribute('fill')); + eventContext_.setCurProperties('fill_opacity', selected.getAttribute('fill-opacity')); + eventContext_.setCurProperties('stroke', selected.getAttribute('stroke')); + eventContext_.setCurProperties('stroke_opacity', selected.getAttribute('stroke-opacity')); + eventContext_.setCurProperties('stroke_width', selected.getAttribute('stroke-width')); + eventContext_.setCurProperties('stroke_dasharray', selected.getAttribute('stroke-dasharray')); + eventContext_.setCurProperties('stroke_linejoin', selected.getAttribute('stroke-linejoin')); + eventContext_.setCurProperties('stroke_linecap', selected.getAttribute('stroke-linecap')); } - // always recalculate dimensions to strip off stray identity transforms - eventContext_.getCanvas().recalculateAllSelectedDimensions(); - // if it was being dragged/resized - if (realX !== eventContext_.getRStartX() || realY !== eventContext_.getRStartY()) { - const len = selectedElements.length; - for (let i = 0; i < len; ++i) { - if (isNullish(selectedElements[i])) { break; } - if (!selectedElements[i].firstChild) { - // Not needed for groups (incorrectly resizes elems), possibly not needed at all? - eventContext_.getCanvas().selectorManager.requestSelector(selectedElements[i]).resize(); - } - } - // no change in position/size, so maybe we should move to pathedit - } else { - t = evt.target; - if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) { - eventContext_.getCanvas().pathActions.select(selectedElements[0]); - // if it was a path - // else, if it was selected and this is a shift-click, remove it from selection - } else if (evt.shiftKey && tempJustSelected !== t) { - eventContext_.getCanvas().removeFromSelection([ t ]); - } - } // no change in mouse position - // Remove non-scaling stroke - if (supportsNonScalingStroke()) { - const elem = selectedElements[0]; - if (elem) { - elem.removeAttribute('style'); - walkTree(elem, function (el) { - el.removeAttribute('style'); - }); - } + if (selected.tagName === 'text') { + eventContext_.setCurText('font_size', selected.getAttribute('font-size')); + eventContext_.setCurText('font_family', selected.getAttribute('font-family')); } - } - return; - case 'zoom': { - if (!isNullish(eventContext_.getRubberBox())) { - eventContext_.getRubberBox().setAttribute('display', 'none'); - } - const factor = evt.shiftKey ? 0.5 : 2; - eventContext_.getCanvas().call('zoomed', { - x: Math.min(eventContext_.getRStartX(), realX), - y: Math.min(eventContext_.getRStartY(), realY), - width: Math.abs(realX - eventContext_.getRStartX()), - height: Math.abs(realY - eventContext_.getRStartY()), - factor - }); - return; - } case 'fhpath': { - // Check that the path contains at least 2 points; a degenerate one-point path - // causes problems. - // Webkit ignores how we set the points attribute with commas and uses space - // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870 - eventContext_.setSumDistance(0); - eventContext_.setControllPoint2('x', 0); - eventContext_.setControllPoint2('y', 0); - eventContext_.setControllPoint1('x', 0); - eventContext_.setControllPoint1('y', 0); - eventContext_.setStart({ x: 0, y: 0 }); - eventContext_.setEnd('x', 0); - eventContext_.setEnd('y', 0); - const coords = element.getAttribute('points'); - const commaIndex = coords.indexOf(','); - keep = commaIndex >= 0 ? coords.includes(',', commaIndex + 1) : coords.includes(' ', coords.indexOf(' ') + 1); - if (keep) { - element = eventContext_.getCanvas().pathActions.smoothPolylineIntoPath(element); - } - break; - } case 'line': { - const x1 = element.getAttribute('x1'); - const y1 = element.getAttribute('y1'); - const x2 = element.getAttribute('x2'); - const y2 = element.getAttribute('y2'); - keep = (x1 !== x2 || y1 !== y2); - } - break; - case 'foreignObject': - case 'square': - case 'rect': - case 'image': { - const width = element.getAttribute('width'); - const height = element.getAttribute('height'); - // Image should be kept regardless of size (use inherit dimensions later) - keep = (width || height) || eventContext_.getCurrentMode() === 'image'; - } - break; - case 'circle': - keep = (element.getAttribute('r') !== '0'); - break; - case 'ellipse': { - const rx = element.getAttribute('rx'); - const ry = element.getAttribute('ry'); - keep = (rx || ry); - } - break; - case 'fhellipse': - if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && - (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { - element = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'ellipse', - curStyles: true, - attr: { - cx: (eventContext_.getFreehand('minx') + eventContext_.getFreehand('maxx')) / 2, - cy: (eventContext_.getFreehand('miny') + eventContext_.getFreehand('maxy')) / 2, - rx: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) / 2, - ry: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) / 2, - id: eventContext_.getId() - } - }); - eventContext_.getCanvas().call('changed', [ element ]); - keep = true; - } - break; - case 'fhrect': - if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && - (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { - element = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'rect', - curStyles: true, - attr: { - x: eventContext_.getFreehand('minx'), - y: eventContext_.getFreehand('miny'), - width: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')), - height: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')), - id: eventContext_.getId() - } - }); - eventContext_.getCanvas().call('changed', [ element ]); - keep = true; - } - break; - case 'text': - keep = true; - eventContext_.getCanvas().selectOnly([ element ]); - eventContext_.getCanvas().textActions.start(element); - break; - case 'path': { - // set element to null here so that it is not removed nor finalized - element = null; - // continue to be set to true so that mouseMove happens - eventContext_.setStarted(true); + eventContext_.getCanvas().selectorManager.requestSelector(selected).showGrips(true); - const res = eventContext_.getCanvas().pathActions.mouseUp(evt, element, mouseX, mouseY); - ({ element } = res); - ({ keep } = res); - break; - } case 'pathedit': - keep = true; - element = null; - eventContext_.getCanvas().pathActions.mouseUp(evt); - break; - case 'textedit': - keep = false; - element = null; - eventContext_.getCanvas().textActions.mouseUp(evt, mouseX, mouseY); - break; - case 'rotate': { - keep = true; - element = null; - eventContext_.setCurrentMode('select'); - const batchCmd = eventContext_.getCanvas().undoMgr.finishUndoableChange(); - if (!batchCmd.isEmpty()) { - eventContext_.addCommandToHistory(batchCmd); + // This shouldn't be necessary as it was done on mouseDown... + // eventContext_.getCanvas().call('selected', [selected]); } - // perform recalculation to weed out any stray identity transforms that might get stuck + // always recalculate dimensions to strip off stray identity transforms eventContext_.getCanvas().recalculateAllSelectedDimensions(); - eventContext_.getCanvas().call('changed', selectedElements); - break; - } default: - // This could occur in an extension - break; + // if it was being dragged/resized + if (realX !== eventContext_.getRStartX() || realY !== eventContext_.getRStartY()) { + const len = selectedElements.length; + for (let i = 0; i < len; ++i) { + if (isNullish(selectedElements[i])) { break; } + if (!selectedElements[i].firstChild) { + // Not needed for groups (incorrectly resizes elems), possibly not needed at all? + eventContext_.getCanvas().selectorManager.requestSelector(selectedElements[i]).resize(); + } + } + // no change in position/size, so maybe we should move to pathedit + } else { + t = evt.target; + if (selectedElements[0].nodeName === 'path' && isNullish(selectedElements[1])) { + eventContext_.getCanvas().pathActions.select(selectedElements[0]); + // if it was a path + // else, if it was selected and this is a shift-click, remove it from selection + } else if (evt.shiftKey && tempJustSelected !== t) { + eventContext_.getCanvas().removeFromSelection([ t ]); + } + } // no change in mouse position + + // Remove non-scaling stroke + if (supportsNonScalingStroke()) { + const elem = selectedElements[0]; + if (elem) { + elem.removeAttribute('style'); + walkTree(elem, function (el) { + el.removeAttribute('style'); + }); + } + } + } + return; + case 'zoom': { + if (!isNullish(eventContext_.getRubberBox())) { + eventContext_.getRubberBox().setAttribute('display', 'none'); + } + const factor = evt.shiftKey ? 0.5 : 2; + eventContext_.getCanvas().call('zoomed', { + x: Math.min(eventContext_.getRStartX(), realX), + y: Math.min(eventContext_.getRStartY(), realY), + width: Math.abs(realX - eventContext_.getRStartX()), + height: Math.abs(realY - eventContext_.getRStartY()), + factor + }); + return; + } case 'fhpath': { + // Check that the path contains at least 2 points; a degenerate one-point path + // causes problems. + // Webkit ignores how we set the points attribute with commas and uses space + // to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870 + eventContext_.setSumDistance(0); + eventContext_.setControllPoint2('x', 0); + eventContext_.setControllPoint2('y', 0); + eventContext_.setControllPoint1('x', 0); + eventContext_.setControllPoint1('y', 0); + eventContext_.setStart({ x: 0, y: 0 }); + eventContext_.setEnd('x', 0); + eventContext_.setEnd('y', 0); + const coords = element.getAttribute('points'); + const commaIndex = coords.indexOf(','); + keep = commaIndex >= 0 ? coords.includes(',', commaIndex + 1) : coords.includes(' ', coords.indexOf(' ') + 1); + if (keep) { + element = eventContext_.getCanvas().pathActions.smoothPolylineIntoPath(element); + } + break; + } case 'line': { + const x1 = element.getAttribute('x1'); + const y1 = element.getAttribute('y1'); + const x2 = element.getAttribute('x2'); + const y2 = element.getAttribute('y2'); + keep = (x1 !== x2 || y1 !== y2); + } + break; + case 'foreignObject': + case 'square': + case 'rect': + case 'image': { + const width = element.getAttribute('width'); + const height = element.getAttribute('height'); + // Image should be kept regardless of size (use inherit dimensions later) + keep = (width || height) || eventContext_.getCurrentMode() === 'image'; + } + break; + case 'circle': + keep = (element.getAttribute('r') !== '0'); + break; + case 'ellipse': { + const rx = element.getAttribute('rx'); + const ry = element.getAttribute('ry'); + keep = (rx || ry); + } + break; + case 'fhellipse': + if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && + (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { + element = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'ellipse', + curStyles: true, + attr: { + cx: (eventContext_.getFreehand('minx') + eventContext_.getFreehand('maxx')) / 2, + cy: (eventContext_.getFreehand('miny') + eventContext_.getFreehand('maxy')) / 2, + rx: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) / 2, + ry: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) / 2, + id: eventContext_.getId() + } + }); + eventContext_.getCanvas().call('changed', [ element ]); + keep = true; + } + break; + case 'fhrect': + if ((eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')) > 0 && + (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')) > 0) { + element = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'rect', + curStyles: true, + attr: { + x: eventContext_.getFreehand('minx'), + y: eventContext_.getFreehand('miny'), + width: (eventContext_.getFreehand('maxx') - eventContext_.getFreehand('minx')), + height: (eventContext_.getFreehand('maxy') - eventContext_.getFreehand('miny')), + id: eventContext_.getId() + } + }); + eventContext_.getCanvas().call('changed', [ element ]); + keep = true; + } + break; + case 'text': + keep = true; + eventContext_.getCanvas().selectOnly([ element ]); + eventContext_.getCanvas().textActions.start(element); + break; + case 'path': { + // set element to null here so that it is not removed nor finalized + element = null; + // continue to be set to true so that mouseMove happens + eventContext_.setStarted(true); + + const res = eventContext_.getCanvas().pathActions.mouseUp(evt, element, mouseX, mouseY); + ({ element } = res); + ({ keep } = res); + break; + } case 'pathedit': + keep = true; + element = null; + eventContext_.getCanvas().pathActions.mouseUp(evt); + break; + case 'textedit': + keep = false; + element = null; + eventContext_.getCanvas().textActions.mouseUp(evt, mouseX, mouseY); + break; + case 'rotate': { + keep = true; + element = null; + eventContext_.setCurrentMode('select'); + const batchCmd = eventContext_.getCanvas().undoMgr.finishUndoableChange(); + if (!batchCmd.isEmpty()) { + eventContext_.addCommandToHistory(batchCmd); + } + // perform recalculation to weed out any stray identity transforms that might get stuck + eventContext_.getCanvas().recalculateAllSelectedDimensions(); + eventContext_.getCanvas().call('changed', selectedElements); + break; + } default: + // This could occur in an extension + break; } /** @@ -989,291 +989,291 @@ export const mouseDownEvent = function (evt) { const tlist = getTransformList(mouseTarget); switch (eventContext_.getCurrentMode()) { - case 'select': - eventContext_.setStarted(true); - eventContext_.setCurrentResizeMode('none'); - if (rightClick) { eventContext_.setStarted(false); } + case 'select': + eventContext_.setStarted(true); + eventContext_.setCurrentResizeMode('none'); + if (rightClick) { eventContext_.setStarted(false); } - if (mouseTarget !== eventContext_.getSVGRoot()) { - // if this element is not yet selected, clear selection and select it - if (!selectedElements.includes(mouseTarget)) { - // only clear selection if shift is not pressed (otherwise, add - // element to selection) - if (!evt.shiftKey) { - // No need to do the call here as it will be done on addToSelection - eventContext_.getCanvas().clearSelection(true); - } - eventContext_.getCanvas().addToSelection([ mouseTarget ]); - eventContext_.setJustSelected(mouseTarget); - eventContext_.getCanvas().pathActions.clear(); + if (mouseTarget !== eventContext_.getSVGRoot()) { + // if this element is not yet selected, clear selection and select it + if (!selectedElements.includes(mouseTarget)) { + // only clear selection if shift is not pressed (otherwise, add + // element to selection) + if (!evt.shiftKey) { + // No need to do the call here as it will be done on addToSelection + eventContext_.getCanvas().clearSelection(true); } - // else if it's a path, go into pathedit mode in mouseup - - if (!rightClick) { - // insert a dummy transform so if the element(s) are moved it will have - // a transform to use for its translate - for (const selectedElement of selectedElements) { - if (isNullish(selectedElement)) { continue; } - const slist = getTransformList(selectedElement); - if (slist.numberOfItems) { - slist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), 0); - } else { - slist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - } - } - } - } else if (!rightClick) { - eventContext_.getCanvas().clearSelection(); - eventContext_.setCurrentMode('multiselect'); - if (isNullish(eventContext_.getRubberBox())) { - eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); - } - eventContext_.setRStartX(eventContext_.getRStartX() * currentZoom); - eventContext_.setRStartY(eventContext_.getRStartY() * currentZoom); - - assignAttributes(eventContext_.getRubberBox(), { - x: eventContext_.getRStartX(), - y: eventContext_.getRStartY(), - width: 0, - height: 0, - display: 'inline' - }, 100); + eventContext_.getCanvas().addToSelection([ mouseTarget ]); + eventContext_.setJustSelected(mouseTarget); + eventContext_.getCanvas().pathActions.clear(); } - break; - case 'zoom': - eventContext_.setStarted(true); + // else if it's a path, go into pathedit mode in mouseup + + if (!rightClick) { + // insert a dummy transform so if the element(s) are moved it will have + // a transform to use for its translate + for (const selectedElement of selectedElements) { + if (isNullish(selectedElement)) { continue; } + const slist = getTransformList(selectedElement); + if (slist.numberOfItems) { + slist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), 0); + } else { + slist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + } + } + } + } else if (!rightClick) { + eventContext_.getCanvas().clearSelection(); + eventContext_.setCurrentMode('multiselect'); if (isNullish(eventContext_.getRubberBox())) { eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); } + eventContext_.setRStartX(eventContext_.getRStartX() * currentZoom); + eventContext_.setRStartY(eventContext_.getRStartY() * currentZoom); + assignAttributes(eventContext_.getRubberBox(), { - x: realX * currentZoom, - y: realX * currentZoom, + x: eventContext_.getRStartX(), + y: eventContext_.getRStartY(), width: 0, height: 0, display: 'inline' }, 100); - break; - case 'resize': { - eventContext_.setStarted(true); - eventContext_.setStartX(x); - eventContext_.setStartY(y); - - // Getting the BBox from the selection box, since we know we - // want to orient around it - eventContext_.setInitBbox(utilsGetBBox($id('selectedBox0'))); - const bb = {}; - for (const [ key, val ] of Object.entries(eventContext_.getInitBbox())) { - bb[key] = val / currentZoom; - } - eventContext_.setInitBbox(bb); - - // append three dummy transforms to the tlist so that - // we can translate,scale,translate in mousemove - const pos = getRotationAngle(mouseTarget) ? 1 : 0; - - if (hasMatrixTransform(tlist)) { - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); - } else { - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); - - if (supportsNonScalingStroke()) { - // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop): - // https://code.google.com/p/svg-edit/issues/detail?id=904 - // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 - // TODO: Remove this workaround once vendor fixes the issue - const iswebkit = isWebkit(); - - let delayedStroke; - if (iswebkit) { - delayedStroke = function (ele) { - const stroke_ = ele.getAttribute('stroke'); - ele.removeAttribute('stroke'); - // Re-apply stroke after delay. Anything higher than 1 seems to cause flicker - if (stroke_ !== null) setTimeout(function () { ele.setAttribute('stroke', stroke_); }, 0); - }; - } - mouseTarget.style.vectorEffect = 'non-scaling-stroke'; - if (iswebkit) { delayedStroke(mouseTarget); } - - const all = mouseTarget.getElementsByTagName('*'), - len = all.length; - for (let i = 0; i < len; i++) { - if (!all[i].style) { // mathML - continue; - } - all[i].style.vectorEffect = 'non-scaling-stroke'; - if (iswebkit) { delayedStroke(all[i]); } - } - } - } - break; } - case 'fhellipse': - case 'fhrect': - case 'fhpath': - eventContext_.setStart({ x: realX, y: realY }); - eventContext_.setControllPoint1('x', 0); - eventContext_.setControllPoint1('y', 0); - eventContext_.setControllPoint2('x', 0); - eventContext_.setControllPoint2('y', 0); - eventContext_.setStarted(true); - eventContext_.setDAttr(realX + ',' + realY + ' '); - // Commented out as doing nothing now: - // strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'polyline', - curStyles: true, - attr: { - points: eventContext_.getDAttr(), - id: eventContext_.getCanvas().getNextId(), - fill: 'none', - opacity: curShape.opacity / 2, - 'stroke-linecap': 'round', - style: 'pointer-events:none' + break; + case 'zoom': + eventContext_.setStarted(true); + if (isNullish(eventContext_.getRubberBox())) { + eventContext_.setRubberBox(eventContext_.getCanvas().selectorManager.getRubberBandBox()); + } + assignAttributes(eventContext_.getRubberBox(), { + x: realX * currentZoom, + y: realX * currentZoom, + width: 0, + height: 0, + display: 'inline' + }, 100); + break; + case 'resize': { + eventContext_.setStarted(true); + eventContext_.setStartX(x); + eventContext_.setStartY(y); + + // Getting the BBox from the selection box, since we know we + // want to orient around it + eventContext_.setInitBbox(utilsGetBBox($id('selectedBox0'))); + const bb = {}; + for (const [ key, val ] of Object.entries(eventContext_.getInitBbox())) { + bb[key] = val / currentZoom; + } + eventContext_.setInitBbox(bb); + + // append three dummy transforms to the tlist so that + // we can translate,scale,translate in mousemove + const pos = getRotationAngle(mouseTarget) ? 1 : 0; + + if (hasMatrixTransform(tlist)) { + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + tlist.insertItemBefore(eventContext_.getSVGRoot().createSVGTransform(), pos); + } else { + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + tlist.appendItem(eventContext_.getSVGRoot().createSVGTransform()); + + if (supportsNonScalingStroke()) { + // Handle crash for newer Chrome and Safari 6 (Mobile and Desktop): + // https://code.google.com/p/svg-edit/issues/detail?id=904 + // Chromium issue: https://code.google.com/p/chromium/issues/detail?id=114625 + // TODO: Remove this workaround once vendor fixes the issue + const iswebkit = isWebkit(); + + let delayedStroke; + if (iswebkit) { + delayedStroke = function (ele) { + const stroke_ = ele.getAttribute('stroke'); + ele.removeAttribute('stroke'); + // Re-apply stroke after delay. Anything higher than 1 seems to cause flicker + if (stroke_ !== null) setTimeout(function () { ele.setAttribute('stroke', stroke_); }, 0); + }; } - }); - eventContext_.setFreehand('minx', realX); - eventContext_.setFreehand('maxx', realX); - eventContext_.setFreehand('miny', realY); - eventContext_.setFreehand('maxy', realY); - break; - case 'image': { - eventContext_.setStarted(true); - const newImage = eventContext_.getCanvas().addSVGElementFromJson({ - element: 'image', - attr: { - x, - y, - width: 0, - height: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2, - style: 'pointer-events:inherit' + mouseTarget.style.vectorEffect = 'non-scaling-stroke'; + if (iswebkit) { delayedStroke(mouseTarget); } + + const all = mouseTarget.getElementsByTagName('*'), + len = all.length; + for (let i = 0; i < len; i++) { + if (!all[i].style) { // mathML + continue; + } + all[i].style.vectorEffect = 'non-scaling-stroke'; + if (iswebkit) { delayedStroke(all[i]); } } - }); - setHref(newImage, eventContext_.getLastGoodImgUrl()); - preventClickDefault(newImage); - break; - } case 'square': + } + } + break; + } + case 'fhellipse': + case 'fhrect': + case 'fhpath': + eventContext_.setStart({ x: realX, y: realY }); + eventContext_.setControllPoint1('x', 0); + eventContext_.setControllPoint1('y', 0); + eventContext_.setControllPoint2('x', 0); + eventContext_.setControllPoint2('y', 0); + eventContext_.setStarted(true); + eventContext_.setDAttr(realX + ',' + realY + ' '); + // Commented out as doing nothing now: + // strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'polyline', + curStyles: true, + attr: { + points: eventContext_.getDAttr(), + id: eventContext_.getCanvas().getNextId(), + fill: 'none', + opacity: curShape.opacity / 2, + 'stroke-linecap': 'round', + style: 'pointer-events:none' + } + }); + eventContext_.setFreehand('minx', realX); + eventContext_.setFreehand('maxx', realX); + eventContext_.setFreehand('miny', realY); + eventContext_.setFreehand('maxy', realY); + break; + case 'image': { + eventContext_.setStarted(true); + const newImage = eventContext_.getCanvas().addSVGElementFromJson({ + element: 'image', + attr: { + x, + y, + width: 0, + height: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2, + style: 'pointer-events:inherit' + } + }); + setHref(newImage, eventContext_.getLastGoodImgUrl()); + preventClickDefault(newImage); + break; + } case 'square': // TODO: once we create the rect, we lose information that this was a square // (for resizing purposes this could be important) // Fallthrough - case 'rect': - eventContext_.setStarted(true); - eventContext_.setStartX(x); - eventContext_.setStartY(y); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'rect', - curStyles: true, - attr: { - x, - y, - width: 0, - height: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'line': { - eventContext_.setStarted(true); - const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'line', - curStyles: true, - attr: { - x1: x, - y1: y, - x2: x, - y2: y, - id: eventContext_.getCanvas().getNextId(), - stroke: curShape.stroke, - 'stroke-width': strokeW, - 'stroke-dasharray': curShape.stroke_dasharray, - 'stroke-linejoin': curShape.stroke_linejoin, - 'stroke-linecap': curShape.stroke_linecap, - 'stroke-opacity': curShape.stroke_opacity, - fill: 'none', - opacity: curShape.opacity / 2, - style: 'pointer-events:none' - } - }); - break; - } case 'circle': - eventContext_.setStarted(true); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'circle', - curStyles: true, - attr: { - cx: x, - cy: y, - r: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'ellipse': - eventContext_.setStarted(true); - eventContext_.getCanvas().addSVGElementFromJson({ - element: 'ellipse', - curStyles: true, - attr: { - cx: x, - cy: y, - rx: 0, - ry: 0, - id: eventContext_.getCanvas().getNextId(), - opacity: curShape.opacity / 2 - } - }); - break; - case 'text': - eventContext_.setStarted(true); + case 'rect': + eventContext_.setStarted(true); + eventContext_.setStartX(x); + eventContext_.setStartY(y); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'rect', + curStyles: true, + attr: { + x, + y, + width: 0, + height: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'line': { + eventContext_.setStarted(true); + const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width; + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'line', + curStyles: true, + attr: { + x1: x, + y1: y, + x2: x, + y2: y, + id: eventContext_.getCanvas().getNextId(), + stroke: curShape.stroke, + 'stroke-width': strokeW, + 'stroke-dasharray': curShape.stroke_dasharray, + 'stroke-linejoin': curShape.stroke_linejoin, + 'stroke-linecap': curShape.stroke_linecap, + 'stroke-opacity': curShape.stroke_opacity, + fill: 'none', + opacity: curShape.opacity / 2, + style: 'pointer-events:none' + } + }); + break; + } case 'circle': + eventContext_.setStarted(true); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'circle', + curStyles: true, + attr: { + cx: x, + cy: y, + r: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'ellipse': + eventContext_.setStarted(true); + eventContext_.getCanvas().addSVGElementFromJson({ + element: 'ellipse', + curStyles: true, + attr: { + cx: x, + cy: y, + rx: 0, + ry: 0, + id: eventContext_.getCanvas().getNextId(), + opacity: curShape.opacity / 2 + } + }); + break; + case 'text': + eventContext_.setStarted(true); /* const newText = */ eventContext_.getCanvas().addSVGElementFromJson({ - element: 'text', - curStyles: true, - attr: { - x, - y, - id: eventContext_.getCanvas().getNextId(), - fill: eventContext_.getCurText('fill'), - 'stroke-width': eventContext_.getCurText('stroke_width'), - 'font-size': eventContext_.getCurText('font_size'), - 'font-family': eventContext_.getCurText('font_family'), - 'text-anchor': 'middle', - 'xml:space': 'preserve', - opacity: curShape.opacity - } - }); - // newText.textContent = 'text'; - break; - case 'path': + element: 'text', + curStyles: true, + attr: { + x, + y, + id: eventContext_.getCanvas().getNextId(), + fill: eventContext_.getCurText('fill'), + 'stroke-width': eventContext_.getCurText('stroke_width'), + 'font-size': eventContext_.getCurText('font_size'), + 'font-family': eventContext_.getCurText('font_family'), + 'text-anchor': 'middle', + 'xml:space': 'preserve', + opacity: curShape.opacity + } + }); + // newText.textContent = 'text'; + break; + case 'path': // Fall through - case 'pathedit': - eventContext_.setStartX(eventContext_.getStartX() * currentZoom); - eventContext_.setStartY(eventContext_.getStartY() * currentZoom); - eventContext_.getCanvas().pathActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); - eventContext_.setStarted(true); - break; - case 'textedit': - eventContext_.setStartX(eventContext_.getStartX() * currentZoom); - eventContext_.setStartY(eventContext_.getStartY() * currentZoom); - eventContext_.getCanvas().textActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); - eventContext_.setStarted(true); - break; - case 'rotate': - eventContext_.setStarted(true); - // we are starting an undoable change (a drag-rotation) - eventContext_.getCanvas().undoMgr.beginUndoableChange('transform', selectedElements); - break; - default: - // This could occur in an extension - break; + case 'pathedit': + eventContext_.setStartX(eventContext_.getStartX() * currentZoom); + eventContext_.setStartY(eventContext_.getStartY() * currentZoom); + eventContext_.getCanvas().pathActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); + eventContext_.setStarted(true); + break; + case 'textedit': + eventContext_.setStartX(eventContext_.getStartX() * currentZoom); + eventContext_.setStartY(eventContext_.getStartY() * currentZoom); + eventContext_.getCanvas().textActions.mouseDown(evt, mouseTarget, eventContext_.getStartX(), eventContext_.getStartY()); + eventContext_.setStarted(true); + break; + case 'rotate': + eventContext_.setStarted(true); + // we are starting an undoable change (a drag-rotation) + eventContext_.getCanvas().undoMgr.beginUndoableChange('transform', selectedElements); + break; + default: + // This could occur in an extension + break; } /** diff --git a/src/svgcanvas/recalculate.js b/src/svgcanvas/recalculate.js index ac34703d..b003f9f2 100644 --- a/src/svgcanvas/recalculate.js +++ b/src/svgcanvas/recalculate.js @@ -163,17 +163,17 @@ export const recalculateDimensions = function (selected) { // If it still has a single [M] or [R][M], return null too (prevents BatchCommand from being returned). switch (selected.tagName) { - // Ignore these elements, as they can absorb the [M] - case 'line': - case 'polyline': - case 'polygon': - case 'path': - break; - default: - if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) || + // Ignore these elements, as they can absorb the [M] + case 'line': + case 'polyline': + case 'polygon': + case 'path': + break; + default: + if ((tlist.numberOfItems === 1 && tlist.getItem(0).type === 1) || (tlist.numberOfItems === 2 && tlist.getItem(0).type === 1 && tlist.getItem(0).type === 4)) { - return null; - } + return null; + } } // Grouped SVG element const gsvg = (dataStorage.has(selected, 'gsvg')) ? dataStorage.get(selected, 'gsvg') : undefined; @@ -185,42 +185,42 @@ export const recalculateDimensions = function (selected) { let initial = null; let attrs = []; switch (selected.tagName) { - case 'line': - attrs = [ 'x1', 'y1', 'x2', 'y2' ]; - break; - case 'circle': - attrs = [ 'cx', 'cy', 'r' ]; - break; - case 'ellipse': - attrs = [ 'cx', 'cy', 'rx', 'ry' ]; - break; - case 'foreignObject': - case 'rect': - case 'image': - attrs = [ 'width', 'height', 'x', 'y' ]; - break; - case 'use': - case 'text': - case 'tspan': - attrs = [ 'x', 'y' ]; - break; - case 'polygon': - case 'polyline': { - initial = {}; - initial.points = selected.getAttribute('points'); - const list = selected.points; - const len = list.numberOfItems; - changes.points = new Array(len); - for (let i = 0; i < len; ++i) { - const pt = list.getItem(i); - changes.points[i] = { x: pt.x, y: pt.y }; - } - break; - } case 'path': - initial = {}; - initial.d = selected.getAttribute('d'); - changes.d = selected.getAttribute('d'); - break; + case 'line': + attrs = [ 'x1', 'y1', 'x2', 'y2' ]; + break; + case 'circle': + attrs = [ 'cx', 'cy', 'r' ]; + break; + case 'ellipse': + attrs = [ 'cx', 'cy', 'rx', 'ry' ]; + break; + case 'foreignObject': + case 'rect': + case 'image': + attrs = [ 'width', 'height', 'x', 'y' ]; + break; + case 'use': + case 'text': + case 'tspan': + attrs = [ 'x', 'y' ]; + break; + case 'polygon': + case 'polyline': { + initial = {}; + initial.points = selected.getAttribute('points'); + const list = selected.points; + const len = list.numberOfItems; + changes.points = new Array(len); + for (let i = 0; i < len; ++i) { + const pt = list.getItem(i); + changes.points[i] = { x: pt.x, y: pt.y }; + } + break; + } case 'path': + initial = {}; + initial.d = selected.getAttribute('d'); + changes.d = selected.getAttribute('d'); + break; } // switch on element type to get initial values if (attrs.length) { @@ -702,34 +702,34 @@ export const recalculateDimensions = function (selected) { // Remap all point-based elements m = transformListToTransform(tlist).matrix; switch (selected.tagName) { - case 'line': - changes = { - x1: selected.getAttribute('x1'), - y1: selected.getAttribute('y1'), - x2: selected.getAttribute('x2'), - y2: selected.getAttribute('y2'), - }; + case 'line': + changes = { + x1: selected.getAttribute('x1'), + y1: selected.getAttribute('y1'), + x2: selected.getAttribute('x2'), + y2: selected.getAttribute('y2'), + }; // Fallthrough - case 'polyline': - case 'polygon': - changes.points = selected.getAttribute('points'); - if (changes.points) { - const list = selected.points; - const len = list.numberOfItems; - changes.points = new Array(len); - for (let i = 0; i < len; ++i) { - const pt = list.getItem(i); - changes.points[i] = { x: pt.x, y: pt.y }; - } + case 'polyline': + case 'polygon': + changes.points = selected.getAttribute('points'); + if (changes.points) { + const list = selected.points; + const len = list.numberOfItems; + changes.points = new Array(len); + for (let i = 0; i < len; ++i) { + const pt = list.getItem(i); + changes.points[i] = { x: pt.x, y: pt.y }; } + } // Fallthrough - case 'path': - changes.d = selected.getAttribute('d'); - operation = 1; - tlist.clear(); - break; - default: - break; + case 'path': + changes.d = selected.getAttribute('d'); + operation = 1; + tlist.clear(); + break; + default: + break; } // if it was a rotation, put the rotate back and return without a command // (this function has zero work to do for a rotate()) diff --git a/src/svgcanvas/selected-elem.js b/src/svgcanvas/selected-elem.js index 3f66bada..92c1e167 100644 --- a/src/svgcanvas/selected-elem.js +++ b/src/svgcanvas/selected-elem.js @@ -237,7 +237,7 @@ export const cloneSelectedElements = function (x, y) { function index(el) { if (!el) return -1; - var i = 0; + let i = 0; do { i++; } while (el == el.previousElementSibling); @@ -300,40 +300,40 @@ export const alignSelectedElements = function (type, relativeTo) { // 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') && + 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') && (curheight === Number.MIN_VALUE || curheight > bboxes[i].height)) - ) { - minx = bboxes[i].x; - miny = bboxes[i].y; - maxx = bboxes[i].x + bboxes[i].width; - maxy = bboxes[i].y + bboxes[i].height; - curwidth = bboxes[i].width; - curheight = bboxes[i].height; - } - break; - case 'largest': - if (((type === 'l' || type === 'c' || type === 'r' || type === 'left' || type === 'center' || type === 'right') && + ) { + minx = bboxes[i].x; + miny = bboxes[i].y; + maxx = bboxes[i].x + bboxes[i].width; + maxy = bboxes[i].y + bboxes[i].height; + curwidth = bboxes[i].width; + curheight = bboxes[i].height; + } + 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') && (curheight === Number.MIN_VALUE || curheight < bboxes[i].height)) - ) { - minx = bboxes[i].x; - miny = bboxes[i].y; - maxx = bboxes[i].x + bboxes[i].width; - maxy = bboxes[i].y + bboxes[i].height; - curwidth = bboxes[i].width; - 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; } - break; + ) { + minx = bboxes[i].x; + miny = bboxes[i].y; + maxx = bboxes[i].x + bboxes[i].width; + maxy = bboxes[i].y + bboxes[i].height; + curwidth = bboxes[i].width; + 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; } + break; } } // loop for each element to find the bbox and adjust min/max @@ -353,30 +353,30 @@ export const alignSelectedElements = function (type, relativeTo) { dx[i] = 0; dy[i] = 0; switch (type) { - case 'l': // left (horizontal) - case 'left': // left (horizontal) - dx[i] = minx - bbox.x; - break; - case 'c': // center (horizontal) - case 'center': // center (horizontal) - dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2); - break; - case 'r': // right (horizontal) - case 'right': // right (horizontal) - dx[i] = maxx - (bbox.x + bbox.width); - break; - case 't': // top (vertical) - case 'top': // top (vertical) - dy[i] = miny - bbox.y; - break; - case 'm': // middle (vertical) - case 'middle': // middle (vertical) - dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2); - break; - case 'b': // bottom (vertical) - case 'bottom': // bottom (vertical) - dy[i] = maxy - (bbox.y + bbox.height); - break; + case 'l': // left (horizontal) + case 'left': // left (horizontal) + dx[i] = minx - bbox.x; + break; + case 'c': // center (horizontal) + case 'center': // center (horizontal) + dx[i] = (minx + maxx) / 2 - (bbox.x + bbox.width / 2); + break; + case 'r': // right (horizontal) + case 'right': // right (horizontal) + dx[i] = maxx - (bbox.x + bbox.width); + break; + case 't': // top (vertical) + case 'top': // top (vertical) + dy[i] = miny - bbox.y; + break; + case 'm': // middle (vertical) + case 'middle': // middle (vertical) + dy[i] = (miny + maxy) / 2 - (bbox.y + bbox.height / 2); + break; + case 'b': // bottom (vertical) + case 'bottom': // bottom (vertical) + dy[i] = maxy - (bbox.y + bbox.height); + break; } } moveSelectedElements(dx, dy); @@ -460,15 +460,15 @@ export const groupSelectedElements = function (type, urlArg) { // eslint-disable-next-line sonarjs/no-small-switch switch (type) { - case 'a': { - cmdStr = 'Make hyperlink'; - url = urlArg || ''; - break; - } default: { - type = 'g'; - cmdStr = 'Group Elements'; - break; - } + case 'a': { + cmdStr = 'Make hyperlink'; + url = urlArg || ''; + break; + } default: { + type = 'g'; + cmdStr = 'Group Elements'; + break; + } } const batchCmd = new BatchCommand(cmdStr); diff --git a/src/svgcanvas/svg-exec.js b/src/svgcanvas/svg-exec.js index 5adeb78d..48a9808b 100644 --- a/src/svgcanvas/svg-exec.js +++ b/src/svgcanvas/svg-exec.js @@ -247,31 +247,31 @@ export const svgToString = function (elem, indent) { for (let i = 0; i < childs.length; i++) { const child = childs.item(i); switch (child.nodeType) { - case 1: // element node - out.push('\n'); - out.push(this.svgToString(child, indent)); - break; - 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 - out.push('\n'); - out.push(new Array(indent + 1).join(' ')); - out.push(''); - break; - case 8: // comment - out.push('\n'); - out.push(new Array(indent + 1).join(' ')); - out.push(''); - break; + case 1: // element node + out.push('\n'); + out.push(this.svgToString(child, indent)); + break; + 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 + out.push('\n'); + out.push(new Array(indent + 1).join(' ')); + out.push(''); + break; + case 8: // comment + out.push('\n'); + out.push(new Array(indent + 1).join(' ')); + out.push(''); + break; } // switch on node type } indent--; diff --git a/src/svgcanvas/svgcanvas.js b/src/svgcanvas/svgcanvas.js index f69c827a..8f7c0c81 100644 --- a/src/svgcanvas/svgcanvas.js +++ b/src/svgcanvas/svgcanvas.js @@ -209,7 +209,7 @@ class SvgCanvas { return this._storage.has(element) && this._storage.get(element).has(key); }, remove: function (element, key) { - var ret = this._storage.get(element).delete(key); + let ret = this._storage.get(element).delete(key); if (!this._storage.get(element).size === 0) { this._storage.delete(element); } @@ -787,65 +787,49 @@ class SvgCanvas { */ // Object to contain image data for raster images that were found encodable - const encodableImages = {}, + const encodableImages = {}; + // Object with save options + /** + * @type {module:svgcanvas.SaveOptions} + */ + const saveOptions = { round_digits: 5 }; + // Object with IDs for imported files, to see if one was already added + const importIds = {}; + // Current text style properties + const curText = allProperties.text; + // Object to contain all included extensions + const extensions = {}; + // Map of deleted reference elements + const removedElements = {}; - // Object with save options - /** - * @type {module:svgcanvas.SaveOptions} - */ - saveOptions = { round_digits: 5 }, - - // Object with IDs for imported files, to see if one was already added - importIds = {}, - - // Current text style properties - curText = allProperties.text, - - // Object to contain all included extensions - extensions = {}, - - // Map of deleted reference elements - removedElements = {}; - - let - // String with image URL of last loadable image - lastGoodImgUrl = curConfig.imgPath + 'logo.svg', - - // Boolean indicating whether or not a draw action has been started - started = false, - - // String with an element's initial transform attribute value - startTransform = null, - - // String indicating the current editor mode - currentMode = 'select', - - // String with the current direction in which an element is being resized - currentResizeMode = 'none', - - // Current general properties - curProperties = curShape, - - // Array with selected elements' Bounding box object - // selectedBBoxes = new Array(1), - - // The DOM element that was just selected - justSelected = null, - - // DOM element for selection rectangle drawn by the user - rubberBox = null, - - // Array of current BBoxes, used in getIntersectionList(). - curBBoxes = [], - - // Canvas point for the most recent right click - lastClickPoint = null; + // String with image URL of last loadable image + let lastGoodImgUrl = curConfig.imgPath + 'logo.svg'; + // Boolean indicating whether or not a draw action has been started + let started = false; + // String with an element's initial transform attribute value + let startTransform = null; + // String indicating the current editor mode + let currentMode = 'select'; + // String with the current direction in which an element is being resized + let currentResizeMode = 'none'; + // Current general properties + let curProperties = curShape; + // Array with selected elements' Bounding box object + // selectedBBoxes = new Array(1), + // The DOM element that was just selected + let justSelected = null; + // DOM element for selection rectangle drawn by the user + let rubberBox = null; + // Array of current BBoxes, used in getIntersectionList(). + let curBBoxes = []; + // Canvas point for the most recent right click + let lastClickPoint = null; this.runExtension = function (name, action, vars) { return this.runExtensions(action, vars, false, (n) => n === name); }; -/* eslint-disable max-len */ -/** + /* eslint-disable max-len */ + /** * @todo Consider: Should this return an array by default, so extension results aren't overwritten? * @todo Would be easier to document if passing in object with key of action and vars as value; could then define an interface which tied both together * @function module:svgcanvas.SvgCanvas#runExtensions @@ -855,7 +839,7 @@ class SvgCanvas { * @param {module:svgcanvas.ExtensionNameFilter} nameFilter * @returns {GenericArray|module:svgcanvas.ExtensionStatus|false} See {@tutorial ExtensionDocs} on the ExtensionStatus. */ -/* eslint-enable max-len */ + /* eslint-enable max-len */ this.runExtensions = runExtensionsMethod; @@ -1037,14 +1021,14 @@ class SvgCanvas { * @event module:svgcanvas.SvgCanvas#event:exportedPDF * @type {module:svgcanvas.PDFExportedResults} */ -/* eslint-disable max-len */ -/** + /* eslint-disable max-len */ + /** * Creating a cover-all class until {@link https://github.com/jsdoc3/jsdoc/issues/1545} may be supported. * `undefined` may be returned by {@link module:svgcanvas.SvgCanvas#event:extension_added} if the extension's `init` returns `undefined` It is also the type for the following events "zoomDone", "unsetnonce", "cleared", and "extensions_added". * @event module:svgcanvas.SvgCanvas#event:GenericCanvasEvent * @type {module:svgcanvas.SvgCanvas#event:selected|module:svgcanvas.SvgCanvas#event:changed|module:svgcanvas.SvgCanvas#event:contextset|module:svgcanvas.SvgCanvas#event:pointsAdded|module:svgcanvas.SvgCanvas#event:extension_added|module:svgcanvas.SvgCanvas#event:extensions_added|module:svgcanvas.SvgCanvas#event:message|module:svgcanvas.SvgCanvas#event:transition|module:svgcanvas.SvgCanvas#event:zoomed|module:svgcanvas.SvgCanvas#event:updateCanvas|module:svgcanvas.SvgCanvas#event:saved|module:svgcanvas.SvgCanvas#event:exported|module:svgcanvas.SvgCanvas#event:exportedPDF|module:svgcanvas.SvgCanvas#event:setnonce|module:svgcanvas.SvgCanvas#event:unsetnonce|void} */ -/* eslint-enable max-len */ + /* eslint-enable max-len */ /** * The promise return, if present, resolves to `undefined` @@ -1059,7 +1043,7 @@ class SvgCanvas { * @listens module:svgcanvas.SvgCanvas#event:GenericCanvasEvent * @returns {module:svgcanvas.EventHandlerReturn} */ -/* eslint-disable max-len */ + /* eslint-disable max-len */ /** * Attaches a callback function to an event. * @function module:svgcanvas.SvgCanvas#bind @@ -1067,7 +1051,7 @@ class SvgCanvas { * @param {module:svgcanvas.EventHandler} f - The callback function to bind to the event * @returns {module:svgcanvas.EventHandler} The previous event */ -/* eslint-enable max-len */ + /* eslint-enable max-len */ canvas.bind = function (ev, f) { const old = events[ev]; events[ev] = f; diff --git a/src/svgcanvas/utilities.js b/src/svgcanvas/utilities.js index 14e7db0d..cd73ddaf 100644 --- a/src/svgcanvas/utilities.js +++ b/src/svgcanvas/utilities.js @@ -628,70 +628,70 @@ export const getBBox = function (elem) { let ret = null; switch (elname) { - case 'text': - if (selected.textContent === '') { - selected.textContent = 'a'; // Some character needed for the selector to use. - ret = selected.getBBox(); - selected.textContent = ''; - } else if (selected.getBBox) { - ret = selected.getBBox(); - } - break; - case 'path': - if (!supportsPathBBox()) { - ret = getPathBBox(selected); - } else if (selected.getBBox) { - ret = selected.getBBox(); - } - break; - case 'g': - case 'a': - ret = groupBBFix(selected); - break; - default: + case 'text': + if (selected.textContent === '') { + selected.textContent = 'a'; // Some character needed for the selector to use. + ret = selected.getBBox(); + selected.textContent = ''; + } else if (selected.getBBox) { + ret = selected.getBBox(); + } + break; + case 'path': + if (!supportsPathBBox()) { + ret = getPathBBox(selected); + } else if (selected.getBBox) { + ret = selected.getBBox(); + } + break; + case 'g': + case 'a': + ret = groupBBFix(selected); + break; + default: - if (elname === 'use') { - ret = groupBBFix(selected); // , true); + if (elname === 'use') { + ret = groupBBFix(selected); // , true); + } + if (elname === 'use' || (elname === 'foreignObject' && isWebkit())) { + if (!ret) { ret = selected.getBBox(); } + // This is resolved in later versions of webkit, perhaps we should + // have a featured detection for correct 'use' behavior? + // —————————— + if (!isWebkit()) { + const { x, y, width, height } = ret; + const bb = { + width, + height, + x: x + Number.parseFloat(selected.getAttribute('x') || 0), + y: y + Number.parseFloat(selected.getAttribute('y') || 0) + }; + ret = bb; } - if (elname === 'use' || (elname === 'foreignObject' && isWebkit())) { - if (!ret) { ret = selected.getBBox(); } - // This is resolved in later versions of webkit, perhaps we should - // have a featured detection for correct 'use' behavior? - // —————————— - if (!isWebkit()) { - const { x, y, width, height } = ret; - const bb = { + } else if (visElemsArr.includes(elname)) { + if (selected) { + try { + ret = selected.getBBox(); + } catch (err) { + // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268 + // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835 + const extent = selected.getExtentOfChar(0); // pos+dimensions of the first glyph + const width = selected.getComputedTextLength(); // width of the tspan + ret = { + x: extent.x, + y: extent.y, width, - height, - x: x + Number.parseFloat(selected.getAttribute('x') || 0), - y: y + Number.parseFloat(selected.getAttribute('y') || 0) + height: extent.height }; - ret = bb; } - } else if (visElemsArr.includes(elname)) { - if (selected) { - try { - ret = selected.getBBox(); - } catch (err) { - // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268 - // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835 - const extent = selected.getExtentOfChar(0); // pos+dimensions of the first glyph - const width = selected.getComputedTextLength(); // width of the tspan - ret = { - x: extent.x, - y: extent.y, - width, - height: extent.height - }; - } - } else { - // Check if element is child of a foreignObject - const fo = getClosest(selected.parentNode, 'foreignObject'); - if (fo.length && fo[0].getBBox) { - ret = fo[0].getBBox(); - } + } else { + // Check if element is child of a foreignObject + const fo = getClosest(selected.parentNode, 'foreignObject'); + if (fo.length && fo[0].getBBox) { + ret = fo[0].getBBox(); } } + } } if (ret) { ret = bboxToObj(ret); @@ -739,76 +739,76 @@ export const getPathDFromElement = function (elem) { let num = 1.81; let d, rx, ry; switch (elem.tagName) { - case 'ellipse': - case 'circle': { - rx = elem.getAttribute('rx'); - ry = elem.getAttribute('ry'); - const cx = elem.getAttribute('cx'); - const cy = elem.getAttribute('cy'); - if (elem.tagName === 'circle' && elem.hasAttribute('r')) { - ry = elem.getAttribute('r'); - rx = ry; - } - d = getPathDFromSegments([ - [ 'M', [ (cx - rx), (cy) ] ], - [ 'C', [ (cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry) ] ], - [ 'C', [ (cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy) ] ], - [ 'C', [ (cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry) ] ], - [ 'C', [ (cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy) ] ], + case 'ellipse': + case 'circle': { + rx = elem.getAttribute('rx'); + ry = elem.getAttribute('ry'); + const cx = elem.getAttribute('cx'); + const cy = elem.getAttribute('cy'); + if (elem.tagName === 'circle' && elem.hasAttribute('r')) { + ry = elem.getAttribute('r'); + rx = ry; + } + d = getPathDFromSegments([ + [ 'M', [ (cx - rx), (cy) ] ], + [ 'C', [ (cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry) ] ], + [ 'C', [ (cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy) ] ], + [ 'C', [ (cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry) ] ], + [ 'C', [ (cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy) ] ], + [ 'Z', [] ] + ]); + break; + } case 'path': + d = elem.getAttribute('d'); + break; + case 'line': { + const x1 = elem.getAttribute('x1'); + const y1 = elem.getAttribute('y1'); + const x2 = elem.getAttribute('x2'); + const y2 = elem.getAttribute('y2'); + d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2; + } + break; + case 'polyline': + d = 'M' + elem.getAttribute('points'); + break; + case 'polygon': + d = 'M' + elem.getAttribute('points') + ' Z'; + break; + case 'rect': { + rx = elem.getAttribute('rx'); + ry = elem.getAttribute('ry'); + const b = elem.getBBox(); + const { x, y } = b, + w = b.width, + h = b.height; + num = 4 - num; // Why? Because! + + d = (!rx && !ry) + // Regular rect + ? getPathDFromSegments([ + [ 'M', [ x, y ] ], + [ 'L', [ x + w, y ] ], + [ 'L', [ x + w, y + h ] ], + [ 'L', [ x, y + h ] ], + [ 'L', [ x, y ] ], + [ 'Z', [] ] + ]) + : getPathDFromSegments([ + [ 'M', [ x, y + ry ] ], + [ 'C', [ x, y + ry / num, x + rx / num, y, x + rx, y ] ], + [ 'L', [ x + w - rx, y ] ], + [ 'C', [ x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry ] ], + [ 'L', [ x + w, y + h - ry ] ], + [ 'C', [ x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h ] ], + [ 'L', [ x + rx, y + h ] ], + [ 'C', [ x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry ] ], + [ 'L', [ x, y + ry ] ], [ 'Z', [] ] ]); - break; - } case 'path': - d = elem.getAttribute('d'); - break; - case 'line': { - const x1 = elem.getAttribute('x1'); - const y1 = elem.getAttribute('y1'); - const x2 = elem.getAttribute('x2'); - const y2 = elem.getAttribute('y2'); - d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2; - } - break; - case 'polyline': - d = 'M' + elem.getAttribute('points'); - break; - case 'polygon': - d = 'M' + elem.getAttribute('points') + ' Z'; - break; - case 'rect': { - rx = elem.getAttribute('rx'); - ry = elem.getAttribute('ry'); - const b = elem.getBBox(); - const { x, y } = b, - w = b.width, - h = b.height; - num = 4 - num; // Why? Because! - - d = (!rx && !ry) - // Regular rect - ? getPathDFromSegments([ - [ 'M', [ x, y ] ], - [ 'L', [ x + w, y ] ], - [ 'L', [ x + w, y + h ] ], - [ 'L', [ x, y + h ] ], - [ 'L', [ x, y ] ], - [ 'Z', [] ] - ]) - : getPathDFromSegments([ - [ 'M', [ x, y + ry ] ], - [ 'C', [ x, y + ry / num, x + rx / num, y, x + rx, y ] ], - [ 'L', [ x + w - rx, y ] ], - [ 'C', [ x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry ] ], - [ 'L', [ x + w, y + h - ry ] ], - [ 'C', [ x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h ] ], - [ 'L', [ x + rx, y + h ] ], - [ 'C', [ x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry ] ], - [ 'L', [ x, y + ry ] ], - [ 'Z', [] ] - ]); - break; - } default: - break; + break; + } default: + break; } return d;