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.
- package/.firebase/hosting.ZG9jcw.cache +338 -0
- package/.github/ISSUE_TEMPLATE/translation.md +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +8 -6
- package/dist/src/Editor.js +8 -4
- package/dist/src/EditorImage.d.ts +3 -0
- package/dist/src/EditorImage.js +7 -0
- package/dist/src/SVGLoader.js +7 -8
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +4 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
- package/dist/src/components/Stroke.js +1 -0
- package/dist/src/components/Text.d.ts +11 -8
- package/dist/src/components/Text.js +63 -20
- package/dist/src/components/UnknownSVGObject.d.ts +1 -0
- package/dist/src/components/UnknownSVGObject.js +3 -0
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +9 -2
- package/dist/src/components/builders/FreehandLineBuilder.js +129 -30
- package/dist/src/components/lib.d.ts +2 -2
- package/dist/src/components/lib.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +49 -22
- package/dist/src/testing/beforeEachFile.js +4 -0
- package/dist/src/toolbar/HTMLToolbar.js +2 -3
- package/dist/src/toolbar/IconProvider.d.ts +30 -0
- package/dist/src/toolbar/IconProvider.js +417 -0
- package/dist/src/toolbar/lib.d.ts +1 -1
- package/dist/src/toolbar/lib.js +1 -2
- package/dist/src/toolbar/localization.d.ts +0 -1
- package/dist/src/toolbar/localization.js +0 -1
- package/dist/src/toolbar/makeColorInput.js +1 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +1 -2
- package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
- package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
- package/dist/src/toolbar/widgets/PenToolWidget.js +10 -8
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
- package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +4 -1
- package/dist/src/tools/PasteHandler.js +2 -22
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
- package/dist/src/tools/TextTool.d.ts +4 -0
- package/dist/src/tools/TextTool.js +73 -15
- package/dist/src/tools/ToolController.js +1 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/package.json +1 -1
- package/src/Editor.toSVG.test.ts +27 -0
- package/src/Editor.ts +15 -9
- package/src/EditorImage.ts +9 -0
- package/src/SVGLoader.test.ts +57 -0
- package/src/SVGLoader.ts +9 -10
- package/src/components/AbstractComponent.ts +5 -0
- package/src/components/SVGGlobalAttributesObject.ts +4 -0
- package/src/components/Stroke.ts +1 -0
- package/src/components/Text.test.ts +3 -18
- package/src/components/Text.ts +78 -25
- package/src/components/UnknownSVGObject.ts +4 -0
- package/src/components/builders/FreehandLineBuilder.ts +162 -34
- package/src/components/lib.ts +3 -3
- package/src/rendering/renderers/CanvasRenderer.ts +2 -2
- package/src/rendering/renderers/SVGRenderer.ts +50 -24
- package/src/testing/beforeEachFile.ts +6 -1
- package/src/toolbar/HTMLToolbar.ts +2 -3
- package/src/toolbar/IconProvider.ts +480 -0
- package/src/toolbar/lib.ts +1 -1
- package/src/toolbar/localization.ts +0 -2
- package/src/toolbar/makeColorInput.ts +1 -2
- package/src/toolbar/widgets/BaseWidget.ts +1 -2
- package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
- package/src/toolbar/widgets/HandToolWidget.ts +42 -20
- package/src/toolbar/widgets/PenToolWidget.ts +11 -8
- package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
- package/src/toolbar/widgets/TextToolWidget.ts +1 -2
- package/src/tools/PanZoom.ts +4 -1
- package/src/tools/PasteHandler.ts +2 -24
- package/src/tools/SelectionTool/SelectionTool.css +1 -0
- package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
- package/src/tools/SelectionTool/SelectionTool.ts +73 -4
- package/src/tools/TextTool.ts +82 -17
- package/src/tools/ToolController.ts +1 -0
- package/src/tools/localization.ts +4 -0
- package/typedoc.json +5 -1
- package/dist/src/toolbar/icons.d.ts +0 -20
- package/dist/src/toolbar/icons.js +0 -385
- package/src/toolbar/icons.ts +0 -443
package/dist/src/Editor.d.ts
CHANGED
@@ -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
|
-
|
90
|
-
|
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;
|
package/dist/src/Editor.js
CHANGED
@@ -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',
|
639
|
-
result.setAttribute('width',
|
640
|
-
result.setAttribute('height',
|
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;
|
package/dist/src/EditorImage.js
CHANGED
@@ -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);
|
package/dist/src/SVGLoader.js
CHANGED
@@ -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
|
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
|
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
|
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;
|
@@ -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
|
-
|
16
|
-
|
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
|
20
|
-
private readonly getTextDimens;
|
18
|
+
private style;
|
21
19
|
protected contentBBox: Rect2;
|
22
|
-
constructor(textObjects: Array<string |
|
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
|
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
|
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 =
|
34
|
-
|
35
|
-
|
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 =
|
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
|
-
|
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.
|
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 =
|
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
|
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
|
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
|
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
|
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
|
-
|
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;
|
@@ -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
|
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;
|