js-draw 1.0.1 → 1.1.0

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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Editor.css +1 -0
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/toolbar/AbstractToolbar.d.ts +9 -13
  6. package/dist/cjs/toolbar/AbstractToolbar.js +14 -19
  7. package/dist/cjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  8. package/dist/cjs/toolbar/widgets/SaveActionWidget.js +26 -0
  9. package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
  10. package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/mjs/toolbar/AbstractToolbar.d.ts +9 -13
  13. package/dist/mjs/toolbar/AbstractToolbar.mjs +14 -19
  14. package/dist/mjs/toolbar/widgets/SaveActionWidget.d.ts +10 -0
  15. package/dist/mjs/toolbar/widgets/SaveActionWidget.mjs +21 -0
  16. package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
  17. package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
  18. package/dist/mjs/version.mjs +1 -1
  19. package/docs/img/readme-images/js-draw.jpg +0 -0
  20. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  21. package/package.json +5 -4
  22. package/src/toolbar/EdgeToolbar.scss +1 -0
  23. package/dist-test/test_imports/package-lock.json +0 -13
  24. package/dist-test/test_imports/package.json +0 -12
  25. package/dist-test/test_imports/test-imports.js +0 -11
  26. package/dist-test/test_imports/test-require.cjs +0 -14
  27. package/src/Editor.loadFrom.test.ts +0 -24
  28. package/src/Editor.test.ts +0 -107
  29. package/src/Editor.toSVG.test.ts +0 -294
  30. package/src/Editor.ts +0 -1443
  31. package/src/EditorImage.test.ts +0 -117
  32. package/src/EditorImage.ts +0 -609
  33. package/src/EventDispatcher.test.ts +0 -123
  34. package/src/EventDispatcher.ts +0 -72
  35. package/src/Pointer.ts +0 -183
  36. package/src/SVGLoader.test.ts +0 -114
  37. package/src/SVGLoader.ts +0 -672
  38. package/src/UndoRedoHistory.test.ts +0 -34
  39. package/src/UndoRedoHistory.ts +0 -102
  40. package/src/Viewport.ts +0 -322
  41. package/src/bundle/bundled.ts +0 -7
  42. package/src/commands/Command.ts +0 -45
  43. package/src/commands/Duplicate.ts +0 -75
  44. package/src/commands/Erase.ts +0 -95
  45. package/src/commands/SerializableCommand.ts +0 -49
  46. package/src/commands/UnresolvedCommand.ts +0 -37
  47. package/src/commands/invertCommand.ts +0 -58
  48. package/src/commands/lib.ts +0 -16
  49. package/src/commands/localization.ts +0 -47
  50. package/src/commands/uniteCommands.test.ts +0 -23
  51. package/src/commands/uniteCommands.ts +0 -140
  52. package/src/components/AbstractComponent.transformBy.test.ts +0 -23
  53. package/src/components/AbstractComponent.ts +0 -383
  54. package/src/components/BackgroundComponent.test.ts +0 -44
  55. package/src/components/BackgroundComponent.ts +0 -348
  56. package/src/components/ImageComponent.ts +0 -176
  57. package/src/components/RestylableComponent.ts +0 -161
  58. package/src/components/SVGGlobalAttributesObject.ts +0 -79
  59. package/src/components/Stroke.test.ts +0 -137
  60. package/src/components/Stroke.ts +0 -294
  61. package/src/components/TextComponent.test.ts +0 -202
  62. package/src/components/TextComponent.ts +0 -429
  63. package/src/components/UnknownSVGObject.test.ts +0 -10
  64. package/src/components/UnknownSVGObject.ts +0 -60
  65. package/src/components/builders/ArrowBuilder.ts +0 -106
  66. package/src/components/builders/CircleBuilder.ts +0 -100
  67. package/src/components/builders/FreehandLineBuilder.test.ts +0 -24
  68. package/src/components/builders/FreehandLineBuilder.ts +0 -210
  69. package/src/components/builders/LineBuilder.ts +0 -77
  70. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -453
  71. package/src/components/builders/RectangleBuilder.ts +0 -73
  72. package/src/components/builders/types.ts +0 -15
  73. package/src/components/lib.ts +0 -31
  74. package/src/components/localization.ts +0 -24
  75. package/src/components/util/StrokeSmoother.ts +0 -302
  76. package/src/components/util/describeComponentList.ts +0 -18
  77. package/src/dialogs/makeAboutDialog.ts +0 -82
  78. package/src/inputEvents.ts +0 -143
  79. package/src/lib.ts +0 -91
  80. package/src/localization.ts +0 -34
  81. package/src/localizations/de.ts +0 -146
  82. package/src/localizations/en.ts +0 -8
  83. package/src/localizations/es.ts +0 -74
  84. package/src/localizations/getLocalizationTable.test.ts +0 -27
  85. package/src/localizations/getLocalizationTable.ts +0 -74
  86. package/src/rendering/Display.ts +0 -247
  87. package/src/rendering/RenderablePathSpec.ts +0 -88
  88. package/src/rendering/RenderingStyle.test.ts +0 -68
  89. package/src/rendering/RenderingStyle.ts +0 -55
  90. package/src/rendering/TextRenderingStyle.ts +0 -55
  91. package/src/rendering/caching/CacheRecord.test.ts +0 -48
  92. package/src/rendering/caching/CacheRecord.ts +0 -76
  93. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  94. package/src/rendering/caching/RenderingCache.test.ts +0 -43
  95. package/src/rendering/caching/RenderingCache.ts +0 -66
  96. package/src/rendering/caching/RenderingCacheNode.ts +0 -404
  97. package/src/rendering/caching/testUtils.ts +0 -35
  98. package/src/rendering/caching/types.ts +0 -34
  99. package/src/rendering/lib.ts +0 -8
  100. package/src/rendering/localization.ts +0 -20
  101. package/src/rendering/renderers/AbstractRenderer.ts +0 -232
  102. package/src/rendering/renderers/CanvasRenderer.ts +0 -312
  103. package/src/rendering/renderers/DummyRenderer.test.ts +0 -41
  104. package/src/rendering/renderers/DummyRenderer.ts +0 -142
  105. package/src/rendering/renderers/SVGRenderer.ts +0 -434
  106. package/src/rendering/renderers/TextOnlyRenderer.test.ts +0 -34
  107. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -68
  108. package/src/shortcuts/KeyBinding.test.ts +0 -61
  109. package/src/shortcuts/KeyBinding.ts +0 -257
  110. package/src/shortcuts/KeyboardShortcutManager.test.ts +0 -95
  111. package/src/shortcuts/KeyboardShortcutManager.ts +0 -163
  112. package/src/shortcuts/lib.ts +0 -3
  113. package/src/testing/createEditor.ts +0 -11
  114. package/src/testing/getUniquePointerId.ts +0 -18
  115. package/src/testing/lib.ts +0 -3
  116. package/src/testing/sendPenEvent.ts +0 -36
  117. package/src/testing/sendTouchEvent.ts +0 -71
  118. package/src/toolbar/AbstractToolbar.ts +0 -542
  119. package/src/toolbar/DropdownToolbar.ts +0 -220
  120. package/src/toolbar/EdgeToolbar.test.ts +0 -54
  121. package/src/toolbar/EdgeToolbar.ts +0 -543
  122. package/src/toolbar/IconProvider.ts +0 -861
  123. package/src/toolbar/constants.ts +0 -1
  124. package/src/toolbar/lib.ts +0 -6
  125. package/src/toolbar/localization.ts +0 -136
  126. package/src/toolbar/types.ts +0 -13
  127. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  128. package/src/toolbar/widgets/BaseToolWidget.ts +0 -81
  129. package/src/toolbar/widgets/BaseWidget.ts +0 -495
  130. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -250
  131. package/src/toolbar/widgets/EraserToolWidget.ts +0 -84
  132. package/src/toolbar/widgets/HandToolWidget.ts +0 -239
  133. package/src/toolbar/widgets/InsertImageWidget.ts +0 -248
  134. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  135. package/src/toolbar/widgets/PenToolWidget.ts +0 -369
  136. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -195
  137. package/src/toolbar/widgets/TextToolWidget.ts +0 -149
  138. package/src/toolbar/widgets/components/makeColorInput.ts +0 -184
  139. package/src/toolbar/widgets/components/makeFileInput.ts +0 -128
  140. package/src/toolbar/widgets/components/makeGridSelector.ts +0 -179
  141. package/src/toolbar/widgets/components/makeSeparator.ts +0 -17
  142. package/src/toolbar/widgets/components/makeThicknessSlider.ts +0 -62
  143. package/src/toolbar/widgets/keybindings.ts +0 -19
  144. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +0 -262
  145. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +0 -71
  146. package/src/toolbar/widgets/layout/types.ts +0 -74
  147. package/src/toolbar/widgets/lib.ts +0 -13
  148. package/src/tools/BaseTool.ts +0 -169
  149. package/src/tools/Eraser.test.ts +0 -103
  150. package/src/tools/Eraser.ts +0 -173
  151. package/src/tools/FindTool.test.ts +0 -67
  152. package/src/tools/FindTool.ts +0 -153
  153. package/src/tools/InputFilter/FunctionMapper.ts +0 -17
  154. package/src/tools/InputFilter/InputMapper.ts +0 -41
  155. package/src/tools/InputFilter/InputPipeline.test.ts +0 -41
  156. package/src/tools/InputFilter/InputPipeline.ts +0 -34
  157. package/src/tools/InputFilter/InputStabilizer.ts +0 -254
  158. package/src/tools/InputFilter/StrokeKeyboardControl.ts +0 -104
  159. package/src/tools/PanZoom.test.ts +0 -339
  160. package/src/tools/PanZoom.ts +0 -525
  161. package/src/tools/PasteHandler.ts +0 -94
  162. package/src/tools/Pen.test.ts +0 -260
  163. package/src/tools/Pen.ts +0 -284
  164. package/src/tools/PipetteTool.ts +0 -84
  165. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -29
  166. package/src/tools/SelectionTool/Selection.ts +0 -647
  167. package/src/tools/SelectionTool/SelectionHandle.ts +0 -142
  168. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -370
  169. package/src/tools/SelectionTool/SelectionTool.ts +0 -510
  170. package/src/tools/SelectionTool/TransformMode.ts +0 -112
  171. package/src/tools/SelectionTool/types.ts +0 -11
  172. package/src/tools/SoundUITool.ts +0 -221
  173. package/src/tools/TextTool.ts +0 -339
  174. package/src/tools/ToolController.ts +0 -224
  175. package/src/tools/ToolEnabledGroup.ts +0 -14
  176. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  177. package/src/tools/ToolbarShortcutHandler.ts +0 -39
  178. package/src/tools/UndoRedoShortcut.test.ts +0 -62
  179. package/src/tools/UndoRedoShortcut.ts +0 -24
  180. package/src/tools/keybindings.ts +0 -85
  181. package/src/tools/lib.ts +0 -22
  182. package/src/tools/localization.ts +0 -76
  183. package/src/types.ts +0 -151
  184. package/src/util/ReactiveValue.test.ts +0 -168
  185. package/src/util/ReactiveValue.ts +0 -241
  186. package/src/util/assertions.ts +0 -55
  187. package/src/util/fileToBase64.ts +0 -18
  188. package/src/util/guessKeyCodeFromKey.ts +0 -36
  189. package/src/util/listPrefixMatch.ts +0 -19
  190. package/src/util/stopPropagationOfScrollingWheelEvents.ts +0 -20
  191. package/src/util/untilNextAnimationFrame.ts +0 -9
  192. package/src/util/waitForAll.ts +0 -18
  193. package/src/util/waitForTimeout.ts +0 -9
  194. package/src/version.test.ts +0 -12
  195. package/src/version.ts +0 -3
  196. package/tools/allLocales.js +0 -4
  197. package/tools/copyREADME.ts +0 -62
@@ -1,429 +0,0 @@
1
- import SerializableCommand from '../commands/SerializableCommand';
2
- import Editor from '../Editor';
3
- import { Vec2, LineSegment2, Rect2, Mat33, Mat33Array } from '@js-draw/math';
4
- import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
5
- import { cloneTextStyle, TextRenderingStyle, textStyleFromJSON, textStyleToJSON } from '../rendering/TextRenderingStyle';
6
- import AbstractComponent from './AbstractComponent';
7
- import { ImageComponentLocalization } from './localization';
8
- import RestyleableComponent, { ComponentStyle, createRestyleComponentCommand } from './RestylableComponent';
9
-
10
- const componentTypeId = 'text';
11
-
12
- export enum TextTransformMode {
13
- /** Absolutely positioned in both the X and Y dimensions. */
14
- ABSOLUTE_XY,
15
-
16
- /** Relatively positioned in both the X and Y dimensions. */
17
- RELATIVE_XY,
18
-
19
- /**Relatively positioned in the X direction, absolutely positioned in the Y direction. */
20
- RELATIVE_X_ABSOLUTE_Y,
21
-
22
- /**Relatively positioned in the Y direction, absolutely positioned in the X direction. */
23
- RELATIVE_Y_ABSOLUTE_X,
24
- }
25
-
26
- type TextElement = TextComponent|string;
27
-
28
- /**
29
- * Displays text.
30
- */
31
- export default class TextComponent extends AbstractComponent implements RestyleableComponent {
32
- protected contentBBox: Rect2;
33
-
34
- // eslint-disable-next-line @typescript-eslint/prefer-as-const
35
- readonly isRestylableComponent: true = true;
36
-
37
- /**
38
- * Creates a new text object from a list of component text or child TextComponents.
39
- *
40
- * @see {@link fromLines}
41
- */
42
- public constructor(
43
- protected readonly textObjects: Array<TextElement>,
44
-
45
- // Transformation relative to this component's parent element.
46
- private transform: Mat33,
47
- private style: TextRenderingStyle,
48
-
49
- // @internal
50
- private transformMode: TextTransformMode = TextTransformMode.ABSOLUTE_XY,
51
- ) {
52
- super(componentTypeId);
53
- this.recomputeBBox();
54
-
55
- // If this has no direct children, choose a style representative of this' content
56
- // (useful for estimating the style of the TextComponent).
57
- const hasDirectContent = textObjects.some(obj => typeof obj === 'string');
58
- if (!hasDirectContent && textObjects.length > 0) {
59
- this.style = (textObjects[0] as TextComponent).getTextStyle();
60
- }
61
- }
62
-
63
- public static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextRenderingStyle) {
64
- // Quote the font family if necessary.
65
- const fontFamily = style.fontFamily.match(/\s/) ? style.fontFamily.replace(/["]/g, '\\"') : style.fontFamily;
66
-
67
- ctx.font = [
68
- style.fontStyle ?? '',
69
- style.fontWeight ?? '',
70
- (style.size ?? 12) + 'px',
71
- `${fontFamily}`
72
- ].join(' ');
73
-
74
- // TODO: Support RTL
75
- ctx.textAlign = 'left';
76
- }
77
-
78
- private static textMeasuringCtx: CanvasRenderingContext2D|null = null;
79
-
80
- // Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
81
- private static estimateTextDimens(text: string, style: TextRenderingStyle): Rect2 {
82
- const widthEst = text.length * style.size;
83
- const heightEst = style.size;
84
-
85
- // Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
86
- // be above (0, 0).
87
- return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
88
- }
89
-
90
- // Returns a set of TextMetrics for the given text, if a canvas is available.
91
- private static getTextMetrics(text: string, style: TextRenderingStyle): TextMetrics|null {
92
- TextComponent.textMeasuringCtx ??= document.createElement('canvas').getContext('2d') ?? null;
93
- if (!TextComponent.textMeasuringCtx) {
94
- return null;
95
- }
96
-
97
- const ctx = TextComponent.textMeasuringCtx;
98
- TextComponent.applyTextStyles(ctx, style);
99
-
100
- return ctx.measureText(text);
101
- }
102
-
103
- // Returns the bounding box of `text`. This is approximate if no Canvas is available.
104
- private static getTextDimens(text: string, style: TextRenderingStyle): Rect2 {
105
- const metrics = this.getTextMetrics(text, style);
106
-
107
- if (!metrics) {
108
- return this.estimateTextDimens(text, style);
109
- }
110
-
111
- // Text is drawn with (0,0) at the bottom left of the baseline.
112
- const textY = -metrics.actualBoundingBoxAscent;
113
- const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
114
- return new Rect2(0, textY, metrics.width, textHeight);
115
- }
116
-
117
- private static getFontHeight(style: TextRenderingStyle): number {
118
- return style.size;
119
- }
120
-
121
- private computeUntransformedBBoxOfPart(part: TextElement) {
122
- if (typeof part === 'string') {
123
- return TextComponent.getTextDimens(part, this.style);
124
- } else {
125
- return part.contentBBox;
126
- }
127
- }
128
-
129
- private recomputeBBox() {
130
- let bbox: Rect2|null = null;
131
- const cursor = new TextComponent.TextCursor(this.transform, this.style);
132
-
133
- for (const textObject of this.textObjects) {
134
- const transform = cursor.update(textObject);
135
- const currentBBox = this.computeUntransformedBBoxOfPart(textObject).transformedBoundingBox(transform);
136
-
137
- bbox ??= currentBBox;
138
- bbox = bbox.union(currentBBox);
139
- }
140
-
141
- this.contentBBox = bbox ?? Rect2.empty;
142
- }
143
-
144
- private renderInternal(canvas: AbstractRenderer) {
145
- const cursor = new TextComponent.TextCursor(this.transform, this.style);
146
-
147
- for (const textObject of this.textObjects) {
148
- const transform = cursor.update(textObject);
149
-
150
- if (typeof textObject === 'string') {
151
- canvas.drawText(textObject, transform, this.style);
152
- } else {
153
- canvas.pushTransform(transform);
154
- textObject.renderInternal(canvas);
155
- canvas.popTransform();
156
- }
157
- }
158
- }
159
-
160
- public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
161
- canvas.startObject(this.contentBBox);
162
- this.renderInternal(canvas);
163
- canvas.endObject(this.getLoadSaveData());
164
- }
165
-
166
- public override getProportionalRenderingTime(): number {
167
- return this.textObjects.length;
168
- }
169
-
170
- public override intersects(lineSegment: LineSegment2): boolean {
171
- const cursor = new TextComponent.TextCursor(this.transform, this.style);
172
-
173
- for (const subObject of this.textObjects) {
174
- // Convert canvas space to internal space relative to the current object.
175
- const invTransform = cursor.update(subObject).inverse();
176
- const transformedLine = lineSegment.transformedBy(invTransform);
177
-
178
- if (typeof subObject === 'string') {
179
- const textBBox = TextComponent.getTextDimens(subObject, this.style);
180
-
181
- // TODO: Use a better intersection check. Perhaps draw the text onto a CanvasElement and
182
- // use pixel-testing to check for intersection with its contour.
183
- if (textBBox.getEdges().some(edge => transformedLine.intersection(edge) !== null)) {
184
- return true;
185
- }
186
- } else {
187
- if (subObject.intersects(transformedLine)) {
188
- return true;
189
- }
190
- }
191
- }
192
-
193
- return false;
194
- }
195
-
196
- public getStyle(): ComponentStyle {
197
- return {
198
- color: this.style.renderingStyle.fill,
199
-
200
- // Make a copy
201
- textStyle: {
202
- ...this.style,
203
- renderingStyle: {
204
- ...this.style.renderingStyle,
205
- },
206
- },
207
- };
208
- }
209
-
210
- public updateStyle(style: ComponentStyle): SerializableCommand {
211
- return createRestyleComponentCommand(this.getStyle(), style, this);
212
- }
213
-
214
- public forceStyle(style: ComponentStyle, editor: Editor|null): void {
215
- if (style.textStyle) {
216
- this.style = cloneTextStyle(style.textStyle);
217
- } else if (style.color) {
218
- this.style = {
219
- ...this.style,
220
- renderingStyle: {
221
- ...this.style.renderingStyle,
222
- fill: style.color,
223
- },
224
- };
225
- } else {
226
- return;
227
- }
228
-
229
- for (const child of this.textObjects) {
230
- if (child instanceof TextComponent) {
231
- child.forceStyle(style, editor);
232
- }
233
- }
234
-
235
- if (editor) {
236
- editor.image.queueRerenderOf(this);
237
- editor.queueRerender();
238
- }
239
- }
240
-
241
- // See {@link getStyle}
242
- public getTextStyle(): TextRenderingStyle {
243
- return cloneTextStyle(this.style);
244
- }
245
-
246
- public getBaselinePos() {
247
- return this.transform.transformVec2(Vec2.zero);
248
- }
249
-
250
- public getTransform(): Mat33 {
251
- return this.transform;
252
- }
253
-
254
- protected applyTransformation(affineTransfm: Mat33): void {
255
- this.transform = affineTransfm.rightMul(this.transform);
256
- this.recomputeBBox();
257
- }
258
-
259
- protected createClone(): AbstractComponent {
260
- const clonedTextObjects = this.textObjects.map(obj => {
261
- if (typeof obj === 'string') {
262
- return obj;
263
- } else {
264
- return obj.createClone() as TextComponent;
265
- }
266
- });
267
- return new TextComponent(clonedTextObjects, this.transform, this.style);
268
- }
269
-
270
- public getText() {
271
- const result: string[] = [];
272
-
273
- for (const textObject of this.textObjects) {
274
- if (typeof textObject === 'string') {
275
- result.push(textObject);
276
- } else {
277
- result.push(textObject.getText());
278
- }
279
- }
280
-
281
- return result.join('\n');
282
- }
283
-
284
- public description(localizationTable: ImageComponentLocalization): string {
285
- return localizationTable.text(this.getText());
286
- }
287
-
288
- // Do not rely on the output of `serializeToJSON` taking any particular format.
289
- protected serializeToJSON(): Record<string, any> {
290
- const serializableStyle = textStyleToJSON(this.style);
291
-
292
- const serializedTextObjects = this.textObjects.map(text => {
293
- if (typeof text === 'string') {
294
- return {
295
- text,
296
- };
297
- } else {
298
- return {
299
- json: text.serializeToJSON(),
300
- };
301
- }
302
- });
303
-
304
- return {
305
- textObjects: serializedTextObjects,
306
- transform: this.transform.toArray(),
307
- style: serializableStyle,
308
- };
309
- }
310
-
311
- // @internal
312
- public static deserializeFromString(json: any): TextComponent {
313
- if (typeof json === 'string') {
314
- json = JSON.parse(json);
315
- }
316
-
317
- const style = textStyleFromJSON(json.style);
318
-
319
- const textObjects: Array<TextElement> = json.textObjects.map((data: any) => {
320
- if ((data.text ?? null) !== null) {
321
- return data.text;
322
- }
323
-
324
- return TextComponent.deserializeFromString(data.json);
325
- });
326
-
327
- json.transform = json.transform.filter((elem: any) => typeof elem === 'number');
328
- if (json.transform.length !== 9) {
329
- throw new Error(`Unable to deserialize transform, ${json.transform}.`);
330
- }
331
-
332
- const transformData = json.transform as Mat33Array;
333
- const transform = new Mat33(...transformData);
334
-
335
- return new TextComponent(textObjects, transform, style);
336
- }
337
-
338
- /**
339
- * Creates a `TextComponent` from `lines`.
340
- *
341
- * @example
342
- * ```ts
343
- * const textStyle = {
344
- * size: 12,
345
- * fontFamily: 'serif',
346
- * renderingStyle: { fill: Color4.black },
347
- * };
348
- *
349
- * const text = TextComponent.fromLines('foo\nbar'.split('\n'), Mat33.identity, textStyle);
350
- * ```
351
- */
352
- public static fromLines(lines: string[], transform: Mat33, style: TextRenderingStyle): AbstractComponent {
353
- let lastComponent: TextComponent|null = null;
354
- const components: TextComponent[] = [];
355
-
356
- const lineMargin = Math.round(this.getFontHeight(style));
357
-
358
- let position = Vec2.zero;
359
- for (const line of lines) {
360
- if (lastComponent) {
361
- position = position.plus(Vec2.unitY.times(lineMargin));
362
- }
363
-
364
- const component = new TextComponent([ line ], Mat33.translation(position), style);
365
- components.push(component);
366
- lastComponent = component;
367
- }
368
-
369
- return new TextComponent(components, transform, style);
370
- }
371
-
372
- private static TextCursor = class {
373
- public transform: Mat33 = Mat33.identity;
374
- public constructor(
375
- private parentTransform: Mat33 = Mat33.identity, private parentStyle: TextRenderingStyle
376
- ) { }
377
-
378
- /**
379
- * Based on previous calls to `update`, returns the transformation of
380
- * the given `element` (including the parentTransform given to this cursor's
381
- * constructor).
382
- *
383
- * The result does not take into account
384
- */
385
- public update(elem: TextElement) {
386
- let elementTransform = Mat33.identity;
387
- let elemInternalTransform = Mat33.identity;
388
- let textSize;
389
- if (typeof(elem) === 'string') {
390
- textSize = TextComponent.getTextDimens(elem, this.parentStyle);
391
- } else {
392
- // TODO: Double-check whether we need to take elem.transform into account here.
393
- // elementTransform = elem.transform;
394
- elemInternalTransform = elem.transform;
395
- textSize = elem.getBBox();
396
- }
397
- const positioning = typeof(elem) === 'string' ? TextTransformMode.RELATIVE_XY : elem.transformMode;
398
-
399
- if (positioning === TextTransformMode.RELATIVE_XY) {
400
- // Position relative to the previous element's transform.
401
- elementTransform = this.transform.rightMul(elementTransform);
402
- } else if (positioning === TextTransformMode.RELATIVE_X_ABSOLUTE_Y || positioning === TextTransformMode.RELATIVE_Y_ABSOLUTE_X) {
403
- // Zero the absolute component of this.transform's translation
404
- const transform = this.transform.mapEntries((component, [row, col]) => {
405
- if (positioning === TextTransformMode.RELATIVE_X_ABSOLUTE_Y) {
406
- // Zero the y component of this.transform's translation
407
- return row === 1 && col === 2 ? 0 : component;
408
- } else if (positioning === TextTransformMode.RELATIVE_Y_ABSOLUTE_X) {
409
- // Zero the x component of this.transform's translation
410
- return row === 0 && col === 2 ? 0 : component;
411
- }
412
-
413
- throw new Error('Unreachable');
414
- return 0;
415
- });
416
-
417
- elementTransform = transform.rightMul(elementTransform);
418
- }
419
-
420
- // Update this.transform so that future calls to update return correct values.
421
- const endShiftTransform = Mat33.translation(Vec2.of(textSize.width, 0));
422
- this.transform = elementTransform.rightMul(elemInternalTransform).rightMul(endShiftTransform);
423
-
424
- return this.parentTransform.rightMul(elementTransform);
425
- }
426
- };
427
- }
428
-
429
- AbstractComponent.registerComponent(componentTypeId, (data: string) => TextComponent.deserializeFromString(data));
@@ -1,10 +0,0 @@
1
- import AbstractComponent from './AbstractComponent';
2
- import UnknownSVGObject from './UnknownSVGObject';
3
-
4
- describe('UnknownSVGObject', () => {
5
- it('should not be deserializable', () => {
6
- const obj = new UnknownSVGObject(document.createElementNS('http://www.w3.org/2000/svg', 'circle'));
7
- const serialized = obj.serialize();
8
- expect(() => AbstractComponent.deserialize(serialized)).toThrow(/.*cannot be deserialized.*/);
9
- });
10
- });
@@ -1,60 +0,0 @@
1
- //
2
- // Stores objects loaded from an SVG that aren't recognised by the editor.
3
- // @internal
4
- // @packageDocumentation
5
- //
6
-
7
- import { LineSegment2, Mat33, Rect2 } from '@js-draw/math';
8
- import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
9
- import SVGRenderer from '../rendering/renderers/SVGRenderer';
10
- import AbstractComponent from './AbstractComponent';
11
- import { ImageComponentLocalization } from './localization';
12
-
13
- const componentId = 'unknown-svg-object';
14
- export default class UnknownSVGObject extends AbstractComponent {
15
- protected contentBBox: Rect2;
16
-
17
- public constructor(private svgObject: SVGElement) {
18
- super(componentId);
19
- this.contentBBox = Rect2.of(svgObject.getBoundingClientRect());
20
- }
21
-
22
- public override render(canvas: AbstractRenderer, _visibleRect?: Rect2): void {
23
- if (!(canvas instanceof SVGRenderer)) {
24
- // Don't draw unrenderable objects if we can't
25
- return;
26
- }
27
-
28
- canvas.startObject(this.contentBBox);
29
- canvas.drawSVGElem(this.svgObject);
30
- canvas.endObject(this.getLoadSaveData());
31
- }
32
-
33
- public override intersects(lineSegment: LineSegment2): boolean {
34
- return this.contentBBox.getEdges().some(edge => edge.intersection(lineSegment) !== null);
35
- }
36
-
37
- protected applyTransformation(_affineTransfm: Mat33): void {
38
- }
39
-
40
- public override isSelectable() {
41
- return false;
42
- }
43
-
44
- protected createClone(): AbstractComponent {
45
- return new UnknownSVGObject(this.svgObject.cloneNode(true) as SVGElement);
46
- }
47
-
48
- public description(localization: ImageComponentLocalization): string {
49
- return localization.svgObject;
50
- }
51
-
52
- protected serializeToJSON(): string | null {
53
- return JSON.stringify({
54
- html: this.svgObject.outerHTML,
55
- });
56
- }
57
- }
58
-
59
- // null: Do not deserialize UnknownSVGObjects.
60
- AbstractComponent.registerComponent(componentId, null);
@@ -1,106 +0,0 @@
1
- import { Path, PathCommandType, Rect2 } from '@js-draw/math';
2
- import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
- import { StrokeDataPoint } from '../../types';
4
- import Viewport from '../../Viewport';
5
- import AbstractComponent from '../AbstractComponent';
6
- import Stroke from '../Stroke';
7
- import { ComponentBuilder, ComponentBuilderFactory } from './types';
8
-
9
- export const makeArrowBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
10
- return new ArrowBuilder(initialPoint, viewport);
11
- };
12
-
13
- export default class ArrowBuilder implements ComponentBuilder {
14
- private endPoint: StrokeDataPoint;
15
-
16
- public constructor(private readonly startPoint: StrokeDataPoint, private readonly viewport: Viewport) {
17
- this.endPoint = startPoint;
18
- }
19
-
20
- private getLineWidth(): number {
21
- return Math.max(this.endPoint.width, this.startPoint.width);
22
- }
23
-
24
- public getBBox(): Rect2 {
25
- const preview = this.buildPreview();
26
- return preview.getBBox();
27
- }
28
-
29
- private buildPreview(): Stroke {
30
- const lineStartPoint = this.startPoint.pos;
31
- const endPoint = this.endPoint.pos;
32
- const toEnd = endPoint.minus(lineStartPoint).normalized();
33
- const arrowLength = endPoint.minus(lineStartPoint).length();
34
-
35
- // Ensure that the arrow tip is smaller than the arrow.
36
- const arrowTipSize = Math.min(this.getLineWidth(), arrowLength / 2);
37
- const startSize = this.startPoint.width / 2;
38
- const endSize = this.endPoint.width / 2;
39
-
40
- const arrowTipBase = endPoint.minus(toEnd.times(arrowTipSize));
41
-
42
- // Scaled normal vectors.
43
- const lineNormal = toEnd.orthog();
44
- const scaledStartNormal = lineNormal.times(startSize);
45
- const scaledBaseNormal = lineNormal.times(endSize);
46
-
47
- const path = new Path(arrowTipBase.minus(scaledBaseNormal), [
48
- // Stem
49
- {
50
- kind: PathCommandType.LineTo,
51
- point: lineStartPoint.minus(scaledStartNormal),
52
- },
53
- {
54
- kind: PathCommandType.LineTo,
55
- point: lineStartPoint.plus(scaledStartNormal),
56
- },
57
- {
58
- kind: PathCommandType.LineTo,
59
- point: arrowTipBase.plus(scaledBaseNormal),
60
- },
61
-
62
- // Head
63
- {
64
- kind: PathCommandType.LineTo,
65
- point: arrowTipBase.plus(lineNormal.times(arrowTipSize).plus(scaledBaseNormal)),
66
- },
67
- {
68
- kind: PathCommandType.LineTo,
69
- point: endPoint.plus(toEnd.times(endSize)),
70
- },
71
- {
72
- kind: PathCommandType.LineTo,
73
- point: arrowTipBase.plus(lineNormal.times(-arrowTipSize).minus(scaledBaseNormal)),
74
- },
75
- {
76
- kind: PathCommandType.LineTo,
77
- point: arrowTipBase.minus(scaledBaseNormal),
78
- },
79
- // Round all points in the arrow (to remove unnecessary decimal places)
80
- ]).mapPoints(point => this.viewport.roundPoint(point));
81
-
82
- const preview = new Stroke([
83
- {
84
- startPoint: path.startPoint,
85
- commands: path.parts,
86
- style: {
87
- fill: this.startPoint.color,
88
- }
89
- }
90
- ]);
91
-
92
- return preview;
93
- }
94
-
95
- public build(): AbstractComponent {
96
- return this.buildPreview();
97
- }
98
-
99
- public preview(renderer: AbstractRenderer): void {
100
- this.buildPreview().render(renderer);
101
- }
102
-
103
- public addPoint(point: StrokeDataPoint): void {
104
- this.endPoint = point;
105
- }
106
- }
@@ -1,100 +0,0 @@
1
- import { Vec2, Path, PathCommand, PathCommandType, Rect2, Color4 } from '@js-draw/math';
2
- import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
3
- import { pathToRenderable } from '../../rendering/RenderablePathSpec';
4
- import { StrokeDataPoint } from '../../types';
5
- import Viewport from '../../Viewport';
6
- import AbstractComponent from '../AbstractComponent';
7
- import Stroke from '../Stroke';
8
- import { ComponentBuilder, ComponentBuilderFactory } from './types';
9
-
10
- export const makeOutlinedCircleBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
11
- return new CircleBuilder(initialPoint, viewport);
12
- };
13
-
14
- class CircleBuilder implements ComponentBuilder {
15
- private endPoint: StrokeDataPoint;
16
-
17
- public constructor(
18
- private readonly startPoint: StrokeDataPoint,
19
- private readonly viewport: Viewport,
20
- ) {
21
- // Initially, the start and end points are the same.
22
- this.endPoint = startPoint;
23
- }
24
-
25
- public getBBox(): Rect2 {
26
- const preview = this.buildPreview();
27
- return preview.getBBox();
28
- }
29
-
30
- private buildPreview(): Stroke {
31
- const pathCommands: PathCommand[] = [];
32
- const numDivisions = 6;
33
- const stepSize = Math.PI * 2 / numDivisions;
34
-
35
- // Round the stroke width so that when exported it doesn't have unnecessary trailing decimals.
36
- const strokeWidth =
37
- Viewport.roundPoint(this.endPoint.width, 5 / this.viewport.getScaleFactor());
38
-
39
- const center = this.startPoint.pos.lerp(this.endPoint.pos, 0.5);
40
- const startEndDelta = this.endPoint.pos.minus(center);
41
- const radius = startEndDelta.length() - strokeWidth / 2;
42
-
43
- const startPoint = center.plus(Vec2.of(radius, 0));
44
-
45
- for (let t = stepSize; t <= Math.PI * 2; t += stepSize) {
46
- const endPoint = Vec2.of(
47
- radius * Math.cos(t),
48
- -radius * Math.sin(t),
49
- ).plus(center);
50
-
51
- // controlPointRadiusScale is selected to make the circles appear circular and
52
- // **does** depend on stepSize.
53
- const controlPointRadiusScale = 1.141;
54
- const controlPoint = Vec2.of(
55
- Math.cos(t - stepSize / 2),
56
- -Math.sin(t - stepSize / 2),
57
- ).times(
58
- radius * controlPointRadiusScale
59
- ).plus(center);
60
-
61
- pathCommands.push({
62
- kind: PathCommandType.QuadraticBezierTo,
63
- controlPoint,
64
- endPoint,
65
- });
66
- }
67
-
68
- pathCommands.push({
69
- kind: PathCommandType.LineTo,
70
- point: startPoint,
71
- });
72
-
73
- const path = new Path(startPoint, pathCommands)
74
- .mapPoints(point => this.viewport.roundPoint(point));
75
-
76
- const preview = new Stroke([
77
- pathToRenderable(path, {
78
- fill: Color4.transparent,
79
- stroke: {
80
- width: strokeWidth,
81
- color: this.endPoint.color,
82
- },
83
- }),
84
- ]);
85
-
86
- return preview;
87
- }
88
-
89
- public build(): AbstractComponent {
90
- return this.buildPreview();
91
- }
92
-
93
- public preview(renderer: AbstractRenderer): void {
94
- this.buildPreview().render(renderer);
95
- }
96
-
97
- public addPoint(point: StrokeDataPoint): void {
98
- this.endPoint = point;
99
- }
100
- }