js-draw 0.1.5 → 0.1.8
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +6 -2
- package/dist/src/Editor.d.ts +1 -0
- package/dist/src/Editor.js +24 -9
- package/dist/src/EditorImage.d.ts +8 -13
- package/dist/src/EditorImage.js +51 -29
- package/dist/src/SVGLoader.js +6 -2
- package/dist/src/Viewport.d.ts +10 -2
- package/dist/src/Viewport.js +8 -6
- package/dist/src/commands/Command.d.ts +9 -8
- package/dist/src/commands/Command.js +15 -14
- package/dist/src/commands/Duplicate.d.ts +14 -0
- package/dist/src/commands/Duplicate.js +34 -0
- package/dist/src/commands/Erase.d.ts +5 -2
- package/dist/src/commands/Erase.js +28 -9
- package/dist/src/commands/SerializableCommand.d.ts +13 -0
- package/dist/src/commands/SerializableCommand.js +28 -0
- package/dist/src/commands/localization.d.ts +2 -0
- package/dist/src/commands/localization.js +2 -0
- package/dist/src/components/AbstractComponent.d.ts +15 -2
- package/dist/src/components/AbstractComponent.js +122 -26
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +6 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +23 -1
- package/dist/src/components/Stroke.d.ts +5 -0
- package/dist/src/components/Stroke.js +32 -1
- package/dist/src/components/Text.d.ts +11 -4
- package/dist/src/components/Text.js +57 -3
- package/dist/src/components/UnknownSVGObject.d.ts +2 -0
- package/dist/src/components/UnknownSVGObject.js +12 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +3 -1
- package/dist/src/components/builders/RectangleBuilder.js +17 -8
- package/dist/src/components/util/describeComponentList.d.ts +4 -0
- package/dist/src/components/util/describeComponentList.js +14 -0
- package/dist/src/geometry/Path.d.ts +4 -1
- package/dist/src/geometry/Path.js +4 -0
- package/dist/src/rendering/Display.d.ts +3 -0
- package/dist/src/rendering/Display.js +13 -0
- package/dist/src/rendering/RenderingStyle.d.ts +24 -0
- package/dist/src/rendering/RenderingStyle.js +32 -0
- package/dist/src/rendering/caching/RenderingCacheNode.js +5 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -8
- package/dist/src/rendering/renderers/AbstractRenderer.js +1 -6
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +1 -1
- package/dist/src/toolbar/HTMLToolbar.js +52 -534
- package/dist/src/toolbar/icons.d.ts +5 -0
- package/dist/src/toolbar/icons.js +186 -13
- package/dist/src/toolbar/localization.d.ts +4 -0
- package/dist/src/toolbar/localization.js +4 -0
- package/dist/src/toolbar/makeColorInput.d.ts +5 -0
- package/dist/src/toolbar/makeColorInput.js +81 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +12 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.js +44 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +148 -0
- package/dist/src/toolbar/widgets/EraserWidget.d.ts +6 -0
- package/dist/src/toolbar/widgets/EraserWidget.js +14 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +133 -0
- package/dist/src/toolbar/widgets/PenWidget.d.ts +20 -0
- package/dist/src/toolbar/widgets/PenWidget.js +131 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +11 -0
- package/dist/src/toolbar/widgets/SelectionWidget.js +56 -0
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/TextToolWidget.js +72 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/PipetteTool.d.ts +20 -0
- package/dist/src/tools/PipetteTool.js +40 -0
- package/dist/src/tools/SelectionTool.d.ts +2 -0
- package/dist/src/tools/SelectionTool.js +41 -23
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.d.ts +3 -1
- package/dist/src/tools/ToolController.js +4 -0
- package/dist/src/tools/localization.d.ts +2 -1
- package/dist/src/tools/localization.js +3 -2
- package/dist/src/types.d.ts +7 -2
- package/dist/src/types.js +1 -0
- package/jest.config.js +2 -0
- package/package.json +6 -6
- package/src/Color4.ts +9 -3
- package/src/Editor.ts +29 -12
- package/src/EditorImage.test.ts +5 -5
- package/src/EditorImage.ts +61 -20
- package/src/SVGLoader.ts +9 -3
- package/src/Viewport.ts +7 -6
- package/src/commands/Command.ts +21 -19
- package/src/commands/Duplicate.ts +49 -0
- package/src/commands/Erase.ts +34 -13
- package/src/commands/SerializableCommand.ts +41 -0
- package/src/commands/localization.ts +5 -0
- package/src/components/AbstractComponent.ts +168 -26
- package/src/components/SVGGlobalAttributesObject.ts +34 -2
- package/src/components/Stroke.test.ts +53 -0
- package/src/components/Stroke.ts +37 -2
- package/src/components/Text.test.ts +38 -0
- package/src/components/Text.ts +80 -5
- package/src/components/UnknownSVGObject.test.ts +10 -0
- package/src/components/UnknownSVGObject.ts +15 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -1
- package/src/components/builders/RectangleBuilder.ts +23 -8
- package/src/components/util/describeComponentList.ts +18 -0
- package/src/geometry/Path.ts +8 -1
- package/src/rendering/Display.ts +17 -1
- package/src/rendering/RenderingStyle.test.ts +68 -0
- package/src/rendering/RenderingStyle.ts +46 -0
- package/src/rendering/caching/RenderingCache.test.ts +1 -1
- package/src/rendering/caching/RenderingCacheNode.ts +6 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -15
- package/src/rendering/renderers/CanvasRenderer.ts +2 -1
- package/src/rendering/renderers/DummyRenderer.ts +2 -1
- package/src/rendering/renderers/SVGRenderer.ts +2 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +2 -1
- package/src/toolbar/HTMLToolbar.ts +58 -660
- package/src/toolbar/icons.ts +205 -13
- package/src/toolbar/localization.ts +10 -2
- package/src/toolbar/makeColorInput.ts +105 -0
- package/src/toolbar/toolbar.css +116 -78
- package/src/toolbar/widgets/BaseToolWidget.ts +53 -0
- package/src/toolbar/widgets/BaseWidget.ts +175 -0
- package/src/toolbar/widgets/EraserWidget.ts +16 -0
- package/src/toolbar/widgets/HandToolWidget.ts +186 -0
- package/src/toolbar/widgets/PenWidget.ts +165 -0
- package/src/toolbar/widgets/SelectionWidget.ts +72 -0
- package/src/toolbar/widgets/TextToolWidget.ts +90 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/PipetteTool.ts +56 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +47 -27
- package/src/tools/TextTool.ts +1 -1
- package/src/tools/ToolController.ts +10 -6
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +6 -3
- package/src/types.ts +12 -1
package/src/components/Text.ts
CHANGED
@@ -1,7 +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
|
4
|
+
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
|
+
import RenderingStyle, { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle';
|
5
6
|
import AbstractComponent from './AbstractComponent';
|
6
7
|
import { ImageComponentLocalization } from './localization';
|
7
8
|
|
@@ -13,12 +14,21 @@ export interface TextStyle {
|
|
13
14
|
renderingStyle: RenderingStyle;
|
14
15
|
}
|
15
16
|
|
17
|
+
type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
|
16
18
|
|
19
|
+
const componentTypeId = 'text';
|
17
20
|
export default class Text extends AbstractComponent {
|
18
21
|
protected contentBBox: Rect2;
|
19
22
|
|
20
|
-
public constructor(
|
21
|
-
|
23
|
+
public constructor(
|
24
|
+
protected readonly textObjects: Array<string|Text>,
|
25
|
+
private transform: Mat33,
|
26
|
+
private readonly style: TextStyle,
|
27
|
+
|
28
|
+
// If not given, an HtmlCanvasElement is used to determine text boundaries.
|
29
|
+
private readonly getTextDimens: GetTextDimensCallback = Text.getTextDimens,
|
30
|
+
) {
|
31
|
+
super(componentTypeId);
|
22
32
|
this.recomputeBBox();
|
23
33
|
}
|
24
34
|
|
@@ -52,7 +62,7 @@ export default class Text extends AbstractComponent {
|
|
52
62
|
|
53
63
|
private computeBBoxOfPart(part: string|Text) {
|
54
64
|
if (typeof part === 'string') {
|
55
|
-
const textBBox =
|
65
|
+
const textBBox = this.getTextDimens(part, this.style);
|
56
66
|
return textBBox.transformedBoundingBox(this.transform);
|
57
67
|
} else {
|
58
68
|
const bbox = part.contentBBox.transformedBoundingBox(this.transform);
|
@@ -120,6 +130,10 @@ export default class Text extends AbstractComponent {
|
|
120
130
|
this.recomputeBBox();
|
121
131
|
}
|
122
132
|
|
133
|
+
protected createClone(): AbstractComponent {
|
134
|
+
return new Text(this.textObjects, this.transform, this.style);
|
135
|
+
}
|
136
|
+
|
123
137
|
private getText() {
|
124
138
|
const result: string[] = [];
|
125
139
|
|
@@ -137,4 +151,65 @@ export default class Text extends AbstractComponent {
|
|
137
151
|
public description(localizationTable: ImageComponentLocalization): string {
|
138
152
|
return localizationTable.text(this.getText());
|
139
153
|
}
|
140
|
-
|
154
|
+
|
155
|
+
protected serializeToString(): string {
|
156
|
+
const serializableStyle = {
|
157
|
+
...this.style,
|
158
|
+
renderingStyle: styleToJSON(this.style.renderingStyle),
|
159
|
+
};
|
160
|
+
|
161
|
+
const textObjects = this.textObjects.map(text => {
|
162
|
+
if (typeof text === 'string') {
|
163
|
+
return {
|
164
|
+
text,
|
165
|
+
};
|
166
|
+
} else {
|
167
|
+
return {
|
168
|
+
json: text.serializeToString(),
|
169
|
+
};
|
170
|
+
}
|
171
|
+
});
|
172
|
+
|
173
|
+
return JSON.stringify({
|
174
|
+
textObjects,
|
175
|
+
transform: this.transform.toArray(),
|
176
|
+
style: serializableStyle,
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
public static deserializeFromString(data: string, getTextDimens: GetTextDimensCallback = Text.getTextDimens): Text {
|
181
|
+
const json = JSON.parse(data);
|
182
|
+
|
183
|
+
const style: TextStyle = {
|
184
|
+
renderingStyle: styleFromJSON(json.style.renderingStyle),
|
185
|
+
size: json.style.size,
|
186
|
+
fontWeight: json.style.fontWeight,
|
187
|
+
fontVariant: json.style.fontVariant,
|
188
|
+
fontFamily: json.style.fontFamily,
|
189
|
+
};
|
190
|
+
|
191
|
+
const textObjects: Array<string|Text> = json.textObjects.map((data: any) => {
|
192
|
+
if ((data.text ?? null) !== null) {
|
193
|
+
return data.text;
|
194
|
+
}
|
195
|
+
|
196
|
+
return Text.deserializeFromString(data.json);
|
197
|
+
});
|
198
|
+
|
199
|
+
json.transform = json.transform.filter((elem: any) => typeof elem === 'number');
|
200
|
+
if (json.transform.length !== 9) {
|
201
|
+
throw new Error(`Unable to deserialize transform, ${json.transform}.`);
|
202
|
+
}
|
203
|
+
|
204
|
+
const transformData = json.transform as [
|
205
|
+
number, number, number,
|
206
|
+
number, number, number,
|
207
|
+
number, number, number,
|
208
|
+
];
|
209
|
+
const transform = new Mat33(...transformData);
|
210
|
+
|
211
|
+
return new Text(textObjects, transform, style, getTextDimens);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
AbstractComponent.registerComponent(componentTypeId, (data: string) => Text.deserializeFromString(data));
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import AbstractComponent from './AbstractComponent';
|
2
|
+
import UnknownSVGObject from './UnknownSVGObject';
|
3
|
+
|
4
|
+
describe('UnknownSVGObject', () => {
|
5
|
+
it('should not be deserializable', () => {
|
6
|
+
const obj = new UnknownSVGObject(document.createElementNS('http://www.w3.org/2000/svg', 'circle'));
|
7
|
+
const serialized = obj.serialize();
|
8
|
+
expect(() => AbstractComponent.deserialize(serialized)).toThrow(/.*cannot be deserialized.*/);
|
9
|
+
});
|
10
|
+
});
|
@@ -6,11 +6,12 @@ import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
|
6
6
|
import AbstractComponent from './AbstractComponent';
|
7
7
|
import { ImageComponentLocalization } from './localization';
|
8
8
|
|
9
|
+
const componentId = 'unknown-svg-object';
|
9
10
|
export default class UnknownSVGObject extends AbstractComponent {
|
10
11
|
protected contentBBox: Rect2;
|
11
12
|
|
12
13
|
public constructor(private svgObject: SVGElement) {
|
13
|
-
super();
|
14
|
+
super(componentId);
|
14
15
|
this.contentBBox = Rect2.of(svgObject.getBoundingClientRect());
|
15
16
|
}
|
16
17
|
|
@@ -30,7 +31,20 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
30
31
|
protected applyTransformation(_affineTransfm: Mat33): void {
|
31
32
|
}
|
32
33
|
|
34
|
+
protected createClone(): AbstractComponent {
|
35
|
+
return new UnknownSVGObject(this.svgObject.cloneNode(true) as SVGElement);
|
36
|
+
}
|
37
|
+
|
33
38
|
public description(localization: ImageComponentLocalization): string {
|
34
39
|
return localization.svgObject;
|
35
40
|
}
|
41
|
+
|
42
|
+
protected serializeToString(): string | null {
|
43
|
+
return JSON.stringify({
|
44
|
+
html: this.svgObject.outerHTML,
|
45
|
+
});
|
46
|
+
}
|
36
47
|
}
|
48
|
+
|
49
|
+
// null: Do not deserialize UnknownSVGObjects.
|
50
|
+
AbstractComponent.registerComponent(componentId, null);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
|
-
import AbstractRenderer, {
|
2
|
+
import AbstractRenderer, { 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';
|
@@ -8,6 +8,7 @@ import Stroke from '../Stroke';
|
|
8
8
|
import Viewport from '../../Viewport';
|
9
9
|
import { StrokeDataPoint } from '../../types';
|
10
10
|
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
11
|
+
import RenderingStyle from '../../rendering/RenderingStyle';
|
11
12
|
|
12
13
|
export const makeFreehandLineBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
13
14
|
// Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import Mat33 from '../../geometry/Mat33';
|
1
2
|
import Path from '../../geometry/Path';
|
2
3
|
import Rect2 from '../../geometry/Rect2';
|
3
4
|
import AbstractRenderer from '../../rendering/renderers/AbstractRenderer';
|
@@ -7,18 +8,22 @@ import AbstractComponent from '../AbstractComponent';
|
|
7
8
|
import Stroke from '../Stroke';
|
8
9
|
import { ComponentBuilder, ComponentBuilderFactory } from './types';
|
9
10
|
|
10
|
-
export const makeFilledRectangleBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint,
|
11
|
-
return new RectangleBuilder(initialPoint, true);
|
11
|
+
export const makeFilledRectangleBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
12
|
+
return new RectangleBuilder(initialPoint, true, viewport);
|
12
13
|
};
|
13
14
|
|
14
|
-
export const makeOutlinedRectangleBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint,
|
15
|
-
return new RectangleBuilder(initialPoint, false);
|
15
|
+
export const makeOutlinedRectangleBuilder: ComponentBuilderFactory = (initialPoint: StrokeDataPoint, viewport: Viewport) => {
|
16
|
+
return new RectangleBuilder(initialPoint, false, viewport);
|
16
17
|
};
|
17
18
|
|
18
19
|
export default class RectangleBuilder implements ComponentBuilder {
|
19
20
|
private endPoint: StrokeDataPoint;
|
20
21
|
|
21
|
-
public constructor(
|
22
|
+
public constructor(
|
23
|
+
private readonly startPoint: StrokeDataPoint,
|
24
|
+
private filled: boolean,
|
25
|
+
private viewport: Viewport,
|
26
|
+
) {
|
22
27
|
// Initially, the start and end points are the same.
|
23
28
|
this.endPoint = startPoint;
|
24
29
|
}
|
@@ -29,11 +34,21 @@ export default class RectangleBuilder implements ComponentBuilder {
|
|
29
34
|
}
|
30
35
|
|
31
36
|
private buildPreview(): Stroke {
|
32
|
-
const
|
33
|
-
const
|
37
|
+
const canvasAngle = this.viewport.getRotationAngle();
|
38
|
+
const rotationMat = Mat33.zRotation(-canvasAngle);
|
39
|
+
|
40
|
+
// Adjust startPoint and endPoint such that applying [rotationMat] to them
|
41
|
+
// brings them to this.startPoint and this.endPoint.
|
42
|
+
const startPoint = rotationMat.inverse().transformVec2(this.startPoint.pos);
|
43
|
+
const endPoint = rotationMat.inverse().transformVec2(this.endPoint.pos);
|
44
|
+
|
45
|
+
const rect = Rect2.fromCorners(startPoint, endPoint);
|
34
46
|
const path = Path.fromRect(
|
35
|
-
|
47
|
+
rect,
|
36
48
|
this.filled ? null : this.endPoint.width,
|
49
|
+
).transformedBy(
|
50
|
+
// Rotate the canvas rectangle so that its rotation matches the screen
|
51
|
+
rotationMat
|
37
52
|
);
|
38
53
|
|
39
54
|
const preview = new Stroke([
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import AbstractComponent from '../AbstractComponent';
|
2
|
+
import { ImageComponentLocalization } from '../localization';
|
3
|
+
|
4
|
+
// Returns the description of all given elements, if identical, otherwise,
|
5
|
+
// returns null.
|
6
|
+
export default (localizationTable: ImageComponentLocalization, elems: AbstractComponent[]) => {
|
7
|
+
if (elems.length === 0) {
|
8
|
+
return null;
|
9
|
+
}
|
10
|
+
|
11
|
+
const description = elems[0].description(localizationTable);
|
12
|
+
for (const elem of elems) {
|
13
|
+
if (elem.description(localizationTable) !== description) {
|
14
|
+
return null;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
return description;
|
18
|
+
};
|
package/src/geometry/Path.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
|
-
import {
|
2
|
+
import { RenderablePathSpec } from '../rendering/renderers/AbstractRenderer';
|
3
|
+
import RenderingStyle from '../rendering/RenderingStyle';
|
3
4
|
import LineSegment2 from './LineSegment2';
|
4
5
|
import Mat33 from './Mat33';
|
5
6
|
import Rect2 from './Rect2';
|
@@ -282,6 +283,10 @@ export default class Path {
|
|
282
283
|
return Path.toString(this.startPoint, this.parts);
|
283
284
|
}
|
284
285
|
|
286
|
+
public serialize(): string {
|
287
|
+
return this.toString();
|
288
|
+
}
|
289
|
+
|
285
290
|
public static toString(startPoint: Point2, parts: PathCommand[]): string {
|
286
291
|
const result: string[] = [];
|
287
292
|
|
@@ -555,4 +560,6 @@ export default class Path {
|
|
555
560
|
|
556
561
|
return new Path(startPos ?? Vec2.zero, commands);
|
557
562
|
}
|
563
|
+
|
564
|
+
public static empty: Path = new Path(Vec2.zero, []);
|
558
565
|
}
|
package/src/rendering/Display.ts
CHANGED
@@ -3,9 +3,10 @@ import CanvasRenderer from './renderers/CanvasRenderer';
|
|
3
3
|
import { Editor } from '../Editor';
|
4
4
|
import { EditorEventType } from '../types';
|
5
5
|
import DummyRenderer from './renderers/DummyRenderer';
|
6
|
-
import { Vec2 } from '../geometry/Vec2';
|
6
|
+
import { Point2, Vec2 } from '../geometry/Vec2';
|
7
7
|
import RenderingCache from './caching/RenderingCache';
|
8
8
|
import TextOnlyRenderer from './renderers/TextOnlyRenderer';
|
9
|
+
import Color4 from '../Color4';
|
9
10
|
|
10
11
|
export enum RenderingMode {
|
11
12
|
DummyRenderer,
|
@@ -88,6 +89,10 @@ export default class Display {
|
|
88
89
|
return this.cache;
|
89
90
|
}
|
90
91
|
|
92
|
+
public getColorAt = (_screenPos: Point2): Color4|null => {
|
93
|
+
return null;
|
94
|
+
};
|
95
|
+
|
91
96
|
private initializeCanvasRendering() {
|
92
97
|
const dryInkCanvas = document.createElement('canvas');
|
93
98
|
const wetInkCanvas = document.createElement('canvas');
|
@@ -132,6 +137,17 @@ export default class Display {
|
|
132
137
|
this.flattenCallback = () => {
|
133
138
|
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
134
139
|
};
|
140
|
+
|
141
|
+
this.getColorAt = (screenPos: Point2) => {
|
142
|
+
const pixel = dryInkCtx.getImageData(screenPos.x, screenPos.y, 1, 1);
|
143
|
+
const data = pixel?.data;
|
144
|
+
|
145
|
+
if (data) {
|
146
|
+
const color = Color4.ofRGBA(data[0] / 255, data[1] / 255, data[2] / 255, data[3] / 255);
|
147
|
+
return color;
|
148
|
+
}
|
149
|
+
return null;
|
150
|
+
};
|
135
151
|
}
|
136
152
|
|
137
153
|
private initializeTextRendering() {
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
import Color4 from '../Color4';
|
3
|
+
import RenderingStyle, { styleFromJSON, stylesEqual, styleToJSON } from './RenderingStyle';
|
4
|
+
|
5
|
+
|
6
|
+
describe('RenderingStyle', () => {
|
7
|
+
it('identical styles should be equal', () => {
|
8
|
+
const redFill: RenderingStyle = {
|
9
|
+
fill: Color4.red,
|
10
|
+
};
|
11
|
+
expect(stylesEqual(redFill, redFill)).toBe(true);
|
12
|
+
expect(stylesEqual(
|
13
|
+
{ fill: Color4.ofRGB(1, 0, 0.3), },
|
14
|
+
{ fill: Color4.ofRGB(1, 0, 0.3), },
|
15
|
+
)).toBe(true);
|
16
|
+
expect(stylesEqual(
|
17
|
+
{ fill: Color4.red },
|
18
|
+
{ fill: Color4.blue },
|
19
|
+
)).toBe(false);
|
20
|
+
|
21
|
+
expect(stylesEqual(
|
22
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.red }},
|
23
|
+
{ fill: Color4.red },
|
24
|
+
)).toBe(false);
|
25
|
+
expect(stylesEqual(
|
26
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.red }},
|
27
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.blue }},
|
28
|
+
)).toBe(false);
|
29
|
+
expect(stylesEqual(
|
30
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.red }},
|
31
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.red }},
|
32
|
+
)).toBe(true);
|
33
|
+
expect(stylesEqual(
|
34
|
+
{ fill: Color4.red, stroke: { width: 1, color: Color4.red }},
|
35
|
+
{ fill: Color4.red, stroke: { width: 2, color: Color4.red }},
|
36
|
+
)).toBe(false);
|
37
|
+
});
|
38
|
+
|
39
|
+
it('styles should be convertable to JSON', () => {
|
40
|
+
expect(styleToJSON({
|
41
|
+
fill: Color4.red,
|
42
|
+
})).toMatchObject({
|
43
|
+
fill: '#ff0000',
|
44
|
+
stroke: undefined,
|
45
|
+
});
|
46
|
+
|
47
|
+
expect(styleToJSON({
|
48
|
+
fill: Color4.blue,
|
49
|
+
stroke: {
|
50
|
+
width: 4,
|
51
|
+
color: Color4.red,
|
52
|
+
}
|
53
|
+
})).toMatchObject({
|
54
|
+
fill: '#0000ff',
|
55
|
+
stroke: {
|
56
|
+
width: 4,
|
57
|
+
color: '#ff0000'
|
58
|
+
},
|
59
|
+
});
|
60
|
+
});
|
61
|
+
|
62
|
+
it('JSON should be convertable into styles', () => {
|
63
|
+
const redFillJSON = { fill: '#ff0000', };
|
64
|
+
const redFillBlueStrokeJSON = { fill: '#ff0000', stroke: { width: 4, color: '#0000ff' }};
|
65
|
+
expect(styleToJSON(styleFromJSON(redFillJSON))).toMatchObject(redFillJSON);
|
66
|
+
expect(styleToJSON(styleFromJSON(redFillBlueStrokeJSON))).toMatchObject(redFillBlueStrokeJSON);
|
67
|
+
});
|
68
|
+
});
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
|
3
|
+
interface RenderingStyle {
|
4
|
+
fill: Color4;
|
5
|
+
stroke?: {
|
6
|
+
color: Color4;
|
7
|
+
width: number;
|
8
|
+
};
|
9
|
+
}
|
10
|
+
|
11
|
+
export default RenderingStyle;
|
12
|
+
|
13
|
+
export const stylesEqual = (a: RenderingStyle, b: RenderingStyle): boolean => {
|
14
|
+
const result = a === b || (a.fill.eq(b.fill)
|
15
|
+
&& (a.stroke == undefined) === (b.stroke == undefined)
|
16
|
+
&& (a.stroke?.color?.eq(b.stroke?.color) ?? true)
|
17
|
+
&& a.stroke?.width === b.stroke?.width);
|
18
|
+
|
19
|
+
// Map undefined/null -> false
|
20
|
+
return result ?? false;
|
21
|
+
};
|
22
|
+
|
23
|
+
// Returns an object that can be converted to a JSON string with
|
24
|
+
// JSON.stringify.
|
25
|
+
export const styleToJSON = (style: RenderingStyle) => {
|
26
|
+
const stroke = !style.stroke ? undefined : {
|
27
|
+
color: style.stroke.color.toHexString(),
|
28
|
+
width: style.stroke.width,
|
29
|
+
};
|
30
|
+
|
31
|
+
return {
|
32
|
+
fill: style.fill.toHexString(),
|
33
|
+
stroke,
|
34
|
+
};
|
35
|
+
};
|
36
|
+
|
37
|
+
export const styleFromJSON = (json: Record<string, any>) => {
|
38
|
+
const stroke = json.stroke ? {
|
39
|
+
color: Color4.fromHex(json.stroke.color),
|
40
|
+
width: json.stroke.width,
|
41
|
+
} : undefined;
|
42
|
+
return {
|
43
|
+
fill: Color4.fromHex(json.fill),
|
44
|
+
stroke,
|
45
|
+
};
|
46
|
+
};
|
@@ -28,7 +28,7 @@ describe('RenderingCache', () => {
|
|
28
28
|
editor.image.renderWithCache(screenRenderer, cache, editor.viewport);
|
29
29
|
expect(lastRenderer).toBeNull();
|
30
30
|
|
31
|
-
editor.dispatch(
|
31
|
+
editor.dispatch(EditorImage.addElement(testStroke));
|
32
32
|
editor.image.renderWithCache(screenRenderer, cache, editor.viewport);
|
33
33
|
|
34
34
|
expect(allocdRenderers).toBeGreaterThanOrEqual(1);
|
@@ -65,6 +65,11 @@ export default class RenderingCacheNode {
|
|
65
65
|
if (this.instantiatedChildren.length === 0) {
|
66
66
|
const childRects = this.region.divideIntoGrid(cacheDivisionSize, cacheDivisionSize);
|
67
67
|
|
68
|
+
if (this.region.size.x === 0 || this.region.size.y === 0) {
|
69
|
+
console.warn('Cache element has zero size! Not generating children.');
|
70
|
+
return;
|
71
|
+
}
|
72
|
+
|
68
73
|
for (const rect of childRects) {
|
69
74
|
const child = new RenderingCacheNode(rect, this.cacheState);
|
70
75
|
child.parent = this;
|
@@ -357,7 +362,7 @@ export default class RenderingCacheNode {
|
|
357
362
|
|
358
363
|
private checkRep() {
|
359
364
|
if (this.instantiatedChildren.length !== cacheDivisionSize * cacheDivisionSize && this.instantiatedChildren.length !== 0) {
|
360
|
-
throw new Error(
|
365
|
+
throw new Error(`Repcheck: Wrong number of children. Got ${this.instantiatedChildren.length}`);
|
361
366
|
}
|
362
367
|
|
363
368
|
if (this.renderedIds[1] !== undefined && this.renderedIds[0] >= this.renderedIds[1]) {
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import Color4 from '../../Color4';
|
2
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
2
|
import { TextStyle } from '../../components/Text';
|
4
3
|
import Mat33 from '../../geometry/Mat33';
|
@@ -6,14 +5,7 @@ import Path, { PathCommand, PathCommandType } from '../../geometry/Path';
|
|
6
5
|
import Rect2 from '../../geometry/Rect2';
|
7
6
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
8
7
|
import Viewport from '../../Viewport';
|
9
|
-
|
10
|
-
export interface RenderingStyle {
|
11
|
-
fill: Color4;
|
12
|
-
stroke?: {
|
13
|
-
color: Color4;
|
14
|
-
width: number;
|
15
|
-
};
|
16
|
-
}
|
8
|
+
import RenderingStyle, { stylesEqual } from '../RenderingStyle';
|
17
9
|
|
18
10
|
export interface RenderablePathSpec {
|
19
11
|
startPoint: Point2;
|
@@ -21,12 +13,6 @@ export interface RenderablePathSpec {
|
|
21
13
|
style: RenderingStyle;
|
22
14
|
}
|
23
15
|
|
24
|
-
const stylesEqual = (a: RenderingStyle, b: RenderingStyle) => {
|
25
|
-
return a === b || (a.fill.eq(b.fill)
|
26
|
-
&& a.stroke?.color?.eq(b.stroke?.color)
|
27
|
-
&& a.stroke?.width === b.stroke?.width);
|
28
|
-
};
|
29
|
-
|
30
16
|
export default abstract class AbstractRenderer {
|
31
17
|
// If null, this' transformation is linked to the Viewport
|
32
18
|
private selfTransform: Mat33|null = null;
|
@@ -5,7 +5,8 @@ import Rect2 from '../../geometry/Rect2';
|
|
5
5
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
6
6
|
import Vec3 from '../../geometry/Vec3';
|
7
7
|
import Viewport from '../../Viewport';
|
8
|
-
import
|
8
|
+
import RenderingStyle from '../RenderingStyle';
|
9
|
+
import AbstractRenderer, { RenderablePathSpec } from './AbstractRenderer';
|
9
10
|
|
10
11
|
export default class CanvasRenderer extends AbstractRenderer {
|
11
12
|
private ignoreObjectsAboveLevel: number|null = null;
|
@@ -6,7 +6,8 @@ import Rect2 from '../../geometry/Rect2';
|
|
6
6
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
7
7
|
import Vec3 from '../../geometry/Vec3';
|
8
8
|
import Viewport from '../../Viewport';
|
9
|
-
import
|
9
|
+
import RenderingStyle from '../RenderingStyle';
|
10
|
+
import AbstractRenderer from './AbstractRenderer';
|
10
11
|
|
11
12
|
export default class DummyRenderer extends AbstractRenderer {
|
12
13
|
// Variables that track the state of what's been rendered
|
@@ -7,7 +7,8 @@ import Rect2 from '../../geometry/Rect2';
|
|
7
7
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
8
8
|
import { svgAttributesDataKey, SVGLoaderUnknownAttribute, SVGLoaderUnknownStyleAttribute, svgStyleAttributesDataKey } from '../../SVGLoader';
|
9
9
|
import Viewport from '../../Viewport';
|
10
|
-
import
|
10
|
+
import RenderingStyle from '../RenderingStyle';
|
11
|
+
import AbstractRenderer from './AbstractRenderer';
|
11
12
|
|
12
13
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
13
14
|
export default class SVGRenderer extends AbstractRenderer {
|
@@ -5,7 +5,8 @@ import { Vec2 } from '../../geometry/Vec2';
|
|
5
5
|
import Vec3 from '../../geometry/Vec3';
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import { TextRendererLocalization } from '../localization';
|
8
|
-
import
|
8
|
+
import RenderingStyle from '../RenderingStyle';
|
9
|
+
import AbstractRenderer from './AbstractRenderer';
|
9
10
|
|
10
11
|
// Outputs a description of what was rendered.
|
11
12
|
|