js-draw 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/version.js +1 -1
  4. package/dist/mjs/version.mjs +1 -1
  5. package/docs/img/readme-images/js-draw.jpg +0 -0
  6. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  7. package/package.json +5 -4
  8. package/dist-test/test_imports/package-lock.json +0 -13
  9. package/dist-test/test_imports/package.json +0 -12
  10. package/dist-test/test_imports/test-imports.js +0 -11
  11. package/dist-test/test_imports/test-require.cjs +0 -14
  12. package/src/Editor.loadFrom.test.ts +0 -24
  13. package/src/Editor.test.ts +0 -107
  14. package/src/Editor.toSVG.test.ts +0 -294
  15. package/src/Editor.ts +0 -1443
  16. package/src/EditorImage.test.ts +0 -117
  17. package/src/EditorImage.ts +0 -609
  18. package/src/EventDispatcher.test.ts +0 -123
  19. package/src/EventDispatcher.ts +0 -72
  20. package/src/Pointer.ts +0 -183
  21. package/src/SVGLoader.test.ts +0 -114
  22. package/src/SVGLoader.ts +0 -672
  23. package/src/UndoRedoHistory.test.ts +0 -34
  24. package/src/UndoRedoHistory.ts +0 -102
  25. package/src/Viewport.ts +0 -322
  26. package/src/bundle/bundled.ts +0 -7
  27. package/src/commands/Command.ts +0 -45
  28. package/src/commands/Duplicate.ts +0 -75
  29. package/src/commands/Erase.ts +0 -95
  30. package/src/commands/SerializableCommand.ts +0 -49
  31. package/src/commands/UnresolvedCommand.ts +0 -37
  32. package/src/commands/invertCommand.ts +0 -58
  33. package/src/commands/lib.ts +0 -16
  34. package/src/commands/localization.ts +0 -47
  35. package/src/commands/uniteCommands.test.ts +0 -23
  36. package/src/commands/uniteCommands.ts +0 -140
  37. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  38. package/src/components/AbstractComponent.ts +0 -383
  39. package/src/components/BackgroundComponent.test.ts +0 -44
  40. package/src/components/BackgroundComponent.ts +0 -348
  41. package/src/components/ImageComponent.ts +0 -176
  42. package/src/components/RestylableComponent.ts +0 -161
  43. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  44. package/src/components/Stroke.test.ts +0 -137
  45. package/src/components/Stroke.ts +0 -294
  46. package/src/components/TextComponent.test.ts +0 -202
  47. package/src/components/TextComponent.ts +0 -429
  48. package/src/components/UnknownSVGObject.test.ts +0 -10
  49. package/src/components/UnknownSVGObject.ts +0 -60
  50. package/src/components/builders/ArrowBuilder.ts +0 -106
  51. package/src/components/builders/CircleBuilder.ts +0 -100
  52. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  53. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  54. package/src/components/builders/LineBuilder.ts +0 -77
  55. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  56. package/src/components/builders/RectangleBuilder.ts +0 -73
  57. package/src/components/builders/types.ts +0 -15
  58. package/src/components/lib.ts +0 -31
  59. package/src/components/localization.ts +0 -24
  60. package/src/components/util/StrokeSmoother.ts +0 -302
  61. package/src/components/util/describeComponentList.ts +0 -18
  62. package/src/dialogs/makeAboutDialog.ts +0 -82
  63. package/src/inputEvents.ts +0 -143
  64. package/src/lib.ts +0 -91
  65. package/src/localization.ts +0 -34
  66. package/src/localizations/de.ts +0 -146
  67. package/src/localizations/en.ts +0 -8
  68. package/src/localizations/es.ts +0 -74
  69. package/src/localizations/getLocalizationTable.test.ts +0 -27
  70. package/src/localizations/getLocalizationTable.ts +0 -74
  71. package/src/rendering/Display.ts +0 -247
  72. package/src/rendering/RenderablePathSpec.ts +0 -88
  73. package/src/rendering/RenderingStyle.test.ts +0 -68
  74. package/src/rendering/RenderingStyle.ts +0 -55
  75. package/src/rendering/TextRenderingStyle.ts +0 -55
  76. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  77. package/src/rendering/caching/CacheRecord.ts +0 -76
  78. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  79. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  80. package/src/rendering/caching/RenderingCache.ts +0 -66
  81. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  82. package/src/rendering/caching/testUtils.ts +0 -35
  83. package/src/rendering/caching/types.ts +0 -34
  84. package/src/rendering/lib.ts +0 -8
  85. package/src/rendering/localization.ts +0 -20
  86. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  87. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  88. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  89. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  90. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  91. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  92. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  93. package/src/shortcuts/KeyBinding.test.ts +0 -61
  94. package/src/shortcuts/KeyBinding.ts +0 -257
  95. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  96. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  97. package/src/shortcuts/lib.ts +0 -3
  98. package/src/testing/createEditor.ts +0 -11
  99. package/src/testing/getUniquePointerId.ts +0 -18
  100. package/src/testing/lib.ts +0 -3
  101. package/src/testing/sendPenEvent.ts +0 -36
  102. package/src/testing/sendTouchEvent.ts +0 -71
  103. package/src/toolbar/AbstractToolbar.ts +0 -542
  104. package/src/toolbar/DropdownToolbar.ts +0 -220
  105. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  106. package/src/toolbar/EdgeToolbar.ts +0 -543
  107. package/src/toolbar/IconProvider.ts +0 -861
  108. package/src/toolbar/constants.ts +0 -1
  109. package/src/toolbar/lib.ts +0 -6
  110. package/src/toolbar/localization.ts +0 -136
  111. package/src/toolbar/types.ts +0 -13
  112. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  113. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  114. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  115. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  116. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  117. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  118. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  119. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  120. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  121. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  122. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  123. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  124. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  125. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  126. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  127. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  128. package/src/toolbar/widgets/keybindings.ts +0 -19
  129. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  130. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  131. package/src/toolbar/widgets/layout/types.ts +0 -74
  132. package/src/toolbar/widgets/lib.ts +0 -13
  133. package/src/tools/BaseTool.ts +0 -169
  134. package/src/tools/Eraser.test.ts +0 -103
  135. package/src/tools/Eraser.ts +0 -173
  136. package/src/tools/FindTool.test.ts +0 -67
  137. package/src/tools/FindTool.ts +0 -153
  138. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  139. package/src/tools/InputFilter/InputMapper.ts +0 -41
  140. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  141. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  142. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  143. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  144. package/src/tools/PanZoom.test.ts +0 -339
  145. package/src/tools/PanZoom.ts +0 -525
  146. package/src/tools/PasteHandler.ts +0 -94
  147. package/src/tools/Pen.test.ts +0 -260
  148. package/src/tools/Pen.ts +0 -284
  149. package/src/tools/PipetteTool.ts +0 -84
  150. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  151. package/src/tools/SelectionTool/Selection.ts +0 -647
  152. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  153. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  154. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  155. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  156. package/src/tools/SelectionTool/types.ts +0 -11
  157. package/src/tools/SoundUITool.ts +0 -221
  158. package/src/tools/TextTool.ts +0 -339
  159. package/src/tools/ToolController.ts +0 -224
  160. package/src/tools/ToolEnabledGroup.ts +0 -14
  161. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  162. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  163. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  164. package/src/tools/UndoRedoShortcut.ts +0 -24
  165. package/src/tools/keybindings.ts +0 -85
  166. package/src/tools/lib.ts +0 -22
  167. package/src/tools/localization.ts +0 -76
  168. package/src/types.ts +0 -151
  169. package/src/util/ReactiveValue.test.ts +0 -168
  170. package/src/util/ReactiveValue.ts +0 -241
  171. package/src/util/assertions.ts +0 -55
  172. package/src/util/fileToBase64.ts +0 -18
  173. package/src/util/guessKeyCodeFromKey.ts +0 -36
  174. package/src/util/listPrefixMatch.ts +0 -19
  175. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  176. package/src/util/untilNextAnimationFrame.ts +0 -9
  177. package/src/util/waitForAll.ts +0 -18
  178. package/src/util/waitForTimeout.ts +0 -9
  179. package/src/version.test.ts +0 -12
  180. package/src/version.ts +0 -3
  181. package/tools/allLocales.js +0 -4
  182. package/tools/copyREADME.ts +0 -62
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = {
4
- number: '1.0.1',
4
+ number: '1.0.2',
5
5
  };
@@ -1,3 +1,3 @@
1
1
  export default {
2
- number: '1.0.1',
2
+ number: '1.0.2',
3
3
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -64,11 +64,11 @@
64
64
  "postpack": "ts-node tools/copyREADME.ts revert"
65
65
  },
66
66
  "dependencies": {
67
- "@js-draw/math": "^1.0.0",
67
+ "@js-draw/math": "^1.0.2",
68
68
  "@melloware/coloris": "0.21.0"
69
69
  },
70
70
  "devDependencies": {
71
- "@js-draw/build-tool": "^1.0.0",
71
+ "@js-draw/build-tool": "^1.0.2",
72
72
  "@types/bezier-js": "4.1.0",
73
73
  "@types/jest": "29.5.3",
74
74
  "@types/jsdom": "21.1.1"
@@ -86,5 +86,6 @@
86
86
  "pen",
87
87
  "freehand",
88
88
  "svg"
89
- ]
89
+ ],
90
+ "gitHead": "f5a92284625b49b2ef6541bdafce1bd926c10441"
90
91
  }
@@ -1,13 +0,0 @@
1
- {
2
- "name": "js-draw-test-imports",
3
- "version": "0.0.1",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "": {
8
- "name": "js-draw-test-imports",
9
- "version": "0.0.1",
10
- "license": "MIT"
11
- }
12
- }
13
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "js-draw-test-imports",
3
- "version": "0.0.1",
4
- "description": "Test module and CommonJS imports",
5
- "author": "Henry Heino",
6
- "license": "MIT",
7
- "private": true,
8
- "type": "module",
9
- "scripts": {
10
- "test": "node test-imports.js && node test-require.cjs"
11
- }
12
- }
@@ -1,11 +0,0 @@
1
- console.log('Testing imports...');
2
-
3
- import { TextComponent, StrokeComponent } from 'js-draw/components';
4
-
5
- if (!TextComponent.fromLines) {
6
- throw new Error('Failed to import module TextComponent');
7
- }
8
-
9
- if (!StrokeComponent.deserializeFromJSON) {
10
- throw new Error('Failed to import StrokeComponent');
11
- }
@@ -1,14 +0,0 @@
1
- console.log('Testing require()...');
2
-
3
- // TODO: Test require('js-draw') (requires removing Coloris because
4
- // Coloris depends on the DOM when first loaded).
5
-
6
- const { TextComponent, StrokeComponent } = require('js-draw/components');
7
-
8
- if (!TextComponent.fromLines) {
9
- throw new Error('Failed to import module TextComponent');
10
- }
11
-
12
- if (!StrokeComponent.deserializeFromJSON) {
13
- throw new Error('Failed to import StrokeComponent');
14
- }
@@ -1,24 +0,0 @@
1
- import { Color4 } from '@js-draw/math';
2
- import { imageBackgroundCSSClassName } from './components/BackgroundComponent';
3
- import { RestyleableComponent } from './lib';
4
- import SVGLoader from './SVGLoader';
5
- import createEditor from './testing/createEditor';
6
-
7
- describe('Editor.loadFrom', () => {
8
- it('should remove existing BackgroundComponents when loading new BackgroundComponents', async () => {
9
- const editor = createEditor();
10
- await editor.dispatch(editor.setBackgroundColor(Color4.red));
11
-
12
- let backgroundComponents = editor.image.getBackgroundComponents();
13
- expect(backgroundComponents).toHaveLength(1);
14
- expect((backgroundComponents[0] as RestyleableComponent).getStyle().color).objEq(Color4.red);
15
-
16
- await editor.loadFrom(SVGLoader.fromString(`<svg viewBox='0 0 100 100'>
17
- <path class='${imageBackgroundCSSClassName}' d='m0,0 L100,0 L100,100 L0,100 z' fill='#000'/>
18
- </svg>`, true));
19
-
20
- backgroundComponents = editor.image.getBackgroundComponents();
21
- expect(backgroundComponents).toHaveLength(1);
22
- expect((backgroundComponents[0] as RestyleableComponent).getStyle().color).objEq(Color4.black);
23
- });
24
- });
@@ -1,107 +0,0 @@
1
- import { BaseTool, InputEvtType } from './lib';
2
- import createEditor from './testing/createEditor';
3
-
4
- describe('Editor', () => {
5
- it('should fire keyup events when the editor loses focus', () => {
6
- const editor = createEditor();
7
- const rootElem = editor.getRootElement();
8
-
9
- const inputArea = rootElem.querySelector('textarea')! as HTMLTextAreaElement;
10
-
11
- // Set the only tool to a tool that reports which keys are pressed.
12
- const keyPressMock = jest.fn(() => true);
13
- const keyReleaseMock = jest.fn();
14
- editor.toolController.setTools([
15
- new (class extends BaseTool {
16
- public constructor() {
17
- super(editor.notifier, 'test');
18
- }
19
-
20
- public override onKeyPress = keyPressMock;
21
- public override onKeyUp = keyReleaseMock;
22
- })()
23
- ]);
24
-
25
- inputArea.focus();
26
-
27
- // Sends a keyboard event to the editor
28
- const dispatchKeyEvent = (kind: 'keydown'|'keyup', code: string, key: string) => {
29
- const event = new KeyboardEvent(kind, {
30
- bubbles: true,
31
- key,
32
- code,
33
- shiftKey: false,
34
- ctrlKey: false,
35
- metaKey: false,
36
- });
37
- inputArea.dispatchEvent(event);
38
- };
39
-
40
- // Press the A key
41
- dispatchKeyEvent('keydown', 'KeyA', 'a');
42
-
43
- const keyAEvent = {
44
- kind: InputEvtType.KeyPressEvent,
45
- key: 'a',
46
- code: 'KeyA',
47
- ctrlKey: false,
48
- altKey: false,
49
- shiftKey: false,
50
- };
51
- expect(keyPressMock).toHaveBeenLastCalledWith(keyAEvent);
52
- expect(keyPressMock).toHaveBeenCalledTimes(1);
53
-
54
- // Press it again
55
- dispatchKeyEvent('keydown', 'KeyA', 'a');
56
-
57
- expect(keyPressMock).toHaveBeenLastCalledWith(keyAEvent);
58
- expect(keyPressMock).toHaveBeenCalledTimes(2);
59
-
60
- // Pressing a different key should send a different keydownb event to the toolbar
61
- dispatchKeyEvent('keydown', 'KeyB', 'b');
62
-
63
- expect(keyPressMock).not.toHaveBeenLastCalledWith(keyAEvent);
64
- expect(keyPressMock).toHaveBeenCalledTimes(3);
65
-
66
- // Press yet another key (and multiple times) -- if this key is still down when the
67
- // editor is blured, a keyup event should only be fired once.
68
- dispatchKeyEvent('keydown', 'KeyF', 'f');
69
- expect(keyPressMock).toHaveBeenCalledTimes(4);
70
-
71
- dispatchKeyEvent('keydown', 'KeyF', 'f');
72
- expect(keyPressMock).toHaveBeenCalledTimes(5);
73
-
74
- dispatchKeyEvent('keyup', 'KeyA', 'a');
75
- expect(keyPressMock).toHaveBeenCalledTimes(5);
76
- expect(keyReleaseMock).toHaveBeenCalledTimes(1);
77
- expect(keyReleaseMock).toHaveBeenLastCalledWith({
78
- ...keyAEvent,
79
- kind: InputEvtType.KeyUpEvent
80
- });
81
-
82
- // Defocus the input --- this should fire a key up event for the keys still down
83
- inputArea.blur();
84
- inputArea.dispatchEvent(new Event('blur', { bubbles: true }));
85
-
86
- const focusable = document.createElement('button');
87
- document.body.appendChild(focusable);
88
- focusable.focus();
89
-
90
- expect(keyReleaseMock).toHaveBeenCalledTimes(3);
91
-
92
- // Events for both keys that were still down should have been fired:
93
- const secondToLastCall = keyReleaseMock.mock.calls[keyReleaseMock.mock.calls.length - 2];
94
- const lastCall = keyReleaseMock.mock.lastCall;
95
-
96
- expect(secondToLastCall).toMatchObject([{
97
- kind: InputEvtType.KeyUpEvent,
98
- key: 'b',
99
- code: 'KeyB',
100
- }]);
101
- expect(lastCall).toMatchObject([{
102
- kind: InputEvtType.KeyUpEvent,
103
- key: 'f',
104
- code: 'KeyF',
105
- }]);
106
- });
107
- });
@@ -1,294 +0,0 @@
1
- import { Color4, Mat33, Rect2, TextComponent, EditorImage, Vec2, StrokeComponent, SelectionTool, sendPenEvent, InputEvtType } from './lib';
2
- import TextRenderingStyle from './rendering/TextRenderingStyle';
3
- import SVGLoader from './SVGLoader';
4
- import createEditor from './testing/createEditor';
5
-
6
- describe('Editor.toSVG', () => {
7
- it('should correctly nest text objects', async () => {
8
- const editor = createEditor();
9
- const textStyle: TextRenderingStyle = {
10
- fontFamily: 'sans', size: 12, renderingStyle: { fill: Color4.black }
11
- };
12
- const text = new TextComponent([
13
- 'Testing...',
14
- new TextComponent([ 'Test 2' ], Mat33.translation(Vec2.of(0, 100)), textStyle),
15
- ], Mat33.identity, textStyle);
16
- editor.dispatch(EditorImage.addElement(text));
17
-
18
- const matches = editor.image.getElementsIntersectingRegion(new Rect2(4, -100, 100, 100));
19
- expect(matches).toHaveLength(1);
20
- expect(text).not.toBeNull();
21
-
22
- const asSVG = editor.toSVG();
23
- const allTSpans = [ ...asSVG.querySelectorAll('tspan') ];
24
- expect(allTSpans).toHaveLength(1);
25
- expect(allTSpans[0].getAttribute('x')).toBe('0');
26
- expect(allTSpans[0].getAttribute('y')).toBe('100');
27
- expect(allTSpans[0].style.transform).toBe('');
28
- });
29
-
30
- it('should preserve empty tspans', async () => {
31
- const editor = createEditor();
32
- await editor.loadFrom(SVGLoader.fromString(`
33
- <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
34
- <style id="js-draw-style-sheet">
35
- path {
36
- stroke-linecap:round;
37
- stroke-linejoin:round;
38
- }
39
- </style>
40
- <text style="transform: matrix(1, 0, 0, 1, 12, 35); font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Testing...<tspan x="3" y="40" style="font-family: sans-serif; font-size: 33px; fill: rgb(128, 51, 128);"></tspan><tspan x="3" y="70">Test 2. ☺</tspan></text>
41
- </svg>
42
- `, true));
43
-
44
- const textNodesInImage = editor.image.getAllElements().filter(elem => elem instanceof TextComponent);
45
- expect(
46
- textNodesInImage
47
- ).toHaveLength(1);
48
-
49
- const asSVG = editor.toSVG();
50
- const textObject = asSVG.querySelector('text');
51
-
52
- if (!textObject) {
53
- throw new Error('No text object found');
54
- }
55
-
56
- const childTextNodes = textObject.querySelectorAll('tspan');
57
- expect(childTextNodes).toHaveLength(2);
58
- });
59
-
60
- it('should preserve text child size/placement while not saving additional properties', async () => {
61
- const secondLineText = 'This is a test of a thing that has been known to break. Will this test catch the issue?';
62
- const thirdLineText = 'This is a test of saving/loading multi-line text...';
63
-
64
- const editor = createEditor();
65
- await editor.loadFrom(SVGLoader.fromString(`
66
- <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
67
- <style id="js-draw-style-sheet">
68
- path {
69
- stroke-linecap:round;
70
- stroke-linejoin:round;
71
- }
72
- </style>
73
- <text style="transform: matrix(1, 0, 0, 1, 12, 35); font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Testing...<tspan x="3" y="40" style="font-family: sans-serif; font-size: 33px; fill: rgb(128, 51, 128);">${secondLineText}</tspan><tspan x="0" y="72" style="font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">${thirdLineText}</tspan><tspan x="0" y="112" style="font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Will it pass or fail?</tspan></text>
74
- </svg>
75
- `, true));
76
-
77
- expect(
78
- editor.image.getAllElements().filter(elem => elem instanceof TextComponent)
79
- ).toHaveLength(1);
80
-
81
- const asSVG = editor.toSVG();
82
- const textObject = asSVG.querySelector('text');
83
-
84
- if (!textObject) {
85
- throw new Error('No text object found');
86
- }
87
-
88
- expect(textObject.style.transform.replace(/\s+/g, '')).toBe('matrix(1,0,0,1,12,35)');
89
- expect(textObject.style.fontFamily).toBe('sans-serif');
90
- expect(textObject.style.fontSize).toBe('32px');
91
-
92
- const childTextNodes = textObject.querySelectorAll('tspan');
93
- expect(childTextNodes).toHaveLength(3);
94
- const firstChild = childTextNodes[0];
95
-
96
- expect(firstChild.textContent).toBe(secondLineText);
97
- expect(firstChild.style.transform).toBe('');
98
- expect(firstChild.style.fontSize).toBe('33px');
99
- expect(firstChild.getAttribute('x')).toBe('3');
100
- expect(firstChild.getAttribute('y')).toBe('40');
101
-
102
- // Should not save a fontSize when not necessary (same fill as parent text node)
103
- const secondChild = childTextNodes[1];
104
- expect(secondChild.style.fontSize ?? '').toBe('');
105
-
106
- // Should not save additional "style" attributes when not necessary
107
- // TODO: Uncomment before some future major version release. Currently a "fill" is set for every
108
- // tspan to work around a loading bug.
109
- //expect(secondChild.outerHTML).toBe(`<tspan x="0" y="72">${thirdLineText}</tspan>`);
110
- });
111
-
112
- it('should preserve group elements', async () => {
113
- const editor = createEditor();
114
- await editor.loadFrom(SVGLoader.fromString(`
115
- <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
116
- <style id="js-draw-style-sheet">
117
- path {
118
- stroke-linecap:round;
119
- stroke-linejoin:round;
120
- }
121
- </style>
122
- <g id='main-group'>
123
- <g id='sub-group-1'>
124
- <path d='M0,0 L10,10 0,10' fill='#f00'/>
125
- <path d='M20,0 L10,10 0,10'/>
126
- </g>
127
- <g id='empty-group-2'></g>
128
- </g>
129
- <g id='empty-group-3'></g>
130
-
131
- <!-- Groups without IDs should also be preserved -->
132
- <g><g><g id='marker-1'></g></g></g>
133
- <g class='test'><g id='marker-2'/></g>
134
-
135
- <!-- Groups with duplicate IDs should preserved (though IDs)
136
- may be changed -->
137
- <g id='empty-group-2'/>
138
- <g id='empty-group-2'><g id='empty-group-2'/></g>
139
- </svg>
140
- `));
141
-
142
- // Both paths should exist.
143
- expect(
144
- editor.image
145
- .getElementsIntersectingRegion(new Rect2(-10, -10, 100, 100))
146
- .filter(elem => elem instanceof StrokeComponent)
147
- ).toHaveLength(2);
148
-
149
- const outputSVG = editor.toSVG();
150
-
151
- // Should still have the expected number of groups
152
- expect(outputSVG.querySelectorAll('g')).toHaveLength(12);
153
-
154
- // Should preserve the empty group.
155
- expect(outputSVG.querySelectorAll('g#empty-group-2')).toHaveLength(1);
156
-
157
- // The empty group should still have the correct parent
158
- expect(outputSVG.querySelectorAll('g#main-group > g#empty-group-2')).toHaveLength(1);
159
-
160
- // Paths should still be children of sub-group-1
161
- expect(outputSVG.querySelectorAll('g#sub-group-1 > path')).toHaveLength(2);
162
-
163
- // sub-group-1 should have the correct parent
164
- expect(outputSVG.querySelectorAll('g#main-group > g#sub-group-1')).toHaveLength(1);
165
-
166
- // And these should be the only paths.
167
- expect(outputSVG.querySelectorAll('path')).toHaveLength(2);
168
-
169
- // Should also preserve groups without IDs
170
- // Selector ref: https://stackoverflow.com/a/18607777
171
- expect(outputSVG.querySelectorAll('svg > g > g > g#marker-1')).toHaveLength(1);
172
- expect(outputSVG.querySelectorAll('svg > g > g#marker-2')).toHaveLength(1);
173
-
174
- // Should preserve class names on `g` objects:
175
- expect(outputSVG.querySelectorAll('svg > g.test > g#marker-2')).toHaveLength(1);
176
-
177
- // Should preserve groups that had duplicate IDs
178
- expect(outputSVG.querySelectorAll('svg > g#empty-group-2--1')).toHaveLength(1);
179
- expect(outputSVG.querySelectorAll('svg > g#empty-group-2--2')).toHaveLength(1);
180
- expect(outputSVG.querySelectorAll('svg > g#empty-group-2--2 > g#empty-group-2--3')).toHaveLength(1);
181
- });
182
-
183
- describe('should not preserve group elements when doing so would change the z order', () => {
184
- it('in an image with few items', async () => {
185
- const editor = createEditor();
186
- await editor.loadFrom(SVGLoader.fromString(`
187
- <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
188
- <g id='main-group-1'>
189
- <path d='M0,0 L-10,10 0,10' fill='#f00'/>
190
- <path d='M20,0 L10,10 0,10'/>
191
- </g>
192
- <path d='M40,40 l10,10 0,10'/>
193
- </svg>
194
- `));
195
-
196
- // All paths should exist.
197
- expect(
198
- editor.image
199
- .getElementsIntersectingRegion(new Rect2(-10, -10, 100, 100))
200
- .filter(elem => elem instanceof StrokeComponent)
201
- ).toHaveLength(3);
202
-
203
- // Before modifying, both paths should be within the main-group-1 group
204
- expect(editor.toSVG().querySelectorAll('svg > g#main-group-1 > path')).toHaveLength(2);
205
-
206
- const selectionTool = editor.toolController.getMatchingTools(SelectionTool)[0];
207
- selectionTool.setEnabled(true);
208
-
209
- // Select the bottommost stroke
210
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(-11, 9));
211
- sendPenEvent(editor, InputEvtType.PointerMoveEvt, Vec2.of(-9, 10));
212
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(-9, 10));
213
-
214
- // The stroke should be selected
215
- expect(selectionTool.getSelectedObjects()).toHaveLength(1);
216
- expect(selectionTool.getSelectedObjects()[0].getBBox())
217
- .objEq(new Rect2(-10, 0, 10, 10));
218
-
219
- // Drag the selection (moves the selected item to the top)
220
- sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(-11, 9));
221
- sendPenEvent(editor, InputEvtType.PointerMoveEvt, Vec2.of(0, 0));
222
- sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(0, 0));
223
-
224
- expect(selectionTool.getSelectedObjects()[0].getBBox())
225
- .not.objEq(new Rect2(-10, 0, 10, 10));
226
- selectionTool.setEnabled(false);
227
-
228
- // One of the items should have been moved out of the main group
229
- const outputSVG = editor.toSVG();
230
- expect(outputSVG.querySelectorAll('svg > path')).toHaveLength(2);
231
- expect(outputSVG.querySelectorAll('svg > g#main-group-1 > path')).toHaveLength(1);
232
- });
233
-
234
- it('in an image with many items in nested groups', async () => {
235
- const editor = createEditor();
236
- await editor.loadFrom(SVGLoader.fromString(`
237
- <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
238
- <path d='M-100,-100 l 2,2 0,2'/>
239
- <g id='group-1'>
240
- <path d='M0,0 L-10,10 0,10' fill='#f00'/>
241
- <path d='M20,0 L10,10 0,10'/>
242
-
243
- <g id='group-2'>
244
- <path d='M100,100 l 2,2 0,2'/>
245
- </g>
246
- </g>
247
- <path d='M40,40 l10,10 0,10'/>
248
- </svg>
249
- `));
250
-
251
- // .expects that all paths have their original parent groups.
252
- const expectGroupParentsToBeOriginal = () => {
253
- expect(
254
- editor.image
255
- .getAllElements()
256
- .filter(elem => elem instanceof StrokeComponent)
257
- ).toHaveLength(5);
258
-
259
- const output = editor.toSVG();
260
- expect(
261
- output.querySelectorAll('svg > g#group-1 path')
262
- ).toHaveLength(3);
263
- expect(
264
- output.querySelectorAll('svg > g#group-1 > g > path')
265
- ).toHaveLength(1);
266
- };
267
-
268
- expectGroupParentsToBeOriginal();
269
-
270
- const nudgePathNear = async (pos: Vec2) => {
271
- const targetElems = editor.image.getElementsIntersectingRegion(Rect2.bboxOf([ pos ], 5));
272
-
273
- expect(targetElems).toHaveLength(1);
274
-
275
- // Move the path to the top
276
- const target = targetElems[0];
277
- await editor.dispatch(target.transformBy(Mat33.scaling2D(1.01)));
278
- };
279
-
280
- // Moving a path that's below all groups should not change group parentings.
281
- nudgePathNear(Vec2.of(-100, -100));
282
- expectGroupParentsToBeOriginal();
283
-
284
- // Moving a path that's within nested groups should move the path out of that group.
285
- nudgePathNear(Vec2.of(100, 100));
286
-
287
- const outputSVG = editor.toSVG();
288
- expect(outputSVG.querySelectorAll('svg > path')).toHaveLength(3);
289
- expect(outputSVG.querySelectorAll('svg > g#group-1 > path')).toHaveLength(2);
290
- expect(outputSVG.querySelectorAll('svg > g#group-1 > g')).toHaveLength(1);
291
- expect(outputSVG.querySelectorAll('svg > g#group-1 > g > *')).toHaveLength(0);
292
- });
293
- });
294
- });