js-draw 0.2.3 → 0.3.1

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 (111) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/bundle.js +1 -1
  3. package/dist/src/EditorImage.js +0 -1
  4. package/dist/src/components/Stroke.js +11 -6
  5. package/dist/src/components/builders/FreehandLineBuilder.js +6 -6
  6. package/dist/src/components/lib.d.ts +2 -0
  7. package/dist/src/components/lib.js +2 -0
  8. package/dist/src/lib.d.ts +5 -1
  9. package/dist/src/lib.js +5 -1
  10. package/dist/src/math/LineSegment2.d.ts +2 -0
  11. package/dist/src/math/LineSegment2.js +6 -0
  12. package/dist/src/math/Path.d.ts +5 -1
  13. package/dist/src/math/Path.js +89 -7
  14. package/dist/src/math/Rect2.js +1 -1
  15. package/dist/src/math/Triangle.d.ts +11 -0
  16. package/dist/src/math/Triangle.js +19 -0
  17. package/dist/src/rendering/Display.js +3 -3
  18. package/dist/src/rendering/caching/RenderingCacheNode.js +2 -1
  19. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +2 -1
  20. package/dist/src/rendering/renderers/CanvasRenderer.js +6 -6
  21. package/dist/src/rendering/renderers/SVGRenderer.d.ts +10 -11
  22. package/dist/src/rendering/renderers/SVGRenderer.js +27 -68
  23. package/dist/src/toolbar/HTMLToolbar.d.ts +5 -1
  24. package/dist/src/toolbar/HTMLToolbar.js +30 -31
  25. package/dist/src/toolbar/icons.d.ts +1 -1
  26. package/dist/src/toolbar/icons.js +4 -0
  27. package/dist/src/toolbar/lib.d.ts +3 -0
  28. package/dist/src/toolbar/lib.js +4 -0
  29. package/dist/src/toolbar/makeColorInput.js +2 -2
  30. package/dist/src/toolbar/types.d.ts +0 -4
  31. package/dist/src/toolbar/types.js +1 -5
  32. package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
  33. package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
  34. package/dist/src/toolbar/widgets/{EraserWidget.d.ts → EraserToolWidget.d.ts} +1 -1
  35. package/dist/src/toolbar/widgets/{EraserWidget.js → EraserToolWidget.js} +1 -1
  36. package/dist/src/toolbar/widgets/{PenWidget.d.ts → PenToolWidget.d.ts} +2 -3
  37. package/dist/src/toolbar/widgets/{PenWidget.js → PenToolWidget.js} +6 -7
  38. package/dist/src/toolbar/widgets/{SelectionWidget.d.ts → SelectionToolWidget.d.ts} +1 -1
  39. package/dist/src/toolbar/widgets/{SelectionWidget.js → SelectionToolWidget.js} +1 -1
  40. package/dist/src/toolbar/widgets/lib.d.ts +8 -0
  41. package/dist/src/toolbar/widgets/lib.js +8 -0
  42. package/dist/src/tools/BaseTool.d.ts +1 -2
  43. package/dist/src/tools/BaseTool.js +6 -0
  44. package/dist/src/tools/Eraser.d.ts +0 -2
  45. package/dist/src/tools/Eraser.js +0 -2
  46. package/dist/src/tools/PanZoom.d.ts +0 -2
  47. package/dist/src/tools/PanZoom.js +0 -2
  48. package/dist/src/tools/Pen.d.ts +9 -9
  49. package/dist/src/tools/Pen.js +23 -6
  50. package/dist/src/tools/PipetteTool.d.ts +0 -2
  51. package/dist/src/tools/PipetteTool.js +0 -2
  52. package/dist/src/tools/SelectionTool.d.ts +0 -2
  53. package/dist/src/tools/SelectionTool.js +4 -3
  54. package/dist/src/tools/TextTool.d.ts +0 -2
  55. package/dist/src/tools/TextTool.js +0 -2
  56. package/dist/src/tools/ToolController.d.ts +8 -11
  57. package/dist/src/tools/ToolController.js +37 -18
  58. package/dist/src/tools/ToolEnabledGroup.js +1 -1
  59. package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
  60. package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
  61. package/dist/src/tools/UndoRedoShortcut.d.ts +0 -2
  62. package/dist/src/tools/UndoRedoShortcut.js +3 -2
  63. package/dist/src/tools/lib.d.ts +13 -0
  64. package/dist/src/tools/lib.js +13 -0
  65. package/dist/src/tools/localization.d.ts +1 -0
  66. package/dist/src/tools/localization.js +1 -0
  67. package/dist/src/types.d.ts +8 -2
  68. package/dist/src/types.js +1 -0
  69. package/package.json +2 -2
  70. package/src/EditorImage.ts +0 -1
  71. package/src/components/Stroke.test.ts +5 -0
  72. package/src/components/Stroke.ts +13 -7
  73. package/src/components/builders/FreehandLineBuilder.ts +6 -6
  74. package/src/components/lib.ts +3 -0
  75. package/src/lib.ts +5 -1
  76. package/src/math/LineSegment2.ts +8 -0
  77. package/src/math/Path.test.ts +53 -0
  78. package/src/math/Path.toString.test.ts +4 -2
  79. package/src/math/Path.ts +109 -11
  80. package/src/math/Rect2.ts +1 -1
  81. package/src/math/Triangle.ts +29 -0
  82. package/src/rendering/Display.ts +3 -3
  83. package/src/rendering/caching/RenderingCacheNode.ts +3 -1
  84. package/src/rendering/renderers/AbstractRenderer.ts +1 -0
  85. package/src/rendering/renderers/CanvasRenderer.ts +6 -6
  86. package/src/rendering/renderers/SVGRenderer.ts +30 -84
  87. package/src/toolbar/HTMLToolbar.ts +35 -38
  88. package/src/toolbar/icons.ts +5 -1
  89. package/src/toolbar/lib.ts +4 -0
  90. package/src/toolbar/makeColorInput.ts +1 -2
  91. package/src/toolbar/types.ts +1 -5
  92. package/src/toolbar/widgets/BaseWidget.ts +27 -1
  93. package/src/toolbar/widgets/{EraserWidget.ts → EraserToolWidget.ts} +1 -1
  94. package/src/toolbar/widgets/{PenWidget.ts → PenToolWidget.ts} +10 -9
  95. package/src/toolbar/widgets/{SelectionWidget.ts → SelectionToolWidget.ts} +1 -1
  96. package/src/toolbar/widgets/lib.ts +10 -0
  97. package/src/tools/BaseTool.ts +8 -3
  98. package/src/tools/Eraser.ts +0 -2
  99. package/src/tools/PanZoom.ts +0 -2
  100. package/src/tools/Pen.ts +32 -13
  101. package/src/tools/PipetteTool.ts +0 -3
  102. package/src/tools/SelectionTool.test.ts +1 -2
  103. package/src/tools/SelectionTool.ts +5 -3
  104. package/src/tools/TextTool.ts +0 -2
  105. package/src/tools/ToolController.ts +44 -20
  106. package/src/tools/ToolEnabledGroup.ts +1 -1
  107. package/src/tools/ToolSwitcherShortcut.ts +34 -0
  108. package/src/tools/UndoRedoShortcut.ts +4 -4
  109. package/src/tools/lib.ts +18 -0
  110. package/src/tools/localization.ts +2 -0
  111. package/src/types.ts +13 -1
@@ -302,7 +302,6 @@ export class ImageNode {
302
302
  this.children = [];
303
303
  }
304
304
  render(renderer, visibleRect) {
305
- // Don't render components that are < 0.1% of the viewport.
306
305
  const leaves = this.getLeavesIntersectingRegion(visibleRect, rect => renderer.isTooSmallToRender(rect));
307
306
  sortLeavesByZIndex(leaves);
308
307
  for (const leaf of leaves) {
@@ -6,7 +6,7 @@ export default class Stroke extends AbstractComponent {
6
6
  constructor(parts) {
7
7
  var _a;
8
8
  super('stroke');
9
- this.parts = parts.map(section => {
9
+ this.parts = parts.map((section) => {
10
10
  const path = Path.fromRenderable(section);
11
11
  const pathBBox = this.bboxForPart(path.bbox, section.style);
12
12
  if (!this.contentBBox) {
@@ -17,7 +17,6 @@ export default class Stroke extends AbstractComponent {
17
17
  }
18
18
  return {
19
19
  path,
20
- bbox: pathBBox,
21
20
  // To implement RenderablePathSpec
22
21
  startPoint: path.startPoint,
23
22
  style: section.style,
@@ -37,10 +36,17 @@ export default class Stroke extends AbstractComponent {
37
36
  render(canvas, visibleRect) {
38
37
  canvas.startObject(this.getBBox());
39
38
  for (const part of this.parts) {
40
- const bbox = part.bbox;
41
- if (!visibleRect || bbox.intersects(visibleRect)) {
42
- canvas.drawPath(part);
39
+ const bbox = this.bboxForPart(part.path.bbox, part.style);
40
+ if (visibleRect) {
41
+ if (!bbox.intersects(visibleRect)) {
42
+ continue;
43
+ }
44
+ const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
45
+ if (muchBiggerThanVisible && !part.path.closedRoughlyIntersects(visibleRect)) {
46
+ continue;
47
+ }
43
48
  }
49
+ canvas.drawPath(part);
44
50
  }
45
51
  canvas.endObject(this.getLoadSaveData());
46
52
  }
@@ -67,7 +73,6 @@ export default class Stroke extends AbstractComponent {
67
73
  }
68
74
  return {
69
75
  path: newPath,
70
- bbox: newBBox,
71
76
  startPoint: newPath.startPoint,
72
77
  commands: newPath.parts,
73
78
  style: part.style,
@@ -7,9 +7,9 @@ import Stroke from '../Stroke';
7
7
  import Viewport from '../../Viewport';
8
8
  export const makeFreehandLineBuilder = (initialPoint, viewport) => {
9
9
  // Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
10
- // less than ± 2 px from the curve.
10
+ // less than ±1 px from the curve.
11
11
  const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
12
- const minSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 2;
12
+ const minSmoothingDist = viewport.getSizeOfPixelOnCanvas();
13
13
  return new FreehandLineBuilder(initialPoint, minSmoothingDist, maxSmoothingDist);
14
14
  };
15
15
  // Handles stroke smoothing and creates Strokes from user/stylus input.
@@ -123,7 +123,7 @@ export default class FreehandLineBuilder {
123
123
  return this.previewStroke();
124
124
  }
125
125
  roundPoint(point) {
126
- let minFit = Math.min(this.minFitAllowed, this.curveStartWidth);
126
+ let minFit = Math.min(this.minFitAllowed, this.curveStartWidth / 2);
127
127
  if (minFit < 1e-10) {
128
128
  minFit = this.minFitAllowed;
129
129
  }
@@ -136,7 +136,7 @@ export default class FreehandLineBuilder {
136
136
  if (!this.isFirstSegment) {
137
137
  return;
138
138
  }
139
- const width = Viewport.roundPoint(this.startPoint.width / 3.5, this.minFitAllowed);
139
+ const width = Viewport.roundPoint(this.startPoint.width / 3.5, Math.min(this.minFitAllowed, this.startPoint.width / 4));
140
140
  const center = this.roundPoint(this.startPoint.pos);
141
141
  // Start on the right, cycle clockwise:
142
142
  // |
@@ -362,7 +362,7 @@ export default class FreehandLineBuilder {
362
362
  for (const point of this.buffer) {
363
363
  const proj = Vec2.ofXY(curve.project(point.xy));
364
364
  const dist = proj.minus(point).magnitude();
365
- const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 2, this.minFitAllowed);
365
+ const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 3, this.minFitAllowed);
366
366
  if (dist > minFit || dist > this.maxFitAllowed) {
367
367
  return false;
368
368
  }
@@ -370,7 +370,7 @@ export default class FreehandLineBuilder {
370
370
  return true;
371
371
  };
372
372
  const approxCurveLen = controlPoint.minus(segmentStart).magnitude() + segmentEnd.minus(controlPoint).magnitude();
373
- if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 2) {
373
+ if (this.buffer.length > 3 && approxCurveLen > this.curveEndWidth / 3) {
374
374
  if (!curveMatchesPoints(this.currentCurve)) {
375
375
  // Use a curve that better fits the points
376
376
  this.currentCurve = prevCurve;
@@ -1,3 +1,5 @@
1
+ export * from './builders/types';
2
+ export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
1
3
  import AbstractComponent from './AbstractComponent';
2
4
  import Stroke from './Stroke';
3
5
  import Text from './Text';
@@ -1,3 +1,5 @@
1
+ export * from './builders/types';
2
+ export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
1
3
  import AbstractComponent from './AbstractComponent';
2
4
  import Stroke from './Stroke';
3
5
  import Text from './Text';
package/dist/src/lib.d.ts CHANGED
@@ -13,13 +13,17 @@
13
13
  * @packageDocumentation
14
14
  */
15
15
  import Editor from './Editor';
16
- export { EditorEventType } from './types';
16
+ export { default as EditorImage } from './EditorImage';
17
+ export * from './types';
17
18
  export { default as getLocalizationTable } from './localizations/getLocalizationTable';
18
19
  export * from './localization';
19
20
  export { default as Color4 } from './Color4';
20
21
  export * from './math/lib';
21
22
  export * from './components/lib';
22
23
  export * from './commands/lib';
24
+ export * from './tools/lib';
25
+ export * from './toolbar/lib';
26
+ export { default as Pointer, PointerDevice } from './Pointer';
23
27
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
24
28
  export { Editor };
25
29
  export default Editor;
package/dist/src/lib.js CHANGED
@@ -13,13 +13,17 @@
13
13
  * @packageDocumentation
14
14
  */
15
15
  import Editor from './Editor';
16
- export { EditorEventType } from './types';
16
+ export { default as EditorImage } from './EditorImage';
17
+ export * from './types';
17
18
  export { default as getLocalizationTable } from './localizations/getLocalizationTable';
18
19
  export * from './localization';
19
20
  export { default as Color4 } from './Color4';
20
21
  export * from './math/lib';
21
22
  export * from './components/lib';
22
23
  export * from './commands/lib';
24
+ export * from './tools/lib';
25
+ export * from './toolbar/lib';
26
+ export { default as Pointer, PointerDevice } from './Pointer';
23
27
  export { default as HTMLToolbar } from './toolbar/HTMLToolbar';
24
28
  export { Editor };
25
29
  export default Editor;
@@ -15,6 +15,8 @@ export default class LineSegment2 {
15
15
  get p2(): Point2;
16
16
  get(t: number): Point2;
17
17
  intersection(other: LineSegment2): IntersectionResult | null;
18
+ intersects(other: LineSegment2): boolean;
18
19
  closestPointTo(target: Point2): import("./Vec3").default;
20
+ toString(): string;
19
21
  }
20
22
  export {};
@@ -97,6 +97,9 @@ export default class LineSegment2 {
97
97
  t: resultT,
98
98
  };
99
99
  }
100
+ intersects(other) {
101
+ return this.intersection(other) !== null;
102
+ }
100
103
  // Returns the closest point on this to [target]
101
104
  closestPointTo(target) {
102
105
  // Distance from P1 along this' direction.
@@ -113,4 +116,7 @@ export default class LineSegment2 {
113
116
  return this.p1;
114
117
  }
115
118
  }
119
+ toString() {
120
+ return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
121
+ }
116
122
  }
@@ -39,18 +39,22 @@ interface IntersectionResult {
39
39
  export default class Path {
40
40
  readonly startPoint: Point2;
41
41
  readonly parts: PathCommand[];
42
- private cachedGeometry;
43
42
  readonly bbox: Rect2;
44
43
  constructor(startPoint: Point2, parts: PathCommand[]);
44
+ private cachedGeometry;
45
45
  get geometry(): Array<LineSegment2 | Bezier>;
46
+ private cachedPolylineApproximation;
47
+ polylineApproximation(): LineSegment2[];
46
48
  static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
47
49
  intersection(line: LineSegment2): IntersectionResult[];
48
50
  mapPoints(mapping: (point: Point2) => Point2): Path;
49
51
  transformedBy(affineTransfm: Mat33): Path;
50
52
  union(other: Path | null): Path;
53
+ closedRoughlyIntersects(rect: Rect2): boolean;
51
54
  static fromRect(rect: Rect2, lineWidth?: number | null): Path;
52
55
  static fromRenderable(renderable: RenderablePathSpec): Path;
53
56
  toRenderable(fill: RenderingStyle): RenderablePathSpec;
57
+ private cachedStringVersion;
54
58
  toString(): string;
55
59
  serialize(): string;
56
60
  static toString(startPoint: Point2, parts: PathCommand[], onlyAbsCommands?: boolean): string;
@@ -15,6 +15,8 @@ export default class Path {
15
15
  this.startPoint = startPoint;
16
16
  this.parts = parts;
17
17
  this.cachedGeometry = null;
18
+ this.cachedPolylineApproximation = null;
19
+ this.cachedStringVersion = null;
18
20
  // Initial bounding box contains one point: the start point.
19
21
  this.bbox = Rect2.bboxOf([startPoint]);
20
22
  // Convert into a representation of the geometry (cache for faster intersection
@@ -52,6 +54,34 @@ export default class Path {
52
54
  this.cachedGeometry = geometry;
53
55
  return this.cachedGeometry;
54
56
  }
57
+ // Approximates this path with a group of line segments.
58
+ polylineApproximation() {
59
+ if (this.cachedPolylineApproximation) {
60
+ return this.cachedPolylineApproximation;
61
+ }
62
+ const points = [];
63
+ for (const part of this.parts) {
64
+ switch (part.kind) {
65
+ case PathCommandType.CubicBezierTo:
66
+ points.push(part.controlPoint1, part.controlPoint2, part.endPoint);
67
+ break;
68
+ case PathCommandType.QuadraticBezierTo:
69
+ points.push(part.controlPoint, part.endPoint);
70
+ break;
71
+ case PathCommandType.MoveTo:
72
+ case PathCommandType.LineTo:
73
+ points.push(part.point);
74
+ break;
75
+ }
76
+ }
77
+ const result = [];
78
+ let prevPoint = this.startPoint;
79
+ for (const point of points) {
80
+ result.push(new LineSegment2(prevPoint, point));
81
+ prevPoint = point;
82
+ }
83
+ return result;
84
+ }
55
85
  static computeBBoxForSegment(startPoint, part) {
56
86
  const points = [startPoint];
57
87
  let exhaustivenessCheck;
@@ -73,6 +103,9 @@ export default class Path {
73
103
  return Rect2.bboxOf(points);
74
104
  }
75
105
  intersection(line) {
106
+ if (!line.bbox.intersects(this.bbox)) {
107
+ return [];
108
+ }
76
109
  const result = [];
77
110
  for (const part of this.geometry) {
78
111
  if (part instanceof LineSegment2) {
@@ -162,6 +195,47 @@ export default class Path {
162
195
  ...other.parts,
163
196
  ]);
164
197
  }
198
+ // Treats this as a closed path and returns true if part of `rect` is roughly within
199
+ // this path's interior.
200
+ //
201
+ // Note: Assumes that this is a closed, non-self-intersecting path.
202
+ closedRoughlyIntersects(rect) {
203
+ if (rect.containsRect(this.bbox)) {
204
+ return true;
205
+ }
206
+ // Choose a point outside of the path.
207
+ const startPt = this.bbox.topLeft.minus(Vec2.of(1, 1));
208
+ const testPts = rect.corners;
209
+ const polygon = this.polylineApproximation();
210
+ for (const point of testPts) {
211
+ const testLine = new LineSegment2(point, startPt);
212
+ let intersectionCount = 0;
213
+ for (const line of polygon) {
214
+ if (line.intersects(testLine)) {
215
+ intersectionCount++;
216
+ }
217
+ }
218
+ // Odd? The point is within the polygon!
219
+ if (intersectionCount % 2 === 1) {
220
+ return true;
221
+ }
222
+ }
223
+ // Grow the rectangle for possible additional precision.
224
+ const grownRect = rect.grownBy(Math.min(rect.size.x, rect.size.y));
225
+ const edges = [];
226
+ for (const subrect of grownRect.divideIntoGrid(4, 4)) {
227
+ edges.push(...subrect.getEdges());
228
+ }
229
+ for (const edge of edges) {
230
+ for (const line of polygon) {
231
+ if (edge.intersects(line)) {
232
+ return true;
233
+ }
234
+ }
235
+ }
236
+ // Even? Probably no intersection.
237
+ return false;
238
+ }
165
239
  // Returns a path that outlines [rect]. If [lineWidth] is not given, the resultant path is
166
240
  // the outline of [rect]. Otherwise, the resultant path represents a line of width [lineWidth]
167
241
  // that traces [rect].
@@ -195,6 +269,9 @@ export default class Path {
195
269
  return new Path(startPoint, commands);
196
270
  }
197
271
  static fromRenderable(renderable) {
272
+ if (renderable.path) {
273
+ return renderable.path;
274
+ }
198
275
  return new Path(renderable.startPoint, renderable.commands);
199
276
  }
200
277
  toRenderable(fill) {
@@ -202,22 +279,25 @@ export default class Path {
202
279
  startPoint: this.startPoint,
203
280
  style: fill,
204
281
  commands: this.parts,
282
+ path: this,
205
283
  };
206
284
  }
207
285
  toString() {
286
+ if (this.cachedStringVersion) {
287
+ return this.cachedStringVersion;
288
+ }
208
289
  // Hueristic: Try to determine whether converting absolute to relative commands is worth it.
209
- // If we're near (0, 0), it probably isn't worth it and if bounding boxes are large,
210
- // it also probably isn't worth it.
211
- const makeRelativeCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.size.x) < 2
212
- && Math.abs(this.bbox.topLeft.y) > 10 && Math.abs(this.bbox.size.y) < 2;
213
- return Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
290
+ const makeRelativeCommands = Math.abs(this.bbox.topLeft.x) > 10 && Math.abs(this.bbox.topLeft.y) > 10;
291
+ const result = Path.toString(this.startPoint, this.parts, !makeRelativeCommands);
292
+ this.cachedStringVersion = result;
293
+ return result;
214
294
  }
215
295
  serialize() {
216
296
  return this.toString();
217
297
  }
218
298
  // @param onlyAbsCommands - True if we should avoid converting absolute coordinates to relative offsets -- such
219
299
  // conversions can lead to smaller output strings, but also take time.
220
- static toString(startPoint, parts, onlyAbsCommands = true) {
300
+ static toString(startPoint, parts, onlyAbsCommands) {
221
301
  const result = [];
222
302
  let prevPoint;
223
303
  const addCommand = (command, ...points) => {
@@ -452,7 +532,9 @@ export default class Path {
452
532
  lastPos = allArgs[allArgs.length - 1];
453
533
  }
454
534
  }
455
- return new Path(startPos !== null && startPos !== void 0 ? startPos : Vec2.zero, commands);
535
+ const result = new Path(startPos !== null && startPos !== void 0 ? startPos : Vec2.zero, commands);
536
+ result.cachedStringVersion = pathString;
537
+ return result;
456
538
  }
457
539
  }
458
540
  Path.empty = new Path(Vec2.zero, []);
@@ -1,6 +1,6 @@
1
1
  import LineSegment2 from './LineSegment2';
2
2
  import { Vec2 } from './Vec2';
3
- // invariant: w > 0, h > 0.
3
+ // invariant: w 0, h 0.
4
4
  export default class Rect2 {
5
5
  constructor(x, y, w, h) {
6
6
  this.x = x;
@@ -0,0 +1,11 @@
1
+ import Mat33 from './Mat33';
2
+ import Vec3 from './Vec3';
3
+ export default class Triangle {
4
+ readonly vertex1: Vec3;
5
+ readonly vertex2: Vec3;
6
+ readonly vertex3: Vec3;
7
+ constructor(vertex1: Vec3, vertex2: Vec3, vertex3: Vec3);
8
+ map(mapping: (vertex: Vec3) => Vec3): Triangle;
9
+ transformed2DBy(affineTransform: Mat33): Triangle;
10
+ transformedBy(linearTransform: Mat33): Triangle;
11
+ }
@@ -0,0 +1,19 @@
1
+ export default class Triangle {
2
+ constructor(vertex1, vertex2, vertex3) {
3
+ this.vertex1 = vertex1;
4
+ this.vertex2 = vertex2;
5
+ this.vertex3 = vertex3;
6
+ }
7
+ map(mapping) {
8
+ return new Triangle(mapping(this.vertex1), mapping(this.vertex2), mapping(this.vertex3));
9
+ }
10
+ // Transform, treating this as composed of 2D points.
11
+ transformed2DBy(affineTransform) {
12
+ return this.map(affineTransform.transformVec2);
13
+ }
14
+ // Transforms this by a linear transform --- verticies are treated as
15
+ // 3D points.
16
+ transformedBy(linearTransform) {
17
+ return this.map(linearTransform.transformVec3);
18
+ }
19
+ }
@@ -71,9 +71,9 @@ export default class Display {
71
71
  return this.dryInkRenderer.canRenderFromWithoutDataLoss(renderer);
72
72
  },
73
73
  blockResolution: cacheBlockResolution,
74
- cacheSize: 500 * 500 * 4 * 150,
75
- maxScale: 1.5,
76
- minComponentsPerCache: 45,
74
+ cacheSize: 600 * 600 * 4 * 90,
75
+ maxScale: 1.4,
76
+ minComponentsPerCache: 20,
77
77
  minComponentsToUseCache: 105,
78
78
  });
79
79
  this.editor.notifier.on(EditorEventType.DisplayResized, event => {
@@ -177,9 +177,10 @@ export default class RenderingCacheNode {
177
177
  }
178
178
  else {
179
179
  // Determine whether we already have rendered the items
180
+ const tooSmallToRender = (rect) => rect.w / this.region.w < 1 / this.cacheState.props.blockResolution.x;
180
181
  const leaves = [];
181
182
  for (const item of items) {
182
- leaves.push(...item.getLeavesIntersectingRegion(this.region, rect => rect.w / this.region.w < 2 / this.cacheState.props.blockResolution.x));
183
+ leaves.push(...item.getLeavesIntersectingRegion(this.region, tooSmallToRender));
183
184
  }
184
185
  sortLeavesByZIndex(leaves);
185
186
  const leavesByIds = this.computeSortedByLeafIds(leaves);
@@ -1,7 +1,7 @@
1
1
  import { LoadSaveDataTable } from '../../components/AbstractComponent';
2
2
  import { TextStyle } from '../../components/Text';
3
3
  import Mat33 from '../../math/Mat33';
4
- import { PathCommand } from '../../math/Path';
4
+ import Path, { PathCommand } from '../../math/Path';
5
5
  import Rect2 from '../../math/Rect2';
6
6
  import { Point2, Vec2 } from '../../math/Vec2';
7
7
  import Viewport from '../../Viewport';
@@ -10,6 +10,7 @@ export interface RenderablePathSpec {
10
10
  startPoint: Point2;
11
11
  commands: PathCommand[];
12
12
  style: RenderingStyle;
13
+ path?: Path;
13
14
  }
14
15
  export default abstract class AbstractRenderer {
15
16
  private viewport;
@@ -37,14 +37,14 @@ export default class CanvasRenderer extends AbstractRenderer {
37
37
  // Set parameters for lower/higher quality rendering
38
38
  setDraftMode(draftMode) {
39
39
  if (draftMode) {
40
- this.minSquareCurveApproxDist = 64;
41
- this.minRenderSizeBothDimens = 8;
42
- this.minRenderSizeAnyDimen = 2;
40
+ this.minSquareCurveApproxDist = 9;
41
+ this.minRenderSizeBothDimens = 2;
42
+ this.minRenderSizeAnyDimen = 0.5;
43
43
  }
44
44
  else {
45
- this.minSquareCurveApproxDist = 1;
46
- this.minRenderSizeBothDimens = 0.5;
47
- this.minRenderSizeAnyDimen = 0;
45
+ this.minSquareCurveApproxDist = 0.5;
46
+ this.minRenderSizeBothDimens = 0.3;
47
+ this.minRenderSizeAnyDimen = 1e-5;
48
48
  }
49
49
  }
50
50
  displaySize() {
@@ -5,30 +5,29 @@ import Rect2 from '../../math/Rect2';
5
5
  import { Point2, Vec2 } from '../../math/Vec2';
6
6
  import Viewport from '../../Viewport';
7
7
  import RenderingStyle from '../RenderingStyle';
8
- import AbstractRenderer from './AbstractRenderer';
8
+ import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
9
9
  export default class SVGRenderer extends AbstractRenderer {
10
10
  private elem;
11
- private currentPath;
12
- private pathStart;
13
11
  private lastPathStyle;
14
- private lastPath;
15
- private lastPathStart;
12
+ private lastPathString;
16
13
  private objectElems;
17
14
  private overwrittenAttrs;
18
15
  constructor(elem: SVGSVGElement, viewport: Viewport);
19
16
  setRootSVGAttribute(name: string, value: string | null): void;
20
17
  displaySize(): Vec2;
21
18
  clear(): void;
22
- protected beginPath(startPoint: Point2): void;
23
- protected endPath(style: RenderingStyle): void;
24
19
  private addPathToSVG;
20
+ drawPath(pathSpec: RenderablePathSpec): void;
25
21
  drawText(text: string, transform: Mat33, style: TextStyle): void;
26
22
  startObject(boundingBox: Rect2): void;
27
23
  endObject(loaderData?: LoadSaveDataTable): void;
28
- protected lineTo(point: Point2): void;
29
- protected moveTo(point: Point2): void;
30
- protected traceCubicBezierCurve(controlPoint1: Point2, controlPoint2: Point2, endPoint: Point2): void;
31
- protected traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
24
+ private unimplementedMessage;
25
+ protected beginPath(_startPoint: Point2): void;
26
+ protected endPath(_style: RenderingStyle): void;
27
+ protected lineTo(_point: Point2): void;
28
+ protected moveTo(_point: Point2): void;
29
+ protected traceCubicBezierCurve(_controlPoint1: Point2, _controlPoint2: Point2, _endPoint: Point2): void;
30
+ protected traceQuadraticBezierCurve(_controlPoint: Point2, _endPoint: Point2): void;
32
31
  drawPoints(...points: Point2[]): void;
33
32
  drawSVGElem(elem: SVGElement): void;
34
33
  isTooSmallToRender(_rect: Rect2): boolean;
@@ -1,5 +1,5 @@
1
1
  import Mat33 from '../../math/Mat33';
2
- import Path, { PathCommandType } from '../../math/Path';
2
+ import Path from '../../math/Path';
3
3
  import { toRoundedString } from '../../math/rounding';
4
4
  import { Vec2 } from '../../math/Vec2';
5
5
  import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
@@ -9,6 +9,8 @@ export default class SVGRenderer extends AbstractRenderer {
9
9
  constructor(elem, viewport) {
10
10
  super(viewport);
11
11
  this.elem = elem;
12
+ this.lastPathStyle = null;
13
+ this.lastPathString = [];
12
14
  this.objectElems = null;
13
15
  this.overwrittenAttrs = {};
14
16
  this.clear();
@@ -41,44 +43,16 @@ export default class SVGRenderer extends AbstractRenderer {
41
43
  }
42
44
  }
43
45
  this.overwrittenAttrs = {};
44
- }
45
- beginPath(startPoint) {
46
- var _a;
47
- this.currentPath = [];
48
- this.pathStart = this.canvasToScreen(startPoint);
49
- (_a = this.lastPathStart) !== null && _a !== void 0 ? _a : (this.lastPathStart = this.pathStart);
50
- }
51
- endPath(style) {
52
- var _a;
53
- if (this.currentPath == null) {
54
- throw new Error('No path exists to end! Make sure beginPath was called!');
55
- }
56
- // Try to extend the previous path, if possible
57
- if (style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) && this.lastPath != null) {
58
- this.lastPath.push({
59
- kind: PathCommandType.MoveTo,
60
- point: this.pathStart,
61
- }, ...this.currentPath);
62
- this.pathStart = null;
63
- this.currentPath = null;
64
- }
65
- else {
66
- this.addPathToSVG();
67
- this.lastPathStart = this.pathStart;
68
- this.lastPathStyle = style;
69
- this.lastPath = this.currentPath;
70
- this.pathStart = null;
71
- this.currentPath = null;
72
- }
46
+ this.lastPathString = [];
73
47
  }
74
48
  // Push [this.fullPath] to the SVG
75
49
  addPathToSVG() {
76
50
  var _a;
77
- if (!this.lastPathStyle || !this.lastPath) {
51
+ if (!this.lastPathStyle || this.lastPathString.length === 0) {
78
52
  return;
79
53
  }
80
54
  const pathElem = document.createElementNS(svgNameSpace, 'path');
81
- pathElem.setAttribute('d', Path.toString(this.lastPathStart, this.lastPath));
55
+ pathElem.setAttribute('d', this.lastPathString.join(' '));
82
56
  const style = this.lastPathStyle;
83
57
  pathElem.setAttribute('fill', style.fill.toHexString());
84
58
  if (style.stroke) {
@@ -88,6 +62,18 @@ export default class SVGRenderer extends AbstractRenderer {
88
62
  this.elem.appendChild(pathElem);
89
63
  (_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
90
64
  }
65
+ drawPath(pathSpec) {
66
+ var _a;
67
+ const style = pathSpec.style;
68
+ const path = Path.fromRenderable(pathSpec);
69
+ // Try to extend the previous path, if possible
70
+ if (!style.fill.eq((_a = this.lastPathStyle) === null || _a === void 0 ? void 0 : _a.fill) || this.lastPathString.length === 0) {
71
+ this.addPathToSVG();
72
+ this.lastPathStyle = style;
73
+ this.lastPathString = [];
74
+ }
75
+ this.lastPathString.push(path.toString());
76
+ }
91
77
  drawText(text, transform, style) {
92
78
  var _a, _b, _c;
93
79
  transform = this.getCanvasToScreenTransform().rightMul(transform);
@@ -118,8 +104,7 @@ export default class SVGRenderer extends AbstractRenderer {
118
104
  startObject(boundingBox) {
119
105
  super.startObject(boundingBox);
120
106
  // Only accumulate a path within an object
121
- this.lastPath = null;
122
- this.lastPathStart = null;
107
+ this.lastPathString = [];
123
108
  this.lastPathStyle = null;
124
109
  this.objectElems = [];
125
110
  }
@@ -146,40 +131,14 @@ export default class SVGRenderer extends AbstractRenderer {
146
131
  }
147
132
  }
148
133
  }
149
- lineTo(point) {
150
- point = this.canvasToScreen(point);
151
- this.currentPath.push({
152
- kind: PathCommandType.LineTo,
153
- point,
154
- });
155
- }
156
- moveTo(point) {
157
- point = this.canvasToScreen(point);
158
- this.currentPath.push({
159
- kind: PathCommandType.MoveTo,
160
- point,
161
- });
162
- }
163
- traceCubicBezierCurve(controlPoint1, controlPoint2, endPoint) {
164
- controlPoint1 = this.canvasToScreen(controlPoint1);
165
- controlPoint2 = this.canvasToScreen(controlPoint2);
166
- endPoint = this.canvasToScreen(endPoint);
167
- this.currentPath.push({
168
- kind: PathCommandType.CubicBezierTo,
169
- controlPoint1,
170
- controlPoint2,
171
- endPoint,
172
- });
173
- }
174
- traceQuadraticBezierCurve(controlPoint, endPoint) {
175
- controlPoint = this.canvasToScreen(controlPoint);
176
- endPoint = this.canvasToScreen(endPoint);
177
- this.currentPath.push({
178
- kind: PathCommandType.QuadraticBezierTo,
179
- controlPoint,
180
- endPoint,
181
- });
182
- }
134
+ // Not implemented -- use drawPath instead.
135
+ unimplementedMessage() { throw new Error('Not implemenented!'); }
136
+ beginPath(_startPoint) { this.unimplementedMessage(); }
137
+ endPath(_style) { this.unimplementedMessage(); }
138
+ lineTo(_point) { this.unimplementedMessage(); }
139
+ moveTo(_point) { this.unimplementedMessage(); }
140
+ traceCubicBezierCurve(_controlPoint1, _controlPoint2, _endPoint) { this.unimplementedMessage(); }
141
+ traceQuadraticBezierCurve(_controlPoint, _endPoint) { this.unimplementedMessage(); }
183
142
  drawPoints(...points) {
184
143
  points.map(point => {
185
144
  const elem = document.createElementNS(svgNameSpace, 'circle');