From 403237c18228df15846374a8ca789cccf5237ec4 Mon Sep 17 00:00:00 2001
From: JFH <20402845+jfhenon@users.noreply.github.com>
Date: Fri, 27 Dec 2024 21:37:05 +0100
Subject: [PATCH] Fix text scale #1008 (#1010)
* update dependencies
* fix #1008 by adjusting font-size
(allowing to keep the text keep his proportions to the font)
* refactor math.js
---
coverage/coverage-summary.json | 142 ++++----
.../scenario-check tool_bold.svg | 2 +-
.../scenario-check tool_clone.svg | 2 +-
.../scenario-check tool_italic.svg | 2 +-
...scenario-check tool_text_align_to_page.svg | 2 +-
.../scenario-check tool_text_change_blur.svg | 2 +-
...nario-check tool_text_change_font_size.svg | 2 +-
...cenario-check tool_text_change_opacity.svg | 2 +-
...heck tool_text_change_stoke_fill_color.svg | 2 +-
...io-check tool_text_change_stroke_width.svg | 2 +-
...-check tool_text_change_x_y_coordinate.svg | 2 +-
cypress/e2e/unit/math.cy.js | 342 ++++++++++++++----
package-lock.json | 201 +++++-----
package.json | 10 +-
packages/svgcanvas/core/coords.js | 38 +-
packages/svgcanvas/core/event.js | 1 +
packages/svgcanvas/core/math.js | 292 +++++++--------
packages/svgcanvas/core/text-actions.js | 289 ++++++++-------
18 files changed, 812 insertions(+), 523 deletions(-)
diff --git a/coverage/coverage-summary.json b/coverage/coverage-summary.json
index 9d5114cee..e9a865f8d 100644
--- a/coverage/coverage-summary.json
+++ b/coverage/coverage-summary.json
@@ -1,71 +1,73 @@
-{"total": {"lines":{"total":6677,"covered":3923,"skipped":0,"pct":58.75},"statements":{"total":6981,"covered":4034,"skipped":0,"pct":57.78},"functions":{"total":1001,"covered":524,"skipped":0,"pct":52.34},"branches":{"total":3421,"covered":1412,"skipped":0,"pct":41.27},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/packages/svgcanvas/common/browser.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":6,"covered":2,"skipped":0,"pct":33.33},"statements":{"total":30,"covered":25,"skipped":0,"pct":83.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/packages/svgcanvas/common/util.js": {"lines":{"total":90,"covered":8,"skipped":0,"pct":8.88},"functions":{"total":7,"covered":3,"skipped":0,"pct":42.85},"statements":{"total":92,"covered":10,"skipped":0,"pct":10.86},"branches":{"total":98,"covered":10,"skipped":0,"pct":10.2}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/ConfigObj.js": {"lines":{"total":101,"covered":39,"skipped":0,"pct":38.61},"functions":{"total":14,"covered":9,"skipped":0,"pct":64.28},"statements":{"total":102,"covered":39,"skipped":0,"pct":38.23},"branches":{"total":96,"covered":25,"skipped":0,"pct":26.04}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/Editor.js": {"lines":{"total":414,"covered":192,"skipped":0,"pct":46.37},"functions":{"total":103,"covered":31,"skipped":0,"pct":30.09},"statements":{"total":420,"covered":193,"skipped":0,"pct":45.95},"branches":{"total":213,"covered":75,"skipped":0,"pct":35.21}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/EditorStartup.js": {"lines":{"total":383,"covered":237,"skipped":0,"pct":61.87},"functions":{"total":57,"covered":31,"skipped":0,"pct":54.38},"statements":{"total":395,"covered":246,"skipped":0,"pct":62.27},"branches":{"total":147,"covered":51,"skipped":0,"pct":34.69}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/MainMenu.js": {"lines":{"total":101,"covered":44,"skipped":0,"pct":43.56},"functions":{"total":14,"covered":7,"skipped":0,"pct":50},"statements":{"total":101,"covered":44,"skipped":0,"pct":43.56},"branches":{"total":44,"covered":7,"skipped":0,"pct":15.9}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/Rulers.js": {"lines":{"total":119,"covered":93,"skipped":0,"pct":78.15},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":124,"covered":98,"skipped":0,"pct":79.03},"branches":{"total":43,"covered":32,"skipped":0,"pct":74.41}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/browser-not-supported.js": {"lines":{"total":4,"covered":3,"skipped":0,"pct":75},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":3,"skipped":0,"pct":75},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/contextmenu.js": {"lines":{"total":22,"covered":9,"skipped":0,"pct":40.9},"functions":{"total":8,"covered":1,"skipped":0,"pct":12.5},"statements":{"total":23,"covered":9,"skipped":0,"pct":39.13},"branches":{"total":13,"covered":0,"skipped":0,"pct":0}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/locale.js": {"lines":{"total":14,"covered":9,"skipped":0,"pct":64.28},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":14,"covered":9,"skipped":0,"pct":64.28},"branches":{"total":10,"covered":3,"skipped":0,"pct":30}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/PaintBox.js": {"lines":{"total":64,"covered":51,"skipped":0,"pct":79.68},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":67,"covered":53,"skipped":0,"pct":79.1},"branches":{"total":33,"covered":20,"skipped":0,"pct":60.6}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/index.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seButton.js": {"lines":{"total":57,"covered":41,"skipped":0,"pct":71.92},"functions":{"total":15,"covered":8,"skipped":0,"pct":53.33},"statements":{"total":60,"covered":43,"skipped":0,"pct":71.66},"branches":{"total":33,"covered":23,"skipped":0,"pct":69.69}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seColorPicker.js": {"lines":{"total":50,"covered":41,"skipped":0,"pct":82},"functions":{"total":16,"covered":11,"skipped":0,"pct":68.75},"statements":{"total":51,"covered":41,"skipped":0,"pct":80.39},"branches":{"total":11,"covered":8,"skipped":0,"pct":72.72}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seExplorerButton.js": {"lines":{"total":99,"covered":76,"skipped":0,"pct":76.76},"functions":{"total":18,"covered":11,"skipped":0,"pct":61.11},"statements":{"total":102,"covered":76,"skipped":0,"pct":74.5},"branches":{"total":30,"covered":17,"skipped":0,"pct":56.66}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seFlyingButton.js": {"lines":{"total":77,"covered":50,"skipped":0,"pct":64.93},"functions":{"total":15,"covered":11,"skipped":0,"pct":73.33},"statements":{"total":78,"covered":51,"skipped":0,"pct":65.38},"branches":{"total":31,"covered":13,"skipped":0,"pct":41.93}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seInput.js": {"lines":{"total":46,"covered":31,"skipped":0,"pct":67.39},"functions":{"total":16,"covered":8,"skipped":0,"pct":50},"statements":{"total":47,"covered":31,"skipped":0,"pct":65.95},"branches":{"total":8,"covered":4,"skipped":0,"pct":50}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seList.js": {"lines":{"total":87,"covered":59,"skipped":0,"pct":67.81},"functions":{"total":21,"covered":10,"skipped":0,"pct":47.61},"statements":{"total":89,"covered":61,"skipped":0,"pct":68.53},"branches":{"total":22,"covered":12,"skipped":0,"pct":54.54}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seListItem.js": {"lines":{"total":39,"covered":26,"skipped":0,"pct":66.66},"functions":{"total":12,"covered":4,"skipped":0,"pct":33.33},"statements":{"total":40,"covered":27,"skipped":0,"pct":67.5},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seMenu.js": {"lines":{"total":26,"covered":20,"skipped":0,"pct":76.92},"functions":{"total":7,"covered":3,"skipped":0,"pct":42.85},"statements":{"total":27,"covered":20,"skipped":0,"pct":74.07},"branches":{"total":5,"covered":3,"skipped":0,"pct":60}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seMenuItem.js": {"lines":{"total":37,"covered":26,"skipped":0,"pct":70.27},"functions":{"total":9,"covered":5,"skipped":0,"pct":55.55},"statements":{"total":40,"covered":27,"skipped":0,"pct":67.5},"branches":{"total":21,"covered":8,"skipped":0,"pct":38.09}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/sePalette.js": {"lines":{"total":53,"covered":44,"skipped":0,"pct":83.01},"functions":{"total":11,"covered":9,"skipped":0,"pct":81.81},"statements":{"total":53,"covered":44,"skipped":0,"pct":83.01},"branches":{"total":15,"covered":10,"skipped":0,"pct":66.66}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/sePlainBorderButton.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/sePlainMenuButton.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seSelect.js": {"lines":{"total":56,"covered":45,"skipped":0,"pct":80.35},"functions":{"total":17,"covered":9,"skipped":0,"pct":52.94},"statements":{"total":59,"covered":47,"skipped":0,"pct":79.66},"branches":{"total":16,"covered":14,"skipped":0,"pct":87.5}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seSpinInput.js": {"lines":{"total":65,"covered":55,"skipped":0,"pct":84.61},"functions":{"total":18,"covered":10,"skipped":0,"pct":55.55},"statements":{"total":66,"covered":55,"skipped":0,"pct":83.33},"branches":{"total":17,"covered":13,"skipped":0,"pct":76.47}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seText.js": {"lines":{"total":28,"covered":18,"skipped":0,"pct":64.28},"functions":{"total":10,"covered":4,"skipped":0,"pct":40},"statements":{"total":29,"covered":18,"skipped":0,"pct":62.06},"branches":{"total":8,"covered":5,"skipped":0,"pct":62.5}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/seZoom.js": {"lines":{"total":102,"covered":93,"skipped":0,"pct":91.17},"functions":{"total":28,"covered":24,"skipped":0,"pct":85.71},"statements":{"total":107,"covered":97,"skipped":0,"pct":90.65},"branches":{"total":36,"covered":21,"skipped":0,"pct":58.33}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/jgraduate/ColorValuePicker.js": {"lines":{"total":231,"covered":54,"skipped":0,"pct":23.37},"functions":{"total":9,"covered":3,"skipped":0,"pct":33.33},"statements":{"total":249,"covered":63,"skipped":0,"pct":25.3},"branches":{"total":157,"covered":34,"skipped":0,"pct":21.65}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/jgraduate/Slider.js": {"lines":{"total":156,"covered":91,"skipped":0,"pct":58.33},"functions":{"total":17,"covered":9,"skipped":0,"pct":52.94},"statements":{"total":176,"covered":96,"skipped":0,"pct":54.54},"branches":{"total":186,"covered":97,"skipped":0,"pct":52.15}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/jgraduate/jQuery.jGraduate.js": {"lines":{"total":580,"covered":275,"skipped":0,"pct":47.41},"functions":{"total":44,"covered":14,"skipped":0,"pct":31.81},"statements":{"total":602,"covered":282,"skipped":0,"pct":46.84},"branches":{"total":278,"covered":100,"skipped":0,"pct":35.97}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/components/jgraduate/jQuery.jPicker.js": {"lines":{"total":840,"covered":449,"skipped":0,"pct":53.45},"functions":{"total":60,"covered":39,"skipped":0,"pct":65},"statements":{"total":926,"covered":475,"skipped":0,"pct":51.29},"branches":{"total":777,"covered":327,"skipped":0,"pct":42.08}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/SePlainAlertDialog.js": {"lines":{"total":12,"covered":2,"skipped":0,"pct":16.66},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":2,"skipped":0,"pct":16.66},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/cmenuDialog.js": {"lines":{"total":120,"covered":112,"skipped":0,"pct":93.33},"functions":{"total":28,"covered":16,"skipped":0,"pct":57.14},"statements":{"total":131,"covered":117,"skipped":0,"pct":89.31},"branches":{"total":23,"covered":19,"skipped":0,"pct":82.6}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/cmenuLayersDialog.js": {"lines":{"total":61,"covered":49,"skipped":0,"pct":80.32},"functions":{"total":16,"covered":6,"skipped":0,"pct":37.5},"statements":{"total":66,"covered":49,"skipped":0,"pct":74.24},"branches":{"total":18,"covered":13,"skipped":0,"pct":72.22}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/editorPreferencesDialog.js": {"lines":{"total":157,"covered":125,"skipped":0,"pct":79.61},"functions":{"total":30,"covered":9,"skipped":0,"pct":30},"statements":{"total":159,"covered":126,"skipped":0,"pct":79.24},"branches":{"total":46,"covered":35,"skipped":0,"pct":76.08}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/exportDialog.js": {"lines":{"total":52,"covered":36,"skipped":0,"pct":69.23},"functions":{"total":14,"covered":5,"skipped":0,"pct":35.71},"statements":{"total":55,"covered":36,"skipped":0,"pct":65.45},"branches":{"total":11,"covered":5,"skipped":0,"pct":45.45}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/imagePropertiesDialog.js": {"lines":{"total":161,"covered":90,"skipped":0,"pct":55.9},"functions":{"total":20,"covered":5,"skipped":0,"pct":25},"statements":{"total":162,"covered":90,"skipped":0,"pct":55.55},"branches":{"total":53,"covered":23,"skipped":0,"pct":43.39}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/index.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/seAlertDialog.js": {"lines":{"total":6,"covered":2,"skipped":0,"pct":33.33},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":6,"covered":2,"skipped":0,"pct":33.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/seConfirmDialog.js": {"lines":{"total":8,"covered":2,"skipped":0,"pct":25},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":2,"skipped":0,"pct":25},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/sePromptDialog.js": {"lines":{"total":24,"covered":5,"skipped":0,"pct":20.83},"functions":{"total":7,"covered":2,"skipped":0,"pct":28.57},"statements":{"total":24,"covered":5,"skipped":0,"pct":20.83},"branches":{"total":9,"covered":0,"skipped":0,"pct":0}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/seSelectDialog.js": {"lines":{"total":8,"covered":2,"skipped":0,"pct":25},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":2,"skipped":0,"pct":25},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/svgSourceDialog.js": {"lines":{"total":74,"covered":56,"skipped":0,"pct":75.67},"functions":{"total":17,"covered":6,"skipped":0,"pct":35.29},"statements":{"total":75,"covered":57,"skipped":0,"pct":76},"branches":{"total":17,"covered":14,"skipped":0,"pct":82.35}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/se-elix/define/NumberSpinBox.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/se-elix/src/base/NumberSpinBox.js": {"lines":{"total":54,"covered":51,"skipped":0,"pct":94.44},"functions":{"total":13,"covered":11,"skipped":0,"pct":84.61},"statements":{"total":54,"covered":51,"skipped":0,"pct":94.44},"branches":{"total":53,"covered":46,"skipped":0,"pct":86.79}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/dialogs/se-elix/src/plain/PlainNumberSpinBox.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-connector/ext-connector.js": {"lines":{"total":287,"covered":84,"skipped":0,"pct":29.26},"functions":{"total":26,"covered":16,"skipped":0,"pct":61.53},"statements":{"total":295,"covered":87,"skipped":0,"pct":29.49},"branches":{"total":140,"covered":21,"skipped":0,"pct":15}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-connector/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-eyedropper/ext-eyedropper.js": {"lines":{"total":99,"covered":57,"skipped":0,"pct":57.57},"functions":{"total":16,"covered":11,"skipped":0,"pct":68.75},"statements":{"total":109,"covered":58,"skipped":0,"pct":53.21},"branches":{"total":65,"covered":19,"skipped":0,"pct":29.23}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-eyedropper/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-grid/ext-grid.js": {"lines":{"total":88,"covered":43,"skipped":0,"pct":48.86},"functions":{"total":8,"covered":4,"skipped":0,"pct":50},"statements":{"total":90,"covered":43,"skipped":0,"pct":47.77},"branches":{"total":10,"covered":4,"skipped":0,"pct":40}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-grid/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-markers/ext-markers.js": {"lines":{"total":149,"covered":46,"skipped":0,"pct":30.87},"functions":{"total":21,"covered":12,"skipped":0,"pct":57.14},"statements":{"total":164,"covered":48,"skipped":0,"pct":29.26},"branches":{"total":80,"covered":22,"skipped":0,"pct":27.5}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-opensave/ext-opensave.js": {"lines":{"total":136,"covered":36,"skipped":0,"pct":26.47},"functions":{"total":13,"covered":3,"skipped":0,"pct":23.07},"statements":{"total":141,"covered":36,"skipped":0,"pct":25.53},"branches":{"total":32,"covered":0,"skipped":0,"pct":0}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-opensave/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-panning/ext-panning.js": {"lines":{"total":30,"covered":22,"skipped":0,"pct":73.33},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":30,"covered":22,"skipped":0,"pct":73.33},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-panning/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-polystar/ext-polystar.js": {"lines":{"total":247,"covered":232,"skipped":0,"pct":93.92},"functions":{"total":18,"covered":16,"skipped":0,"pct":88.88},"statements":{"total":256,"covered":241,"skipped":0,"pct":94.14},"branches":{"total":62,"covered":39,"skipped":0,"pct":62.9}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-polystar/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-shapes/ext-shapes.js": {"lines":{"total":76,"covered":74,"skipped":0,"pct":97.36},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":80,"covered":78,"skipped":0,"pct":97.5},"branches":{"total":26,"covered":17,"skipped":0,"pct":65.38}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-shapes/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-storage/ext-storage.js": {"lines":{"total":91,"covered":37,"skipped":0,"pct":40.65},"functions":{"total":12,"covered":5,"skipped":0,"pct":41.66},"statements":{"total":91,"covered":37,"skipped":0,"pct":40.65},"branches":{"total":68,"covered":16,"skipped":0,"pct":23.52}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/extensions/ext-storage/storageDialog.js": {"lines":{"total":60,"covered":56,"skipped":0,"pct":93.33},"functions":{"total":10,"covered":7,"skipped":0,"pct":70},"statements":{"total":62,"covered":57,"skipped":0,"pct":91.93},"branches":{"total":15,"covered":13,"skipped":0,"pct":86.66}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/locale/lang.en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/panels/BottomPanel.js": {"lines":{"total":77,"covered":61,"skipped":0,"pct":79.22},"functions":{"total":22,"covered":14,"skipped":0,"pct":63.63},"statements":{"total":77,"covered":61,"skipped":0,"pct":79.22},"branches":{"total":30,"covered":16,"skipped":0,"pct":53.33}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/panels/LayersPanel.js": {"lines":{"total":162,"covered":75,"skipped":0,"pct":46.29},"functions":{"total":27,"covered":6,"skipped":0,"pct":22.22},"statements":{"total":168,"covered":76,"skipped":0,"pct":45.23},"branches":{"total":46,"covered":6,"skipped":0,"pct":13.04}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/panels/LeftPanel.js": {"lines":{"total":57,"covered":46,"skipped":0,"pct":80.7},"functions":{"total":20,"covered":15,"skipped":0,"pct":75},"statements":{"total":58,"covered":46,"skipped":0,"pct":79.31},"branches":{"total":32,"covered":12,"skipped":0,"pct":37.5}}
-,"/Users/jean-francoishenon/Work/svgedit/src/editor/panels/TopPanel.js": {"lines":{"total":409,"covered":313,"skipped":0,"pct":76.52},"functions":{"total":76,"covered":52,"skipped":0,"pct":68.42},"statements":{"total":421,"covered":316,"skipped":0,"pct":75.05},"branches":{"total":209,"covered":127,"skipped":0,"pct":60.76}}
+{"total": {"lines":{"total":6724,"covered":3944,"skipped":0,"pct":58.65},"statements":{"total":7028,"covered":4055,"skipped":0,"pct":57.69},"functions":{"total":1009,"covered":529,"skipped":0,"pct":52.42},"branches":{"total":3434,"covered":1414,"skipped":0,"pct":41.17},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/packages/svgcanvas/common/browser.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":6,"covered":2,"skipped":0,"pct":33.33},"statements":{"total":30,"covered":25,"skipped":0,"pct":83.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/packages/svgcanvas/common/util.js": {"lines":{"total":90,"covered":8,"skipped":0,"pct":8.88},"functions":{"total":7,"covered":3,"skipped":0,"pct":42.85},"statements":{"total":92,"covered":10,"skipped":0,"pct":10.86},"branches":{"total":98,"covered":10,"skipped":0,"pct":10.2}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/ConfigObj.js": {"lines":{"total":101,"covered":39,"skipped":0,"pct":38.61},"functions":{"total":14,"covered":9,"skipped":0,"pct":64.28},"statements":{"total":102,"covered":39,"skipped":0,"pct":38.23},"branches":{"total":95,"covered":25,"skipped":0,"pct":26.31}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/Editor.js": {"lines":{"total":414,"covered":192,"skipped":0,"pct":46.37},"functions":{"total":103,"covered":31,"skipped":0,"pct":30.09},"statements":{"total":420,"covered":193,"skipped":0,"pct":45.95},"branches":{"total":213,"covered":75,"skipped":0,"pct":35.21}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/EditorStartup.js": {"lines":{"total":383,"covered":237,"skipped":0,"pct":61.87},"functions":{"total":57,"covered":31,"skipped":0,"pct":54.38},"statements":{"total":395,"covered":246,"skipped":0,"pct":62.27},"branches":{"total":147,"covered":51,"skipped":0,"pct":34.69}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/MainMenu.js": {"lines":{"total":101,"covered":44,"skipped":0,"pct":43.56},"functions":{"total":14,"covered":7,"skipped":0,"pct":50},"statements":{"total":101,"covered":44,"skipped":0,"pct":43.56},"branches":{"total":44,"covered":7,"skipped":0,"pct":15.9}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/Rulers.js": {"lines":{"total":119,"covered":93,"skipped":0,"pct":78.15},"functions":{"total":6,"covered":5,"skipped":0,"pct":83.33},"statements":{"total":124,"covered":98,"skipped":0,"pct":79.03},"branches":{"total":43,"covered":32,"skipped":0,"pct":74.41}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/browser-not-supported.js": {"lines":{"total":4,"covered":3,"skipped":0,"pct":75},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":3,"skipped":0,"pct":75},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/contextmenu.js": {"lines":{"total":22,"covered":9,"skipped":0,"pct":40.9},"functions":{"total":8,"covered":1,"skipped":0,"pct":12.5},"statements":{"total":23,"covered":9,"skipped":0,"pct":39.13},"branches":{"total":13,"covered":0,"skipped":0,"pct":0}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/locale.js": {"lines":{"total":14,"covered":9,"skipped":0,"pct":64.28},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":14,"covered":9,"skipped":0,"pct":64.28},"branches":{"total":10,"covered":3,"skipped":0,"pct":30}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/PaintBox.js": {"lines":{"total":64,"covered":51,"skipped":0,"pct":79.68},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":67,"covered":53,"skipped":0,"pct":79.1},"branches":{"total":33,"covered":20,"skipped":0,"pct":60.6}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/index.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seButton.js": {"lines":{"total":57,"covered":42,"skipped":0,"pct":73.68},"functions":{"total":15,"covered":9,"skipped":0,"pct":60},"statements":{"total":60,"covered":44,"skipped":0,"pct":73.33},"branches":{"total":33,"covered":23,"skipped":0,"pct":69.69}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seColorPicker.js": {"lines":{"total":50,"covered":41,"skipped":0,"pct":82},"functions":{"total":16,"covered":11,"skipped":0,"pct":68.75},"statements":{"total":51,"covered":41,"skipped":0,"pct":80.39},"branches":{"total":11,"covered":8,"skipped":0,"pct":72.72}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seExplorerButton.js": {"lines":{"total":99,"covered":76,"skipped":0,"pct":76.76},"functions":{"total":18,"covered":11,"skipped":0,"pct":61.11},"statements":{"total":102,"covered":76,"skipped":0,"pct":74.5},"branches":{"total":30,"covered":17,"skipped":0,"pct":56.66}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seFlyingButton.js": {"lines":{"total":77,"covered":50,"skipped":0,"pct":64.93},"functions":{"total":15,"covered":11,"skipped":0,"pct":73.33},"statements":{"total":78,"covered":51,"skipped":0,"pct":65.38},"branches":{"total":31,"covered":13,"skipped":0,"pct":41.93}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seInput.js": {"lines":{"total":46,"covered":31,"skipped":0,"pct":67.39},"functions":{"total":16,"covered":8,"skipped":0,"pct":50},"statements":{"total":47,"covered":31,"skipped":0,"pct":65.95},"branches":{"total":8,"covered":4,"skipped":0,"pct":50}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seList.js": {"lines":{"total":87,"covered":59,"skipped":0,"pct":67.81},"functions":{"total":21,"covered":10,"skipped":0,"pct":47.61},"statements":{"total":89,"covered":61,"skipped":0,"pct":68.53},"branches":{"total":22,"covered":12,"skipped":0,"pct":54.54}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seListItem.js": {"lines":{"total":39,"covered":26,"skipped":0,"pct":66.66},"functions":{"total":12,"covered":4,"skipped":0,"pct":33.33},"statements":{"total":40,"covered":27,"skipped":0,"pct":67.5},"branches":{"total":10,"covered":8,"skipped":0,"pct":80}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seMenu.js": {"lines":{"total":26,"covered":20,"skipped":0,"pct":76.92},"functions":{"total":7,"covered":3,"skipped":0,"pct":42.85},"statements":{"total":27,"covered":20,"skipped":0,"pct":74.07},"branches":{"total":5,"covered":3,"skipped":0,"pct":60}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seMenuItem.js": {"lines":{"total":37,"covered":26,"skipped":0,"pct":70.27},"functions":{"total":9,"covered":5,"skipped":0,"pct":55.55},"statements":{"total":40,"covered":27,"skipped":0,"pct":67.5},"branches":{"total":21,"covered":8,"skipped":0,"pct":38.09}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/sePalette.js": {"lines":{"total":53,"covered":44,"skipped":0,"pct":83.01},"functions":{"total":11,"covered":9,"skipped":0,"pct":81.81},"statements":{"total":53,"covered":44,"skipped":0,"pct":83.01},"branches":{"total":15,"covered":10,"skipped":0,"pct":66.66}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/sePlainBorderButton.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/sePlainMenuButton.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seSelect.js": {"lines":{"total":56,"covered":45,"skipped":0,"pct":80.35},"functions":{"total":17,"covered":9,"skipped":0,"pct":52.94},"statements":{"total":59,"covered":47,"skipped":0,"pct":79.66},"branches":{"total":16,"covered":14,"skipped":0,"pct":87.5}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seSpinInput.js": {"lines":{"total":65,"covered":55,"skipped":0,"pct":84.61},"functions":{"total":18,"covered":10,"skipped":0,"pct":55.55},"statements":{"total":66,"covered":55,"skipped":0,"pct":83.33},"branches":{"total":17,"covered":13,"skipped":0,"pct":76.47}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seText.js": {"lines":{"total":28,"covered":18,"skipped":0,"pct":64.28},"functions":{"total":10,"covered":4,"skipped":0,"pct":40},"statements":{"total":29,"covered":18,"skipped":0,"pct":62.06},"branches":{"total":8,"covered":5,"skipped":0,"pct":62.5}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/seZoom.js": {"lines":{"total":102,"covered":94,"skipped":0,"pct":92.15},"functions":{"total":28,"covered":25,"skipped":0,"pct":89.28},"statements":{"total":107,"covered":98,"skipped":0,"pct":91.58},"branches":{"total":36,"covered":22,"skipped":0,"pct":61.11}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/ColorValuePicker.js": {"lines":{"total":231,"covered":54,"skipped":0,"pct":23.37},"functions":{"total":9,"covered":3,"skipped":0,"pct":33.33},"statements":{"total":249,"covered":63,"skipped":0,"pct":25.3},"branches":{"total":157,"covered":34,"skipped":0,"pct":21.65}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/Slider.js": {"lines":{"total":156,"covered":91,"skipped":0,"pct":58.33},"functions":{"total":17,"covered":9,"skipped":0,"pct":52.94},"statements":{"total":176,"covered":96,"skipped":0,"pct":54.54},"branches":{"total":186,"covered":97,"skipped":0,"pct":52.15}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/jQuery.jGraduate.js": {"lines":{"total":580,"covered":275,"skipped":0,"pct":47.41},"functions":{"total":44,"covered":14,"skipped":0,"pct":31.81},"statements":{"total":602,"covered":282,"skipped":0,"pct":46.84},"branches":{"total":278,"covered":100,"skipped":0,"pct":35.97}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/components/jgraduate/jQuery.jPicker.js": {"lines":{"total":840,"covered":449,"skipped":0,"pct":53.45},"functions":{"total":60,"covered":39,"skipped":0,"pct":65},"statements":{"total":926,"covered":475,"skipped":0,"pct":51.29},"branches":{"total":777,"covered":327,"skipped":0,"pct":42.08}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/SePlainAlertDialog.js": {"lines":{"total":12,"covered":2,"skipped":0,"pct":16.66},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":2,"skipped":0,"pct":16.66},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/cmenuDialog.js": {"lines":{"total":120,"covered":112,"skipped":0,"pct":93.33},"functions":{"total":28,"covered":16,"skipped":0,"pct":57.14},"statements":{"total":131,"covered":117,"skipped":0,"pct":89.31},"branches":{"total":23,"covered":19,"skipped":0,"pct":82.6}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/cmenuLayersDialog.js": {"lines":{"total":61,"covered":49,"skipped":0,"pct":80.32},"functions":{"total":16,"covered":6,"skipped":0,"pct":37.5},"statements":{"total":66,"covered":49,"skipped":0,"pct":74.24},"branches":{"total":18,"covered":13,"skipped":0,"pct":72.22}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/editorPreferencesDialog.js": {"lines":{"total":157,"covered":125,"skipped":0,"pct":79.61},"functions":{"total":30,"covered":9,"skipped":0,"pct":30},"statements":{"total":159,"covered":126,"skipped":0,"pct":79.24},"branches":{"total":46,"covered":35,"skipped":0,"pct":76.08}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/exportDialog.js": {"lines":{"total":52,"covered":36,"skipped":0,"pct":69.23},"functions":{"total":14,"covered":5,"skipped":0,"pct":35.71},"statements":{"total":55,"covered":36,"skipped":0,"pct":65.45},"branches":{"total":11,"covered":5,"skipped":0,"pct":45.45}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/imagePropertiesDialog.js": {"lines":{"total":161,"covered":90,"skipped":0,"pct":55.9},"functions":{"total":20,"covered":5,"skipped":0,"pct":25},"statements":{"total":162,"covered":90,"skipped":0,"pct":55.55},"branches":{"total":53,"covered":23,"skipped":0,"pct":43.39}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/index.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/seAlertDialog.js": {"lines":{"total":6,"covered":2,"skipped":0,"pct":33.33},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":6,"covered":2,"skipped":0,"pct":33.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/seConfirmDialog.js": {"lines":{"total":8,"covered":2,"skipped":0,"pct":25},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":2,"skipped":0,"pct":25},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/sePromptDialog.js": {"lines":{"total":24,"covered":5,"skipped":0,"pct":20.83},"functions":{"total":7,"covered":2,"skipped":0,"pct":28.57},"statements":{"total":24,"covered":5,"skipped":0,"pct":20.83},"branches":{"total":9,"covered":0,"skipped":0,"pct":0}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/seSelectDialog.js": {"lines":{"total":8,"covered":2,"skipped":0,"pct":25},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":8,"covered":2,"skipped":0,"pct":25},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/svgSourceDialog.js": {"lines":{"total":74,"covered":56,"skipped":0,"pct":75.67},"functions":{"total":17,"covered":6,"skipped":0,"pct":35.29},"statements":{"total":75,"covered":57,"skipped":0,"pct":76},"branches":{"total":17,"covered":14,"skipped":0,"pct":82.35}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/se-elix/define/NumberSpinBox.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/se-elix/src/base/NumberSpinBox.js": {"lines":{"total":54,"covered":51,"skipped":0,"pct":94.44},"functions":{"total":13,"covered":11,"skipped":0,"pct":84.61},"statements":{"total":54,"covered":51,"skipped":0,"pct":94.44},"branches":{"total":53,"covered":46,"skipped":0,"pct":86.79}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/dialogs/se-elix/src/plain/PlainNumberSpinBox.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-connector/ext-connector.js": {"lines":{"total":287,"covered":84,"skipped":0,"pct":29.26},"functions":{"total":26,"covered":16,"skipped":0,"pct":61.53},"statements":{"total":295,"covered":87,"skipped":0,"pct":29.49},"branches":{"total":140,"covered":21,"skipped":0,"pct":15}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-connector/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-eyedropper/ext-eyedropper.js": {"lines":{"total":99,"covered":57,"skipped":0,"pct":57.57},"functions":{"total":16,"covered":11,"skipped":0,"pct":68.75},"statements":{"total":109,"covered":58,"skipped":0,"pct":53.21},"branches":{"total":65,"covered":19,"skipped":0,"pct":29.23}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-eyedropper/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-grid/ext-grid.js": {"lines":{"total":88,"covered":43,"skipped":0,"pct":48.86},"functions":{"total":8,"covered":4,"skipped":0,"pct":50},"statements":{"total":90,"covered":43,"skipped":0,"pct":47.77},"branches":{"total":10,"covered":4,"skipped":0,"pct":40}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-grid/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-layer_view/ext-layer_view.js": {"lines":{"total":43,"covered":21,"skipped":0,"pct":48.83},"functions":{"total":8,"covered":4,"skipped":0,"pct":50},"statements":{"total":43,"covered":21,"skipped":0,"pct":48.83},"branches":{"total":14,"covered":2,"skipped":0,"pct":14.28}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-layer_view/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-markers/ext-markers.js": {"lines":{"total":149,"covered":46,"skipped":0,"pct":30.87},"functions":{"total":21,"covered":12,"skipped":0,"pct":57.14},"statements":{"total":164,"covered":48,"skipped":0,"pct":29.26},"branches":{"total":80,"covered":22,"skipped":0,"pct":27.5}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-opensave/ext-opensave.js": {"lines":{"total":137,"covered":36,"skipped":0,"pct":26.27},"functions":{"total":13,"covered":3,"skipped":0,"pct":23.07},"statements":{"total":142,"covered":36,"skipped":0,"pct":25.35},"branches":{"total":32,"covered":0,"skipped":0,"pct":0}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-opensave/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-panning/ext-panning.js": {"lines":{"total":30,"covered":22,"skipped":0,"pct":73.33},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":30,"covered":22,"skipped":0,"pct":73.33},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-panning/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-polystar/ext-polystar.js": {"lines":{"total":247,"covered":232,"skipped":0,"pct":93.92},"functions":{"total":18,"covered":16,"skipped":0,"pct":88.88},"statements":{"total":256,"covered":241,"skipped":0,"pct":94.14},"branches":{"total":62,"covered":39,"skipped":0,"pct":62.9}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-polystar/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-shapes/ext-shapes.js": {"lines":{"total":76,"covered":74,"skipped":0,"pct":97.36},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":80,"covered":78,"skipped":0,"pct":97.5},"branches":{"total":26,"covered":17,"skipped":0,"pct":65.38}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-shapes/locale/en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-storage/ext-storage.js": {"lines":{"total":91,"covered":37,"skipped":0,"pct":40.65},"functions":{"total":12,"covered":5,"skipped":0,"pct":41.66},"statements":{"total":91,"covered":37,"skipped":0,"pct":40.65},"branches":{"total":68,"covered":16,"skipped":0,"pct":23.52}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/extensions/ext-storage/storageDialog.js": {"lines":{"total":60,"covered":56,"skipped":0,"pct":93.33},"functions":{"total":10,"covered":7,"skipped":0,"pct":70},"statements":{"total":62,"covered":57,"skipped":0,"pct":91.93},"branches":{"total":15,"covered":13,"skipped":0,"pct":86.66}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/locale/lang.en.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/panels/BottomPanel.js": {"lines":{"total":77,"covered":61,"skipped":0,"pct":79.22},"functions":{"total":22,"covered":14,"skipped":0,"pct":63.63},"statements":{"total":77,"covered":61,"skipped":0,"pct":79.22},"branches":{"total":30,"covered":16,"skipped":0,"pct":53.33}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/panels/LayersPanel.js": {"lines":{"total":165,"covered":76,"skipped":0,"pct":46.06},"functions":{"total":27,"covered":6,"skipped":0,"pct":22.22},"statements":{"total":171,"covered":77,"skipped":0,"pct":45.02},"branches":{"total":46,"covered":6,"skipped":0,"pct":13.04}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/panels/LeftPanel.js": {"lines":{"total":57,"covered":46,"skipped":0,"pct":80.7},"functions":{"total":20,"covered":15,"skipped":0,"pct":75},"statements":{"total":58,"covered":46,"skipped":0,"pct":79.31},"branches":{"total":32,"covered":12,"skipped":0,"pct":37.5}}
+,"/Users/jfh/Documents/GitHub/svgedit/src/editor/panels/TopPanel.js": {"lines":{"total":409,"covered":310,"skipped":0,"pct":75.79},"functions":{"total":76,"covered":51,"skipped":0,"pct":67.1},"statements":{"total":421,"covered":313,"skipped":0,"pct":74.34},"branches":{"total":209,"covered":126,"skipped":0,"pct":60.28}}
}
diff --git a/cypress/__svgSnapshots__/scenario-check tool_bold.svg b/cypress/__svgSnapshots__/scenario-check tool_bold.svg
index 33fc767d2..6f78b5466 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_bold.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_bold.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_clone.svg b/cypress/__svgSnapshots__/scenario-check tool_clone.svg
index 49a40a1ea..8356343ee 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_clone.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_clone.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_italic.svg b/cypress/__svgSnapshots__/scenario-check tool_italic.svg
index 67f60f7e9..884cb0cc8 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_italic.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_italic.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_align_to_page.svg b/cypress/__svgSnapshots__/scenario-check tool_text_align_to_page.svg
index 490ababf5..614975fd1 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_align_to_page.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_align_to_page.svg
@@ -2,7 +2,7 @@
Layer 1
AB
- AB
+ AB
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_blur.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_blur.svg
index 3cd30e527..3384e2b0e 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_blur.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_blur.svg
@@ -2,7 +2,7 @@
Layer 1
AB
- AB
+ AB
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_font_size.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_font_size.svg
index 577070c99..eb930043e 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_font_size.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_font_size.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_opacity.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_opacity.svg
index d85cc40d6..f4f5cffb5 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_opacity.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_opacity.svg
@@ -2,7 +2,7 @@
Layer 1
AB
- AB
+ AB
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_stoke_fill_color.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_stoke_fill_color.svg
index e6fb49891..9f4570fc9 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_stoke_fill_color.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_stoke_fill_color.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_stroke_width.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_stroke_width.svg
index 5710c7e0f..8bdb91833 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_stroke_width.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_stroke_width.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/__svgSnapshots__/scenario-check tool_text_change_x_y_coordinate.svg b/cypress/__svgSnapshots__/scenario-check tool_text_change_x_y_coordinate.svg
index b8fa5cbc1..8aa197965 100644
--- a/cypress/__svgSnapshots__/scenario-check tool_text_change_x_y_coordinate.svg
+++ b/cypress/__svgSnapshots__/scenario-check tool_text_change_x_y_coordinate.svg
@@ -2,6 +2,6 @@
Layer 1
AB
- AB
+ AB
\ No newline at end of file
diff --git a/cypress/e2e/unit/math.cy.js b/cypress/e2e/unit/math.cy.js
index a3a4ebc3c..665695cb5 100644
--- a/cypress/e2e/unit/math.cy.js
+++ b/cypress/e2e/unit/math.cy.js
@@ -4,103 +4,317 @@ import * as math from '../../../packages/svgcanvas/core/math.js'
describe('math', function () {
const svg = document.createElementNS(NS.SVG, 'svg')
- it('Test svgedit.math package', function () {
- assert.ok(math)
- assert.ok(math.transformPoint)
- assert.ok(math.isIdentity)
- assert.ok(math.matrixMultiply)
- assert.equal(typeof math.transformPoint, typeof function () { /* empty fn */ })
- assert.equal(typeof math.isIdentity, typeof function () { /* empty fn */ })
- assert.equal(typeof math.matrixMultiply, typeof function () { /* empty fn */ })
+ before(() => {
+ // Ensure the SVG element is attached to the document for transform list tests
+ document.body.appendChild(svg)
+ })
+
+ after(() => {
+ // Cleanup
+ document.body.removeChild(svg)
+ })
+
+ it('Test svgedit.math package exports', function () {
+ assert.ok(math, 'math module should exist')
+ const expectedFunctions = [
+ 'transformPoint',
+ 'getTransformList',
+ 'isIdentity',
+ 'matrixMultiply',
+ 'hasMatrixTransform',
+ 'transformBox',
+ 'transformListToTransform',
+ 'getMatrix',
+ 'snapToAngle',
+ 'rectsIntersect'
+ ]
+ expectedFunctions.forEach(fn => {
+ assert.ok(
+ typeof math[fn] === 'function',
+ `Expected "${fn}" to be a function`
+ )
+ })
})
it('Test svgedit.math.transformPoint() function', function () {
const { transformPoint } = math
const m = svg.createSVGMatrix()
- m.a = 1; m.b = 0
- m.c = 0; m.d = 1
- m.e = 0; m.f = 0
+ m.a = 1
+ m.b = 0
+ m.c = 0
+ m.d = 1
+ m.e = 0
+ m.f = 0
let pt = transformPoint(100, 200, m)
- assert.equal(pt.x, 100)
- assert.equal(pt.y, 200)
+ assert.equal(pt.x, 100, 'X should be unchanged by identity matrix')
+ assert.equal(pt.y, 200, 'Y should be unchanged by identity matrix')
- m.e = 300; m.f = 400
+ m.e = 300
+ m.f = 400
pt = transformPoint(100, 200, m)
- assert.equal(pt.x, 400)
- assert.equal(pt.y, 600)
+ assert.equal(pt.x, 400, 'X should be translated by 300')
+ assert.equal(pt.y, 600, 'Y should be translated by 400')
- m.a = 0.5; m.b = 0.75
- m.c = 1.25; m.d = 2
+ m.a = 0.5
+ m.b = 0.75
+ m.c = 1.25
+ m.d = 2
pt = transformPoint(100, 200, m)
- assert.equal(pt.x, 100 * m.a + 200 * m.c + m.e)
- assert.equal(pt.y, 100 * m.b + 200 * m.d + m.f)
+ assert.equal(
+ pt.x,
+ 100 * m.a + 200 * m.c + m.e,
+ 'X should match matrix multiplication'
+ )
+ assert.equal(
+ pt.y,
+ 100 * m.b + 200 * m.d + m.f,
+ 'Y should match matrix multiplication'
+ )
})
it('Test svgedit.math.isIdentity() function', function () {
- assert.ok(math.isIdentity(svg.createSVGMatrix()))
+ const { isIdentity } = math
+
+ assert.ok(
+ isIdentity(svg.createSVGMatrix()),
+ 'Default matrix should be identity'
+ )
const m = svg.createSVGMatrix()
- m.a = 1; m.b = 0
- m.c = 0; m.d = 1
- m.e = 0; m.f = 0
- assert.ok(math.isIdentity(m))
+ m.a = 1
+ m.b = 0
+ m.c = 0
+ m.d = 1
+ m.e = 0
+ m.f = 0
+ assert.ok(
+ isIdentity(m),
+ 'Modified matrix matching identity values should be identity'
+ )
+
+ m.e = 10
+ assert.notOk(isIdentity(m), 'Matrix with translation is not identity')
})
it('Test svgedit.math.matrixMultiply() function', function () {
- const mult = math.matrixMultiply
- const { isIdentity } = math
+ const { matrixMultiply, isIdentity } = math
+
+ // Test empty arguments
+ const iDefault = matrixMultiply()
+ assert.ok(
+ isIdentity(iDefault),
+ 'No arguments should return identity matrix'
+ )
- // translate there and back
+ // Translate there and back
const tr1 = svg.createSVGMatrix().translate(100, 50)
const tr2 = svg.createSVGMatrix().translate(-90, 0)
const tr3 = svg.createSVGMatrix().translate(-10, -50)
- let I = mult(tr1, tr2, tr3)
- assert.ok(isIdentity(I), 'Expected identity matrix when translating there and back')
+ let I = matrixMultiply(tr1, tr2, tr3)
+ assert.ok(isIdentity(I), 'Translating there and back should yield identity')
- // rotate there and back
- // TODO: currently Mozilla fails this when rotating back at -50 and then -40 degrees
- // (b and c are *almost* zero, but not zero)
+ // Rotate there and back
const rotThere = svg.createSVGMatrix().rotate(90)
- const rotBack = svg.createSVGMatrix().rotate(-90) // TODO: set this to -50
- const rotBackMore = svg.createSVGMatrix().rotate(0) // TODO: set this to -40
- I = mult(rotThere, rotBack, rotBackMore)
- assert.ok(isIdentity(I), 'Expected identity matrix when rotating there and back')
+ const rotBack = svg.createSVGMatrix().rotate(-90)
+ I = matrixMultiply(rotThere, rotBack)
+ assert.ok(isIdentity(I), 'Rotating and rotating back should yield identity')
- // scale up and down
+ // Scale up and down
const scaleUp = svg.createSVGMatrix().scale(4)
- const scaleDown = svg.createSVGMatrix().scaleNonUniform(0.25, 1)
- const scaleDownMore = svg.createSVGMatrix().scaleNonUniform(1, 0.25)
- I = mult(scaleUp, scaleDown, scaleDownMore)
- assert.ok(isIdentity(I), 'Expected identity matrix when scaling up and down')
-
- // test multiplication with its inverse
- I = mult(rotThere, rotThere.inverse())
- assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse')
- I = mult(rotThere.inverse(), rotThere)
- assert.ok(isIdentity(I), 'Expected identity matrix when multiplying a matrix by its inverse')
+ const scaleDownX = svg.createSVGMatrix().scaleNonUniform(0.25, 1)
+ const scaleDownY = svg.createSVGMatrix().scaleNonUniform(1, 0.25)
+ I = matrixMultiply(scaleUp, scaleDownX, scaleDownY)
+ assert.ok(
+ isIdentity(I),
+ 'Scaling up and then scaling down back to original should yield identity'
+ )
+
+ // Multiplying a matrix by its inverse
+ const someMatrix = svg
+ .createSVGMatrix()
+ .rotate(33)
+ .translate(100, 200)
+ .scale(2)
+ I = matrixMultiply(someMatrix, someMatrix.inverse())
+ cy.log(I)
+ cy.log('-----------------------------------------')
+ assert.ok(
+ isIdentity(I),
+ 'Matrix multiplied by its inverse should be identity'
+ )
})
it('Test svgedit.math.transformBox() function', function () {
const { transformBox } = math
const m = svg.createSVGMatrix()
- m.a = 1; m.b = 0
- m.c = 0; m.d = 1
- m.e = 0; m.f = 0
-
+ // Identity
const r = transformBox(10, 10, 200, 300, m)
- assert.equal(r.tl.x, 10)
- assert.equal(r.tl.y, 10)
- assert.equal(r.tr.x, 210)
- assert.equal(r.tr.y, 10)
- assert.equal(r.bl.x, 10)
- assert.equal(r.bl.y, 310)
- assert.equal(r.br.x, 210)
- assert.equal(r.br.y, 310)
- assert.equal(r.aabox.x, 10)
- assert.equal(r.aabox.y, 10)
- assert.equal(r.aabox.width, 200)
- assert.equal(r.aabox.height, 300)
+ assert.equal(r.tl.x, 10, 'Top-left X should be 10')
+ assert.equal(r.tl.y, 10, 'Top-left Y should be 10')
+ assert.equal(r.tr.x, 210, 'Top-right X should be 210')
+ assert.equal(r.tr.y, 10, 'Top-right Y should be 10')
+ assert.equal(r.bl.x, 10, 'Bottom-left X should be 10')
+ assert.equal(r.bl.y, 310, 'Bottom-left Y should be 310')
+ assert.equal(r.br.x, 210, 'Bottom-right X should be 210')
+ assert.equal(r.br.y, 310, 'Bottom-right Y should be 310')
+ assert.equal(r.aabox.x, 10, 'AABBox X should be 10')
+ assert.equal(r.aabox.y, 10, 'AABBox Y should be 10')
+ assert.equal(r.aabox.width, 200, 'AABBox width should be 200')
+ assert.equal(r.aabox.height, 300, 'AABBox height should be 300')
+
+ // Transformed box
+ m.e = 50
+ m.f = 50
+ const r2 = transformBox(0, 0, 100, 100, m)
+ assert.equal(r2.aabox.x, 50, 'AABBox x should be translated by 50')
+ assert.equal(r2.aabox.y, 50, 'AABBox y should be translated by 50')
+ })
+
+ it('Test svgedit.math.getTransformList() and hasMatrixTransform() functions', function () {
+ const { getTransformList, hasMatrixTransform } = math
+
+ // An element with no transform
+ const rect = document.createElementNS(NS.SVG, 'rect')
+ svg.appendChild(rect)
+ const tlist = getTransformList(rect)
+ assert.ok(tlist, 'Should get a transform list (empty)')
+ assert.equal(tlist.numberOfItems, 0, 'Transform list should be empty')
+ assert.notOk(
+ hasMatrixTransform(tlist),
+ 'No matrix transform in an empty transform list'
+ )
+
+ // Add a non-identity matrix transform
+ const nonIdentityMatrix = svg.createSVGMatrix().translate(10, 20).scale(2)
+ const tf = svg.createSVGTransformFromMatrix(nonIdentityMatrix)
+ tlist.appendItem(tf)
+ assert.equal(tlist.numberOfItems, 1, 'Transform list should have one item')
+ assert.ok(
+ hasMatrixTransform(tlist),
+ 'Non-identity matrix transform should be detected'
+ )
+
+ // Add an identity transform
+ const tfIdentity = svg.createSVGTransformFromMatrix(svg.createSVGMatrix()) // identity matrix
+ tlist.appendItem(tfIdentity)
+ assert.equal(
+ tlist.numberOfItems,
+ 2,
+ 'Transform list should have two items now'
+ )
+ // Still should have a non-identity matrix transform present
+ assert.ok(
+ hasMatrixTransform(tlist),
+ 'Still have a non-identity matrix transform after adding an identity transform'
+ )
+
+ // Cleanup
+ svg.removeChild(rect)
+ })
+
+ it('Test svgedit.math.transformListToTransform() and getMatrix() functions', function () {
+ const { transformListToTransform, getMatrix } = math
+
+ const g = document.createElementNS(NS.SVG, 'g')
+ svg.appendChild(g)
+
+ const tlist = g.transform.baseVal
+ const m1 = svg.createSVGTransformFromMatrix(
+ svg.createSVGMatrix().translate(10, 20)
+ )
+ const m2 = svg.createSVGTransformFromMatrix(
+ svg.createSVGMatrix().rotate(45)
+ )
+ tlist.appendItem(m1)
+ tlist.appendItem(m2)
+
+ const consolidated = transformListToTransform(tlist)
+ const expected = m1.matrix.multiply(m2.matrix)
+ assert.equal(
+ consolidated.matrix.a,
+ expected.a,
+ 'Consolidated matrix a should match expected'
+ )
+ assert.equal(
+ consolidated.matrix.d,
+ expected.d,
+ 'Consolidated matrix d should match expected'
+ )
+
+ const elemMatrix = getMatrix(g)
+ assert.equal(
+ elemMatrix.a,
+ expected.a,
+ 'Element matrix a should match expected'
+ )
+ assert.equal(
+ elemMatrix.d,
+ expected.d,
+ 'Element matrix d should match expected'
+ )
+
+ svg.removeChild(g)
+ })
+
+ it('Test svgedit.math.snapToAngle() function', function () {
+ const { snapToAngle } = math
+
+ const result = snapToAngle(0, 0, 10, 0) // Expect snap to 0 degrees
+ assert.equal(
+ result.x,
+ 10,
+ 'Snapped x should remain 10 when angle is already at 0°'
+ )
+ assert.equal(
+ result.y,
+ 0,
+ 'Snapped y should remain 0 when angle is already at 0°'
+ )
+
+ // 45-degree snap from an angle close to 45° (e.g., 50°)
+ const angleDegrees = 50
+ const angleRadians = angleDegrees * (Math.PI / 180)
+ const dx = Math.cos(angleRadians) * 100
+ const dy = Math.sin(angleRadians) * 100
+ const snapped = snapToAngle(0, 0, dx, dy)
+ // Should snap to exactly 45°
+ const expectedAngle = Math.PI / 4
+ const dist = Math.hypot(dx, dy)
+ assert.closeTo(
+ snapped.x,
+ dist * Math.cos(expectedAngle),
+ 0.00001,
+ 'X should be close to 45° projection'
+ )
+ assert.closeTo(
+ snapped.y,
+ dist * Math.sin(expectedAngle),
+ 0.00001,
+ 'Y should be close to 45° projection'
+ )
+ })
+
+ it('Test svgedit.math.rectsIntersect() function', function () {
+ const { rectsIntersect } = math
+ const r1 = { x: 0, y: 0, width: 50, height: 50 }
+ const r2 = { x: 25, y: 25, width: 50, height: 50 }
+ const r3 = { x: 100, y: 100, width: 10, height: 10 }
+
+ assert.ok(rectsIntersect(r1, r2), 'Rectangles overlapping should intersect')
+ assert.notOk(
+ rectsIntersect(r1, r3),
+ 'Non-overlapping rectangles should not intersect'
+ )
+
+ // Edge case: touching edges
+ const r4 = { x: 50, y: 0, width: 50, height: 50 }
+ // Note: Depending on interpretation, touching at the border might be considered intersecting or not.
+ // The given function checks strict overlapping (not just touching), so this should return false.
+ assert.notOk(
+ rectsIntersect(r1, r4),
+ 'Rectangles touching at the edge should not be considered intersecting'
+ )
})
})
diff --git a/package-lock.json b/package-lock.json
index 04c8a4105..1b5071747 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,7 +17,7 @@
"browser-fs-access": "0.35.0",
"core-js": "3.39.0",
"elix": "15.0.1",
- "i18next": "23.16.5",
+ "i18next": "24.0.5",
"jspdf": "2.5.2",
"pathseg": "1.2.1",
"regenerator-runtime": "0.14.1",
@@ -29,7 +29,7 @@
"@babel/preset-env": "7.26.0",
"@babel/register": "7.25.9",
"@babel/runtime-corejs3": "7.26.0",
- "@cypress/code-coverage": "3.13.6",
+ "@cypress/code-coverage": "3.13.8",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "^28",
"@rollup/plugin-dynamic-import-vars": "2.1.5",
@@ -43,7 +43,7 @@
"babel-plugin-transform-object-rest-spread": "7.0.0-beta.3",
"core-js-bundle": "3.39.0",
"cp-cli": "2.0.0",
- "cypress": "13.15.2",
+ "cypress": "13.16.1",
"cypress-multi-reporters": "2.0.4",
"jamilih": "0.60.0",
"jsdoc": "4.0.4",
@@ -57,7 +57,7 @@
"remark-cli": "12.0.1",
"remark-lint-ordered-list-marker-value": "4.0.0",
"rimraf": "6.0.1",
- "rollup": "4.25.0",
+ "rollup": "4.28.1",
"rollup-plugin-copy": "3.5.0",
"rollup-plugin-filesize": "10.0.0",
"rollup-plugin-html": "0.2.1",
@@ -70,7 +70,7 @@
"node": ">=20"
},
"optionalDependencies": {
- "@rollup/rollup-linux-x64-gnu": "4.25.0"
+ "@rollup/rollup-linux-x64-gnu": "4.28.1"
}
},
"node_modules/@ampproject/remapping": {
@@ -1789,9 +1789,9 @@
}
},
"node_modules/@cypress/code-coverage": {
- "version": "3.13.6",
- "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.13.6.tgz",
- "integrity": "sha512-nNVDYDK6r9zPqDIv9k7FibPP9/dATGRR3us9Ued/ldcxPz5x8WbVthjV5OIjqotRKEmS7wxiXFHSDhKJqaZNuw==",
+ "version": "3.13.8",
+ "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.13.8.tgz",
+ "integrity": "sha512-liUtYL42nbEUkMKtB8H2sudI7gREeKNSTO1VbvFRjiRxj+2hurEkxgbVB8fmjm10iQrDeiX6XGEIL2IefcdC0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3359,9 +3359,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz",
- "integrity": "sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz",
+ "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==",
"cpu": [
"arm"
],
@@ -3373,9 +3373,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz",
- "integrity": "sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz",
+ "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==",
"cpu": [
"arm64"
],
@@ -3387,9 +3387,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz",
- "integrity": "sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz",
+ "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==",
"cpu": [
"arm64"
],
@@ -3401,9 +3401,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz",
- "integrity": "sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz",
+ "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==",
"cpu": [
"x64"
],
@@ -3415,9 +3415,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz",
- "integrity": "sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz",
+ "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==",
"cpu": [
"arm64"
],
@@ -3429,9 +3429,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz",
- "integrity": "sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz",
+ "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==",
"cpu": [
"x64"
],
@@ -3443,9 +3443,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz",
- "integrity": "sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz",
+ "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==",
"cpu": [
"arm"
],
@@ -3457,9 +3457,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz",
- "integrity": "sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz",
+ "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==",
"cpu": [
"arm"
],
@@ -3471,9 +3471,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz",
- "integrity": "sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz",
+ "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==",
"cpu": [
"arm64"
],
@@ -3485,9 +3485,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz",
- "integrity": "sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz",
+ "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==",
"cpu": [
"arm64"
],
@@ -3498,10 +3498,24 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz",
- "integrity": "sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz",
+ "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==",
"cpu": [
"ppc64"
],
@@ -3513,9 +3527,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz",
- "integrity": "sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz",
+ "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==",
"cpu": [
"riscv64"
],
@@ -3527,9 +3541,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz",
- "integrity": "sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz",
+ "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==",
"cpu": [
"s390x"
],
@@ -3541,9 +3555,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz",
- "integrity": "sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz",
+ "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==",
"cpu": [
"x64"
],
@@ -3554,9 +3568,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz",
- "integrity": "sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz",
+ "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==",
"cpu": [
"x64"
],
@@ -3568,9 +3582,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz",
- "integrity": "sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz",
+ "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==",
"cpu": [
"arm64"
],
@@ -3582,9 +3596,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz",
- "integrity": "sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==",
"cpu": [
"ia32"
],
@@ -3596,9 +3610,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz",
- "integrity": "sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz",
+ "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==",
"cpu": [
"x64"
],
@@ -6484,9 +6498,9 @@
}
},
"node_modules/cypress": {
- "version": "13.15.2",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz",
- "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==",
+ "version": "13.16.1",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz",
+ "integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -9094,9 +9108,9 @@
}
},
"node_modules/i18next": {
- "version": "23.16.5",
- "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.5.tgz",
- "integrity": "sha512-KTlhE3EP9x6pPTAW7dy0WKIhoCpfOGhRQlO+jttQLgzVaoOjWwBWramu7Pp0i+8wDNduuzXfe3kkVbzrKyrbTA==",
+ "version": "24.0.5",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.0.5.tgz",
+ "integrity": "sha512-1jSdEzgFPGLZRsQwydoMFCBBaV+PmrVEO5WhANllZPX4y2JSGTxUjJ+xVklHIsiS95uR8gYc/y0hYZWevucNjg==",
"funding": [
{
"type": "individual",
@@ -9114,6 +9128,14 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
+ },
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
"node_modules/iconv-lite": {
@@ -14892,9 +14914,9 @@
}
},
"node_modules/rollup": {
- "version": "4.25.0",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz",
- "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==",
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz",
+ "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14908,24 +14930,25 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.25.0",
- "@rollup/rollup-android-arm64": "4.25.0",
- "@rollup/rollup-darwin-arm64": "4.25.0",
- "@rollup/rollup-darwin-x64": "4.25.0",
- "@rollup/rollup-freebsd-arm64": "4.25.0",
- "@rollup/rollup-freebsd-x64": "4.25.0",
- "@rollup/rollup-linux-arm-gnueabihf": "4.25.0",
- "@rollup/rollup-linux-arm-musleabihf": "4.25.0",
- "@rollup/rollup-linux-arm64-gnu": "4.25.0",
- "@rollup/rollup-linux-arm64-musl": "4.25.0",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.25.0",
- "@rollup/rollup-linux-riscv64-gnu": "4.25.0",
- "@rollup/rollup-linux-s390x-gnu": "4.25.0",
- "@rollup/rollup-linux-x64-gnu": "4.25.0",
- "@rollup/rollup-linux-x64-musl": "4.25.0",
- "@rollup/rollup-win32-arm64-msvc": "4.25.0",
- "@rollup/rollup-win32-ia32-msvc": "4.25.0",
- "@rollup/rollup-win32-x64-msvc": "4.25.0",
+ "@rollup/rollup-android-arm-eabi": "4.28.1",
+ "@rollup/rollup-android-arm64": "4.28.1",
+ "@rollup/rollup-darwin-arm64": "4.28.1",
+ "@rollup/rollup-darwin-x64": "4.28.1",
+ "@rollup/rollup-freebsd-arm64": "4.28.1",
+ "@rollup/rollup-freebsd-x64": "4.28.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.28.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.28.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.28.1",
+ "@rollup/rollup-linux-arm64-musl": "4.28.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.28.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.28.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-gnu": "4.28.1",
+ "@rollup/rollup-linux-x64-musl": "4.28.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.28.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.28.1",
+ "@rollup/rollup-win32-x64-msvc": "4.28.1",
"fsevents": "~2.3.2"
}
},
diff --git a/package.json b/package.json
index f7da55caf..1cec41c47 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,7 @@
"browser-fs-access": "0.35.0",
"core-js": "3.39.0",
"elix": "15.0.1",
- "i18next": "23.16.5",
+ "i18next": "24.0.5",
"jspdf": "2.5.2",
"pathseg": "1.2.1",
"regenerator-runtime": "0.14.1",
@@ -101,7 +101,7 @@
"@babel/preset-env": "7.26.0",
"@babel/register": "7.25.9",
"@babel/runtime-corejs3": "7.26.0",
- "@cypress/code-coverage": "3.13.6",
+ "@cypress/code-coverage": "3.13.8",
"@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "^28",
"@rollup/plugin-dynamic-import-vars": "2.1.5",
@@ -115,7 +115,7 @@
"babel-plugin-transform-object-rest-spread": "7.0.0-beta.3",
"core-js-bundle": "3.39.0",
"cp-cli": "2.0.0",
- "cypress": "13.15.2",
+ "cypress": "13.16.1",
"cypress-multi-reporters": "2.0.4",
"jamilih": "0.60.0",
"jsdoc": "4.0.4",
@@ -129,7 +129,7 @@
"remark-cli": "12.0.1",
"remark-lint-ordered-list-marker-value": "4.0.0",
"rimraf": "6.0.1",
- "rollup": "4.25.0",
+ "rollup": "4.28.1",
"rollup-plugin-copy": "3.5.0",
"rollup-plugin-filesize": "10.0.0",
"rollup-plugin-html": "0.2.1",
@@ -139,6 +139,6 @@
"start-server-and-test": "2.0.8"
},
"optionalDependencies": {
- "@rollup/rollup-linux-x64-gnu": "4.25.0"
+ "@rollup/rollup-linux-x64-gnu": "4.28.1"
}
}
diff --git a/packages/svgcanvas/core/coords.js b/packages/svgcanvas/core/coords.js
index 7a1961b1a..508418332 100644
--- a/packages/svgcanvas/core/coords.js
+++ b/packages/svgcanvas/core/coords.js
@@ -124,7 +124,7 @@ export const remapElement = (selected, changes, m) => {
case 'image': {
// Allow images to be inverted (give them matrix when flipped)
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
- // Convert to matrix
+ // Convert to matrix if flipped
const chlist = getTransformList(selected)
const mt = svgCanvas.getSvgRoot().createSVGTransform()
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
@@ -177,6 +177,19 @@ export const remapElement = (selected, changes, m) => {
const pt = remap(changes.x, changes.y)
changes.x = pt.x
changes.y = pt.y
+
+ // Scale font-size
+ let fontSize = selected.getAttribute('font-size')
+ if (!fontSize) {
+ // If not directly set, try computed style
+ fontSize = window.getComputedStyle(selected).fontSize
+ }
+ const fontSizeNum = parseFloat(fontSize)
+ if (!isNaN(fontSizeNum)) {
+ // Assume uniform scaling and use m.a
+ changes['font-size'] = fontSizeNum * Math.abs(m.a)
+ }
+
finishUp()
// Handle child 'tspan' elements
@@ -197,7 +210,17 @@ export const remapElement = (selected, changes, m) => {
const childPtY = remap(changes.x, childY).y
childChanges.y = childPtY
}
- if (hasX || hasY) {
+
+ let tspanFS = child.getAttribute('font-size')
+ if (!tspanFS) {
+ tspanFS = window.getComputedStyle(child).fontSize
+ }
+ const tspanFSNum = parseFloat(tspanFS)
+ if (!isNaN(tspanFSNum)) {
+ childChanges['font-size'] = tspanFSNum * Math.abs(m.a)
+ }
+
+ if (hasX || hasY || childChanges['font-size']) {
assignAttributes(child, childChanges, 1000, true)
}
}
@@ -208,6 +231,17 @@ export const remapElement = (selected, changes, m) => {
const pt = remap(changes.x, changes.y)
changes.x = pt.x
changes.y = pt.y
+
+ // Handle tspan font-size scaling
+ let tspanFS = selected.getAttribute('font-size')
+ if (!tspanFS) {
+ tspanFS = window.getComputedStyle(selected).fontSize
+ }
+ const tspanFSNum = parseFloat(tspanFS)
+ if (!isNaN(tspanFSNum)) {
+ changes['font-size'] = tspanFSNum * Math.abs(m.a)
+ }
+
finishUp()
break
}
diff --git a/packages/svgcanvas/core/event.js b/packages/svgcanvas/core/event.js
index 7e24d40b1..068c63863 100644
--- a/packages/svgcanvas/core/event.js
+++ b/packages/svgcanvas/core/event.js
@@ -570,6 +570,7 @@ const mouseOutEvent = () => {
* @returns {void}
*/
const mouseUpEvent = (evt) => {
+ evt.preventDefault()
moveSelectionThresholdReached = false
if (evt.button === 2) { return }
if (!svgCanvas.getStarted()) { return }
diff --git a/packages/svgcanvas/core/math.js b/packages/svgcanvas/core/math.js
index 7d4bc5c64..b954778eb 100644
--- a/packages/svgcanvas/core/math.js
+++ b/packages/svgcanvas/core/math.js
@@ -3,117 +3,111 @@
* @module math
* @license MIT
*
- * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
+ * ©2010 Alexis Deveria, ©2010 Jeff Schiller
*/
/**
- * @typedef {PlainObject} module:math.AngleCoord45
- * @property {Float} x - The angle-snapped x value
- * @property {Float} y - The angle-snapped y value
- * @property {Integer} a - The angle at which to snap
+ * @typedef {Object} AngleCoord45
+ * @property {number} x - The angle-snapped x value
+ * @property {number} y - The angle-snapped y value
+ * @property {number} a - The angle (in radians) at which to snap
*/
/**
- * @typedef {PlainObject} module:math.XYObject
- * @property {Float} x
- * @property {Float} y
+ * @typedef {Object} XYObject
+ * @property {number} x
+ * @property {number} y
*/
import { NS } from './namespaces.js'
// Constants
-const NEAR_ZERO = 1e-14
+const NEAR_ZERO = 1e-10
-// Throw away SVGSVGElement used for creating matrices/transforms.
+// Create a throwaway SVG element for matrix operations
const svg = document.createElementNS(NS.SVG, 'svg')
/**
- * A (hopefully) quicker function to transform a point by a matrix
- * (this function avoids any DOM calls and just does the math).
- * @function module:math.transformPoint
- * @param {Float} x - Float representing the x coordinate
- * @param {Float} y - Float representing the y coordinate
- * @param {SVGMatrix} m - Matrix object to transform the point with
- * @returns {module:math.XYObject} An x, y object representing the transformed point
+ * Transforms a point by a given matrix without DOM calls.
+ * @function transformPoint
+ * @param {number} x - The x coordinate
+ * @param {number} y - The y coordinate
+ * @param {SVGMatrix} m - The transformation matrix
+ * @returns {XYObject} The transformed point
*/
-export const transformPoint = function (x, y, m) {
- return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f }
-}
+export const transformPoint = (x, y, m) => ({
+ x: m.a * x + m.c * y + m.e,
+ y: m.b * x + m.d * y + m.f
+})
-export const getTransformList = (elem) => {
- if (elem.transform) {
- return elem.transform?.baseVal
+/**
+ * Gets the transform list (baseVal) from an element if it exists.
+ * @function getTransformList
+ * @param {Element} elem - An SVG element or element with a transform list
+ * @returns {SVGTransformList|undefined} The transform list, if any
+ */
+export const getTransformList = elem => {
+ if (elem.transform?.baseVal) {
+ return elem.transform.baseVal
}
- if (elem.gradientTransform) {
- return elem.gradientTransform?.baseVal
+ if (elem.gradientTransform?.baseVal) {
+ return elem.gradientTransform.baseVal
}
- if (elem.patternTransform) {
- return elem.patternTransform?.baseVal
+ if (elem.patternTransform?.baseVal) {
+ return elem.patternTransform.baseVal
}
- console.warn('no transform list found - check browser version', elem)
+ console.warn('No transform list found. Check browser compatibility.', elem)
}
/**
- * Helper function to check if the matrix performs no actual transform
- * (i.e. exists for identity purposes).
- * @function module:math.isIdentity
- * @param {SVGMatrix} m - The matrix object to check
- * @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
+ * Checks if a matrix is the identity matrix.
+ * @function isIdentity
+ * @param {SVGMatrix} m - The matrix to check
+ * @returns {boolean} True if it's an identity matrix (1,0,0,1,0,0)
*/
-export const isIdentity = function (m) {
- return (
- m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
- )
-}
+export const isIdentity = m =>
+ m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
/**
- * This function tries to return a `SVGMatrix` that is the multiplication `m1 * m2`.
- * We also round to zero when it's near zero.
- * @function module:math.matrixMultiply
- * @param {...SVGMatrix} args - Matrix objects to multiply
- * @returns {SVGMatrix} The matrix object resulting from the calculation
+ * Multiplies multiple matrices together (m1 * m2 * ...).
+ * Near-zero values are rounded to zero.
+ * @function matrixMultiply
+ * @param {...SVGMatrix} args - The matrices to multiply
+ * @returns {SVGMatrix} The resulting matrix
*/
-export const matrixMultiply = function (...args) {
- const m = args.reduceRight((prev, m1) => {
- return m1.multiply(prev)
- })
-
- if (Math.abs(m.a) < NEAR_ZERO) {
- m.a = 0
- }
- if (Math.abs(m.b) < NEAR_ZERO) {
- m.b = 0
- }
- if (Math.abs(m.c) < NEAR_ZERO) {
- m.c = 0
- }
- if (Math.abs(m.d) < NEAR_ZERO) {
- m.d = 0
- }
- if (Math.abs(m.e) < NEAR_ZERO) {
- m.e = 0
- }
- if (Math.abs(m.f) < NEAR_ZERO) {
- m.f = 0
+export const matrixMultiply = (...args) => {
+ // If no matrices are given, return an identity matrix
+ if (args.length === 0) {
+ return svg.createSVGMatrix()
}
+ const m = args.reduceRight((prev, curr) => curr.multiply(prev))
+
+ // Round near-zero values to zero
+ if (Math.abs(m.a) < NEAR_ZERO) m.a = 0
+ if (Math.abs(m.b) < NEAR_ZERO) m.b = 0
+ if (Math.abs(m.c) < NEAR_ZERO) m.c = 0
+ if (Math.abs(m.d) < NEAR_ZERO) m.d = 0
+ if (Math.abs(m.e) < NEAR_ZERO) m.e = 0
+ if (Math.abs(m.f) < NEAR_ZERO) m.f = 0
+
return m
}
/**
- * See if the given transformlist includes a non-indentity matrix transform.
- * @function module:math.hasMatrixTransform
- * @param {SVGTransformList} [tlist] - The transformlist to check
- * @returns {boolean} Whether or not a matrix transform was found
+ * Checks if a transform list includes a non-identity matrix transform.
+ * @function hasMatrixTransform
+ * @param {SVGTransformList} [tlist] - The transform list to check
+ * @returns {boolean} True if a matrix transform is found
*/
-export const hasMatrixTransform = function (tlist) {
- if (!tlist) {
- return false
- }
- let num = tlist.numberOfItems
- while (num--) {
- const xform = tlist.getItem(num)
- if (xform.type === 1 && !isIdentity(xform.matrix)) {
+export const hasMatrixTransform = tlist => {
+ if (!tlist) return false
+ for (let i = 0; i < tlist.numberOfItems; i++) {
+ const xform = tlist.getItem(i)
+ if (
+ xform.type === SVGTransform.SVG_TRANSFORM_MATRIX &&
+ !isIdentity(xform.matrix)
+ ) {
return true
}
}
@@ -121,29 +115,29 @@ export const hasMatrixTransform = function (tlist) {
}
/**
- * @typedef {PlainObject} module:math.TransformedBox An object with the following values
- * @property {module:math.XYObject} tl - The top left coordinate
- * @property {module:math.XYObject} tr - The top right coordinate
- * @property {module:math.XYObject} bl - The bottom left coordinate
- * @property {module:math.XYObject} br - The bottom right coordinate
- * @property {PlainObject} aabox - Object with the following values:
- * @property {Float} aabox.x - Float with the axis-aligned x coordinate
- * @property {Float} aabox.y - Float with the axis-aligned y coordinate
- * @property {Float} aabox.width - Float with the axis-aligned width coordinate
- * @property {Float} aabox.height - Float with the axis-aligned height coordinate
+ * @typedef {Object} TransformedBox
+ * @property {XYObject} tl - Top-left coordinate
+ * @property {XYObject} tr - Top-right coordinate
+ * @property {XYObject} bl - Bottom-left coordinate
+ * @property {XYObject} br - Bottom-right coordinate
+ * @property {Object} aabox
+ * @property {number} aabox.x - Axis-aligned x
+ * @property {number} aabox.y - Axis-aligned y
+ * @property {number} aabox.width - Axis-aligned width
+ * @property {number} aabox.height - Axis-aligned height
*/
/**
- * Transforms a rectangle based on the given matrix.
- * @function module:math.transformBox
- * @param {Float} l - Float with the box's left coordinate
- * @param {Float} t - Float with the box's top coordinate
- * @param {Float} w - Float with the box width
- * @param {Float} h - Float with the box height
- * @param {SVGMatrix} m - Matrix object to transform the box by
- * @returns {module:math.TransformedBox}
+ * Transforms a rectangular box using a given matrix.
+ * @function transformBox
+ * @param {number} l - Left coordinate
+ * @param {number} t - Top coordinate
+ * @param {number} w - Width
+ * @param {number} h - Height
+ * @param {SVGMatrix} m - Transformation matrix
+ * @returns {TransformedBox} The transformed box information
*/
-export const transformBox = function (l, t, w, h, m) {
+export const transformBox = (l, t, w, h, m) => {
const tl = transformPoint(l, t, m)
const tr = transformPoint(l + w, t, m)
const bl = transformPoint(l, t + h, m)
@@ -169,91 +163,81 @@ export const transformBox = function (l, t, w, h, m) {
}
/**
- * This returns a single matrix Transform for a given Transform List
- * (this is the equivalent of `SVGTransformList.consolidate()` but unlike
- * that method, this one does not modify the actual `SVGTransformList`).
- * This function is very liberal with its `min`, `max` arguments.
- * @function module:math.transformListToTransform
- * @param {SVGTransformList} tlist - The transformlist object
- * @param {Integer} [min=0] - Optional integer indicating start transform position
- * @param {Integer} [max] - Optional integer indicating end transform position;
- * defaults to one less than the tlist's `numberOfItems`
- * @returns {SVGTransform} A single matrix transform object
+ * Consolidates a transform list into a single matrix transform without modifying the original list.
+ * @function transformListToTransform
+ * @param {SVGTransformList} tlist - The transform list
+ * @param {number} [min=0] - Optional start index
+ * @param {number} [max] - Optional end index, defaults to tlist length-1
+ * @returns {SVGTransform} A single transform from the combined matrices
*/
-export const transformListToTransform = function (tlist, min, max) {
+export const transformListToTransform = (tlist, min = 0, max = null) => {
if (!tlist) {
- // Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix())
}
- min = min || 0
- max = max || tlist.numberOfItems - 1
- min = Number.parseInt(min)
- max = Number.parseInt(max)
- if (min > max) {
- const temp = max
- max = min
- min = temp
- }
- let m = svg.createSVGMatrix()
- for (let i = min; i <= max; ++i) {
- // if our indices are out of range, just use a harmless identity matrix
- const mtom =
+
+ const start = Number.parseInt(min, 10)
+ const end = Number.parseInt(max ?? tlist.numberOfItems - 1, 10)
+ const low = Math.min(start, end)
+ const high = Math.max(start, end)
+
+ let combinedMatrix = svg.createSVGMatrix()
+ for (let i = low; i <= high; i++) {
+ // If out of range, use identity
+ const currentMatrix =
i >= 0 && i < tlist.numberOfItems
? tlist.getItem(i).matrix
: svg.createSVGMatrix()
- m = matrixMultiply(m, mtom)
+ combinedMatrix = matrixMultiply(combinedMatrix, currentMatrix)
}
- return svg.createSVGTransformFromMatrix(m)
+
+ return svg.createSVGTransformFromMatrix(combinedMatrix)
}
/**
- * Get the matrix object for a given element.
- * @function module:math.getMatrix
- * @param {Element} elem - The DOM element to check
- * @returns {SVGMatrix} The matrix object associated with the element's transformlist
+ * Gets the matrix of a given element's transform list.
+ * @function getMatrix
+ * @param {Element} elem - The element to check
+ * @returns {SVGMatrix} The transformation matrix
*/
-export const getMatrix = (elem) => {
+export const getMatrix = elem => {
const tlist = getTransformList(elem)
return transformListToTransform(tlist).matrix
}
/**
- * Returns a 45 degree angle coordinate associated with the two given
- * coordinates.
- * @function module:math.snapToAngle
- * @param {Integer} x1 - First coordinate's x value
- * @param {Integer} y1 - First coordinate's y value
- * @param {Integer} x2 - Second coordinate's x value
- * @param {Integer} y2 - Second coordinate's y value
- * @returns {module:math.AngleCoord45}
+ * Returns a coordinate snapped to the nearest 45-degree angle.
+ * @function snapToAngle
+ * @param {number} x1 - First point's x
+ * @param {number} y1 - First point's y
+ * @param {number} x2 - Second point's x
+ * @param {number} y2 - Second point's y
+ * @returns {AngleCoord45} The angle-snapped coordinates and angle
*/
export const snapToAngle = (x1, y1, x2, y2) => {
const snap = Math.PI / 4 // 45 degrees
const dx = x2 - x1
const dy = y2 - y1
const angle = Math.atan2(dy, dx)
- const dist = Math.sqrt(dx * dx + dy * dy)
- const snapangle = Math.round(angle / snap) * snap
+ const dist = Math.hypot(dx, dy)
+ const snapAngle = Math.round(angle / snap) * snap
return {
- x: x1 + dist * Math.cos(snapangle),
- y: y1 + dist * Math.sin(snapangle),
- a: snapangle
+ x: x1 + dist * Math.cos(snapAngle),
+ y: y1 + dist * Math.sin(snapAngle),
+ a: snapAngle
}
}
/**
- * Check if two rectangles (BBoxes objects) intersect each other.
- * @function module:math.rectsIntersect
- * @param {SVGRect} r1 - The first BBox-like object
- * @param {SVGRect} r2 - The second BBox-like object
- * @returns {boolean} True if rectangles intersect
+ * Checks if two rectangles intersect.
+ * Both r1 and r2 are expected to have {x, y, width, height}.
+ * @function rectsIntersect
+ * @param {{x:number,y:number,width:number,height:number}} r1 - First rectangle
+ * @param {{x:number,y:number,width:number,height:number}} r2 - Second rectangle
+ * @returns {boolean} True if the rectangles intersect
*/
-export const rectsIntersect = (r1, r2) => {
- return (
- r2.x < r1.x + r1.width &&
- r2.x + r2.width > r1.x &&
- r2.y < r1.y + r1.height &&
- r2.y + r2.height > r1.y
- )
-}
+export const rectsIntersect = (r1, r2) =>
+ r2.x < r1.x + r1.width &&
+ r2.x + r2.width > r1.x &&
+ r2.y < r1.y + r1.height &&
+ r2.y + r2.height > r1.y
diff --git a/packages/svgcanvas/core/text-actions.js b/packages/svgcanvas/core/text-actions.js
index 9832324ae..73fd209cf 100644
--- a/packages/svgcanvas/core/text-actions.js
+++ b/packages/svgcanvas/core/text-actions.js
@@ -6,33 +6,31 @@
*/
import { NS } from './namespaces.js'
+import { transformPoint, getMatrix } from './math.js'
import {
- transformPoint, getMatrix
-} from './math.js'
-import {
- assignAttributes, getElement, getBBox as utilsGetBBox
+ assignAttributes,
+ getElement,
+ getBBox as utilsGetBBox
} from './utilities.js'
-import {
- supportsGoodTextCharPos
-} from '../common/browser.js'
+import { supportsGoodTextCharPos } from '../common/browser.js'
let svgCanvas = null
/**
-* @function module:text-actions.init
-* @param {module:text-actions.svgCanvas} textActionsContext
-* @returns {void}
-*/
-export const init = (canvas) => {
+ * @function module:text-actions.init
+ * @param {module:text-actions.svgCanvas} textActionsContext
+ * @returns {void}
+ */
+export const init = canvas => {
svgCanvas = canvas
}
/**
-* Group: Text edit functions
-* Functions relating to editing text elements.
-* @namespace {PlainObject} textActions
-* @memberof module:svgcanvas.SvgCanvas#
-*/
+ * Group: Text edit functions
+ * Functions relating to editing text elements.
+ * @namespace {PlainObject} textActions
+ * @memberof module:svgcanvas.SvgCanvas#
+ */
export const textActionsMethod = (function () {
let curtext
let textinput
@@ -42,23 +40,26 @@ export const textActionsMethod = (function () {
let chardata = []
let textbb // , transbb;
let matrix
- let lastX; let lastY
+ let lastX
+ let lastY
let allowDbl
/**
-*
-* @param {Integer} index
-* @returns {void}
-*/
+ *
+ * @param {Integer} index
+ * @returns {void}
+ */
function setCursor (index) {
- const empty = (textinput.value === '')
+ const empty = textinput.value === ''
textinput.focus()
if (!arguments.length) {
if (empty) {
index = 0
} else {
- if (textinput.selectionEnd !== textinput.selectionStart) { return }
+ if (textinput.selectionEnd !== textinput.selectionStart) {
+ return
+ }
index = textinput.selectionEnd
}
}
@@ -80,13 +81,13 @@ export const textActionsMethod = (function () {
if (!blinker) {
blinker = setInterval(function () {
- const show = (cursor.getAttribute('display') === 'none')
+ const show = cursor.getAttribute('display') === 'none'
cursor.setAttribute('display', show ? 'inline' : 'none')
}, 600)
}
const startPt = ptToScreen(charbb.x, textbb.y)
- const endPt = ptToScreen(charbb.x, (textbb.y + textbb.height))
+ const endPt = ptToScreen(charbb.x, textbb.y + textbb.height)
assignAttributes(cursor, {
x1: startPt.x,
@@ -97,16 +98,18 @@ export const textActionsMethod = (function () {
display: 'inline'
})
- if (selblock) { selblock.setAttribute('d', '') }
+ if (selblock) {
+ selblock.setAttribute('d', '')
+ }
}
/**
-*
-* @param {Integer} start
-* @param {Integer} end
-* @param {boolean} skipInput
-* @returns {void}
-*/
+ *
+ * @param {Integer} start
+ * @param {Integer} end
+ * @param {boolean} skipInput
+ * @returns {void}
+ */
function setSelection (start, end, skipInput) {
if (start === end) {
setCursor(end)
@@ -137,12 +140,29 @@ export const textActionsMethod = (function () {
const tl = ptToScreen(startbb.x, textbb.y)
const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y)
const bl = ptToScreen(startbb.x, textbb.y + textbb.height)
- const br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height)
-
- const dstr = 'M' + tl.x + ',' + tl.y +
-' L' + tr.x + ',' + tr.y +
-' ' + br.x + ',' + br.y +
-' ' + bl.x + ',' + bl.y + 'z'
+ const br = ptToScreen(
+ startbb.x + (endbb.x - startbb.x),
+ textbb.y + textbb.height
+ )
+
+ const dstr =
+ 'M' +
+ tl.x +
+ ',' +
+ tl.y +
+ ' L' +
+ tr.x +
+ ',' +
+ tr.y +
+ ' ' +
+ br.x +
+ ',' +
+ br.y +
+ ' ' +
+ bl.x +
+ ',' +
+ bl.y +
+ 'z'
assignAttributes(selblock, {
d: dstr,
@@ -151,11 +171,11 @@ export const textActionsMethod = (function () {
}
/**
-*
-* @param {Float} mouseX
-* @param {Float} mouseY
-* @returns {Integer}
-*/
+ *
+ * @param {Float} mouseX
+ * @param {Float} mouseY
+ * @returns {Integer}
+ */
function getIndexFromPoint (mouseX, mouseY) {
// Position cursor here
const pt = svgCanvas.getSvgRoot().createSVGPoint()
@@ -163,7 +183,9 @@ export const textActionsMethod = (function () {
pt.y = mouseY
// No content, so return 0
- if (chardata.length === 1) { return 0 }
+ if (chardata.length === 1) {
+ return 0
+ }
// Determine if cursor should be on left or right of character
let charpos = curtext.getCharNumAtPosition(pt)
if (charpos < 0) {
@@ -176,7 +198,7 @@ export const textActionsMethod = (function () {
charpos = chardata.length - 2
}
const charbb = chardata[charpos]
- const mid = charbb.x + (charbb.width / 2)
+ const mid = charbb.x + charbb.width / 2
if (mouseX > mid) {
charpos++
}
@@ -184,22 +206,22 @@ export const textActionsMethod = (function () {
}
/**
-*
-* @param {Float} mouseX
-* @param {Float} mouseY
-* @returns {void}
-*/
+ *
+ * @param {Float} mouseX
+ * @param {Float} mouseY
+ * @returns {void}
+ */
function setCursorFromPoint (mouseX, mouseY) {
setCursor(getIndexFromPoint(mouseX, mouseY))
}
/**
-*
-* @param {Float} x
-* @param {Float} y
-* @param {boolean} apply
-* @returns {void}
-*/
+ *
+ * @param {Float} x
+ * @param {Float} y
+ * @param {boolean} apply
+ * @returns {void}
+ */
function setEndSelectionFromPoint (x, y, apply) {
const i1 = textinput.selectionStart
const i2 = getIndexFromPoint(x, y)
@@ -210,11 +232,11 @@ export const textActionsMethod = (function () {
}
/**
-*
-* @param {Float} xIn
-* @param {Float} yIn
-* @returns {module:math.XYObject}
-*/
+ *
+ * @param {Float} xIn
+ * @param {Float} yIn
+ * @returns {module:math.XYObject}
+ */
function screenToPt (xIn, yIn) {
const out = {
x: xIn,
@@ -234,11 +256,11 @@ export const textActionsMethod = (function () {
}
/**
-*
-* @param {Float} xIn
-* @param {Float} yIn
-* @returns {module:math.XYObject}
-*/
+ *
+ * @param {Float} xIn
+ * @param {Float} yIn
+ * @returns {module:math.XYObject}
+ */
function ptToScreen (xIn, yIn) {
const out = {
x: xIn,
@@ -258,22 +280,24 @@ export const textActionsMethod = (function () {
}
/**
-*
-* @param {Event} evt
-* @returns {void}
-*/
+ *
+ * @param {Event} evt
+ * @returns {void}
+ */
function selectAll (evt) {
setSelection(0, curtext.textContent.length)
evt.target.removeEventListener('click', selectAll)
}
/**
-*
-* @param {Event} evt
-* @returns {void}
-*/
+ *
+ * @param {Event} evt
+ * @returns {void}
+ */
function selectWord (evt) {
- if (!allowDbl || !curtext) { return }
+ if (!allowDbl || !curtext) {
+ return
+ }
const zoom = svgCanvas.getZoom()
const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm())
const mouseX = ept.x * zoom
@@ -297,30 +321,30 @@ export const textActionsMethod = (function () {
return /** @lends module:svgcanvas.SvgCanvas#textActions */ {
/**
-* @param {Element} target
-* @param {Float} x
-* @param {Float} y
-* @returns {void}
-*/
+ * @param {Element} target
+ * @param {Float} x
+ * @param {Float} y
+ * @returns {void}
+ */
select (target, x, y) {
curtext = target
svgCanvas.textActions.toEditMode(x, y)
},
/**
-* @param {Element} elem
-* @returns {void}
-*/
+ * @param {Element} elem
+ * @returns {void}
+ */
start (elem) {
curtext = elem
svgCanvas.textActions.toEditMode()
},
/**
-* @param {external:MouseEvent} evt
-* @param {Element} mouseTarget
-* @param {Float} startX
-* @param {Float} startY
-* @returns {void}
-*/
+ * @param {external:MouseEvent} evt
+ * @param {Element} mouseTarget
+ * @param {Float} startX
+ * @param {Float} startY
+ * @returns {void}
+ */
mouseDown (evt, mouseTarget, startX, startY) {
const pt = screenToPt(startX, startY)
@@ -332,20 +356,20 @@ export const textActionsMethod = (function () {
// TODO: Find way to block native selection
},
/**
-* @param {Float} mouseX
-* @param {Float} mouseY
-* @returns {void}
-*/
+ * @param {Float} mouseX
+ * @param {Float} mouseY
+ * @returns {void}
+ */
mouseMove (mouseX, mouseY) {
const pt = screenToPt(mouseX, mouseY)
setEndSelectionFromPoint(pt.x, pt.y)
},
/**
-* @param {external:MouseEvent} evt
-* @param {Float} mouseX
-* @param {Float} mouseY
-* @returns {void}
-*/
+ * @param {external:MouseEvent} evt
+ * @param {Float} mouseX
+ * @param {Float} mouseY
+ * @returns {void}
+ */
mouseUp (evt, mouseX, mouseY) {
const pt = screenToPt(mouseX, mouseY)
@@ -359,25 +383,25 @@ export const textActionsMethod = (function () {
if (
evt.target !== curtext &&
- mouseX < lastX + 2 &&
- mouseX > lastX - 2 &&
- mouseY < lastY + 2 &&
- mouseY > lastY - 2
+ mouseX < lastX + 2 &&
+ mouseX > lastX - 2 &&
+ mouseY < lastY + 2 &&
+ mouseY > lastY - 2
) {
svgCanvas.textActions.toSelectMode(true)
}
},
/**
-* @function
-* @param {Integer} index
-* @returns {void}
-*/
+ * @function
+ * @param {Integer} index
+ * @returns {void}
+ */
setCursor,
/**
-* @param {Float} x
-* @param {Float} y
-* @returns {void}
-*/
+ * @param {Float} x
+ * @param {Float} y
+ * @returns {void}
+ */
toEditMode (x, y) {
allowDbl = false
svgCanvas.setCurrentMode('textedit')
@@ -407,16 +431,20 @@ export const textActionsMethod = (function () {
}, 300)
},
/**
-* @param {boolean|Element} selectElem
-* @fires module:svgcanvas.SvgCanvas#event:selected
-* @returns {void}
-*/
+ * @param {boolean|Element} selectElem
+ * @fires module:svgcanvas.SvgCanvas#event:selected
+ * @returns {void}
+ */
toSelectMode (selectElem) {
svgCanvas.setCurrentMode('select')
clearInterval(blinker)
blinker = null
- if (selblock) { selblock.setAttribute('display', 'none') }
- if (cursor) { cursor.setAttribute('visibility', 'hidden') }
+ if (selblock) {
+ selblock.setAttribute('display', 'none')
+ }
+ if (cursor) {
+ cursor.setAttribute('visibility', 'hidden')
+ }
curtext.style.cursor = 'move'
if (selectElem) {
@@ -440,27 +468,30 @@ export const textActionsMethod = (function () {
// }
},
/**
-* @param {Element} elem
-* @returns {void}
-*/
+ * @param {Element} elem
+ * @returns {void}
+ */
setInputElem (elem) {
textinput = elem
},
/**
-* @returns {void}
-*/
+ * @returns {void}
+ */
clear () {
if (svgCanvas.getCurrentMode() === 'textedit') {
svgCanvas.textActions.toSelectMode()
}
},
/**
-* @param {Element} _inputElem Not in use
-* @returns {void}
-*/
+ * @param {Element} _inputElem Not in use
+ * @returns {void}
+ */
init (_inputElem) {
- if (!curtext) { return }
- let i; let end
+ if (!curtext) {
+ return
+ }
+ let i
+ let end
// if (supportsEditableText()) {
// curtext.select();
// return;
@@ -490,7 +521,7 @@ export const textActionsMethod = (function () {
curtext.addEventListener('dblclick', selectWord)
if (!len) {
- end = { x: textbb.x + (textbb.width / 2), width: 0 }
+ end = { x: textbb.x + textbb.width / 2, width: 0 }
}
for (i = 0; i < len; i++) {
@@ -527,4 +558,4 @@ export const textActionsMethod = (function () {
setSelection(textinput.selectionStart, textinput.selectionEnd, true)
}
}
-}())
+})()