js-draw 0.3.0 → 0.3.2
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/.github/ISSUE_TEMPLATE/translation.md +4 -1
- package/CHANGELOG.md +15 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +4 -1
- package/dist/src/Editor.js +117 -2
- package/dist/src/EditorImage.js +4 -1
- package/dist/src/SVGLoader.d.ts +4 -1
- package/dist/src/SVGLoader.js +78 -33
- package/dist/src/UndoRedoHistory.d.ts +1 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/Viewport.d.ts +1 -0
- package/dist/src/Viewport.js +12 -4
- package/dist/src/commands/lib.d.ts +2 -1
- package/dist/src/commands/lib.js +2 -1
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/commands/uniteCommands.d.ts +4 -0
- package/dist/src/commands/uniteCommands.js +105 -0
- package/dist/src/components/AbstractComponent.d.ts +2 -0
- package/dist/src/components/AbstractComponent.js +41 -5
- package/dist/src/components/ImageComponent.d.ts +27 -0
- package/dist/src/components/ImageComponent.js +129 -0
- package/dist/src/components/Stroke.js +11 -6
- package/dist/src/components/builders/FreehandLineBuilder.js +7 -7
- package/dist/src/components/lib.d.ts +4 -2
- package/dist/src/components/lib.js +4 -2
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/math/LineSegment2.d.ts +4 -0
- package/dist/src/math/LineSegment2.js +9 -0
- package/dist/src/math/Path.d.ts +5 -1
- package/dist/src/math/Path.js +89 -7
- package/dist/src/math/Rect2.js +1 -1
- package/dist/src/math/Triangle.d.ts +11 -0
- package/dist/src/math/Triangle.js +19 -0
- package/dist/src/rendering/Display.js +2 -2
- package/dist/src/rendering/localization.d.ts +3 -0
- package/dist/src/rendering/localization.js +3 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +9 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +14 -12
- package/dist/src/rendering/renderers/SVGRenderer.js +71 -87
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -0
- package/dist/src/toolbar/HTMLToolbar.js +1 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
- package/dist/src/tools/BaseTool.d.ts +4 -1
- package/dist/src/tools/BaseTool.js +12 -0
- package/dist/src/tools/PasteHandler.d.ts +16 -0
- package/dist/src/tools/PasteHandler.js +142 -0
- package/dist/src/tools/Pen.d.ts +2 -1
- package/dist/src/tools/Pen.js +16 -0
- package/dist/src/tools/SelectionTool.d.ts +7 -1
- package/dist/src/tools/SelectionTool.js +63 -5
- package/dist/src/tools/ToolController.d.ts +1 -0
- package/dist/src/tools/ToolController.js +45 -29
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
- package/dist/src/tools/lib.d.ts +2 -0
- package/dist/src/tools/lib.js +2 -0
- package/dist/src/tools/localization.d.ts +4 -0
- package/dist/src/tools/localization.js +4 -0
- package/dist/src/types.d.ts +21 -4
- package/dist/src/types.js +3 -0
- package/package.json +2 -2
- package/src/Editor.ts +131 -2
- package/src/EditorImage.ts +7 -1
- package/src/SVGLoader.ts +90 -36
- package/src/UndoRedoHistory.test.ts +33 -0
- package/src/UndoRedoHistory.ts +8 -0
- package/src/Viewport.ts +13 -4
- package/src/commands/lib.ts +2 -0
- package/src/commands/localization.ts +2 -0
- package/src/commands/uniteCommands.test.ts +23 -0
- package/src/commands/uniteCommands.ts +121 -0
- package/src/components/AbstractComponent.ts +55 -9
- package/src/components/ImageComponent.ts +153 -0
- package/src/components/Stroke.test.ts +5 -0
- package/src/components/Stroke.ts +13 -7
- package/src/components/builders/FreehandLineBuilder.ts +7 -7
- package/src/components/lib.ts +7 -2
- package/src/components/localization.ts +4 -0
- package/src/math/LineSegment2.test.ts +9 -0
- package/src/math/LineSegment2.ts +13 -0
- package/src/math/Path.test.ts +53 -0
- package/src/math/Path.toString.test.ts +4 -2
- package/src/math/Path.ts +109 -11
- package/src/math/Rect2.ts +1 -1
- package/src/math/Triangle.ts +29 -0
- package/src/rendering/Display.ts +2 -2
- package/src/rendering/localization.ts +6 -0
- package/src/rendering/renderers/AbstractRenderer.ts +17 -0
- package/src/rendering/renderers/CanvasRenderer.ts +10 -1
- package/src/rendering/renderers/DummyRenderer.ts +6 -1
- package/src/rendering/renderers/SVGRenderer.ts +76 -101
- package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/types.ts +1 -1
- package/src/toolbar/widgets/BaseWidget.ts +27 -1
- package/src/tools/BaseTool.ts +17 -1
- package/src/tools/PasteHandler.ts +156 -0
- package/src/tools/Pen.ts +20 -1
- package/src/tools/SelectionTool.ts +80 -8
- package/src/tools/ToolController.ts +60 -46
- package/src/tools/ToolSwitcherShortcut.ts +34 -0
- package/src/tools/lib.ts +2 -0
- package/src/tools/localization.ts +10 -0
- package/src/types.ts +29 -3
@@ -113,9 +113,45 @@ export default class AbstractComponent {
|
|
113
113
|
// Topmost z-index
|
114
114
|
AbstractComponent.zIndexCounter = 0;
|
115
115
|
AbstractComponent.deserializationCallbacks = {};
|
116
|
+
AbstractComponent.transformElementCommandId = 'transform-element';
|
117
|
+
AbstractComponent.UnresolvedTransformElementCommand = class extends SerializableCommand {
|
118
|
+
constructor(affineTransfm, componentID) {
|
119
|
+
super(AbstractComponent.transformElementCommandId);
|
120
|
+
this.affineTransfm = affineTransfm;
|
121
|
+
this.componentID = componentID;
|
122
|
+
this.command = null;
|
123
|
+
}
|
124
|
+
resolveCommand(editor) {
|
125
|
+
if (this.command) {
|
126
|
+
return;
|
127
|
+
}
|
128
|
+
const component = editor.image.lookupElement(this.componentID);
|
129
|
+
if (!component) {
|
130
|
+
throw new Error(`Unable to resolve component with ID ${this.componentID}`);
|
131
|
+
}
|
132
|
+
this.command = new AbstractComponent.TransformElementCommand(this.affineTransfm, component);
|
133
|
+
}
|
134
|
+
apply(editor) {
|
135
|
+
this.resolveCommand(editor);
|
136
|
+
this.command.apply(editor);
|
137
|
+
}
|
138
|
+
unapply(editor) {
|
139
|
+
this.resolveCommand(editor);
|
140
|
+
this.command.unapply(editor);
|
141
|
+
}
|
142
|
+
description(_editor, localizationTable) {
|
143
|
+
return localizationTable.transformedElements(1);
|
144
|
+
}
|
145
|
+
serializeToJSON() {
|
146
|
+
return {
|
147
|
+
id: this.componentID,
|
148
|
+
transfm: this.affineTransfm.toArray(),
|
149
|
+
};
|
150
|
+
}
|
151
|
+
};
|
116
152
|
AbstractComponent.TransformElementCommand = (_a = class extends SerializableCommand {
|
117
153
|
constructor(affineTransfm, component) {
|
118
|
-
super(
|
154
|
+
super(AbstractComponent.transformElementCommandId);
|
119
155
|
this.affineTransfm = affineTransfm;
|
120
156
|
this.component = component;
|
121
157
|
this.origZIndex = component.zIndex;
|
@@ -155,13 +191,13 @@ AbstractComponent.TransformElementCommand = (_a = class extends SerializableComm
|
|
155
191
|
}
|
156
192
|
},
|
157
193
|
(() => {
|
158
|
-
SerializableCommand.register(
|
194
|
+
SerializableCommand.register(AbstractComponent.transformElementCommandId, (json, editor) => {
|
159
195
|
const elem = editor.image.lookupElement(json.id);
|
196
|
+
const transform = new Mat33(...json.transfm);
|
160
197
|
if (!elem) {
|
161
|
-
|
198
|
+
return new AbstractComponent.UnresolvedTransformElementCommand(transform, json.id);
|
162
199
|
}
|
163
|
-
|
164
|
-
return new AbstractComponent.TransformElementCommand(new Mat33(...transform), elem);
|
200
|
+
return new AbstractComponent.TransformElementCommand(transform, elem);
|
165
201
|
});
|
166
202
|
})(),
|
167
203
|
_a);
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import LineSegment2 from '../math/LineSegment2';
|
2
|
+
import Mat33 from '../math/Mat33';
|
3
|
+
import Rect2 from '../math/Rect2';
|
4
|
+
import AbstractRenderer, { RenderableImage } from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import AbstractComponent from './AbstractComponent';
|
6
|
+
import { ImageComponentLocalization } from './localization';
|
7
|
+
export default class ImageComponent extends AbstractComponent {
|
8
|
+
protected contentBBox: Rect2;
|
9
|
+
private image;
|
10
|
+
constructor(image: RenderableImage);
|
11
|
+
private getImageRect;
|
12
|
+
private recomputeBBox;
|
13
|
+
static fromImage(elem: HTMLImageElement, transform: Mat33): Promise<ImageComponent>;
|
14
|
+
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
15
|
+
intersects(lineSegment: LineSegment2): boolean;
|
16
|
+
protected serializeToJSON(): {
|
17
|
+
src: string;
|
18
|
+
label: string | undefined;
|
19
|
+
width: number;
|
20
|
+
height: number;
|
21
|
+
transform: number[];
|
22
|
+
};
|
23
|
+
protected applyTransformation(affineTransfm: Mat33): void;
|
24
|
+
description(localizationTable: ImageComponentLocalization): string;
|
25
|
+
protected createClone(): AbstractComponent;
|
26
|
+
static deserializeFromJSON(data: any): ImageComponent;
|
27
|
+
}
|
@@ -0,0 +1,129 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
10
|
+
import Mat33 from '../math/Mat33';
|
11
|
+
import Rect2 from '../math/Rect2';
|
12
|
+
import AbstractComponent from './AbstractComponent';
|
13
|
+
// Represents a raster image.
|
14
|
+
export default class ImageComponent extends AbstractComponent {
|
15
|
+
constructor(image) {
|
16
|
+
var _a, _b, _c;
|
17
|
+
super('image-component');
|
18
|
+
this.image = Object.assign(Object.assign({}, image), { label: (_c = (_b = (_a = image.label) !== null && _a !== void 0 ? _a : image.image.getAttribute('alt')) !== null && _b !== void 0 ? _b : image.image.getAttribute('aria-label')) !== null && _c !== void 0 ? _c : undefined });
|
19
|
+
const isHTMLImageElem = (elem) => {
|
20
|
+
return elem.getAttribute('src') !== undefined;
|
21
|
+
};
|
22
|
+
if (isHTMLImageElem(image.image) && !image.image.complete) {
|
23
|
+
image.image.onload = () => this.recomputeBBox();
|
24
|
+
}
|
25
|
+
this.recomputeBBox();
|
26
|
+
}
|
27
|
+
getImageRect() {
|
28
|
+
return new Rect2(0, 0, this.image.image.width, this.image.image.height);
|
29
|
+
}
|
30
|
+
recomputeBBox() {
|
31
|
+
this.contentBBox = this.getImageRect();
|
32
|
+
this.contentBBox = this.contentBBox.transformedBoundingBox(this.image.transform);
|
33
|
+
}
|
34
|
+
// Load from an image. Waits for the image to load if incomplete.
|
35
|
+
static fromImage(elem, transform) {
|
36
|
+
var _a;
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
38
|
+
if (!elem.complete) {
|
39
|
+
yield new Promise((resolve, reject) => {
|
40
|
+
elem.onload = resolve;
|
41
|
+
elem.onerror = reject;
|
42
|
+
elem.onabort = reject;
|
43
|
+
});
|
44
|
+
}
|
45
|
+
let width, height;
|
46
|
+
if (typeof elem.width === 'number' && typeof elem.height === 'number'
|
47
|
+
&& elem.width !== 0 && elem.height !== 0) {
|
48
|
+
width = elem.width;
|
49
|
+
height = elem.height;
|
50
|
+
}
|
51
|
+
else {
|
52
|
+
width = elem.clientWidth;
|
53
|
+
height = elem.clientHeight;
|
54
|
+
}
|
55
|
+
let image;
|
56
|
+
let url = (_a = elem.src) !== null && _a !== void 0 ? _a : '';
|
57
|
+
if (!url.startsWith('data:image/')) {
|
58
|
+
// Convert to a data URL:
|
59
|
+
const canvas = document.createElement('canvas');
|
60
|
+
canvas.width = width;
|
61
|
+
canvas.height = height;
|
62
|
+
const ctx = canvas.getContext('2d');
|
63
|
+
ctx.drawImage(elem, 0, 0, canvas.width, canvas.height);
|
64
|
+
url = canvas.toDataURL();
|
65
|
+
image = canvas;
|
66
|
+
}
|
67
|
+
else {
|
68
|
+
image = new Image();
|
69
|
+
image.src = url;
|
70
|
+
image.width = width;
|
71
|
+
image.height = height;
|
72
|
+
}
|
73
|
+
return new ImageComponent({
|
74
|
+
image,
|
75
|
+
base64Url: url,
|
76
|
+
transform: transform,
|
77
|
+
});
|
78
|
+
});
|
79
|
+
}
|
80
|
+
render(canvas, _visibleRect) {
|
81
|
+
canvas.drawImage(this.image);
|
82
|
+
}
|
83
|
+
intersects(lineSegment) {
|
84
|
+
const rect = this.getImageRect();
|
85
|
+
const edges = rect.getEdges().map(edge => edge.transformedBy(this.image.transform));
|
86
|
+
for (const edge of edges) {
|
87
|
+
if (edge.intersects(lineSegment)) {
|
88
|
+
return true;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
return false;
|
92
|
+
}
|
93
|
+
serializeToJSON() {
|
94
|
+
return {
|
95
|
+
src: this.image.base64Url,
|
96
|
+
label: this.image.label,
|
97
|
+
// Store the width and height for bounding box computations while the image is loading.
|
98
|
+
width: this.image.image.width,
|
99
|
+
height: this.image.image.height,
|
100
|
+
transform: this.image.transform.toArray(),
|
101
|
+
};
|
102
|
+
}
|
103
|
+
applyTransformation(affineTransfm) {
|
104
|
+
this.image.transform = affineTransfm.rightMul(this.image.transform);
|
105
|
+
this.recomputeBBox();
|
106
|
+
}
|
107
|
+
description(localizationTable) {
|
108
|
+
return this.image.label ? localizationTable.imageNode(this.image.label) : localizationTable.unlabeledImageNode;
|
109
|
+
}
|
110
|
+
createClone() {
|
111
|
+
return new ImageComponent(Object.assign({}, this.image));
|
112
|
+
}
|
113
|
+
static deserializeFromJSON(data) {
|
114
|
+
if (!(typeof data.src === 'string')) {
|
115
|
+
throw new Error(`${data} has invalid format! Expected src property.`);
|
116
|
+
}
|
117
|
+
const image = new Image();
|
118
|
+
image.src = data.src;
|
119
|
+
image.width = data.width;
|
120
|
+
image.height = data.height;
|
121
|
+
return new ImageComponent({
|
122
|
+
image: image,
|
123
|
+
base64Url: image.src,
|
124
|
+
label: data.label,
|
125
|
+
transform: new Mat33(...data.transform),
|
126
|
+
});
|
127
|
+
}
|
128
|
+
}
|
129
|
+
AbstractComponent.registerComponent('image-component', ImageComponent.deserializeFromJSON);
|
@@ -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 (
|
42
|
-
|
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 ±
|
10
|
+
// less than ±1 px from the curve.
|
11
11
|
const maxSmoothingDist = viewport.getSizeOfPixelOnCanvas() * 7;
|
12
|
-
const minSmoothingDist = viewport.getSizeOfPixelOnCanvas()
|
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.
|
@@ -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
|
// |
|
@@ -237,9 +237,9 @@ export default class FreehandLineBuilder {
|
|
237
237
|
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
238
238
|
return upperBoundary.intersects(lowerBoundary).length > 0;
|
239
239
|
};
|
240
|
-
// If the boundaries have
|
240
|
+
// If the boundaries have intersections, increasing the half vector's length could fix this.
|
241
241
|
if (boundariesIntersect()) {
|
242
|
-
halfVec = halfVec.times(
|
242
|
+
halfVec = halfVec.times(1.1);
|
243
243
|
}
|
244
244
|
// Each starts at startPt ± startVec
|
245
245
|
const lowerCurve = {
|
@@ -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) /
|
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 /
|
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,6 +1,8 @@
|
|
1
1
|
export * from './builders/types';
|
2
2
|
export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
3
|
-
|
3
|
+
export * from './AbstractComponent';
|
4
|
+
export { default as AbstractComponent } from './AbstractComponent';
|
4
5
|
import Stroke from './Stroke';
|
5
6
|
import Text from './Text';
|
6
|
-
|
7
|
+
import ImageComponent from './ImageComponent';
|
8
|
+
export { Stroke, Text, Text as TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -1,6 +1,8 @@
|
|
1
1
|
export * from './builders/types';
|
2
2
|
export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
3
|
-
|
3
|
+
export * from './AbstractComponent';
|
4
|
+
export { default as AbstractComponent } from './AbstractComponent';
|
4
5
|
import Stroke from './Stroke';
|
5
6
|
import Text from './Text';
|
6
|
-
|
7
|
+
import ImageComponent from './ImageComponent';
|
8
|
+
export { Stroke, Text, Text as TextComponent, Stroke as StrokeComponent, ImageComponent, };
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import Mat33 from './Mat33';
|
1
2
|
import Rect2 from './Rect2';
|
2
3
|
import { Vec2, Point2 } from './Vec2';
|
3
4
|
interface IntersectionResult {
|
@@ -15,6 +16,9 @@ export default class LineSegment2 {
|
|
15
16
|
get p2(): Point2;
|
16
17
|
get(t: number): Point2;
|
17
18
|
intersection(other: LineSegment2): IntersectionResult | null;
|
19
|
+
intersects(other: LineSegment2): boolean;
|
18
20
|
closestPointTo(target: Point2): import("./Vec3").default;
|
21
|
+
transformedBy(affineTransfm: Mat33): LineSegment2;
|
22
|
+
toString(): string;
|
19
23
|
}
|
20
24
|
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,10 @@ export default class LineSegment2 {
|
|
113
116
|
return this.p1;
|
114
117
|
}
|
115
118
|
}
|
119
|
+
transformedBy(affineTransfm) {
|
120
|
+
return new LineSegment2(affineTransfm.transformVec2(this.p1), affineTransfm.transformVec2(this.p2));
|
121
|
+
}
|
122
|
+
toString() {
|
123
|
+
return `LineSegment(${this.p1.toString()}, ${this.p2.toString()})`;
|
124
|
+
}
|
116
125
|
}
|
package/dist/src/math/Path.d.ts
CHANGED
@@ -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;
|
package/dist/src/math/Path.js
CHANGED
@@ -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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
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
|
-
|
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, []);
|
package/dist/src/math/Rect2.js
CHANGED
@@ -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: 600 * 600 * 4 *
|
74
|
+
cacheSize: 600 * 600 * 4 * 90,
|
75
75
|
maxScale: 1.4,
|
76
|
-
minComponentsPerCache:
|
76
|
+
minComponentsPerCache: 20,
|
77
77
|
minComponentsToUseCache: 105,
|
78
78
|
});
|
79
79
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
@@ -1,7 +1,10 @@
|
|
1
1
|
export interface TextRendererLocalization {
|
2
2
|
pathNodeCount(pathCount: number): string;
|
3
3
|
textNodeCount(nodeCount: number): string;
|
4
|
+
imageNodeCount(nodeCount: number): string;
|
4
5
|
textNode(content: string): string;
|
6
|
+
unlabeledImageNode: string;
|
7
|
+
imageNode(label: string): string;
|
5
8
|
rerenderAsText: string;
|
6
9
|
}
|
7
10
|
export declare const defaultTextRendererLocalization: TextRendererLocalization;
|
@@ -1,6 +1,9 @@
|
|
1
1
|
export const defaultTextRendererLocalization = {
|
2
2
|
pathNodeCount: (count) => `There are ${count} visible path objects.`,
|
3
3
|
textNodeCount: (count) => `There are ${count} visible text nodes.`,
|
4
|
+
imageNodeCount: (nodeCount) => `There are ${nodeCount} visible image nodes.`,
|
4
5
|
textNode: (content) => `Text: ${content}`,
|
6
|
+
imageNode: (label) => `Image: ${label}`,
|
7
|
+
unlabeledImageNode: 'Unlabeled image',
|
5
8
|
rerenderAsText: 'Re-render as text',
|
6
9
|
};
|