js-draw 1.16.1 → 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (132) hide show
  1. package/README.md +70 -10
  2. package/dist/bundle.js +2 -2
  3. package/dist/bundledStyles.js +1 -1
  4. package/dist/cjs/Editor.d.ts +75 -7
  5. package/dist/cjs/Editor.js +93 -90
  6. package/dist/cjs/Pointer.d.ts +2 -1
  7. package/dist/cjs/Pointer.js +9 -2
  8. package/dist/cjs/commands/localization.d.ts +1 -0
  9. package/dist/cjs/commands/localization.js +1 -0
  10. package/dist/cjs/commands/uniteCommands.d.ts +5 -1
  11. package/dist/cjs/commands/uniteCommands.js +33 -7
  12. package/dist/cjs/components/AbstractComponent.d.ts +17 -5
  13. package/dist/cjs/components/AbstractComponent.js +15 -15
  14. package/dist/cjs/components/Stroke.d.ts +4 -1
  15. package/dist/cjs/components/Stroke.js +158 -2
  16. package/dist/cjs/components/TextComponent.d.ts +36 -1
  17. package/dist/cjs/components/TextComponent.js +39 -1
  18. package/dist/cjs/components/builders/ArrowBuilder.js +1 -1
  19. package/dist/cjs/components/builders/PolylineBuilder.d.ts +35 -0
  20. package/dist/cjs/components/builders/PolylineBuilder.js +122 -0
  21. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
  22. package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +44 -51
  23. package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +1 -1
  24. package/dist/cjs/components/lib.d.ts +1 -0
  25. package/dist/cjs/components/lib.js +3 -1
  26. package/dist/cjs/components/util/StrokeSmoother.js +4 -4
  27. package/dist/cjs/image/EditorImage.d.ts +4 -1
  28. package/dist/cjs/image/EditorImage.js +5 -2
  29. package/dist/cjs/inputEvents.d.ts +11 -1
  30. package/dist/cjs/localizations/comments.d.ts +3 -0
  31. package/dist/cjs/localizations/comments.js +3 -0
  32. package/dist/cjs/localizations/de.js +1 -3
  33. package/dist/cjs/localizations/es.js +3 -3
  34. package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
  35. package/dist/cjs/rendering/renderers/CanvasRenderer.js +16 -0
  36. package/dist/cjs/rendering/renderers/SVGRenderer.js +1 -1
  37. package/dist/cjs/testing/createEditor.d.ts +2 -2
  38. package/dist/cjs/testing/createEditor.js +2 -2
  39. package/dist/cjs/toolbar/IconProvider.d.ts +9 -4
  40. package/dist/cjs/toolbar/IconProvider.js +21 -7
  41. package/dist/cjs/toolbar/localization.d.ts +6 -1
  42. package/dist/cjs/toolbar/localization.js +7 -2
  43. package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +24 -1
  44. package/dist/cjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
  45. package/dist/cjs/toolbar/widgets/EraserToolWidget.js +45 -5
  46. package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
  47. package/dist/cjs/toolbar/widgets/PenToolWidget.js +17 -4
  48. package/dist/cjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
  49. package/dist/cjs/toolbar/widgets/keybindings.js +1 -1
  50. package/dist/cjs/tools/Eraser.d.ts +24 -4
  51. package/dist/cjs/tools/Eraser.js +108 -21
  52. package/dist/cjs/tools/InputFilter/InputStabilizer.js +3 -3
  53. package/dist/cjs/tools/PasteHandler.js +35 -10
  54. package/dist/cjs/tools/Pen.js +2 -2
  55. package/dist/cjs/tools/SelectionTool/SelectionTool.js +23 -4
  56. package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.js +1 -1
  57. package/dist/cjs/tools/ToolController.d.ts +17 -1
  58. package/dist/cjs/tools/ToolController.js +21 -8
  59. package/dist/cjs/tools/lib.d.ts +1 -4
  60. package/dist/cjs/tools/lib.js +2 -4
  61. package/dist/cjs/tools/localization.d.ts +2 -2
  62. package/dist/cjs/tools/localization.js +2 -2
  63. package/dist/cjs/util/ClipboardHandler.d.ts +27 -0
  64. package/dist/cjs/util/ClipboardHandler.js +205 -0
  65. package/dist/cjs/util/ClipboardHandler.test.d.ts +1 -0
  66. package/dist/cjs/version.d.ts +5 -0
  67. package/dist/cjs/version.js +6 -1
  68. package/dist/mjs/Editor.d.ts +75 -7
  69. package/dist/mjs/Editor.mjs +93 -90
  70. package/dist/mjs/Pointer.d.ts +2 -1
  71. package/dist/mjs/Pointer.mjs +9 -2
  72. package/dist/mjs/commands/localization.d.ts +1 -0
  73. package/dist/mjs/commands/localization.mjs +1 -0
  74. package/dist/mjs/commands/uniteCommands.d.ts +5 -1
  75. package/dist/mjs/commands/uniteCommands.mjs +33 -7
  76. package/dist/mjs/components/AbstractComponent.d.ts +17 -5
  77. package/dist/mjs/components/AbstractComponent.mjs +15 -15
  78. package/dist/mjs/components/Stroke.d.ts +4 -1
  79. package/dist/mjs/components/Stroke.mjs +159 -3
  80. package/dist/mjs/components/TextComponent.d.ts +36 -1
  81. package/dist/mjs/components/TextComponent.mjs +40 -2
  82. package/dist/mjs/components/builders/ArrowBuilder.mjs +1 -1
  83. package/dist/mjs/components/builders/PolylineBuilder.d.ts +35 -0
  84. package/dist/mjs/components/builders/PolylineBuilder.mjs +115 -0
  85. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
  86. package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +45 -52
  87. package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +1 -1
  88. package/dist/mjs/components/lib.d.ts +1 -0
  89. package/dist/mjs/components/lib.mjs +1 -0
  90. package/dist/mjs/components/util/StrokeSmoother.mjs +4 -4
  91. package/dist/mjs/image/EditorImage.d.ts +4 -1
  92. package/dist/mjs/image/EditorImage.mjs +5 -2
  93. package/dist/mjs/inputEvents.d.ts +11 -1
  94. package/dist/mjs/localizations/comments.d.ts +3 -0
  95. package/dist/mjs/localizations/comments.mjs +3 -0
  96. package/dist/mjs/localizations/de.mjs +1 -3
  97. package/dist/mjs/localizations/es.mjs +3 -3
  98. package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
  99. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +16 -0
  100. package/dist/mjs/rendering/renderers/SVGRenderer.mjs +1 -1
  101. package/dist/mjs/testing/createEditor.d.ts +2 -2
  102. package/dist/mjs/testing/createEditor.mjs +2 -2
  103. package/dist/mjs/toolbar/IconProvider.d.ts +9 -4
  104. package/dist/mjs/toolbar/IconProvider.mjs +21 -7
  105. package/dist/mjs/toolbar/localization.d.ts +6 -1
  106. package/dist/mjs/toolbar/localization.mjs +7 -2
  107. package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +24 -1
  108. package/dist/mjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
  109. package/dist/mjs/toolbar/widgets/EraserToolWidget.mjs +47 -6
  110. package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
  111. package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +17 -4
  112. package/dist/mjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
  113. package/dist/mjs/toolbar/widgets/keybindings.mjs +1 -1
  114. package/dist/mjs/tools/Eraser.d.ts +24 -4
  115. package/dist/mjs/tools/Eraser.mjs +108 -22
  116. package/dist/mjs/tools/InputFilter/InputStabilizer.mjs +3 -3
  117. package/dist/mjs/tools/PasteHandler.mjs +35 -10
  118. package/dist/mjs/tools/Pen.mjs +2 -2
  119. package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +23 -4
  120. package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.mjs +1 -1
  121. package/dist/mjs/tools/ToolController.d.ts +17 -1
  122. package/dist/mjs/tools/ToolController.mjs +21 -8
  123. package/dist/mjs/tools/lib.d.ts +1 -4
  124. package/dist/mjs/tools/lib.mjs +1 -4
  125. package/dist/mjs/tools/localization.d.ts +2 -2
  126. package/dist/mjs/tools/localization.mjs +2 -2
  127. package/dist/mjs/util/ClipboardHandler.d.ts +27 -0
  128. package/dist/mjs/util/ClipboardHandler.mjs +200 -0
  129. package/dist/mjs/util/ClipboardHandler.test.d.ts +1 -0
  130. package/dist/mjs/version.d.ts +5 -0
  131. package/dist/mjs/version.mjs +6 -1
  132. package/package.json +6 -6
@@ -44,8 +44,8 @@ class Stroke extends AbstractComponent_1.default {
44
44
  * ]);
45
45
  * ```
46
46
  */
47
- constructor(parts) {
48
- super('stroke');
47
+ constructor(parts, initialZIndex) {
48
+ super('stroke', initialZIndex);
49
49
  // @internal
50
50
  // eslint-disable-next-line @typescript-eslint/prefer-as-const
51
51
  this.isRestylableComponent = true;
@@ -123,6 +123,162 @@ class Stroke extends AbstractComponent_1.default {
123
123
  editor.queueRerender();
124
124
  }
125
125
  }
126
+ /** @beta -- May fail for concave `path`s */
127
+ withRegionErased(eraserPath, viewport) {
128
+ const polyline = eraserPath.polylineApproximation();
129
+ const isPointInsideEraser = (point) => {
130
+ return eraserPath.closedContainsPoint(point);
131
+ };
132
+ const newStrokes = [];
133
+ let failedAssertions = false;
134
+ for (const part of this.parts) {
135
+ const path = part.path;
136
+ const makeStroke = (path) => {
137
+ if (part.style.fill.a > 0) {
138
+ // Remove visually empty paths.
139
+ if (path.parts.length < 1 || (path.parts.length === 1 && path.parts[0].kind === math_1.PathCommandType.LineTo)) {
140
+ // TODO: If this isn't present, a very large number of strokes are created while erasing.
141
+ return null;
142
+ }
143
+ else {
144
+ // Filled paths must be closed (allows for optimizations elsewhere)
145
+ path = path.asClosed();
146
+ }
147
+ }
148
+ if (isNaN(path.getExactBBox().area)) {
149
+ console.warn('Prevented creating a stroke with NaN area');
150
+ failedAssertions = true;
151
+ return null;
152
+ }
153
+ return new Stroke([(0, RenderablePathSpec_1.pathToRenderable)(path, part.style)], this.getZIndex());
154
+ };
155
+ const intersectionPoints = [];
156
+ // If stroked, finds intersections with the middle of the stroke.
157
+ // If filled, finds intersections with the edge of the stroke.
158
+ for (const segment of polyline) {
159
+ intersectionPoints.push(...path.intersection(segment));
160
+ }
161
+ // When stroked, if the stroke width is significantly larger than the eraser,
162
+ // it can't intersect both the edge of the stroke and its middle at the same time
163
+ // (generally, erasing is triggered by the eraser touching the edge of this stroke).
164
+ //
165
+ // As such, we also look for intersections along the edge of this, if none with the
166
+ // center were found, but only within a certain range of sizes because:
167
+ // 1. Intersection testing with stroked paths is generally much slower than with
168
+ // non-stroked paths.
169
+ // 2. If zoomed in significantly, it's unlikely that the user wants to erase a large
170
+ // part of the stroke.
171
+ let isErasingFromEdge = false;
172
+ if (intersectionPoints.length === 0
173
+ && part.style.stroke
174
+ && part.style.stroke.width > eraserPath.bbox.minDimension * 0.3
175
+ && part.style.stroke.width < eraserPath.bbox.maxDimension * 30) {
176
+ for (const segment of polyline) {
177
+ intersectionPoints.push(...path.intersection(segment, part.style.stroke.width / 2));
178
+ }
179
+ isErasingFromEdge = true;
180
+ }
181
+ // Sort first by curve index, then by parameter value
182
+ intersectionPoints.sort(math_1.comparePathIndices);
183
+ const isInsideJustBeforeFirst = (() => {
184
+ if (intersectionPoints.length === 0) {
185
+ return false;
186
+ }
187
+ // The eraser may not be near the center of the curve -- approximate.
188
+ if (isErasingFromEdge) {
189
+ return intersectionPoints[0].curveIndex === 0 && intersectionPoints[0].parameterValue <= 0;
190
+ }
191
+ const justBeforeFirstIntersection = (0, math_1.stepPathIndexBy)(intersectionPoints[0], -1e-10);
192
+ return isPointInsideEraser(path.at(justBeforeFirstIntersection));
193
+ })();
194
+ let intersectionCount = isInsideJustBeforeFirst ? 1 : 0;
195
+ const addNewPath = (path, knownToBeInside) => {
196
+ const component = makeStroke(path);
197
+ let isInside = intersectionCount % 2 === 1;
198
+ intersectionCount++;
199
+ if (knownToBeInside !== undefined) {
200
+ isInside = knownToBeInside;
201
+ }
202
+ // Here, we work around bugs in the underlying Bezier curve library
203
+ // (including https://github.com/Pomax/bezierjs/issues/179).
204
+ // Even if not all intersections are returned correctly, we still want
205
+ // isInside to be roughly correct.
206
+ if (knownToBeInside === undefined && !isInside && eraserPath.closedContainsPoint(path.getExactBBox().center)) {
207
+ isInside = !isInside;
208
+ }
209
+ if (!component) {
210
+ return;
211
+ }
212
+ // Assertion: Avoid deleting sections that are much larger than the eraser.
213
+ failedAssertions ||= isInside && path.getExactBBox().maxDimension > eraserPath.getExactBBox().maxDimension * 2;
214
+ if (!isInside) {
215
+ newStrokes.push(component);
216
+ }
217
+ };
218
+ if (part.style.fill.a === 0) { // Not filled?
219
+ // An additional case where we erase completely -- without the padding of the stroke,
220
+ // the path is smaller than the eraser (allows us to erase dots completely).
221
+ const shouldEraseCompletely = eraserPath.getExactBBox().maxDimension / 10 > path.getExactBBox().maxDimension;
222
+ if (!shouldEraseCompletely) {
223
+ const split = path.splitAt(intersectionPoints, { mapNewPoint: p => viewport.roundPoint(p) });
224
+ for (const splitPart of split) {
225
+ addNewPath(splitPart);
226
+ }
227
+ }
228
+ }
229
+ else if (intersectionPoints.length >= 2 && intersectionPoints.length % 2 === 0) {
230
+ // TODO: Support subtractive erasing on small scales -- see https://github.com/personalizedrefrigerator/js-draw/pull/63/commits/568686e2384219ad0bb07617ea4efff1540aed00
231
+ // for a broken implementation.
232
+ //
233
+ // We currently assume that a 4-point intersection means that the intersection
234
+ // looks similar to this:
235
+ // -----------
236
+ // | STROKE |
237
+ // | |
238
+ //%%x-----------x%%%%%%%
239
+ //% %
240
+ //% ERASER %
241
+ //% %
242
+ //%%x-----------x%%%%%%%
243
+ // | STROKE |
244
+ // -----------
245
+ //
246
+ // Our goal is to separate STROKE into the contiguous parts outside
247
+ // of the eraser (as shown above).
248
+ //
249
+ // To do this, we split STROKE at each intersection:
250
+ // 3 3 3 3 3 3
251
+ // 3 STROKE 3
252
+ // 3 3
253
+ // x x
254
+ // 2 4
255
+ // 2 STROKE 4
256
+ // 2 4
257
+ // x x
258
+ // 1 STROKE 5
259
+ // . 5 5 5 5 5
260
+ // ^
261
+ // Start
262
+ //
263
+ // The difficulty here is correctly pairing edges to create the the output
264
+ // strokes, particularly because we don't know the order of intersection points.
265
+ const parts = path.splitAt(intersectionPoints, { mapNewPoint: p => viewport.roundPoint(p) });
266
+ for (let i = 0; i < Math.floor(parts.length / 2); i++) {
267
+ addNewPath(parts[i].union(parts[parts.length - i - 1]).asClosed());
268
+ }
269
+ if (parts.length % 2 !== 0) {
270
+ addNewPath(parts[Math.floor(parts.length / 2)].asClosed());
271
+ }
272
+ }
273
+ else {
274
+ addNewPath(path, false);
275
+ }
276
+ }
277
+ if (failedAssertions) {
278
+ return [this];
279
+ }
280
+ return newStrokes;
281
+ }
126
282
  intersects(line) {
127
283
  for (const part of this.parts) {
128
284
  const strokeWidth = part.style.stroke?.width;
@@ -19,6 +19,41 @@ export declare enum TextTransformMode {
19
19
  type TextElement = TextComponent | string;
20
20
  /**
21
21
  * Displays text.
22
+ *
23
+ * A `TextComponent` is a collection of `TextElement`s (`string`s or {@link TextComponent}s).
24
+ *
25
+ * **Example**:
26
+ *
27
+ * ```ts,runnable
28
+ * import { Editor, TextComponent, Mat33, Vec2, Color4, TextRenderingStyle } from 'js-draw';
29
+ * const editor = new Editor(document.body);
30
+ * editor.dispatch(editor.setBackgroundStyle({ color: Color4.black, autoresize: true ));
31
+ * ---visible---
32
+ * /// Adding a simple TextComponent
33
+ * ///------------------------------
34
+ *
35
+ * const positioning1 = Mat33.translation(Vec2.of(10, 10));
36
+ * const style: TextRenderingStyle = {
37
+ * fontFamily: 'sans', size: 12, renderingStyle: { fill: Color4.green },
38
+ * };
39
+ *
40
+ * editor.dispatch(
41
+ * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
42
+ * );
43
+ *
44
+ *
45
+ * /// Adding nested TextComponents
46
+ * ///-----------------------------
47
+ *
48
+ * // Add another TextComponent that contains text and a TextComponent. Observe that '[Test]'
49
+ * // is placed directly after 'Test'.
50
+ * const positioning2 = Mat33.translation(Vec2.of(10, 50));
51
+ * editor.dispatch(
52
+ * editor.image.addElement(
53
+ * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
54
+ * ),
55
+ * );
56
+ * ```
22
57
  */
23
58
  export default class TextComponent extends AbstractComponent implements RestyleableComponent {
24
59
  protected readonly textObjects: Array<TextElement>;
@@ -32,7 +67,7 @@ export default class TextComponent extends AbstractComponent implements Restylea
32
67
  *
33
68
  * @see {@link fromLines}
34
69
  */
35
- constructor(textObjects: Array<TextElement>, transform: Mat33, style: TextRenderingStyle, transformMode?: TextTransformMode);
70
+ constructor(textObjects: Array<TextElement>, transform: Mat33, style?: TextRenderingStyle, transformMode?: TextTransformMode);
36
71
  static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextRenderingStyle): void;
37
72
  private static textMeasuringCtx;
38
73
  private static estimateTextDimens;
@@ -20,8 +20,46 @@ var TextTransformMode;
20
20
  /**Relatively positioned in the Y direction, absolutely positioned in the X direction. */
21
21
  TextTransformMode[TextTransformMode["RELATIVE_Y_ABSOLUTE_X"] = 3] = "RELATIVE_Y_ABSOLUTE_X";
22
22
  })(TextTransformMode || (exports.TextTransformMode = TextTransformMode = {}));
23
+ const defaultTextStyle = {
24
+ fontFamily: 'sans', size: 12, renderingStyle: { fill: math_1.Color4.purple },
25
+ };
23
26
  /**
24
27
  * Displays text.
28
+ *
29
+ * A `TextComponent` is a collection of `TextElement`s (`string`s or {@link TextComponent}s).
30
+ *
31
+ * **Example**:
32
+ *
33
+ * ```ts,runnable
34
+ * import { Editor, TextComponent, Mat33, Vec2, Color4, TextRenderingStyle } from 'js-draw';
35
+ * const editor = new Editor(document.body);
36
+ * editor.dispatch(editor.setBackgroundStyle({ color: Color4.black, autoresize: true ));
37
+ * ---visible---
38
+ * /// Adding a simple TextComponent
39
+ * ///------------------------------
40
+ *
41
+ * const positioning1 = Mat33.translation(Vec2.of(10, 10));
42
+ * const style: TextRenderingStyle = {
43
+ * fontFamily: 'sans', size: 12, renderingStyle: { fill: Color4.green },
44
+ * };
45
+ *
46
+ * editor.dispatch(
47
+ * editor.image.addElement(new TextComponent(['Hello, world'], positioning1, style)),
48
+ * );
49
+ *
50
+ *
51
+ * /// Adding nested TextComponents
52
+ * ///-----------------------------
53
+ *
54
+ * // Add another TextComponent that contains text and a TextComponent. Observe that '[Test]'
55
+ * // is placed directly after 'Test'.
56
+ * const positioning2 = Mat33.translation(Vec2.of(10, 50));
57
+ * editor.dispatch(
58
+ * editor.image.addElement(
59
+ * new TextComponent([ new TextComponent(['Test'], positioning1, style), '[Test]' ], positioning2, style)
60
+ * ),
61
+ * );
62
+ * ```
25
63
  */
26
64
  class TextComponent extends AbstractComponent_1.default {
27
65
  /**
@@ -31,7 +69,7 @@ class TextComponent extends AbstractComponent_1.default {
31
69
  */
32
70
  constructor(textObjects,
33
71
  // Transformation relative to this component's parent element.
34
- transform, style,
72
+ transform, style = defaultTextStyle,
35
73
  // @internal
36
74
  transformMode = TextTransformMode.ABSOLUTE_XY) {
37
75
  super(componentTypeId);
@@ -27,7 +27,7 @@ class ArrowBuilder {
27
27
  const lineStartPoint = this.startPoint.pos;
28
28
  const endPoint = this.endPoint.pos;
29
29
  const toEnd = endPoint.minus(lineStartPoint).normalized();
30
- const arrowLength = endPoint.minus(lineStartPoint).length();
30
+ const arrowLength = endPoint.distanceTo(lineStartPoint);
31
31
  // Ensure that the arrow tip is smaller than the arrow.
32
32
  const arrowTipSize = Math.min(this.getLineWidth(), arrowLength / 2);
33
33
  const startSize = this.startPoint.width / 2;
@@ -0,0 +1,35 @@
1
+ import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
2
+ import RenderablePathSpec from '../../rendering/RenderablePathSpec';
3
+ import { Rect2 } from '@js-draw/math';
4
+ import Stroke from '../Stroke';
5
+ import Viewport from '../../Viewport';
6
+ import { StrokeDataPoint } from '../../types';
7
+ import { ComponentBuilder, ComponentBuilderFactory } from './types';
8
+ import RenderingStyle from '../../rendering/RenderingStyle';
9
+ /**
10
+ * Creates strokes from line segments rather than Bézier curves.
11
+ *
12
+ */
13
+ export declare const makePolylineBuilder: ComponentBuilderFactory;
14
+ export default class PolylineBuilder implements ComponentBuilder {
15
+ private minFitAllowed;
16
+ private viewport;
17
+ private parts;
18
+ private bbox;
19
+ private averageWidth;
20
+ private widthAverageNumSamples;
21
+ private lastPoint;
22
+ private startPoint;
23
+ private lastLineSegment;
24
+ constructor(startPoint: StrokeDataPoint, minFitAllowed: number, viewport: Viewport);
25
+ getBBox(): Rect2;
26
+ protected getRenderingStyle(): RenderingStyle;
27
+ protected previewCurrentPath(): RenderablePathSpec;
28
+ protected previewFullPath(): RenderablePathSpec[];
29
+ preview(renderer: AbstractRenderer): void;
30
+ build(): Stroke;
31
+ private getMinFit;
32
+ private roundPoint;
33
+ private roundDistance;
34
+ addPoint(newPoint: StrokeDataPoint): void;
35
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makePolylineBuilder = void 0;
7
+ const math_1 = require("@js-draw/math");
8
+ const Stroke_1 = __importDefault(require("../Stroke"));
9
+ const Viewport_1 = __importDefault(require("../../Viewport"));
10
+ const makeShapeFitAutocorrect_1 = __importDefault(require("./autocorrect/makeShapeFitAutocorrect"));
11
+ /**
12
+ * Creates strokes from line segments rather than Bézier curves.
13
+ *
14
+ */
15
+ exports.makePolylineBuilder = (0, makeShapeFitAutocorrect_1.default)((initialPoint, viewport) => {
16
+ const minFit = viewport.getSizeOfPixelOnCanvas();
17
+ return new PolylineBuilder(initialPoint, minFit, viewport);
18
+ });
19
+ class PolylineBuilder {
20
+ constructor(startPoint, minFitAllowed, viewport) {
21
+ this.minFitAllowed = minFitAllowed;
22
+ this.viewport = viewport;
23
+ this.parts = [];
24
+ this.widthAverageNumSamples = 1;
25
+ this.lastLineSegment = null;
26
+ this.averageWidth = startPoint.width;
27
+ this.startPoint = {
28
+ ...startPoint,
29
+ pos: this.roundPoint(startPoint.pos),
30
+ };
31
+ this.lastPoint = this.startPoint.pos;
32
+ this.bbox = new math_1.Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0);
33
+ this.parts = [
34
+ {
35
+ kind: math_1.PathCommandType.MoveTo,
36
+ point: this.startPoint.pos,
37
+ },
38
+ ];
39
+ }
40
+ getBBox() {
41
+ return this.bbox.grownBy(this.averageWidth);
42
+ }
43
+ getRenderingStyle() {
44
+ return {
45
+ fill: math_1.Color4.transparent,
46
+ stroke: {
47
+ color: this.startPoint.color,
48
+ width: this.roundDistance(this.averageWidth),
49
+ }
50
+ };
51
+ }
52
+ previewCurrentPath() {
53
+ const startPoint = this.startPoint.pos;
54
+ const commands = [...this.parts];
55
+ // TODO: For now, this is necesary for the path to be visible.
56
+ if (commands.length <= 1) {
57
+ commands.push({
58
+ kind: math_1.PathCommandType.LineTo,
59
+ point: startPoint.plus(math_1.Vec2.of(this.averageWidth / 4, 0)),
60
+ });
61
+ }
62
+ return {
63
+ startPoint,
64
+ commands,
65
+ style: this.getRenderingStyle(),
66
+ };
67
+ }
68
+ previewFullPath() {
69
+ return [this.previewCurrentPath()];
70
+ }
71
+ preview(renderer) {
72
+ const paths = this.previewFullPath();
73
+ if (paths) {
74
+ const approxBBox = this.viewport.visibleRect;
75
+ renderer.startObject(approxBBox);
76
+ for (const path of paths) {
77
+ renderer.drawPath(path);
78
+ }
79
+ renderer.endObject();
80
+ }
81
+ }
82
+ build() {
83
+ return new Stroke_1.default(this.previewFullPath());
84
+ }
85
+ getMinFit() {
86
+ let minFit = Math.min(this.minFitAllowed, this.averageWidth / 3);
87
+ if (minFit < 1e-10) {
88
+ minFit = this.minFitAllowed;
89
+ }
90
+ return minFit;
91
+ }
92
+ roundPoint(point) {
93
+ const minFit = this.getMinFit();
94
+ return Viewport_1.default.roundPoint(point, minFit);
95
+ }
96
+ roundDistance(dist) {
97
+ const minFit = this.getMinFit();
98
+ return Viewport_1.default.roundPoint(dist, minFit);
99
+ }
100
+ addPoint(newPoint) {
101
+ this.widthAverageNumSamples++;
102
+ this.averageWidth =
103
+ this.averageWidth * (this.widthAverageNumSamples - 1) / this.widthAverageNumSamples
104
+ + newPoint.width / this.widthAverageNumSamples;
105
+ const roundedPoint = this.roundPoint(newPoint.pos);
106
+ if (!roundedPoint.eq(this.lastPoint)) {
107
+ // If almost exactly in the same line as the previous
108
+ if (this.lastLineSegment && this.lastLineSegment.direction.dot(roundedPoint.minus(this.lastPoint).normalized()) > 0.997) {
109
+ this.parts.pop();
110
+ this.lastPoint = this.lastLineSegment.p1;
111
+ }
112
+ this.parts.push({
113
+ kind: math_1.PathCommandType.LineTo,
114
+ point: this.roundPoint(newPoint.pos),
115
+ });
116
+ this.bbox = this.bbox.grownToPoint(roundedPoint);
117
+ this.lastLineSegment = new math_1.LineSegment2(this.lastPoint, roundedPoint);
118
+ this.lastPoint = roundedPoint;
119
+ }
120
+ }
121
+ }
122
+ exports.default = PolylineBuilder;
@@ -12,6 +12,7 @@ export default class PressureSensitiveFreehandLineBuilder implements ComponentBu
12
12
  private isFirstSegment;
13
13
  private pathStartConnector;
14
14
  private mostRecentConnector;
15
+ private nextCurveStartConnector;
15
16
  private upperSegments;
16
17
  private lowerSegments;
17
18
  private lastUpperBezier;
@@ -25,7 +26,6 @@ export default class PressureSensitiveFreehandLineBuilder implements ComponentBu
25
26
  private getRenderingStyle;
26
27
  private previewCurrentPath;
27
28
  private previewFullPath;
28
- private previewStroke;
29
29
  preview(renderer: AbstractRenderer): void;
30
30
  build(): Stroke;
31
31
  private roundPoint;