js-draw 0.1.1 → 0.1.4

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 (86) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +21 -12
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +2 -1
  5. package/dist/src/Editor.js +24 -6
  6. package/dist/src/EditorImage.js +3 -0
  7. package/dist/src/Pointer.d.ts +3 -2
  8. package/dist/src/Pointer.js +12 -3
  9. package/dist/src/SVGLoader.d.ts +11 -0
  10. package/dist/src/SVGLoader.js +113 -4
  11. package/dist/src/Viewport.d.ts +1 -1
  12. package/dist/src/Viewport.js +12 -2
  13. package/dist/src/components/AbstractComponent.d.ts +6 -0
  14. package/dist/src/components/AbstractComponent.js +11 -0
  15. package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
  16. package/dist/src/components/Stroke.js +1 -1
  17. package/dist/src/components/Text.d.ts +30 -0
  18. package/dist/src/components/Text.js +111 -0
  19. package/dist/src/components/localization.d.ts +1 -0
  20. package/dist/src/components/localization.js +1 -0
  21. package/dist/src/geometry/Mat33.d.ts +1 -0
  22. package/dist/src/geometry/Mat33.js +30 -0
  23. package/dist/src/geometry/Path.js +105 -67
  24. package/dist/src/geometry/Rect2.d.ts +2 -0
  25. package/dist/src/geometry/Rect2.js +6 -0
  26. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -1
  27. package/dist/src/rendering/renderers/AbstractRenderer.js +13 -1
  28. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
  29. package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
  30. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
  31. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  32. package/dist/src/rendering/renderers/SVGRenderer.d.ts +6 -2
  33. package/dist/src/rendering/renderers/SVGRenderer.js +50 -7
  34. package/dist/src/testing/loadExpectExtensions.js +1 -4
  35. package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
  36. package/dist/src/toolbar/HTMLToolbar.js +242 -154
  37. package/dist/src/toolbar/icons.d.ts +12 -0
  38. package/dist/src/toolbar/icons.js +198 -0
  39. package/dist/src/toolbar/localization.d.ts +5 -1
  40. package/dist/src/toolbar/localization.js +5 -1
  41. package/dist/src/toolbar/types.d.ts +4 -0
  42. package/dist/src/tools/PanZoom.d.ts +9 -6
  43. package/dist/src/tools/PanZoom.js +30 -21
  44. package/dist/src/tools/Pen.js +8 -3
  45. package/dist/src/tools/SelectionTool.js +1 -1
  46. package/dist/src/tools/TextTool.d.ts +30 -0
  47. package/dist/src/tools/TextTool.js +173 -0
  48. package/dist/src/tools/ToolController.d.ts +5 -5
  49. package/dist/src/tools/ToolController.js +10 -9
  50. package/dist/src/tools/localization.d.ts +3 -0
  51. package/dist/src/tools/localization.js +3 -0
  52. package/dist-test/test-dist-bundle.html +8 -1
  53. package/package.json +1 -1
  54. package/src/Editor.css +2 -0
  55. package/src/Editor.ts +26 -7
  56. package/src/EditorImage.ts +4 -0
  57. package/src/Pointer.ts +13 -4
  58. package/src/SVGLoader.ts +146 -5
  59. package/src/Viewport.ts +15 -3
  60. package/src/components/AbstractComponent.ts +16 -1
  61. package/src/components/SVGGlobalAttributesObject.ts +0 -1
  62. package/src/components/Stroke.ts +1 -1
  63. package/src/components/Text.ts +140 -0
  64. package/src/components/localization.ts +2 -0
  65. package/src/geometry/Mat33.test.ts +44 -0
  66. package/src/geometry/Mat33.ts +41 -0
  67. package/src/geometry/Path.fromString.test.ts +94 -4
  68. package/src/geometry/Path.toString.test.ts +7 -3
  69. package/src/geometry/Path.ts +110 -68
  70. package/src/geometry/Rect2.ts +8 -0
  71. package/src/rendering/renderers/AbstractRenderer.ts +18 -1
  72. package/src/rendering/renderers/CanvasRenderer.ts +34 -10
  73. package/src/rendering/renderers/DummyRenderer.ts +8 -0
  74. package/src/rendering/renderers/SVGRenderer.ts +57 -10
  75. package/src/testing/loadExpectExtensions.ts +1 -4
  76. package/src/toolbar/HTMLToolbar.ts +294 -170
  77. package/src/toolbar/icons.ts +227 -0
  78. package/src/toolbar/localization.ts +11 -2
  79. package/src/toolbar/toolbar.css +27 -11
  80. package/src/toolbar/types.ts +5 -0
  81. package/src/tools/PanZoom.ts +37 -27
  82. package/src/tools/Pen.ts +7 -3
  83. package/src/tools/SelectionTool.ts +1 -1
  84. package/src/tools/TextTool.ts +225 -0
  85. package/src/tools/ToolController.ts +7 -5
  86. package/src/tools/localization.ts +7 -0
@@ -1,4 +1,5 @@
1
1
  import Color4 from '../../Color4';
2
+ import Text, { TextStyle } from '../../components/Text';
2
3
  import Mat33 from '../../geometry/Mat33';
3
4
  import Rect2 from '../../geometry/Rect2';
4
5
  import { Point2, Vec2 } from '../../geometry/Vec2';
@@ -26,16 +27,7 @@ export default class CanvasRenderer extends AbstractRenderer {
26
27
  this.setDraftMode(false);
27
28
  }
28
29
 
29
- public canRenderFromWithoutDataLoss(other: AbstractRenderer) {
30
- return other instanceof CanvasRenderer;
31
- }
32
-
33
- public renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void {
34
- if (!(other instanceof CanvasRenderer)) {
35
- throw new Error(`${other} cannot be rendered onto ${this}`);
36
- }
37
- transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
38
- this.ctx.save();
30
+ private transformBy(transformBy: Mat33) {
39
31
  // From MDN, transform(a,b,c,d,e,f)
40
32
  // takes input such that
41
33
  // ⎡ a c e ⎤
@@ -46,6 +38,19 @@ export default class CanvasRenderer extends AbstractRenderer {
46
38
  transformBy.a2, transformBy.b2, // c, d
47
39
  transformBy.a3, transformBy.b3, // e, f
48
40
  );
41
+ }
42
+
43
+ public canRenderFromWithoutDataLoss(other: AbstractRenderer) {
44
+ return other instanceof CanvasRenderer;
45
+ }
46
+
47
+ public renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void {
48
+ if (!(other instanceof CanvasRenderer)) {
49
+ throw new Error(`${other} cannot be rendered onto ${this}`);
50
+ }
51
+ transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
52
+ this.ctx.save();
53
+ this.transformBy(transformBy);
49
54
  this.ctx.drawImage(other.ctx.canvas, 0, 0);
50
55
  this.ctx.restore();
51
56
  }
@@ -143,6 +148,25 @@ export default class CanvasRenderer extends AbstractRenderer {
143
148
  super.drawPath(path);
144
149
  }
145
150
 
151
+ public drawText(text: string, transform: Mat33, style: TextStyle): void {
152
+ this.ctx.save();
153
+ transform = this.getCanvasToScreenTransform().rightMul(transform);
154
+ this.transformBy(transform);
155
+ Text.applyTextStyles(this.ctx, style);
156
+
157
+ if (style.renderingStyle.fill.a !== 0) {
158
+ this.ctx.fillStyle = style.renderingStyle.fill.toHexString();
159
+ this.ctx.fillText(text, 0, 0);
160
+ }
161
+ if (style.renderingStyle.stroke) {
162
+ this.ctx.strokeStyle = style.renderingStyle.stroke.color.toHexString();
163
+ this.ctx.lineWidth = style.renderingStyle.stroke.width;
164
+ this.ctx.strokeText(text, 0, 0);
165
+ }
166
+
167
+ this.ctx.restore();
168
+ }
169
+
146
170
  private clipLevels: number[] = [];
147
171
  public startObject(boundingBox: Rect2, clip: boolean) {
148
172
  if (this.isTooSmallToRender(boundingBox)) {
@@ -1,5 +1,6 @@
1
1
  // Renderer that outputs nothing. Useful for automated tests.
2
2
 
3
+ import { TextStyle } from '../../components/Text';
3
4
  import Mat33 from '../../geometry/Mat33';
4
5
  import Rect2 from '../../geometry/Rect2';
5
6
  import { Point2, Vec2 } from '../../geometry/Vec2';
@@ -14,6 +15,7 @@ export default class DummyRenderer extends AbstractRenderer {
14
15
  public lastFillStyle: RenderingStyle|null = null;
15
16
  public lastPoint: Point2|null = null;
16
17
  public objectNestingLevel: number = 0;
18
+ public lastText: string|null = null;
17
19
 
18
20
  // List of points drawn since the last clear.
19
21
  public pointBuffer: Point2[] = [];
@@ -40,6 +42,7 @@ export default class DummyRenderer extends AbstractRenderer {
40
42
  this.clearedCount ++;
41
43
  this.renderedPathCount = 0;
42
44
  this.pointBuffer = [];
45
+ this.lastText = null;
43
46
 
44
47
  // Ensure all objects finished rendering
45
48
  if (this.objectNestingLevel > 0) {
@@ -88,6 +91,11 @@ export default class DummyRenderer extends AbstractRenderer {
88
91
  // As such, it is unlikely to be the target of automated tests.
89
92
  }
90
93
 
94
+
95
+ public drawText(text: string, _transform: Mat33, _style: TextStyle): void {
96
+ this.lastText = text;
97
+ }
98
+
91
99
  public startObject(boundingBox: Rect2, _clip: boolean) {
92
100
  super.startObject(boundingBox);
93
101
 
@@ -1,7 +1,11 @@
1
1
 
2
+ import { LoadSaveDataTable } from '../../components/AbstractComponent';
3
+ import { TextStyle } from '../../components/Text';
4
+ import Mat33 from '../../geometry/Mat33';
2
5
  import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
3
6
  import Rect2 from '../../geometry/Rect2';
4
7
  import { Point2, Vec2 } from '../../geometry/Vec2';
8
+ import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
5
9
  import Viewport from '../../Viewport';
6
10
  import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
7
11
 
@@ -13,8 +17,8 @@ export default class SVGRenderer extends AbstractRenderer {
13
17
  private lastPathStyle: RenderingStyle|null;
14
18
  private lastPath: PathCommand[]|null;
15
19
  private lastPathStart: Point2|null;
20
+ private objectElems: SVGElement[]|null = null;
16
21
 
17
- private mainGroup: SVGGElement;
18
22
  private overwrittenAttrs: Record<string, string|null> = {};
19
23
 
20
24
  public constructor(private elem: SVGSVGElement, viewport: Viewport) {
@@ -41,8 +45,6 @@ export default class SVGRenderer extends AbstractRenderer {
41
45
  }
42
46
 
43
47
  public clear() {
44
- this.mainGroup = document.createElementNS(svgNameSpace, 'g');
45
-
46
48
  // Restore all alltributes
47
49
  for (const attrName in this.overwrittenAttrs) {
48
50
  const value = this.overwrittenAttrs[attrName];
@@ -54,9 +56,6 @@ export default class SVGRenderer extends AbstractRenderer {
54
56
  }
55
57
  }
56
58
  this.overwrittenAttrs = {};
57
-
58
- // Remove all children
59
- this.elem.replaceChildren(this.mainGroup);
60
59
  }
61
60
 
62
61
  protected beginPath(startPoint: Point2) {
@@ -106,7 +105,34 @@ export default class SVGRenderer extends AbstractRenderer {
106
105
  pathElem.setAttribute('stroke-width', style.stroke.width.toString());
107
106
  }
108
107
 
109
- this.mainGroup.appendChild(pathElem);
108
+ this.elem.appendChild(pathElem);
109
+ this.objectElems?.push(pathElem);
110
+ }
111
+
112
+ public drawText(text: string, transform: Mat33, style: TextStyle): void {
113
+ transform = this.getCanvasToScreenTransform().rightMul(transform);
114
+
115
+ const textElem = document.createElementNS(svgNameSpace, 'text');
116
+ textElem.appendChild(document.createTextNode(text));
117
+ textElem.style.transform = `matrix(
118
+ ${transform.a1}, ${transform.b1},
119
+ ${transform.a2}, ${transform.b2},
120
+ ${transform.a3}, ${transform.b3}
121
+ )`;
122
+ textElem.style.fontFamily = style.fontFamily;
123
+ textElem.style.fontVariant = style.fontVariant ?? '';
124
+ textElem.style.fontWeight = style.fontWeight ?? '';
125
+ textElem.style.fontSize = style.size + 'px';
126
+ textElem.style.fill = style.renderingStyle.fill.toHexString();
127
+
128
+ if (style.renderingStyle.stroke) {
129
+ const strokeStyle = style.renderingStyle.stroke;
130
+ textElem.style.stroke = strokeStyle.color.toHexString();
131
+ textElem.style.strokeWidth = strokeStyle.width + 'px';
132
+ }
133
+
134
+ this.elem.appendChild(textElem);
135
+ this.objectElems?.push(textElem);
110
136
  }
111
137
 
112
138
  public startObject(boundingBox: Rect2) {
@@ -116,13 +142,34 @@ export default class SVGRenderer extends AbstractRenderer {
116
142
  this.lastPath = null;
117
143
  this.lastPathStart = null;
118
144
  this.lastPathStyle = null;
145
+ this.objectElems = [];
119
146
  }
120
147
 
121
- public endObject() {
122
- super.endObject();
148
+ public endObject(loaderData?: LoadSaveDataTable) {
149
+ super.endObject(loaderData);
123
150
 
124
151
  // Don't extend paths across objects
125
152
  this.addPathToSVG();
153
+
154
+ if (loaderData) {
155
+ // Restore any attributes unsupported by the app.
156
+ for (const elem of this.objectElems ?? []) {
157
+ const attrs = loaderData[svgAttributesDataKey] as SVGLoaderUnknownAttribute[]|undefined;
158
+ const styleAttrs = loaderData[svgStyleAttributesDataKey] as SVGLoaderUnknownStyleAttribute[]|undefined;
159
+
160
+ if (attrs) {
161
+ for (const [ attr, value ] of attrs) {
162
+ elem.setAttribute(attr, value);
163
+ }
164
+ }
165
+
166
+ if (styleAttrs) {
167
+ for (const attr of styleAttrs) {
168
+ elem.style.setProperty(attr.key, attr.value, attr.priority);
169
+ }
170
+ }
171
+ }
172
+ }
126
173
  }
127
174
 
128
175
  protected lineTo(point: Point2) {
@@ -175,7 +222,7 @@ export default class SVGRenderer extends AbstractRenderer {
175
222
  elem.setAttribute('cx', `${point.x}`);
176
223
  elem.setAttribute('cy', `${point.y}`);
177
224
  elem.setAttribute('r', '15');
178
- this.mainGroup.appendChild(elem);
225
+ this.elem.appendChild(elem);
179
226
  });
180
227
  }
181
228
 
@@ -15,10 +15,7 @@ export const loadExpectExtensions = () => {
15
15
  return {
16
16
  pass,
17
17
  message: () => {
18
- if (pass) {
19
- return `Expected ${expected} not to .eq ${actual}. Options(${eqArgs})`;
20
- }
21
- return `Expected ${expected} to .eq ${actual}. Options(${eqArgs})`;
18
+ return `Expected ${pass ? '!' : ''}(${actual}).eq(${expected}). Options(${eqArgs})`;
22
19
  },
23
20
  };
24
21
  },