js-draw 0.0.10 → 0.1.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/CHANGELOG.md +11 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -2
- package/dist/src/Editor.js +17 -7
- package/dist/src/EditorImage.d.ts +15 -7
- package/dist/src/EditorImage.js +46 -37
- package/dist/src/Pointer.d.ts +3 -2
- package/dist/src/Pointer.js +12 -3
- package/dist/src/SVGLoader.d.ts +6 -2
- package/dist/src/SVGLoader.js +20 -8
- package/dist/src/Viewport.d.ts +4 -0
- package/dist/src/Viewport.js +51 -0
- package/dist/src/components/AbstractComponent.d.ts +9 -2
- package/dist/src/components/AbstractComponent.js +14 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
- package/dist/src/components/Stroke.d.ts +1 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/UnknownSVGObject.d.ts +1 -1
- package/dist/src/components/UnknownSVGObject.js +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.js +1 -1
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/geometry/Mat33.js +3 -0
- package/dist/src/geometry/Path.d.ts +1 -1
- package/dist/src/geometry/Path.js +102 -69
- package/dist/src/geometry/Rect2.d.ts +1 -0
- package/dist/src/geometry/Rect2.js +47 -9
- package/dist/src/{Display.d.ts → rendering/Display.d.ts} +5 -2
- package/dist/src/{Display.js → rendering/Display.js} +34 -4
- package/dist/src/rendering/caching/CacheRecord.d.ts +19 -0
- package/dist/src/rendering/caching/CacheRecord.js +52 -0
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +11 -0
- package/dist/src/rendering/caching/CacheRecordManager.js +31 -0
- package/dist/src/rendering/caching/RenderingCache.d.ts +12 -0
- package/dist/src/rendering/caching/RenderingCache.js +42 -0
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +28 -0
- package/dist/src/rendering/caching/RenderingCacheNode.js +301 -0
- package/dist/src/rendering/caching/testUtils.d.ts +9 -0
- package/dist/src/rendering/caching/testUtils.js +20 -0
- package/dist/src/rendering/caching/types.d.ts +21 -0
- package/dist/src/rendering/caching/types.js +1 -0
- package/dist/src/rendering/{AbstractRenderer.d.ts → renderers/AbstractRenderer.d.ts} +20 -9
- package/dist/src/rendering/{AbstractRenderer.js → renderers/AbstractRenderer.js} +37 -3
- package/dist/src/rendering/{CanvasRenderer.d.ts → renderers/CanvasRenderer.d.ts} +10 -5
- package/dist/src/rendering/{CanvasRenderer.js → renderers/CanvasRenderer.js} +60 -20
- package/dist/src/rendering/{DummyRenderer.d.ts → renderers/DummyRenderer.d.ts} +9 -5
- package/dist/src/rendering/{DummyRenderer.js → renderers/DummyRenderer.js} +35 -4
- package/dist/src/rendering/{SVGRenderer.d.ts → renderers/SVGRenderer.d.ts} +7 -5
- package/dist/src/rendering/{SVGRenderer.js → renderers/SVGRenderer.js} +35 -18
- package/dist/src/testing/createEditor.js +1 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +165 -154
- package/dist/src/toolbar/icons.d.ts +10 -0
- package/dist/src/toolbar/icons.js +180 -0
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/types.d.ts +4 -0
- package/dist/src/tools/PanZoom.d.ts +9 -6
- package/dist/src/tools/PanZoom.js +30 -21
- package/dist/src/tools/Pen.js +8 -3
- package/dist/src/tools/SelectionTool.js +9 -24
- package/dist/src/tools/ToolController.d.ts +5 -6
- package/dist/src/tools/ToolController.js +8 -10
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/Editor.ts +19 -8
- package/src/EditorImage.test.ts +2 -2
- package/src/EditorImage.ts +58 -42
- package/src/Pointer.ts +13 -4
- package/src/SVGLoader.ts +36 -10
- package/src/Viewport.ts +68 -0
- package/src/components/AbstractComponent.ts +21 -2
- package/src/components/SVGGlobalAttributesObject.ts +2 -2
- package/src/components/Stroke.ts +2 -2
- package/src/components/UnknownSVGObject.ts +2 -2
- package/src/components/builders/ArrowBuilder.ts +1 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -2
- package/src/components/builders/LineBuilder.ts +1 -1
- package/src/components/builders/RectangleBuilder.ts +1 -1
- package/src/components/builders/types.ts +1 -1
- package/src/geometry/Mat33.ts +3 -0
- package/src/geometry/Path.fromString.test.ts +94 -4
- package/src/geometry/Path.toString.test.ts +12 -2
- package/src/geometry/Path.ts +107 -71
- package/src/geometry/Rect2.test.ts +47 -8
- package/src/geometry/Rect2.ts +57 -9
- package/src/{Display.ts → rendering/Display.ts} +39 -6
- package/src/rendering/caching/CacheRecord.test.ts +49 -0
- package/src/rendering/caching/CacheRecord.ts +73 -0
- package/src/rendering/caching/CacheRecordManager.ts +45 -0
- package/src/rendering/caching/RenderingCache.test.ts +44 -0
- package/src/rendering/caching/RenderingCache.ts +63 -0
- package/src/rendering/caching/RenderingCacheNode.ts +378 -0
- package/src/rendering/caching/testUtils.ts +35 -0
- package/src/rendering/caching/types.ts +39 -0
- package/src/rendering/{AbstractRenderer.ts → renderers/AbstractRenderer.ts} +57 -9
- package/src/rendering/{CanvasRenderer.ts → renderers/CanvasRenderer.ts} +74 -25
- package/src/rendering/renderers/DummyRenderer.test.ts +43 -0
- package/src/rendering/{DummyRenderer.ts → renderers/DummyRenderer.ts} +50 -7
- package/src/rendering/{SVGRenderer.ts → renderers/SVGRenderer.ts} +39 -23
- package/src/testing/createEditor.ts +1 -1
- package/src/toolbar/HTMLToolbar.ts +199 -170
- package/src/toolbar/icons.ts +203 -0
- package/src/toolbar/localization.ts +9 -2
- package/src/toolbar/toolbar.css +21 -8
- package/src/toolbar/types.ts +5 -0
- package/src/tools/PanZoom.ts +37 -27
- package/src/tools/Pen.ts +7 -3
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +12 -33
- package/src/tools/ToolController.ts +3 -5
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +10 -3
- package/tsconfig.json +1 -0
- package/dist/__mocks__/coloris.d.ts +0 -2
- package/dist/__mocks__/coloris.js +0 -5
package/src/SVGLoader.ts
CHANGED
@@ -5,17 +5,25 @@ import SVGGlobalAttributesObject from './components/SVGGlobalAttributesObject';
|
|
5
5
|
import UnknownSVGObject from './components/UnknownSVGObject';
|
6
6
|
import Path from './geometry/Path';
|
7
7
|
import Rect2 from './geometry/Rect2';
|
8
|
-
import { RenderablePathSpec, RenderingStyle } from './rendering/AbstractRenderer';
|
9
|
-
import { ComponentAddedListener, ImageLoader, OnProgressListener } from './types';
|
8
|
+
import { RenderablePathSpec, RenderingStyle } from './rendering/renderers/AbstractRenderer';
|
9
|
+
import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnProgressListener } from './types';
|
10
10
|
|
11
11
|
type OnFinishListener = ()=> void;
|
12
12
|
|
13
13
|
// Size of a loaded image if no size is specified.
|
14
14
|
export const defaultSVGViewRect = new Rect2(0, 0, 500, 500);
|
15
15
|
|
16
|
+
// Key to retrieve unrecognised attributes from an AbstractComponent
|
17
|
+
export const svgAttributesDataKey = 'svgAttrs';
|
18
|
+
|
19
|
+
// [key, value]
|
20
|
+
export type SVGLoaderUnknownAttribute = [ string, string ];
|
21
|
+
|
16
22
|
export default class SVGLoader implements ImageLoader {
|
17
23
|
private onAddComponent: ComponentAddedListener|null = null;
|
18
24
|
private onProgress: OnProgressListener|null = null;
|
25
|
+
private onDetermineExportRect: OnDetermineExportRectListener|null = null;
|
26
|
+
|
19
27
|
private processedCount: number = 0;
|
20
28
|
private totalToProcess: number = 0;
|
21
29
|
private rootViewBox: Rect2|null;
|
@@ -86,12 +94,31 @@ export default class SVGLoader implements ImageLoader {
|
|
86
94
|
return result;
|
87
95
|
}
|
88
96
|
|
97
|
+
private attachUnrecognisedAttrs(
|
98
|
+
elem: AbstractComponent,
|
99
|
+
node: SVGElement,
|
100
|
+
supportedAttrs: Set<string>
|
101
|
+
) {
|
102
|
+
for (const attr of node.getAttributeNames()) {
|
103
|
+
if (supportedAttrs.has(attr)) {
|
104
|
+
continue;
|
105
|
+
}
|
106
|
+
|
107
|
+
elem.attachLoadSaveData(svgAttributesDataKey,
|
108
|
+
[ attr, node.getAttribute(attr) ] as SVGLoaderUnknownAttribute,
|
109
|
+
);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
89
113
|
// Adds a stroke with a single path
|
90
114
|
private addPath(node: SVGPathElement) {
|
91
115
|
let elem: AbstractComponent;
|
92
116
|
try {
|
93
117
|
const strokeData = this.strokeDataFromElem(node);
|
94
118
|
elem = new Stroke(strokeData);
|
119
|
+
this.attachUnrecognisedAttrs(
|
120
|
+
elem, node, new Set([ 'stroke', 'fill', 'stroke-width', 'd' ]),
|
121
|
+
);
|
95
122
|
} catch (e) {
|
96
123
|
console.error(
|
97
124
|
'Invalid path in node', node,
|
@@ -126,6 +153,7 @@ export default class SVGLoader implements ImageLoader {
|
|
126
153
|
}
|
127
154
|
|
128
155
|
this.rootViewBox = new Rect2(x, y, width, height);
|
156
|
+
this.onDetermineExportRect?.(this.rootViewBox);
|
129
157
|
}
|
130
158
|
|
131
159
|
private updateSVGAttrs(node: SVGSVGElement) {
|
@@ -172,10 +200,12 @@ export default class SVGLoader implements ImageLoader {
|
|
172
200
|
}
|
173
201
|
|
174
202
|
public async start(
|
175
|
-
onAddComponent: ComponentAddedListener, onProgress: OnProgressListener
|
176
|
-
|
203
|
+
onAddComponent: ComponentAddedListener, onProgress: OnProgressListener,
|
204
|
+
onDetermineExportRect: OnDetermineExportRectListener|null = null
|
205
|
+
): Promise<void> {
|
177
206
|
this.onAddComponent = onAddComponent;
|
178
207
|
this.onProgress = onProgress;
|
208
|
+
this.onDetermineExportRect = onDetermineExportRect;
|
179
209
|
|
180
210
|
// Estimate the number of tags to process.
|
181
211
|
this.totalToProcess = this.source.childElementCount;
|
@@ -185,14 +215,12 @@ export default class SVGLoader implements ImageLoader {
|
|
185
215
|
await this.visit(this.source);
|
186
216
|
|
187
217
|
const viewBox = this.rootViewBox;
|
188
|
-
let result = defaultSVGViewRect;
|
189
218
|
|
190
|
-
if (viewBox) {
|
191
|
-
|
219
|
+
if (!viewBox) {
|
220
|
+
this.onDetermineExportRect?.(defaultSVGViewRect);
|
192
221
|
}
|
193
222
|
|
194
223
|
this.onFinish?.();
|
195
|
-
return result;
|
196
224
|
}
|
197
225
|
|
198
226
|
// TODO: Handling unsafe data! Tripple-check that this is secure!
|
@@ -211,9 +239,7 @@ export default class SVGLoader implements ImageLoader {
|
|
211
239
|
throw new Error('SVG loading iframe is not sandboxed.');
|
212
240
|
}
|
213
241
|
|
214
|
-
// Try running JavaScript within the iframe
|
215
242
|
const sandboxDoc = sandbox.contentWindow?.document ?? sandbox.contentDocument;
|
216
|
-
|
217
243
|
if (sandboxDoc == null) throw new Error('Unable to open a sandboxed iframe!');
|
218
244
|
|
219
245
|
sandboxDoc.open();
|
package/src/Viewport.ts
CHANGED
@@ -118,12 +118,20 @@ export class Viewport {
|
|
118
118
|
return this.transform;
|
119
119
|
}
|
120
120
|
|
121
|
+
public getResolution(): Vec2 {
|
122
|
+
return this.screenRect.size;
|
123
|
+
}
|
124
|
+
|
121
125
|
// Returns the amount a vector on the canvas is scaled to become a vector on the screen.
|
122
126
|
public getScaleFactor(): number {
|
123
127
|
// Use transformVec3 to avoid translating the vector
|
124
128
|
return this.transform.transformVec3(Vec3.unitX).magnitude();
|
125
129
|
}
|
126
130
|
|
131
|
+
public getSizeOfPixelOnCanvas(): number {
|
132
|
+
return 1/this.getScaleFactor();
|
133
|
+
}
|
134
|
+
|
127
135
|
// Returns the angle of the canvas in radians
|
128
136
|
public getRotationAngle(): number {
|
129
137
|
return this.transform.transformVec3(Vec3.unitX).angle();
|
@@ -158,6 +166,66 @@ export class Viewport {
|
|
158
166
|
public roundPoint(point: Point2): Point2 {
|
159
167
|
return Viewport.roundPoint(point, 1 / this.getScaleFactor());
|
160
168
|
}
|
169
|
+
|
170
|
+
// Returns a Command that transforms the view such that [rect] is visible, and perhaps
|
171
|
+
// centered in the viewport.
|
172
|
+
// Returns null if no transformation is necessary
|
173
|
+
public zoomTo(toMakeVisible: Rect2): Command {
|
174
|
+
let transform = Mat33.identity;
|
175
|
+
|
176
|
+
if (toMakeVisible.w === 0 || toMakeVisible.h === 0) {
|
177
|
+
throw new Error(`${toMakeVisible.toString()} rectangle is empty! Cannot zoom to!`);
|
178
|
+
}
|
179
|
+
|
180
|
+
if (isNaN(toMakeVisible.size.magnitude())) {
|
181
|
+
throw new Error(`${toMakeVisible.toString()} rectangle has NaN size! Cannot zoom to!`);
|
182
|
+
}
|
183
|
+
|
184
|
+
// Try to move the selection within the center 2/3rds of the viewport.
|
185
|
+
const recomputeTargetRect = () => {
|
186
|
+
// transform transforms objects on the canvas. As such, we need to invert it
|
187
|
+
// to transform the viewport.
|
188
|
+
const visibleRect = this.visibleRect.transformedBoundingBox(transform.inverse());
|
189
|
+
return visibleRect.transformedBoundingBox(Mat33.scaling2D(2 / 3, visibleRect.center));
|
190
|
+
};
|
191
|
+
|
192
|
+
let targetRect = recomputeTargetRect();
|
193
|
+
const largerThanTarget = targetRect.w < toMakeVisible.w || targetRect.h < toMakeVisible.h;
|
194
|
+
|
195
|
+
// Ensure that toMakeVisible is at least 1/8th of the visible region.
|
196
|
+
const muchSmallerThanTarget = toMakeVisible.maxDimension / targetRect.maxDimension < 0.125;
|
197
|
+
|
198
|
+
if (largerThanTarget || muchSmallerThanTarget) {
|
199
|
+
// If larger than the target, ensure that the longest axis is visible.
|
200
|
+
// If smaller, shrink the visible rectangle as much as possible
|
201
|
+
const multiplier = (largerThanTarget ? Math.max : Math.min)(
|
202
|
+
toMakeVisible.w / targetRect.w, toMakeVisible.h / targetRect.h
|
203
|
+
);
|
204
|
+
const visibleRectTransform = Mat33.scaling2D(multiplier, targetRect.topLeft);
|
205
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
206
|
+
|
207
|
+
transform = transform.rightMul(viewportContentTransform);
|
208
|
+
}
|
209
|
+
|
210
|
+
targetRect = recomputeTargetRect();
|
211
|
+
|
212
|
+
// Ensure that the center of the region is visible
|
213
|
+
if (!targetRect.containsRect(toMakeVisible)) {
|
214
|
+
// target position - current position
|
215
|
+
const translation = toMakeVisible.center.minus(targetRect.center);
|
216
|
+
const visibleRectTransform = Mat33.translation(translation);
|
217
|
+
const viewportContentTransform = visibleRectTransform.inverse();
|
218
|
+
|
219
|
+
transform = transform.rightMul(viewportContentTransform);
|
220
|
+
}
|
221
|
+
|
222
|
+
if (!transform.invertable()) {
|
223
|
+
console.warn('Unable to zoom to ', toMakeVisible, '! Computed transform', transform, 'is singular.');
|
224
|
+
transform = Mat33.identity;
|
225
|
+
}
|
226
|
+
|
227
|
+
return new Viewport.ViewportTransform(transform);
|
228
|
+
}
|
161
229
|
}
|
162
230
|
|
163
231
|
export namespace Viewport { // eslint-disable-line
|
@@ -4,13 +4,16 @@ import EditorImage from '../EditorImage';
|
|
4
4
|
import LineSegment2 from '../geometry/LineSegment2';
|
5
5
|
import Mat33 from '../geometry/Mat33';
|
6
6
|
import Rect2 from '../geometry/Rect2';
|
7
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
7
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
8
8
|
import { ImageComponentLocalization } from './localization';
|
9
9
|
|
10
|
+
type LoadSaveData = unknown;
|
11
|
+
export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
12
|
+
|
10
13
|
export default abstract class AbstractComponent {
|
11
14
|
protected lastChangedTime: number;
|
12
15
|
protected abstract contentBBox: Rect2;
|
13
|
-
|
16
|
+
private zIndex: number;
|
14
17
|
|
15
18
|
// Topmost z-index
|
16
19
|
private static zIndexCounter: number = 0;
|
@@ -20,9 +23,25 @@ export default abstract class AbstractComponent {
|
|
20
23
|
this.zIndex = AbstractComponent.zIndexCounter++;
|
21
24
|
}
|
22
25
|
|
26
|
+
// Get and manage data attached by a loader.
|
27
|
+
private loadSaveData: LoadSaveDataTable = {};
|
28
|
+
public attachLoadSaveData(key: string, data: LoadSaveData) {
|
29
|
+
if (!this.loadSaveData[key]) {
|
30
|
+
this.loadSaveData[key] = [];
|
31
|
+
}
|
32
|
+
this.loadSaveData[key].push(data);
|
33
|
+
}
|
34
|
+
public getLoadSaveData(): LoadSaveDataTable {
|
35
|
+
return this.loadSaveData;
|
36
|
+
}
|
37
|
+
|
38
|
+
public getZIndex(): number {
|
39
|
+
return this.zIndex;
|
40
|
+
}
|
23
41
|
public getBBox(): Rect2 {
|
24
42
|
return this.contentBBox;
|
25
43
|
}
|
44
|
+
|
26
45
|
public abstract render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
27
46
|
public abstract intersects(lineSegment: LineSegment2): boolean;
|
28
47
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import LineSegment2 from '../geometry/LineSegment2';
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Rect2 from '../geometry/Rect2';
|
4
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
5
|
-
import SVGRenderer from '../rendering/SVGRenderer';
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
package/src/components/Stroke.ts
CHANGED
@@ -2,7 +2,7 @@ import LineSegment2 from '../geometry/LineSegment2';
|
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Path from '../geometry/Path';
|
4
4
|
import Rect2 from '../geometry/Rect2';
|
5
|
-
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from '../rendering/AbstractRenderer';
|
5
|
+
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
@@ -58,7 +58,7 @@ export default class Stroke extends AbstractComponent {
|
|
58
58
|
canvas.drawPath(part);
|
59
59
|
}
|
60
60
|
}
|
61
|
-
canvas.endObject();
|
61
|
+
canvas.endObject(this.getLoadSaveData());
|
62
62
|
}
|
63
63
|
|
64
64
|
// Grows the bounding box for a given stroke part based on that part's style.
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import LineSegment2 from '../geometry/LineSegment2';
|
2
2
|
import Mat33 from '../geometry/Mat33';
|
3
3
|
import Rect2 from '../geometry/Rect2';
|
4
|
-
import AbstractRenderer from '../rendering/AbstractRenderer';
|
5
|
-
import SVGRenderer from '../rendering/SVGRenderer';
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { PathCommandType } from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
|
-
import AbstractRenderer, { RenderingStyle, RenderablePathSpec } from '../../rendering/AbstractRenderer';
|
2
|
+
import AbstractRenderer, { RenderingStyle, RenderablePathSpec } from '../../rendering/renderers/AbstractRenderer';
|
3
3
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
4
4
|
import Rect2 from '../../geometry/Rect2';
|
5
5
|
import { PathCommand, PathCommandType } from '../../geometry/Path';
|
@@ -212,7 +212,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
212
212
|
const lowerBoundary = computeBoundaryCurve(-1, halfVec);
|
213
213
|
|
214
214
|
// If the boundaries have two intersections, increasing the half vector's length could fix this.
|
215
|
-
if (upperBoundary.intersects(lowerBoundary).length
|
215
|
+
if (upperBoundary.intersects(lowerBoundary).length > 0) {
|
216
216
|
halfVec = halfVec.times(2);
|
217
217
|
}
|
218
218
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { PathCommandType } from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Path from '../../geometry/Path';
|
2
2
|
import Rect2 from '../../geometry/Rect2';
|
3
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
3
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
4
4
|
import { StrokeDataPoint } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
6
6
|
import AbstractComponent from '../AbstractComponent';
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Rect2 from '../../geometry/Rect2';
|
2
|
-
import AbstractRenderer from '../../rendering/AbstractRenderer';
|
2
|
+
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
3
3
|
import { StrokeDataPoint } from '../../types';
|
4
4
|
import Viewport from '../../Viewport';
|
5
5
|
import AbstractComponent from '../AbstractComponent';
|
package/src/geometry/Mat33.ts
CHANGED
@@ -90,15 +90,38 @@ describe('Path.fromString', () => {
|
|
90
90
|
]);
|
91
91
|
});
|
92
92
|
|
93
|
+
it('should break compoents at -s', () => {
|
94
|
+
const path = Path.fromString('m1-1 L-1-1-3-4-5-6,5-1');
|
95
|
+
expect(path.parts.length).toBe(4);
|
96
|
+
expect(path.parts).toMatchObject([
|
97
|
+
{
|
98
|
+
kind: PathCommandType.LineTo,
|
99
|
+
point: Vec2.of(-1, -1),
|
100
|
+
},
|
101
|
+
{
|
102
|
+
kind: PathCommandType.LineTo,
|
103
|
+
point: Vec2.of(-3, -4),
|
104
|
+
},
|
105
|
+
{
|
106
|
+
kind: PathCommandType.LineTo,
|
107
|
+
point: Vec2.of(-5, -6),
|
108
|
+
},
|
109
|
+
{
|
110
|
+
kind: PathCommandType.LineTo,
|
111
|
+
point: Vec2.of(5, -1),
|
112
|
+
},
|
113
|
+
]);
|
114
|
+
});
|
115
|
+
|
93
116
|
it('should properly handle cubic Bézier curves', () => {
|
94
|
-
const path = Path.fromString('c1,1 0
|
117
|
+
const path = Path.fromString('m1,1 c1,1 0-3 4 5 C1,1 0.1, 0.1 0, 0');
|
95
118
|
expect(path.parts.length).toBe(2);
|
96
119
|
expect(path.parts).toMatchObject([
|
97
120
|
{
|
98
121
|
kind: PathCommandType.CubicBezierTo,
|
99
|
-
controlPoint1: Vec2.of(
|
122
|
+
controlPoint1: Vec2.of(2, 2),
|
100
123
|
controlPoint2: Vec2.of(1, -2),
|
101
|
-
endPoint: Vec2.of(5,
|
124
|
+
endPoint: Vec2.of(5, 6),
|
102
125
|
},
|
103
126
|
{
|
104
127
|
kind: PathCommandType.CubicBezierTo,
|
@@ -120,7 +143,7 @@ describe('Path.fromString', () => {
|
|
120
143
|
{
|
121
144
|
kind: PathCommandType.QuadraticBezierTo,
|
122
145
|
controlPoint: Vec2.of(1, 1),
|
123
|
-
endPoint: Vec2.of(-
|
146
|
+
endPoint: Vec2.of(-1, -1),
|
124
147
|
},
|
125
148
|
{
|
126
149
|
kind: PathCommandType.QuadraticBezierTo,
|
@@ -130,4 +153,71 @@ describe('Path.fromString', () => {
|
|
130
153
|
]);
|
131
154
|
expect(path.startPoint).toMatchObject(Vec2.of(1, 1));
|
132
155
|
});
|
156
|
+
|
157
|
+
it('should correctly handle a command followed by multiple sets of arguments', () => {
|
158
|
+
// Commands followed by multiple sets of arguments, for example,
|
159
|
+
// l 5,10 5,4 3,2,
|
160
|
+
// should be interpreted as multiple commands. Our example, is therefore equivalent to,
|
161
|
+
// l 5,10 l 5,4 l 3,2
|
162
|
+
|
163
|
+
const path = Path.fromString(`
|
164
|
+
L5,10 1,1
|
165
|
+
2,2 -3,-1
|
166
|
+
q 1,2 1,1
|
167
|
+
-1,-1 -3,-4
|
168
|
+
h -4 -1
|
169
|
+
V 3 5 1
|
170
|
+
`);
|
171
|
+
expect(path.parts).toMatchObject([
|
172
|
+
{
|
173
|
+
kind: PathCommandType.LineTo,
|
174
|
+
point: Vec2.of(1, 1),
|
175
|
+
},
|
176
|
+
{
|
177
|
+
kind: PathCommandType.LineTo,
|
178
|
+
point: Vec2.of(2, 2),
|
179
|
+
},
|
180
|
+
{
|
181
|
+
kind: PathCommandType.LineTo,
|
182
|
+
point: Vec2.of(-3, -1),
|
183
|
+
},
|
184
|
+
|
185
|
+
// q 1,2 1,1 -1,-1 -3,-4
|
186
|
+
{
|
187
|
+
kind: PathCommandType.QuadraticBezierTo,
|
188
|
+
controlPoint: Vec2.of(-2, 1),
|
189
|
+
endPoint: Vec2.of(-2, 0),
|
190
|
+
},
|
191
|
+
{
|
192
|
+
kind: PathCommandType.QuadraticBezierTo,
|
193
|
+
controlPoint: Vec2.of(-3, -1),
|
194
|
+
endPoint: Vec2.of(-5, -4),
|
195
|
+
},
|
196
|
+
|
197
|
+
// h -4 -1
|
198
|
+
{
|
199
|
+
kind: PathCommandType.LineTo,
|
200
|
+
point: Vec2.of(-9, -4),
|
201
|
+
},
|
202
|
+
{
|
203
|
+
kind: PathCommandType.LineTo,
|
204
|
+
point: Vec2.of(-10, -4),
|
205
|
+
},
|
206
|
+
|
207
|
+
// V 3 5 1
|
208
|
+
{
|
209
|
+
kind: PathCommandType.LineTo,
|
210
|
+
point: Vec2.of(-10, 3),
|
211
|
+
},
|
212
|
+
{
|
213
|
+
kind: PathCommandType.LineTo,
|
214
|
+
point: Vec2.of(-10, 5),
|
215
|
+
},
|
216
|
+
{
|
217
|
+
kind: PathCommandType.LineTo,
|
218
|
+
point: Vec2.of(-10, 1),
|
219
|
+
},
|
220
|
+
]);
|
221
|
+
expect(path.startPoint).toMatchObject(Vec2.of(5, 10));
|
222
|
+
});
|
133
223
|
});
|
@@ -19,13 +19,23 @@ describe('Path.toString', () => {
|
|
19
19
|
});
|
20
20
|
|
21
21
|
it('should fix rounding errors', () => {
|
22
|
-
const path = new Path(Vec2.of(0.
|
22
|
+
const path = new Path(Vec2.of(0.10000001, 0.19999999), [
|
23
23
|
{
|
24
24
|
kind: PathCommandType.QuadraticBezierTo,
|
25
25
|
controlPoint: Vec2.of(9999, -10.999999995),
|
26
|
-
endPoint: Vec2.of(0.000300001, 1.
|
26
|
+
endPoint: Vec2.of(0.000300001, 1.40000002),
|
27
27
|
},
|
28
28
|
]);
|
29
29
|
expect(path.toString()).toBe('M0.1,0.2Q9999,-11 0.0003,1.4');
|
30
30
|
});
|
31
|
+
|
32
|
+
it('should not remove trailing zeroes before decimal points', () => {
|
33
|
+
const path = new Path(Vec2.of(1000, 2_000_000), [
|
34
|
+
{
|
35
|
+
kind: PathCommandType.LineTo,
|
36
|
+
point: Vec2.of(30.0001, 40.000000001),
|
37
|
+
},
|
38
|
+
]);
|
39
|
+
expect(path.toString()).toBe('M1000,2000000L30.0001,40');
|
40
|
+
});
|
31
41
|
});
|