js-draw 0.1.11 → 0.1.12

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 (167) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/Editor.d.ts +4 -2
  4. package/dist/src/Editor.js +30 -10
  5. package/dist/src/EditorImage.d.ts +1 -1
  6. package/dist/src/EditorImage.js +2 -2
  7. package/dist/src/Pointer.d.ts +1 -1
  8. package/dist/src/Pointer.js +1 -1
  9. package/dist/src/SVGLoader.d.ts +1 -1
  10. package/dist/src/SVGLoader.js +14 -6
  11. package/dist/src/Viewport.d.ts +8 -25
  12. package/dist/src/Viewport.js +15 -10
  13. package/dist/src/commands/Command.d.ts +2 -2
  14. package/dist/src/commands/Command.js +4 -4
  15. package/dist/src/commands/Duplicate.d.ts +1 -1
  16. package/dist/src/commands/Duplicate.js +1 -1
  17. package/dist/src/commands/Erase.d.ts +1 -1
  18. package/dist/src/commands/Erase.js +1 -1
  19. package/dist/src/commands/localization.d.ts +1 -1
  20. package/dist/src/components/AbstractComponent.d.ts +3 -3
  21. package/dist/src/components/AbstractComponent.js +2 -2
  22. package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
  23. package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
  24. package/dist/src/components/Stroke.d.ts +4 -4
  25. package/dist/src/components/Stroke.js +2 -2
  26. package/dist/src/components/Text.d.ts +3 -3
  27. package/dist/src/components/Text.js +3 -3
  28. package/dist/src/components/UnknownSVGObject.d.ts +3 -3
  29. package/dist/src/components/UnknownSVGObject.js +1 -1
  30. package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
  31. package/dist/src/components/builders/ArrowBuilder.js +1 -1
  32. package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
  33. package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
  34. package/dist/src/components/builders/LineBuilder.d.ts +1 -1
  35. package/dist/src/components/builders/LineBuilder.js +1 -1
  36. package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
  37. package/dist/src/components/builders/RectangleBuilder.js +3 -3
  38. package/dist/src/components/builders/types.d.ts +1 -1
  39. package/dist/src/localization.d.ts +1 -0
  40. package/dist/src/localization.js +5 -1
  41. package/dist/src/localizations/es.js +1 -1
  42. package/dist/src/{geometry → math}/LineSegment2.d.ts +0 -0
  43. package/dist/src/{geometry → math}/LineSegment2.js +0 -0
  44. package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
  45. package/dist/src/{geometry → math}/Mat33.js +0 -0
  46. package/dist/src/{geometry → math}/Path.d.ts +2 -1
  47. package/dist/src/{geometry → math}/Path.js +58 -51
  48. package/dist/src/{geometry → math}/Rect2.d.ts +0 -0
  49. package/dist/src/{geometry → math}/Rect2.js +0 -0
  50. package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
  51. package/dist/src/{geometry → math}/Vec2.js +0 -0
  52. package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
  53. package/dist/src/{geometry → math}/Vec3.js +1 -1
  54. package/dist/src/math/rounding.d.ts +3 -0
  55. package/dist/src/math/rounding.js +120 -0
  56. package/dist/src/rendering/Display.d.ts +3 -1
  57. package/dist/src/rendering/Display.js +16 -10
  58. package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
  59. package/dist/src/rendering/caching/CacheRecord.js +1 -1
  60. package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
  61. package/dist/src/rendering/caching/RenderingCache.js +1 -1
  62. package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
  63. package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
  64. package/dist/src/rendering/caching/testUtils.js +1 -1
  65. package/dist/src/rendering/caching/types.d.ts +1 -1
  66. package/dist/src/rendering/localization.d.ts +2 -0
  67. package/dist/src/rendering/localization.js +2 -0
  68. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
  69. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
  70. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
  71. package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
  72. package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
  73. package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
  74. package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
  75. package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
  76. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
  77. package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
  78. package/dist/src/toolbar/icons.d.ts +3 -0
  79. package/dist/src/toolbar/icons.js +142 -132
  80. package/dist/src/toolbar/localization.d.ts +2 -1
  81. package/dist/src/toolbar/localization.js +2 -1
  82. package/dist/src/toolbar/makeColorInput.js +2 -1
  83. package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
  84. package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
  85. package/dist/src/toolbar/widgets/BaseWidget.js +2 -0
  86. package/dist/src/toolbar/widgets/HandToolWidget.js +3 -3
  87. package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
  88. package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
  89. package/dist/src/tools/Eraser.js +1 -1
  90. package/dist/src/tools/PanZoom.d.ts +1 -1
  91. package/dist/src/tools/PanZoom.js +24 -14
  92. package/dist/src/tools/SelectionTool.d.ts +3 -3
  93. package/dist/src/tools/SelectionTool.js +6 -6
  94. package/dist/src/tools/TextTool.js +1 -1
  95. package/dist/src/types.d.ts +4 -4
  96. package/package.json +1 -1
  97. package/src/Editor.ts +34 -12
  98. package/src/EditorImage.test.ts +2 -4
  99. package/src/EditorImage.ts +2 -2
  100. package/src/Pointer.ts +1 -1
  101. package/src/SVGLoader.ts +14 -6
  102. package/src/Viewport.ts +19 -17
  103. package/src/commands/Command.ts +5 -5
  104. package/src/commands/Duplicate.ts +1 -1
  105. package/src/commands/Erase.ts +1 -1
  106. package/src/commands/localization.ts +1 -1
  107. package/src/components/AbstractComponent.ts +4 -4
  108. package/src/components/SVGGlobalAttributesObject.ts +3 -3
  109. package/src/components/Stroke.test.ts +3 -5
  110. package/src/components/Stroke.ts +4 -4
  111. package/src/components/Text.test.ts +2 -2
  112. package/src/components/Text.ts +3 -3
  113. package/src/components/UnknownSVGObject.ts +3 -3
  114. package/src/components/builders/ArrowBuilder.ts +2 -2
  115. package/src/components/builders/FreehandLineBuilder.ts +190 -80
  116. package/src/components/builders/LineBuilder.ts +2 -2
  117. package/src/components/builders/RectangleBuilder.ts +3 -3
  118. package/src/components/builders/types.ts +1 -1
  119. package/src/localization.ts +6 -0
  120. package/src/localizations/es.ts +2 -1
  121. package/src/{geometry → math}/LineSegment2.test.ts +0 -0
  122. package/src/{geometry → math}/LineSegment2.ts +0 -0
  123. package/src/{geometry → math}/Mat33.test.ts +0 -0
  124. package/src/{geometry → math}/Mat33.ts +0 -0
  125. package/src/{geometry → math}/Path.fromString.test.ts +0 -0
  126. package/src/{geometry → math}/Path.test.ts +0 -0
  127. package/src/{geometry → math}/Path.toString.test.ts +11 -2
  128. package/src/{geometry → math}/Path.ts +60 -57
  129. package/src/{geometry → math}/Rect2.test.ts +0 -0
  130. package/src/{geometry → math}/Rect2.ts +0 -0
  131. package/src/{geometry → math}/Vec2.test.ts +0 -0
  132. package/src/{geometry → math}/Vec2.ts +0 -0
  133. package/src/{geometry → math}/Vec3.test.ts +0 -0
  134. package/src/{geometry → math}/Vec3.ts +2 -2
  135. package/src/math/rounding.test.ts +40 -0
  136. package/src/math/rounding.ts +145 -0
  137. package/src/rendering/Display.ts +18 -10
  138. package/src/rendering/caching/CacheRecord.test.ts +2 -2
  139. package/src/rendering/caching/CacheRecord.ts +2 -2
  140. package/src/rendering/caching/CacheRecordManager.ts +1 -1
  141. package/src/rendering/caching/RenderingCache.test.ts +3 -3
  142. package/src/rendering/caching/RenderingCache.ts +1 -1
  143. package/src/rendering/caching/RenderingCacheNode.ts +23 -7
  144. package/src/rendering/caching/testUtils.ts +1 -1
  145. package/src/rendering/caching/types.ts +1 -1
  146. package/src/rendering/localization.ts +4 -0
  147. package/src/rendering/renderers/AbstractRenderer.ts +4 -4
  148. package/src/rendering/renderers/CanvasRenderer.ts +4 -4
  149. package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
  150. package/src/rendering/renderers/DummyRenderer.ts +4 -4
  151. package/src/rendering/renderers/SVGRenderer.ts +10 -4
  152. package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
  153. package/src/toolbar/icons.ts +157 -137
  154. package/src/toolbar/localization.ts +4 -2
  155. package/src/toolbar/makeColorInput.ts +2 -1
  156. package/src/toolbar/toolbar.css +1 -1
  157. package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
  158. package/src/toolbar/widgets/BaseWidget.ts +2 -0
  159. package/src/toolbar/widgets/HandToolWidget.ts +3 -3
  160. package/src/toolbar/widgets/SelectionWidget.ts +46 -41
  161. package/src/tools/Eraser.ts +2 -2
  162. package/src/tools/PanZoom.ts +28 -16
  163. package/src/tools/SelectionTool.test.ts +2 -4
  164. package/src/tools/SelectionTool.ts +6 -6
  165. package/src/tools/TextTool.ts +2 -2
  166. package/src/tools/UndoRedoShortcut.test.ts +1 -1
  167. package/src/types.ts +4 -4
@@ -115,9 +115,9 @@ export default class Vec3 {
115
115
  }
116
116
 
117
117
  // Returns a vector with each component acted on by [fn]
118
- public map(fn: (component: number)=> number): Vec3 {
118
+ public map(fn: (component: number, index: number)=> number): Vec3 {
119
119
  return Vec3.of(
120
- fn(this.x), fn(this.y), fn(this.z)
120
+ fn(this.x, 0), fn(this.y, 1), fn(this.z, 2)
121
121
  );
122
122
  }
123
123
 
@@ -0,0 +1,40 @@
1
+ import { toRoundedString, toStringOfSamePrecision } from './rounding';
2
+
3
+ describe('toRoundedString', () => {
4
+ it('should round up numbers endings similar to .999999999999999', () => {
5
+ expect(toRoundedString(0.999999999)).toBe('1');
6
+ expect(toRoundedString(0.899999999)).toBe('.9');
7
+ expect(toRoundedString(9.999999999)).toBe('10');
8
+ expect(toRoundedString(-10.999999999)).toBe('-11');
9
+ });
10
+
11
+ it('should round up numbers similar to 10.999999998', () => {
12
+ expect(toRoundedString(10.999999998)).toBe('11');
13
+ });
14
+
15
+ // Handling this creates situations with potential error:
16
+ //it('should round strings with multiple digits after the ending decimal points', () => {
17
+ // expect(toRoundedString(292.2 - 292.8)).toBe('-0.6');
18
+ //});
19
+
20
+ it('should round down strings ending endings similar to .00000001', () => {
21
+ expect(toRoundedString(10.00000001)).toBe('10');
22
+ });
23
+ });
24
+
25
+ it('toStringOfSamePrecision', () => {
26
+ expect(toStringOfSamePrecision(1.23456, '1.12')).toBe('1.23');
27
+ expect(toStringOfSamePrecision(1.23456, '1.1')).toBe('1.2');
28
+ expect(toStringOfSamePrecision(1.23456, '1.1', '5.32')).toBe('1.23');
29
+ expect(toStringOfSamePrecision(-1.23456, '1.1', '5.32')).toBe('-1.23');
30
+ expect(toStringOfSamePrecision(-1.99999, '1.1', '5.32')).toBe('-2');
31
+ expect(toStringOfSamePrecision(1.99999, '1.1', '5.32')).toBe('2');
32
+ expect(toStringOfSamePrecision(1.89999, '1.1', '5.32')).toBe('1.9');
33
+ expect(toStringOfSamePrecision(9.99999999, '-1.1234')).toBe('10');
34
+ expect(toStringOfSamePrecision(9.999999998999996, '100')).toBe('10');
35
+ expect(toStringOfSamePrecision(0.000012345, '0.000012')).toBe('.000012');
36
+ expect(toStringOfSamePrecision(0.000012645, '.000012')).toBe('.000013');
37
+ expect(toStringOfSamePrecision(-0.09999999999999432, '291.3')).toBe('-.1');
38
+ expect(toStringOfSamePrecision(-0.9999999999999432, '291.3')).toBe('-1');
39
+ expect(toStringOfSamePrecision(9998.9, '.1', '-11')).toBe('9998.9');
40
+ });
@@ -0,0 +1,145 @@
1
+ // Clean up stringified numbers
2
+ const cleanUpNumber = (text: string) => {
3
+ // Regular expression substitions can be somewhat expensive. Only do them
4
+ // if necessary.
5
+ const lastChar = text.charAt(text.length - 1);
6
+ if (lastChar === '0' || lastChar === '.') {
7
+ // Remove trailing zeroes
8
+ text = text.replace(/([.]\d*[^0]+)0+$/, '$1');
9
+ text = text.replace(/[.]0+$/, '.');
10
+
11
+ // Remove trailing period
12
+ text = text.replace(/[.]$/, '');
13
+
14
+ if (text === '-0') {
15
+ return '0';
16
+ }
17
+ }
18
+
19
+ const firstChar = text.charAt(0);
20
+ if (firstChar === '0' || firstChar === '-') {
21
+ // Remove unnecessary leading zeroes.
22
+ text = text.replace(/^(0+)[.]/, '.');
23
+ text = text.replace(/^-(0+)[.]/, '-.');
24
+ }
25
+
26
+ return text;
27
+ };
28
+
29
+ export const toRoundedString = (num: number): string => {
30
+ // Try to remove rounding errors. If the number ends in at least three/four zeroes
31
+ // (or nines) just one or two digits, it's probably a rounding error.
32
+ const fixRoundingUpExp = /^([-]?\d*\.\d{3,})0{4,}\d{1,2}$/;
33
+ const hasRoundingDownExp = /^([-]?)(\d*)\.(\d{3,}9{4,})\d{1,2}$/;
34
+
35
+ let text = num.toString(10);
36
+ if (text.indexOf('.') === -1) {
37
+ return text;
38
+ }
39
+
40
+ const roundingDownMatch = hasRoundingDownExp.exec(text);
41
+ if (roundingDownMatch) {
42
+ const negativeSign = roundingDownMatch[1];
43
+ const postDecimalString = roundingDownMatch[3];
44
+ const lastDigit = parseInt(postDecimalString.charAt(postDecimalString.length - 1), 10);
45
+ const postDecimal = parseInt(postDecimalString, 10);
46
+ const preDecimal = parseInt(roundingDownMatch[2], 10);
47
+
48
+ const origPostDecimalString = roundingDownMatch[3];
49
+
50
+ let newPostDecimal = (postDecimal + 10 - lastDigit).toString();
51
+ let carry = 0;
52
+ if (newPostDecimal.length > postDecimal.toString().length) {
53
+ // Left-shift
54
+ newPostDecimal = newPostDecimal.substring(1);
55
+ carry = 1;
56
+ }
57
+
58
+ // parseInt(...).toString() removes leading zeroes. Add them back.
59
+ while (newPostDecimal.length < origPostDecimalString.length) {
60
+ newPostDecimal = carry.toString(10) + newPostDecimal;
61
+ carry = 0;
62
+ }
63
+
64
+ text = `${negativeSign + (preDecimal + carry).toString()}.${newPostDecimal}`;
65
+ }
66
+
67
+ text = text.replace(fixRoundingUpExp, '$1');
68
+
69
+ return cleanUpNumber(text);
70
+ };
71
+
72
+ const numberExp = /^([-]?)(\d*)[.](\d+)$/;
73
+ export const getLenAfterDecimal = (numberAsString: string) => {
74
+ const numberMatch = numberExp.exec(numberAsString);
75
+ if (!numberMatch) {
76
+ // If not a match, either the number is exponential notation (or is something
77
+ // like NaN or Infinity)
78
+ if (numberAsString.search(/[eE]/) !== -1 || /^[a-zA-Z]+$/.exec(numberAsString)) {
79
+ return -1;
80
+ // Or it has no decimal point
81
+ } else {
82
+ return 0;
83
+ }
84
+ }
85
+
86
+ const afterDecimalLen = numberMatch[3].length;
87
+ return afterDecimalLen;
88
+ };
89
+
90
+ // [reference] should be a string representation of a base-10 number (no exponential (e.g. 10e10))
91
+ export const toStringOfSamePrecision = (num: number, ...references: string[]): string => {
92
+ const text = num.toString(10);
93
+ const textMatch = numberExp.exec(text);
94
+ if (!textMatch) {
95
+ return text;
96
+ }
97
+
98
+ let decimalPlaces = -1;
99
+ for (const reference of references) {
100
+ decimalPlaces = Math.max(getLenAfterDecimal(reference), decimalPlaces);
101
+ }
102
+
103
+ if (decimalPlaces === -1) {
104
+ return toRoundedString(num);
105
+ }
106
+
107
+ // Make text's after decimal length match [afterDecimalLen].
108
+ let postDecimal = textMatch[3].substring(0, decimalPlaces);
109
+ let preDecimal = textMatch[2];
110
+ const nextDigit = textMatch[3].charAt(decimalPlaces);
111
+
112
+ if (nextDigit !== '') {
113
+ const asNumber = parseInt(nextDigit, 10);
114
+ if (asNumber >= 5) {
115
+ // Don't attempt to parseInt() an empty string.
116
+ if (postDecimal.length > 0) {
117
+ const leadingZeroMatch = /^(0+)(\d*)$/.exec(postDecimal);
118
+
119
+ let leadingZeroes = '';
120
+ let postLeading = postDecimal;
121
+ if (leadingZeroMatch) {
122
+ leadingZeroes = leadingZeroMatch[1];
123
+ postLeading = leadingZeroMatch[2];
124
+ }
125
+
126
+ postDecimal = (parseInt(postDecimal) + 1).toString();
127
+
128
+ // If postDecimal got longer, remove leading zeroes if possible
129
+ if (postDecimal.length > postLeading.length && leadingZeroes.length > 0) {
130
+ leadingZeroes = leadingZeroes.substring(1);
131
+ }
132
+
133
+ postDecimal = leadingZeroes + postDecimal;
134
+ }
135
+
136
+ if (postDecimal.length === 0 || postDecimal.length > decimalPlaces) {
137
+ preDecimal = (parseInt(preDecimal) + 1).toString();
138
+ postDecimal = postDecimal.substring(1);
139
+ }
140
+ }
141
+ }
142
+
143
+ const negativeSign = textMatch[1];
144
+ return cleanUpNumber(`${negativeSign}${preDecimal}.${postDecimal}`);
145
+ };
@@ -3,7 +3,7 @@ import CanvasRenderer from './renderers/CanvasRenderer';
3
3
  import { Editor } from '../Editor';
4
4
  import { EditorEventType } from '../types';
5
5
  import DummyRenderer from './renderers/DummyRenderer';
6
- import { Point2, Vec2 } from '../geometry/Vec2';
6
+ import { Point2, Vec2 } from '../math/Vec2';
7
7
  import RenderingCache from './caching/RenderingCache';
8
8
  import TextOnlyRenderer from './renderers/TextOnlyRenderer';
9
9
  import Color4 from '../Color4';
@@ -18,6 +18,7 @@ export default class Display {
18
18
  private dryInkRenderer: AbstractRenderer;
19
19
  private wetInkRenderer: AbstractRenderer;
20
20
  private textRenderer: TextOnlyRenderer;
21
+ private textRerenderOutput: HTMLElement|null = null;
21
22
  private cache: RenderingCache;
22
23
  private resizeSurfacesCallback?: ()=> void;
23
24
  private flattenCallback?: ()=> void;
@@ -59,10 +60,10 @@ export default class Display {
59
60
  return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
60
61
  },
61
62
  blockResolution: cacheBlockResolution,
62
- cacheSize: 500 * 500 * 4 * 200,
63
+ cacheSize: 500 * 500 * 4 * 220,
63
64
  maxScale: 1.5,
64
- minComponentsPerCache: 50,
65
- minComponentsToUseCache: 120,
65
+ minComponentsPerCache: 45,
66
+ minComponentsToUseCache: 105,
66
67
  });
67
68
 
68
69
  this.editor.notifier.on(EditorEventType.DisplayResized, event => {
@@ -158,19 +159,26 @@ export default class Display {
158
159
  rerenderButton.classList.add('rerenderButton');
159
160
  rerenderButton.innerText = this.editor.localization.rerenderAsText;
160
161
 
161
- const rerenderOutput = document.createElement('div');
162
- rerenderOutput.ariaLive = 'polite';
162
+ this.textRerenderOutput = document.createElement('div');
163
+ this.textRerenderOutput.setAttribute('aria-live', 'polite');
163
164
 
164
165
  rerenderButton.onclick = () => {
165
- this.textRenderer.clear();
166
- this.editor.image.render(this.textRenderer, this.editor.viewport);
167
- rerenderOutput.innerText = this.textRenderer.getDescription();
166
+ this.rerenderAsText();
168
167
  };
169
168
 
170
- textRendererOutputContainer.replaceChildren(rerenderButton, rerenderOutput);
169
+ textRendererOutputContainer.replaceChildren(rerenderButton, this.textRerenderOutput);
171
170
  this.editor.createHTMLOverlay(textRendererOutputContainer);
172
171
  }
173
172
 
173
+ public rerenderAsText() {
174
+ this.textRenderer.clear();
175
+ this.editor.image.render(this.textRenderer, this.editor.viewport);
176
+
177
+ if (this.textRerenderOutput) {
178
+ this.textRerenderOutput.innerText = this.textRenderer.getDescription();
179
+ }
180
+ }
181
+
174
182
  // Clears the drawing surfaces and otherwise prepares for a rerender.
175
183
  public startRerender(): AbstractRenderer {
176
184
  this.resizeSurfacesCallback?.();
@@ -1,7 +1,7 @@
1
1
  /* @jest-environment jsdom */
2
2
 
3
- import Rect2 from '../../geometry/Rect2';
4
- import { Vec2 } from '../../geometry/Vec2';
3
+ import Rect2 from '../../math/Rect2';
4
+ import { Vec2 } from '../../math/Vec2';
5
5
  import CacheRecord from './CacheRecord';
6
6
  import { createCache } from './testUtils';
7
7
 
@@ -1,5 +1,5 @@
1
- import Mat33 from '../../geometry/Mat33';
2
- import Rect2 from '../../geometry/Rect2';
1
+ import Mat33 from '../../math/Mat33';
2
+ import Rect2 from '../../math/Rect2';
3
3
  import AbstractRenderer from '../renderers/AbstractRenderer';
4
4
  import { BeforeDeallocCallback, CacheState } from './types';
5
5
 
@@ -1,6 +1,6 @@
1
1
  import { BeforeDeallocCallback, PartialCacheState } from './types';
2
2
  import CacheRecord from './CacheRecord';
3
- import Rect2 from '../../geometry/Rect2';
3
+ import Rect2 from '../../math/Rect2';
4
4
 
5
5
 
6
6
  export class CacheRecordManager {
@@ -3,11 +3,11 @@
3
3
  import DummyRenderer from '../renderers/DummyRenderer';
4
4
  import { createCache } from './testUtils';
5
5
  import Stroke from '../../components/Stroke';
6
- import Path from '../../geometry/Path';
6
+ import Path from '../../math/Path';
7
7
  import Color4 from '../../Color4';
8
8
  import EditorImage from '../../EditorImage';
9
9
  import Viewport from '../../Viewport';
10
- import Mat33 from '../../geometry/Mat33';
10
+ import Mat33 from '../../math/Mat33';
11
11
 
12
12
  describe('RenderingCache', () => {
13
13
  const testPath = Path.fromString('M0,0 l100,500 l-20,20 L-100,-100');
@@ -35,7 +35,7 @@ describe('RenderingCache', () => {
35
35
  expect(lastRenderer).not.toBeNull();
36
36
  expect(lastRenderer!.renderedPathCount).toBe(1);
37
37
 
38
- editor.dispatch(new Viewport.ViewportTransform(Mat33.scaling2D(0.1)));
38
+ editor.dispatch(Viewport.transformBy(Mat33.scaling2D(0.1)));
39
39
  editor.image.renderWithCache(screenRenderer, cache, editor.viewport);
40
40
  expect(allocdRenderers).toBe(1);
41
41
  expect(lastRenderer!.renderedPathCount).toBe(1);
@@ -1,5 +1,5 @@
1
1
  import { ImageNode } from '../../EditorImage';
2
- import Rect2 from '../../geometry/Rect2';
2
+ import Rect2 from '../../math/Rect2';
3
3
  import Viewport from '../../Viewport';
4
4
  import AbstractRenderer from '../renderers/AbstractRenderer';
5
5
  import RenderingCacheNode from './RenderingCacheNode';
@@ -3,7 +3,7 @@
3
3
 
4
4
  import Color4 from '../../Color4';
5
5
  import { ImageNode, sortLeavesByZIndex } from '../../EditorImage';
6
- import Rect2 from '../../geometry/Rect2';
6
+ import Rect2 from '../../math/Rect2';
7
7
  import Viewport from '../../Viewport';
8
8
  import AbstractRenderer from '../renderers/AbstractRenderer';
9
9
  import CacheRecord from './CacheRecord';
@@ -151,12 +151,15 @@ export default class RenderingCacheNode {
151
151
  return result;
152
152
  }
153
153
 
154
- private renderingIsUpToDate(sortedIds: number[]) {
155
- if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
154
+ // Returns true iff all elems of this.renderedIds are in sortedIds.
155
+ // sortedIds should be sorted by z-index (or some other order, so long as they are
156
+ // sorted by the same thing as this.renderedIds.)
157
+ private allRenderedIdsIn(sortedIds: number[]) {
158
+ if (this.renderedIds.length > sortedIds.length) {
156
159
  return false;
157
160
  }
158
161
 
159
- for (let i = 0; i < sortedIds.length; i++) {
162
+ for (let i = 0; i < this.renderedIds.length; i++) {
160
163
  if (sortedIds[i] !== this.renderedIds[i]) {
161
164
  return false;
162
165
  }
@@ -165,6 +168,14 @@ export default class RenderingCacheNode {
165
168
  return true;
166
169
  }
167
170
 
171
+ private renderingIsUpToDate(sortedIds: number[]) {
172
+ if (this.cachedRenderer === null || sortedIds.length !== this.renderedIds.length) {
173
+ return false;
174
+ }
175
+
176
+ return this.allRenderedIdsIn(sortedIds);
177
+ }
178
+
168
179
  // Render all [items] within [viewport]
169
180
  public renderItems(screenRenderer: AbstractRenderer, items: ImageNode[], viewport: Viewport) {
170
181
  if (
@@ -238,8 +249,7 @@ export default class RenderingCacheNode {
238
249
  }
239
250
 
240
251
  // Is it worth it to render the items?
241
- // TODO: Replace this with something performace based.
242
- // TODO: Determine whether it is 'worth it' to cache this depending on rendering time.
252
+ // TODO: Consider replacing this with something performace based.
243
253
  if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {
244
254
  let fullRerenderNeeded = true;
245
255
  if (!this.cachedRenderer) {
@@ -247,7 +257,7 @@ export default class RenderingCacheNode {
247
257
  this.region,
248
258
  () => this.onRegionDealloc()
249
259
  );
250
- } else if (leavesByIds.length > this.renderedIds.length && this.renderedMaxZIndex !== null) {
260
+ } else if (leavesByIds.length > this.renderedIds.length && this.allRenderedIdsIn(leafIds) && this.renderedMaxZIndex !== null) {
251
261
  // We often don't need to do a full re-render even if something's changed.
252
262
  // Check whether we can just draw on top of the existing cache.
253
263
  const newLeaves = [];
@@ -287,6 +297,12 @@ export default class RenderingCacheNode {
287
297
  screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
288
298
  }
289
299
  }
300
+ } else if (debugMode) {
301
+ console.log('Decided on a full re-render. Reason: At least one of the following is false:',
302
+ '\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length,
303
+ '\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds),
304
+ '\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null,
305
+ '\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
290
306
  }
291
307
 
292
308
  if (fullRerenderNeeded) {
@@ -1,4 +1,4 @@
1
- import { Vec2 } from '../../geometry/Vec2';
1
+ import { Vec2 } from '../../math/Vec2';
2
2
  import DummyRenderer from '../renderers/DummyRenderer';
3
3
  import createEditor from '../../testing/createEditor';
4
4
  import AbstractRenderer from '../renderers/AbstractRenderer';
@@ -1,4 +1,4 @@
1
- import { Vec2 } from '../../geometry/Vec2';
1
+ import { Vec2 } from '../../math/Vec2';
2
2
  import AbstractRenderer from '../renderers/AbstractRenderer';
3
3
  import { CacheRecordManager } from './CacheRecordManager';
4
4
 
@@ -1,10 +1,14 @@
1
1
 
2
2
  export interface TextRendererLocalization {
3
+ pathNodeCount(pathCount: number): string;
4
+ textNodeCount(nodeCount: number): string;
3
5
  textNode(content: string): string;
4
6
  rerenderAsText: string;
5
7
  }
6
8
 
7
9
  export const defaultTextRendererLocalization: TextRendererLocalization = {
10
+ pathNodeCount: (count: number) => `There are ${count} visible path objects.`,
11
+ textNodeCount: (count: number) => `There are ${count} visible text nodes.`,
8
12
  textNode: (content: string) => `Text: ${content}`,
9
13
  rerenderAsText: 'Re-render as text',
10
14
  };
@@ -1,9 +1,9 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
2
  import { TextStyle } from '../../components/Text';
3
- import Mat33 from '../../geometry/Mat33';
4
- import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
5
- import Rect2 from '../../geometry/Rect2';
6
- import { Point2, Vec2 } from '../../geometry/Vec2';
3
+ import Mat33 from '../../math/Mat33';
4
+ import Path, { PathCommand, PathCommandType } from '../../math/Path';
5
+ import Rect2 from '../../math/Rect2';
6
+ import { Point2, Vec2 } from '../../math/Vec2';
7
7
  import Viewport from '../../Viewport';
8
8
  import RenderingStyle, { stylesEqual } from '../RenderingStyle';
9
9
 
@@ -1,9 +1,9 @@
1
1
  import Color4 from '../../Color4';
2
2
  import Text, { TextStyle } from '../../components/Text';
3
- import Mat33 from '../../geometry/Mat33';
4
- import Rect2 from '../../geometry/Rect2';
5
- import { Point2, Vec2 } from '../../geometry/Vec2';
6
- import Vec3 from '../../geometry/Vec3';
3
+ import Mat33 from '../../math/Mat33';
4
+ import Rect2 from '../../math/Rect2';
5
+ import { Point2, Vec2 } from '../../math/Vec2';
6
+ import Vec3 from '../../math/Vec3';
7
7
  import Viewport from '../../Viewport';
8
8
  import RenderingStyle from '../RenderingStyle';
9
9
  import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
@@ -1,7 +1,7 @@
1
1
 
2
2
  import EventDispatcher from '../../EventDispatcher';
3
- import Mat33 from '../../geometry/Mat33';
4
- import { Vec2 } from '../../geometry/Vec2';
3
+ import Mat33 from '../../math/Mat33';
4
+ import { Vec2 } from '../../math/Vec2';
5
5
  import Viewport from '../../Viewport';
6
6
  import DummyRenderer from './DummyRenderer';
7
7
 
@@ -1,10 +1,10 @@
1
1
  // Renderer that outputs nothing. Useful for automated tests.
2
2
 
3
3
  import { TextStyle } from '../../components/Text';
4
- import Mat33 from '../../geometry/Mat33';
5
- import Rect2 from '../../geometry/Rect2';
6
- import { Point2, Vec2 } from '../../geometry/Vec2';
7
- import Vec3 from '../../geometry/Vec3';
4
+ import Mat33 from '../../math/Mat33';
5
+ import Rect2 from '../../math/Rect2';
6
+ import { Point2, Vec2 } from '../../math/Vec2';
7
+ import Vec3 from '../../math/Vec3';
8
8
  import Viewport from '../../Viewport';
9
9
  import RenderingStyle from '../RenderingStyle';
10
10
  import AbstractRenderer from './AbstractRenderer';
@@ -1,10 +1,11 @@
1
1
 
2
2
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
3
3
  import { TextStyle } from '../../components/Text';
4
- import Mat33 from '../../geometry/Mat33';
5
- import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
6
- import Rect2 from '../../geometry/Rect2';
7
- import { Point2, Vec2 } from '../../geometry/Vec2';
4
+ import Mat33 from '../../math/Mat33';
5
+ import Path, { PathCommand, PathCommandType } from '../../math/Path';
6
+ import Rect2 from '../../math/Rect2';
7
+ import { toRoundedString } from '../../math/rounding';
8
+ import { Point2, Vec2 } from '../../math/Vec2';
8
9
  import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
9
10
  import Viewport from '../../Viewport';
10
11
  import RenderingStyle from '../RenderingStyle';
@@ -113,6 +114,9 @@ export default class SVGRenderer extends AbstractRenderer {
113
114
  public drawText(text: string, transform: Mat33, style: TextStyle): void {
114
115
  transform = this.getCanvasToScreenTransform().rightMul(transform);
115
116
 
117
+ const translation = transform.transformVec2(Vec2.zero);
118
+ transform = transform.rightMul(Mat33.translation(translation.times(-1)));
119
+
116
120
  const textElem = document.createElementNS(svgNameSpace, 'text');
117
121
  textElem.appendChild(document.createTextNode(text));
118
122
  textElem.style.transform = `matrix(
@@ -125,6 +129,8 @@ export default class SVGRenderer extends AbstractRenderer {
125
129
  textElem.style.fontWeight = style.fontWeight ?? '';
126
130
  textElem.style.fontSize = style.size + 'px';
127
131
  textElem.style.fill = style.renderingStyle.fill.toHexString();
132
+ textElem.setAttribute('x', `${toRoundedString(translation.x)}`);
133
+ textElem.setAttribute('y', `${toRoundedString(translation.y)}`);
128
134
 
129
135
  if (style.renderingStyle.stroke) {
130
136
  const strokeStyle = style.renderingStyle.stroke;
@@ -1,8 +1,8 @@
1
1
  import { TextStyle } from '../../components/Text';
2
- import Mat33 from '../../geometry/Mat33';
3
- import Rect2 from '../../geometry/Rect2';
4
- import { Vec2 } from '../../geometry/Vec2';
5
- import Vec3 from '../../geometry/Vec3';
2
+ import Mat33 from '../../math/Mat33';
3
+ import Rect2 from '../../math/Rect2';
4
+ import { Vec2 } from '../../math/Vec2';
5
+ import Vec3 from '../../math/Vec3';
6
6
  import Viewport from '../../Viewport';
7
7
  import { TextRendererLocalization } from '../localization';
8
8
  import RenderingStyle from '../RenderingStyle';
@@ -12,6 +12,9 @@ import AbstractRenderer from './AbstractRenderer';
12
12
 
13
13
  export default class TextOnlyRenderer extends AbstractRenderer {
14
14
  private descriptionBuilder: string[] = [];
15
+ private pathCount: number = 0;
16
+ private textNodeCount: number = 0;
17
+
15
18
  public constructor(viewport: Viewport, private localizationTable: TextRendererLocalization) {
16
19
  super(viewport);
17
20
  }
@@ -23,15 +26,22 @@ export default class TextOnlyRenderer extends AbstractRenderer {
23
26
 
24
27
  public clear(): void {
25
28
  this.descriptionBuilder = [];
29
+ this.pathCount = 0;
30
+ this.textNodeCount = 0;
26
31
  }
27
32
 
28
33
  public getDescription(): string {
29
- return this.descriptionBuilder.join('\n');
34
+ return [
35
+ this.localizationTable.pathNodeCount(this.pathCount),
36
+ this.localizationTable.textNodeCount(this.textNodeCount),
37
+ ...this.descriptionBuilder
38
+ ].join('\n');
30
39
  }
31
40
 
32
41
  protected beginPath(_startPoint: Vec3): void {
33
42
  }
34
43
  protected endPath(_style: RenderingStyle): void {
44
+ this.pathCount ++;
35
45
  }
36
46
  protected lineTo(_point: Vec3): void {
37
47
  }
@@ -43,9 +53,10 @@ export default class TextOnlyRenderer extends AbstractRenderer {
43
53
  }
44
54
  public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
45
55
  this.descriptionBuilder.push(this.localizationTable.textNode(text));
56
+ this.textNodeCount ++;
46
57
  }
47
58
  public isTooSmallToRender(rect: Rect2): boolean {
48
- return rect.maxDimension < 10 / this.getSizeOfCanvasPixelOnScreen();
59
+ return rect.maxDimension < 15 / this.getSizeOfCanvasPixelOnScreen();
49
60
  }
50
61
  public drawPoints(..._points: Vec3[]): void {
51
62
  }