js-draw 0.5.0 → 0.7.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 (92) hide show
  1. package/.firebase/hosting.ZG9jcw.cache +338 -0
  2. package/.github/ISSUE_TEMPLATE/translation.md +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.d.ts +8 -6
  6. package/dist/src/Editor.js +8 -4
  7. package/dist/src/EditorImage.d.ts +3 -0
  8. package/dist/src/EditorImage.js +7 -0
  9. package/dist/src/SVGLoader.js +7 -8
  10. package/dist/src/components/AbstractComponent.d.ts +1 -0
  11. package/dist/src/components/AbstractComponent.js +4 -0
  12. package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
  13. package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
  14. package/dist/src/components/Stroke.js +1 -0
  15. package/dist/src/components/Text.d.ts +11 -8
  16. package/dist/src/components/Text.js +63 -20
  17. package/dist/src/components/UnknownSVGObject.d.ts +1 -0
  18. package/dist/src/components/UnknownSVGObject.js +3 -0
  19. package/dist/src/components/builders/FreehandLineBuilder.d.ts +9 -2
  20. package/dist/src/components/builders/FreehandLineBuilder.js +129 -30
  21. package/dist/src/components/lib.d.ts +2 -2
  22. package/dist/src/components/lib.js +2 -2
  23. package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
  24. package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
  25. package/dist/src/rendering/renderers/SVGRenderer.js +49 -22
  26. package/dist/src/testing/beforeEachFile.js +4 -0
  27. package/dist/src/toolbar/HTMLToolbar.js +2 -3
  28. package/dist/src/toolbar/IconProvider.d.ts +30 -0
  29. package/dist/src/toolbar/IconProvider.js +417 -0
  30. package/dist/src/toolbar/lib.d.ts +1 -1
  31. package/dist/src/toolbar/lib.js +1 -2
  32. package/dist/src/toolbar/localization.d.ts +0 -1
  33. package/dist/src/toolbar/localization.js +0 -1
  34. package/dist/src/toolbar/makeColorInput.js +1 -2
  35. package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
  36. package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
  37. package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
  38. package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
  39. package/dist/src/toolbar/widgets/PenToolWidget.js +10 -8
  40. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
  41. package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
  42. package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
  43. package/dist/src/tools/PanZoom.d.ts +1 -1
  44. package/dist/src/tools/PanZoom.js +4 -1
  45. package/dist/src/tools/PasteHandler.js +2 -22
  46. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
  47. package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
  48. package/dist/src/tools/TextTool.d.ts +4 -0
  49. package/dist/src/tools/TextTool.js +73 -15
  50. package/dist/src/tools/ToolController.js +1 -0
  51. package/dist/src/tools/localization.d.ts +1 -0
  52. package/dist/src/tools/localization.js +1 -0
  53. package/package.json +1 -1
  54. package/src/Editor.toSVG.test.ts +27 -0
  55. package/src/Editor.ts +15 -9
  56. package/src/EditorImage.ts +9 -0
  57. package/src/SVGLoader.test.ts +57 -0
  58. package/src/SVGLoader.ts +9 -10
  59. package/src/components/AbstractComponent.ts +5 -0
  60. package/src/components/SVGGlobalAttributesObject.ts +4 -0
  61. package/src/components/Stroke.ts +1 -0
  62. package/src/components/Text.test.ts +3 -18
  63. package/src/components/Text.ts +78 -25
  64. package/src/components/UnknownSVGObject.ts +4 -0
  65. package/src/components/builders/FreehandLineBuilder.ts +162 -34
  66. package/src/components/lib.ts +3 -3
  67. package/src/rendering/renderers/CanvasRenderer.ts +2 -2
  68. package/src/rendering/renderers/SVGRenderer.ts +50 -24
  69. package/src/testing/beforeEachFile.ts +6 -1
  70. package/src/toolbar/HTMLToolbar.ts +2 -3
  71. package/src/toolbar/IconProvider.ts +480 -0
  72. package/src/toolbar/lib.ts +1 -1
  73. package/src/toolbar/localization.ts +0 -2
  74. package/src/toolbar/makeColorInput.ts +1 -2
  75. package/src/toolbar/widgets/BaseWidget.ts +1 -2
  76. package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
  77. package/src/toolbar/widgets/HandToolWidget.ts +42 -20
  78. package/src/toolbar/widgets/PenToolWidget.ts +11 -8
  79. package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
  80. package/src/toolbar/widgets/TextToolWidget.ts +1 -2
  81. package/src/tools/PanZoom.ts +4 -1
  82. package/src/tools/PasteHandler.ts +2 -24
  83. package/src/tools/SelectionTool/SelectionTool.css +1 -0
  84. package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
  85. package/src/tools/SelectionTool/SelectionTool.ts +73 -4
  86. package/src/tools/TextTool.ts +82 -17
  87. package/src/tools/ToolController.ts +1 -0
  88. package/src/tools/localization.ts +4 -0
  89. package/typedoc.json +5 -1
  90. package/dist/src/toolbar/icons.d.ts +0 -20
  91. package/dist/src/toolbar/icons.js +0 -385
  92. package/src/toolbar/icons.ts +0 -443
@@ -28,6 +28,7 @@ import Display, { RenderingMode } from './rendering/Display';
28
28
  import Pointer from './Pointer';
29
29
  import Rect2 from './math/Rect2';
30
30
  import { EditorLocalization } from './localization';
31
+ import IconProvider from './toolbar/IconProvider';
31
32
  declare type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
32
33
  declare type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;
33
34
  export interface EditorSettings {
@@ -44,6 +45,7 @@ export interface EditorSettings {
44
45
  /** Minimum zoom fraction (e.g. 0.5 → 50% zoom). */
45
46
  minZoom: number;
46
47
  maxZoom: number;
48
+ iconProvider: IconProvider;
47
49
  }
48
50
  export declare class Editor {
49
51
  private container;
@@ -81,18 +83,18 @@ export declare class Editor {
81
83
  * editor.dispatch(addElementCommand);
82
84
  * ```
83
85
  */
84
- image: EditorImage;
86
+ readonly image: EditorImage;
85
87
  /** Viewport for the exported/imported image. */
86
88
  private importExportViewport;
87
89
  /** @internal */
88
- localization: EditorLocalization;
89
- viewport: Viewport;
90
- toolController: ToolController;
90
+ readonly localization: EditorLocalization;
91
+ readonly icons: IconProvider;
92
+ readonly viewport: Viewport;
93
+ readonly toolController: ToolController;
91
94
  /**
92
95
  * Global event dispatcher/subscriber.
93
- * @see {@link types.EditorEventType}
94
96
  */
95
- notifier: EditorNotifier;
97
+ readonly notifier: EditorNotifier;
96
98
  private loadingWarning;
97
99
  private accessibilityAnnounceArea;
98
100
  private accessibilityControlArea;
@@ -41,6 +41,8 @@ import SVGLoader from './SVGLoader';
41
41
  import Pointer from './Pointer';
42
42
  import Mat33 from './math/Mat33';
43
43
  import getLocalizationTable from './localizations/getLocalizationTable';
44
+ import IconProvider from './toolbar/IconProvider';
45
+ import { toRoundedString } from './math/rounding';
44
46
  // { @inheritDoc Editor! }
45
47
  export class Editor {
46
48
  /**
@@ -67,7 +69,7 @@ export class Editor {
67
69
  * ```
68
70
  */
69
71
  constructor(parent, settings = {}) {
70
- var _a, _b, _c, _d;
72
+ var _a, _b, _c, _d, _e;
71
73
  this.eventListenerTargets = [];
72
74
  this.previousAccessibilityAnnouncement = '';
73
75
  this.pointers = {};
@@ -86,7 +88,9 @@ export class Editor {
86
88
  localization: this.localization,
87
89
  minZoom: (_c = settings.minZoom) !== null && _c !== void 0 ? _c : 2e-10,
88
90
  maxZoom: (_d = settings.maxZoom) !== null && _d !== void 0 ? _d : 1e12,
91
+ iconProvider: (_e = settings.iconProvider) !== null && _e !== void 0 ? _e : new IconProvider(),
89
92
  };
93
+ this.icons = this.settings.iconProvider;
90
94
  this.container = document.createElement('div');
91
95
  this.renderingRegion = document.createElement('div');
92
96
  this.container.appendChild(this.renderingRegion);
@@ -635,9 +639,9 @@ export class Editor {
635
639
  importExportViewport.resetTransform(origTransform);
636
640
  // Just show the main region
637
641
  const rect = importExportViewport.visibleRect;
638
- result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
639
- result.setAttribute('width', `${rect.w}`);
640
- result.setAttribute('height', `${rect.h}`);
642
+ result.setAttribute('viewBox', [rect.x, rect.y, rect.w, rect.h].map(part => toRoundedString(part)).join(' '));
643
+ result.setAttribute('width', toRoundedString(rect.w));
644
+ result.setAttribute('height', toRoundedString(rect.h));
641
645
  // Ensure the image can be identified as an SVG if downloaded.
642
646
  // See https://jwatt.org/svg/authoring/
643
647
  result.setAttribute('version', '1.1');
@@ -16,6 +16,9 @@ export default class EditorImage {
16
16
  render(renderer: AbstractRenderer, viewport: Viewport): void;
17
17
  /** Renders all nodes, even ones not within the viewport. @internal */
18
18
  renderAll(renderer: AbstractRenderer): void;
19
+ /** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
20
+ getAllElements(): AbstractComponent[];
21
+ /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
19
22
  getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
20
23
  /** @internal */
21
24
  onDestroyElement(elem: AbstractComponent): void;
@@ -39,6 +39,13 @@ export default class EditorImage {
39
39
  leaf.getContent().render(renderer, leaf.getBBox());
40
40
  }
41
41
  }
42
+ /** @returns all elements in the image, sorted by z-index. This can be slow for large images. */
43
+ getAllElements() {
44
+ const leaves = this.root.getLeaves();
45
+ sortLeavesByZIndex(leaves);
46
+ return leaves.map(leaf => leaf.getContent());
47
+ }
48
+ /** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
42
49
  getElementsIntersectingRegion(region) {
43
50
  const leaves = this.root.getLeavesIntersectingRegion(region);
44
51
  sortLeavesByZIndex(leaves);
@@ -11,7 +11,7 @@ import Color4 from './Color4';
11
11
  import ImageComponent from './components/ImageComponent';
12
12
  import Stroke from './components/Stroke';
13
13
  import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
14
- import Text from './components/Text';
14
+ import TextComponent from './components/Text';
15
15
  import UnknownSVGObject from './components/UnknownSVGObject';
16
16
  import Mat33 from './math/Mat33';
17
17
  import Path from './math/Path';
@@ -99,7 +99,7 @@ export default class SVGLoader {
99
99
  }
100
100
  elem.attachLoadSaveData(svgAttributesDataKey, [attr, node.getAttribute(attr)]);
101
101
  }
102
- if (supportedStyleAttrs) {
102
+ if (supportedStyleAttrs && node.style) {
103
103
  for (const attr of node.style) {
104
104
  if (attr === '' || !attr) {
105
105
  continue;
@@ -157,9 +157,9 @@ export default class SVGLoader {
157
157
  }
158
158
  const elemX = elem.getAttribute('x');
159
159
  const elemY = elem.getAttribute('y');
160
- if (elemX && elemY) {
161
- const x = parseFloat(elemX);
162
- const y = parseFloat(elemY);
160
+ if (elemX || elemY) {
161
+ const x = parseFloat(elemX !== null && elemX !== void 0 ? elemX : '0');
162
+ const y = parseFloat(elemY !== null && elemY !== void 0 ? elemY : '0');
163
163
  if (!isNaN(x) && !isNaN(y)) {
164
164
  supportedAttrs === null || supportedAttrs === void 0 ? void 0 : supportedAttrs.push('x', 'y');
165
165
  transform = transform.rightMul(Mat33.translation(Vec2.of(x, y)));
@@ -204,12 +204,12 @@ export default class SVGLoader {
204
204
  size: fontSize,
205
205
  fontFamily: computedStyles.fontFamily || elem.style.fontFamily || 'sans-serif',
206
206
  renderingStyle: {
207
- fill: Color4.fromString(computedStyles.fill)
207
+ fill: Color4.fromString(computedStyles.fill || elem.style.fill || '#000')
208
208
  },
209
209
  };
210
210
  const supportedAttrs = [];
211
211
  const transform = this.getTransform(elem, supportedAttrs, computedStyles);
212
- const result = new Text(contentList, transform, style);
212
+ const result = new TextComponent(contentList, transform, style);
213
213
  this.attachUnrecognisedAttrs(result, elem, new Set(supportedAttrs), new Set(supportedStyleAttrs));
214
214
  return result;
215
215
  }
@@ -339,7 +339,6 @@ export default class SVGLoader {
339
339
  (_b = this.onFinish) === null || _b === void 0 ? void 0 : _b.call(this);
340
340
  });
341
341
  }
342
- // TODO: Handling unsafe data! Tripple-check that this is secure!
343
342
  // @param sanitize - if `true`, don't store unknown attributes.
344
343
  static fromString(text, sanitize = false) {
345
344
  var _a, _b;
@@ -28,6 +28,7 @@ export default abstract class AbstractComponent {
28
28
  protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
29
29
  protected abstract applyTransformation(affineTransfm: Mat33): void;
30
30
  transformBy(affineTransfm: Mat33): SerializableCommand;
31
+ isSelectable(): boolean;
31
32
  private static transformElementCommandId;
32
33
  private static UnresolvedTransformElementCommand;
33
34
  private static TransformElementCommand;
@@ -48,6 +48,10 @@ export default class AbstractComponent {
48
48
  transformBy(affineTransfm) {
49
49
  return new AbstractComponent.TransformElementCommand(affineTransfm, this);
50
50
  }
51
+ // @returns true iff this component can be selected (e.g. by the selection tool.)
52
+ isSelectable() {
53
+ return true;
54
+ }
51
55
  // Returns a copy of this component.
52
56
  clone() {
53
57
  const clone = this.createClone();
@@ -12,6 +12,7 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
12
12
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
13
13
  intersects(_lineSegment: LineSegment2): boolean;
14
14
  protected applyTransformation(_affineTransfm: Mat33): void;
15
+ isSelectable(): boolean;
15
16
  protected createClone(): SVGGlobalAttributesObject;
16
17
  description(localization: ImageComponentLocalization): string;
17
18
  protected serializeToJSON(): string | null;
@@ -29,6 +29,9 @@ export default class SVGGlobalAttributesObject extends AbstractComponent {
29
29
  }
30
30
  applyTransformation(_affineTransfm) {
31
31
  }
32
+ isSelectable() {
33
+ return false;
34
+ }
32
35
  createClone() {
33
36
  return new SVGGlobalAttributesObject(this.attrs);
34
37
  }
@@ -3,6 +3,7 @@ import Rect2 from '../math/Rect2';
3
3
  import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';
4
4
  import AbstractComponent from './AbstractComponent';
5
5
  export default class Stroke extends AbstractComponent {
6
+ // Creates a `Stroke` from the given `parts`.
6
7
  constructor(parts) {
7
8
  var _a;
8
9
  super('stroke');
@@ -12,26 +12,29 @@ export interface TextStyle {
12
12
  fontVariant?: string;
13
13
  renderingStyle: RenderingStyle;
14
14
  }
15
- declare type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
16
- export default class Text extends AbstractComponent {
17
- protected readonly textObjects: Array<string | Text>;
15
+ export default class TextComponent extends AbstractComponent {
16
+ protected readonly textObjects: Array<string | TextComponent>;
18
17
  private transform;
19
- private readonly style;
20
- private readonly getTextDimens;
18
+ private style;
21
19
  protected contentBBox: Rect2;
22
- constructor(textObjects: Array<string | Text>, transform: Mat33, style: TextStyle, getTextDimens?: GetTextDimensCallback);
20
+ constructor(textObjects: Array<string | TextComponent>, transform: Mat33, style: TextStyle);
23
21
  static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextStyle): void;
24
22
  private static textMeasuringCtx;
23
+ private static estimateTextDimens;
25
24
  private static getTextDimens;
26
25
  private computeBBoxOfPart;
27
26
  private recomputeBBox;
27
+ private renderInternal;
28
28
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
29
29
  intersects(lineSegment: LineSegment2): boolean;
30
+ getBaselinePos(): import("../lib").Vec3;
31
+ getTextStyle(): TextStyle;
32
+ getTransform(): Mat33;
30
33
  protected applyTransformation(affineTransfm: Mat33): void;
31
34
  protected createClone(): AbstractComponent;
32
35
  getText(): string;
33
36
  description(localizationTable: ImageComponentLocalization): string;
34
37
  protected serializeToJSON(): Record<string, any>;
35
- static deserializeFromString(json: any, getTextDimens?: GetTextDimensCallback): Text;
38
+ static deserializeFromString(json: any): TextComponent;
39
+ static fromLines(lines: string[], transform: Mat33, style: TextStyle): AbstractComponent;
36
40
  }
37
- export {};
@@ -1,20 +1,23 @@
1
1
  import LineSegment2 from '../math/LineSegment2';
2
2
  import Mat33 from '../math/Mat33';
3
3
  import Rect2 from '../math/Rect2';
4
+ import { Vec2 } from '../math/Vec2';
4
5
  import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';
5
6
  import AbstractComponent from './AbstractComponent';
6
7
  const componentTypeId = 'text';
7
- export default class Text extends AbstractComponent {
8
- constructor(textObjects, transform, style,
9
- // If not given, an HtmlCanvasElement is used to determine text boundaries.
10
- // @internal
11
- getTextDimens = Text.getTextDimens) {
8
+ export default class TextComponent extends AbstractComponent {
9
+ constructor(textObjects, transform, style) {
12
10
  super(componentTypeId);
13
11
  this.textObjects = textObjects;
14
12
  this.transform = transform;
15
13
  this.style = style;
16
- this.getTextDimens = getTextDimens;
17
14
  this.recomputeBBox();
15
+ // If this has no direct children, choose a style representative of this' content
16
+ // (useful for estimating the style of the TextComponent).
17
+ const hasDirectContent = textObjects.some(obj => typeof obj === 'string');
18
+ if (!hasDirectContent && textObjects.length > 0) {
19
+ this.style = textObjects[0].getTextStyle();
20
+ }
18
21
  }
19
22
  static applyTextStyles(ctx, style) {
20
23
  var _a, _b;
@@ -28,11 +31,23 @@ export default class Text extends AbstractComponent {
28
31
  ].join(' ');
29
32
  ctx.textAlign = 'left';
30
33
  }
34
+ // Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
35
+ static estimateTextDimens(text, style) {
36
+ const widthEst = text.length * style.size;
37
+ const heightEst = style.size;
38
+ // Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
39
+ // be above (0, 0).
40
+ return new Rect2(0, -heightEst * 2 / 3, widthEst, heightEst);
41
+ }
42
+ // Returns the bounding box of `text`. This is approximate if no Canvas is available.
31
43
  static getTextDimens(text, style) {
32
- var _a;
33
- (_a = Text.textMeasuringCtx) !== null && _a !== void 0 ? _a : (Text.textMeasuringCtx = document.createElement('canvas').getContext('2d'));
34
- const ctx = Text.textMeasuringCtx;
35
- Text.applyTextStyles(ctx, style);
44
+ var _a, _b;
45
+ (_a = TextComponent.textMeasuringCtx) !== null && _a !== void 0 ? _a : (TextComponent.textMeasuringCtx = (_b = document.createElement('canvas').getContext('2d')) !== null && _b !== void 0 ? _b : null);
46
+ if (!TextComponent.textMeasuringCtx) {
47
+ return this.estimateTextDimens(text, style);
48
+ }
49
+ const ctx = TextComponent.textMeasuringCtx;
50
+ TextComponent.applyTextStyles(ctx, style);
36
51
  const measure = ctx.measureText(text);
37
52
  // Text is drawn with (0,0) at the bottom left of the baseline.
38
53
  const textY = -measure.actualBoundingBoxAscent;
@@ -41,7 +56,7 @@ export default class Text extends AbstractComponent {
41
56
  }
42
57
  computeBBoxOfPart(part) {
43
58
  if (typeof part === 'string') {
44
- const textBBox = this.getTextDimens(part, this.style);
59
+ const textBBox = TextComponent.getTextDimens(part, this.style);
45
60
  return textBBox.transformedBoundingBox(this.transform);
46
61
  }
47
62
  else {
@@ -58,19 +73,22 @@ export default class Text extends AbstractComponent {
58
73
  }
59
74
  this.contentBBox = bbox !== null && bbox !== void 0 ? bbox : Rect2.empty;
60
75
  }
61
- render(canvas, _visibleRect) {
76
+ renderInternal(canvas) {
62
77
  const cursor = this.transform;
63
- canvas.startObject(this.contentBBox);
64
78
  for (const textObject of this.textObjects) {
65
79
  if (typeof textObject === 'string') {
66
80
  canvas.drawText(textObject, cursor, this.style);
67
81
  }
68
82
  else {
69
83
  canvas.pushTransform(cursor);
70
- textObject.render(canvas);
84
+ textObject.renderInternal(canvas);
71
85
  canvas.popTransform();
72
86
  }
73
87
  }
88
+ }
89
+ render(canvas, _visibleRect) {
90
+ canvas.startObject(this.contentBBox);
91
+ this.renderInternal(canvas);
74
92
  canvas.endObject(this.getLoadSaveData());
75
93
  }
76
94
  intersects(lineSegment) {
@@ -81,7 +99,7 @@ export default class Text extends AbstractComponent {
81
99
  lineSegment = new LineSegment2(p1InThisSpace, p2InThisSpace);
82
100
  for (const subObject of this.textObjects) {
83
101
  if (typeof subObject === 'string') {
84
- const textBBox = Text.getTextDimens(subObject, this.style);
102
+ const textBBox = TextComponent.getTextDimens(subObject, this.style);
85
103
  // TODO: Use a better intersection check. Perhaps draw the text onto a CanvasElement and
86
104
  // use pixel-testing to check for intersection with its contour.
87
105
  if (textBBox.getEdges().some(edge => lineSegment.intersection(edge) !== null)) {
@@ -96,12 +114,21 @@ export default class Text extends AbstractComponent {
96
114
  }
97
115
  return false;
98
116
  }
117
+ getBaselinePos() {
118
+ return this.transform.transformVec2(Vec2.zero);
119
+ }
120
+ getTextStyle() {
121
+ return this.style;
122
+ }
123
+ getTransform() {
124
+ return this.transform;
125
+ }
99
126
  applyTransformation(affineTransfm) {
100
127
  this.transform = affineTransfm.rightMul(this.transform);
101
128
  this.recomputeBBox();
102
129
  }
103
130
  createClone() {
104
- return new Text(this.textObjects, this.transform, this.style);
131
+ return new TextComponent(this.textObjects, this.transform, this.style);
105
132
  }
106
133
  getText() {
107
134
  const result = [];
@@ -138,7 +165,7 @@ export default class Text extends AbstractComponent {
138
165
  style: serializableStyle,
139
166
  };
140
167
  }
141
- static deserializeFromString(json, getTextDimens = Text.getTextDimens) {
168
+ static deserializeFromString(json) {
142
169
  const style = {
143
170
  renderingStyle: styleFromJSON(json.style.renderingStyle),
144
171
  size: json.style.size,
@@ -151,7 +178,7 @@ export default class Text extends AbstractComponent {
151
178
  if (((_a = data.text) !== null && _a !== void 0 ? _a : null) !== null) {
152
179
  return data.text;
153
180
  }
154
- return Text.deserializeFromString(data.json);
181
+ return TextComponent.deserializeFromString(data.json);
155
182
  });
156
183
  json.transform = json.transform.filter((elem) => typeof elem === 'number');
157
184
  if (json.transform.length !== 9) {
@@ -159,7 +186,23 @@ export default class Text extends AbstractComponent {
159
186
  }
160
187
  const transformData = json.transform;
161
188
  const transform = new Mat33(...transformData);
162
- return new Text(textObjects, transform, style, getTextDimens);
189
+ return new TextComponent(textObjects, transform, style);
190
+ }
191
+ static fromLines(lines, transform, style) {
192
+ let lastComponent = null;
193
+ const components = [];
194
+ for (const line of lines) {
195
+ let position = Vec2.zero;
196
+ if (lastComponent) {
197
+ const lineMargin = Math.floor(style.size);
198
+ position = lastComponent.getBBox().bottomLeft.plus(Vec2.unitY.times(lineMargin));
199
+ }
200
+ const component = new TextComponent([line], Mat33.translation(position), style);
201
+ components.push(component);
202
+ lastComponent = component;
203
+ }
204
+ return new TextComponent(components, transform, style);
163
205
  }
164
206
  }
165
- AbstractComponent.registerComponent(componentTypeId, (data) => Text.deserializeFromString(data));
207
+ TextComponent.textMeasuringCtx = null;
208
+ AbstractComponent.registerComponent(componentTypeId, (data) => TextComponent.deserializeFromString(data));
@@ -11,6 +11,7 @@ export default class UnknownSVGObject extends AbstractComponent {
11
11
  render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
12
12
  intersects(lineSegment: LineSegment2): boolean;
13
13
  protected applyTransformation(_affineTransfm: Mat33): void;
14
+ isSelectable(): boolean;
14
15
  protected createClone(): AbstractComponent;
15
16
  description(localization: ImageComponentLocalization): string;
16
17
  protected serializeToJSON(): string | null;
@@ -25,6 +25,9 @@ export default class UnknownSVGObject extends AbstractComponent {
25
25
  }
26
26
  applyTransformation(_affineTransfm) {
27
27
  }
28
+ isSelectable() {
29
+ return false;
30
+ }
28
31
  createClone() {
29
32
  return new UnknownSVGObject(this.svgObject.cloneNode(true));
30
33
  }
@@ -1,6 +1,7 @@
1
1
  import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
2
2
  import Rect2 from '../../math/Rect2';
3
3
  import Stroke from '../Stroke';
4
+ import Viewport from '../../Viewport';
4
5
  import { StrokeDataPoint } from '../../types';
5
6
  import { ComponentBuilder, ComponentBuilderFactory } from './types';
6
7
  export declare const makeFreehandLineBuilder: ComponentBuilderFactory;
@@ -8,11 +9,15 @@ export default class FreehandLineBuilder implements ComponentBuilder {
8
9
  private startPoint;
9
10
  private minFitAllowed;
10
11
  private maxFitAllowed;
12
+ private viewport;
11
13
  private isFirstSegment;
12
14
  private pathStartConnector;
13
15
  private mostRecentConnector;
14
16
  private upperSegments;
15
17
  private lowerSegments;
18
+ private lastUpperBezier;
19
+ private lastLowerBezier;
20
+ private parts;
16
21
  private buffer;
17
22
  private lastPoint;
18
23
  private lastExitingVec;
@@ -21,14 +26,16 @@ export default class FreehandLineBuilder implements ComponentBuilder {
21
26
  private curveEndWidth;
22
27
  private momentum;
23
28
  private bbox;
24
- constructor(startPoint: StrokeDataPoint, minFitAllowed: number, maxFitAllowed: number);
29
+ constructor(startPoint: StrokeDataPoint, minFitAllowed: number, maxFitAllowed: number, viewport: Viewport);
25
30
  getBBox(): Rect2;
26
31
  private getRenderingStyle;
27
- private previewPath;
32
+ private previewCurrentPath;
33
+ private previewFullPath;
28
34
  private previewStroke;
29
35
  preview(renderer: AbstractRenderer): void;
30
36
  build(): Stroke;
31
37
  private roundPoint;
38
+ private shouldStartNewSegment;
32
39
  private approxCurrentCurveLength;
33
40
  private finalizeCurrentCurve;
34
41
  private currentSegmentToPath;