js-draw 0.1.2 → 0.1.5
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 +14 -0
- package/README.md +21 -12
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -1
- package/dist/src/Editor.js +20 -6
- package/dist/src/SVGLoader.d.ts +8 -0
- package/dist/src/SVGLoader.js +105 -6
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/Viewport.js +5 -5
- package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
- package/dist/src/components/Text.d.ts +30 -0
- package/dist/src/components/Text.js +111 -0
- package/dist/src/components/localization.d.ts +1 -0
- package/dist/src/components/localization.js +1 -0
- package/dist/src/geometry/Mat33.d.ts +1 -0
- package/dist/src/geometry/Mat33.js +30 -0
- package/dist/src/geometry/Path.js +8 -1
- package/dist/src/geometry/Rect2.d.ts +2 -0
- package/dist/src/geometry/Rect2.js +6 -0
- package/dist/src/localization.d.ts +2 -1
- package/dist/src/localization.js +2 -1
- package/dist/src/rendering/Display.d.ts +2 -0
- package/dist/src/rendering/Display.js +19 -0
- package/dist/src/rendering/localization.d.ts +5 -0
- package/dist/src/rendering/localization.js +4 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +5 -0
- package/dist/src/rendering/renderers/AbstractRenderer.js +12 -0
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +30 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +24 -0
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +40 -0
- package/dist/src/testing/loadExpectExtensions.js +1 -4
- package/dist/src/toolbar/HTMLToolbar.js +78 -1
- package/dist/src/toolbar/icons.d.ts +2 -0
- package/dist/src/toolbar/icons.js +18 -0
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/TextTool.d.ts +31 -0
- package/dist/src/tools/TextTool.js +174 -0
- package/dist/src/tools/ToolController.d.ts +2 -1
- package/dist/src/tools/ToolController.js +4 -1
- package/dist/src/tools/localization.d.ts +3 -1
- package/dist/src/tools/localization.js +3 -1
- package/dist-test/test-dist-bundle.html +8 -1
- package/package.json +1 -1
- package/src/Editor.css +12 -0
- package/src/Editor.ts +22 -7
- package/src/SVGLoader.ts +124 -6
- package/src/Viewport.ts +5 -5
- package/src/components/SVGGlobalAttributesObject.ts +0 -1
- package/src/components/Text.ts +140 -0
- package/src/components/localization.ts +2 -0
- package/src/geometry/Mat33.test.ts +44 -0
- package/src/geometry/Mat33.ts +41 -0
- package/src/geometry/Path.toString.test.ts +7 -3
- package/src/geometry/Path.ts +11 -1
- package/src/geometry/Rect2.ts +8 -0
- package/src/localization.ts +3 -1
- package/src/rendering/Display.ts +26 -0
- package/src/rendering/localization.ts +10 -0
- package/src/rendering/renderers/AbstractRenderer.ts +16 -0
- package/src/rendering/renderers/CanvasRenderer.ts +34 -10
- package/src/rendering/renderers/DummyRenderer.ts +8 -0
- package/src/rendering/renderers/SVGRenderer.ts +36 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +51 -0
- package/src/testing/loadExpectExtensions.ts +1 -4
- package/src/toolbar/HTMLToolbar.ts +96 -1
- package/src/toolbar/icons.ts +24 -0
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/toolbar.css +6 -3
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/TextTool.ts +229 -0
- package/src/tools/ToolController.ts +4 -0
- package/src/tools/localization.ts +7 -2
@@ -3,6 +3,7 @@ import { EditorEventType } from '../types';
|
|
3
3
|
import DummyRenderer from './renderers/DummyRenderer';
|
4
4
|
import { Vec2 } from '../geometry/Vec2';
|
5
5
|
import RenderingCache from './caching/RenderingCache';
|
6
|
+
import TextOnlyRenderer from './renderers/TextOnlyRenderer';
|
6
7
|
export var RenderingMode;
|
7
8
|
(function (RenderingMode) {
|
8
9
|
RenderingMode[RenderingMode["DummyRenderer"] = 0] = "DummyRenderer";
|
@@ -23,6 +24,8 @@ export default class Display {
|
|
23
24
|
else {
|
24
25
|
throw new Error(`Unknown rendering mode, ${mode}!`);
|
25
26
|
}
|
27
|
+
this.textRenderer = new TextOnlyRenderer(editor.viewport, editor.localization);
|
28
|
+
this.initializeTextRendering();
|
26
29
|
const cacheBlockResolution = Vec2.of(600, 600);
|
27
30
|
this.cache = new RenderingCache({
|
28
31
|
createRenderer: () => {
|
@@ -104,6 +107,22 @@ export default class Display {
|
|
104
107
|
dryInkCtx.drawImage(wetInkCanvas, 0, 0);
|
105
108
|
};
|
106
109
|
}
|
110
|
+
initializeTextRendering() {
|
111
|
+
const textRendererOutputContainer = document.createElement('div');
|
112
|
+
textRendererOutputContainer.classList.add('textRendererOutputContainer');
|
113
|
+
const rerenderButton = document.createElement('button');
|
114
|
+
rerenderButton.classList.add('rerenderButton');
|
115
|
+
rerenderButton.innerText = this.editor.localization.rerenderAsText;
|
116
|
+
const rerenderOutput = document.createElement('div');
|
117
|
+
rerenderOutput.ariaLive = 'polite';
|
118
|
+
rerenderButton.onclick = () => {
|
119
|
+
this.textRenderer.clear();
|
120
|
+
this.editor.image.render(this.textRenderer, this.editor.viewport);
|
121
|
+
rerenderOutput.innerText = this.textRenderer.getDescription();
|
122
|
+
};
|
123
|
+
textRendererOutputContainer.replaceChildren(rerenderButton, rerenderOutput);
|
124
|
+
this.editor.createHTMLOverlay(textRendererOutputContainer);
|
125
|
+
}
|
107
126
|
// Clears the drawing surfaces and otherwise prepares for a rerender.
|
108
127
|
startRerender() {
|
109
128
|
var _a;
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
2
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
3
|
+
import { TextStyle } from '../../components/Text';
|
3
4
|
import Mat33 from '../../geometry/Mat33';
|
4
5
|
import { PathCommand } from '../../geometry/Path';
|
5
6
|
import Rect2 from '../../geometry/Rect2';
|
@@ -20,6 +21,7 @@ export interface RenderablePathSpec {
|
|
20
21
|
export default abstract class AbstractRenderer {
|
21
22
|
private viewport;
|
22
23
|
private selfTransform;
|
24
|
+
private transformStack;
|
23
25
|
protected constructor(viewport: Viewport);
|
24
26
|
protected getViewport(): Viewport;
|
25
27
|
abstract displaySize(): Vec2;
|
@@ -30,6 +32,7 @@ export default abstract class AbstractRenderer {
|
|
30
32
|
protected abstract moveTo(point: Point2): void;
|
31
33
|
protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
|
32
34
|
protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
|
35
|
+
abstract drawText(text: string, transform: Mat33, style: TextStyle): void;
|
33
36
|
abstract isTooSmallToRender(rect: Rect2): boolean;
|
34
37
|
setDraftMode(_draftMode: boolean): void;
|
35
38
|
protected objectLevel: number;
|
@@ -44,6 +47,8 @@ export default abstract class AbstractRenderer {
|
|
44
47
|
canRenderFromWithoutDataLoss(_other: AbstractRenderer): boolean;
|
45
48
|
renderFromOtherOfSameType(_renderTo: Mat33, other: AbstractRenderer): void;
|
46
49
|
setTransform(transform: Mat33 | null): void;
|
50
|
+
pushTransform(transform: Mat33): void;
|
51
|
+
popTransform(): void;
|
47
52
|
getCanvasToScreenTransform(): Mat33;
|
48
53
|
canvasToScreen(vec: Vec2): Vec2;
|
49
54
|
getSizeOfCanvasPixelOnScreen(): number;
|
@@ -11,6 +11,7 @@ export default class AbstractRenderer {
|
|
11
11
|
this.viewport = viewport;
|
12
12
|
// If null, this' transformation is linked to the Viewport
|
13
13
|
this.selfTransform = null;
|
14
|
+
this.transformStack = [];
|
14
15
|
this.objectLevel = 0;
|
15
16
|
this.currentPaths = null;
|
16
17
|
}
|
@@ -104,6 +105,17 @@ export default class AbstractRenderer {
|
|
104
105
|
setTransform(transform) {
|
105
106
|
this.selfTransform = transform;
|
106
107
|
}
|
108
|
+
pushTransform(transform) {
|
109
|
+
this.transformStack.push(this.selfTransform);
|
110
|
+
this.setTransform(this.getCanvasToScreenTransform().rightMul(transform));
|
111
|
+
}
|
112
|
+
popTransform() {
|
113
|
+
var _a;
|
114
|
+
if (this.transformStack.length === 0) {
|
115
|
+
throw new Error('Unable to pop more transforms than have been pushed!');
|
116
|
+
}
|
117
|
+
this.setTransform((_a = this.transformStack.pop()) !== null && _a !== void 0 ? _a : null);
|
118
|
+
}
|
107
119
|
// Get the matrix that transforms a vector on the canvas to a vector on this'
|
108
120
|
// rendering target.
|
109
121
|
getCanvasToScreenTransform() {
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { TextStyle } from '../../components/Text';
|
1
2
|
import Mat33 from '../../geometry/Mat33';
|
2
3
|
import Rect2 from '../../geometry/Rect2';
|
3
4
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
@@ -12,6 +13,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
12
13
|
private minRenderSizeAnyDimen;
|
13
14
|
private minRenderSizeBothDimens;
|
14
15
|
constructor(ctx: CanvasRenderingContext2D, viewport: Viewport);
|
16
|
+
private transformBy;
|
15
17
|
canRenderFromWithoutDataLoss(other: AbstractRenderer): boolean;
|
16
18
|
renderFromOtherOfSameType(transformBy: Mat33, other: AbstractRenderer): void;
|
17
19
|
setDraftMode(draftMode: boolean): void;
|
@@ -24,6 +26,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
24
26
|
protected traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
|
25
27
|
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
|
26
28
|
drawPath(path: RenderablePathSpec): void;
|
29
|
+
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
27
30
|
private clipLevels;
|
28
31
|
startObject(boundingBox: Rect2, clip: boolean): void;
|
29
32
|
endObject(): void;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
|
+
import Text from '../../components/Text';
|
2
3
|
import { Vec2 } from '../../geometry/Vec2';
|
3
4
|
import AbstractRenderer from './AbstractRenderer';
|
4
5
|
export default class CanvasRenderer extends AbstractRenderer {
|
@@ -10,6 +11,16 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
10
11
|
this.clipLevels = [];
|
11
12
|
this.setDraftMode(false);
|
12
13
|
}
|
14
|
+
transformBy(transformBy) {
|
15
|
+
// From MDN, transform(a,b,c,d,e,f)
|
16
|
+
// takes input such that
|
17
|
+
// ⎡ a c e ⎤
|
18
|
+
// ⎢ b d f ⎥ transforms content drawn to [ctx].
|
19
|
+
// ⎣ 0 0 1 ⎦
|
20
|
+
this.ctx.transform(transformBy.a1, transformBy.b1, // a, b
|
21
|
+
transformBy.a2, transformBy.b2, // c, d
|
22
|
+
transformBy.a3, transformBy.b3);
|
23
|
+
}
|
13
24
|
canRenderFromWithoutDataLoss(other) {
|
14
25
|
return other instanceof CanvasRenderer;
|
15
26
|
}
|
@@ -19,14 +30,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
19
30
|
}
|
20
31
|
transformBy = this.getCanvasToScreenTransform().rightMul(transformBy);
|
21
32
|
this.ctx.save();
|
22
|
-
|
23
|
-
// takes input such that
|
24
|
-
// ⎡ a c e ⎤
|
25
|
-
// ⎢ b d f ⎥ transforms content drawn to [ctx].
|
26
|
-
// ⎣ 0 0 1 ⎦
|
27
|
-
this.ctx.transform(transformBy.a1, transformBy.b1, // a, b
|
28
|
-
transformBy.a2, transformBy.b2, // c, d
|
29
|
-
transformBy.a3, transformBy.b3);
|
33
|
+
this.transformBy(transformBy);
|
30
34
|
this.ctx.drawImage(other.ctx.canvas, 0, 0);
|
31
35
|
this.ctx.restore();
|
32
36
|
}
|
@@ -105,6 +109,22 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
105
109
|
}
|
106
110
|
super.drawPath(path);
|
107
111
|
}
|
112
|
+
drawText(text, transform, style) {
|
113
|
+
this.ctx.save();
|
114
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
115
|
+
this.transformBy(transform);
|
116
|
+
Text.applyTextStyles(this.ctx, style);
|
117
|
+
if (style.renderingStyle.fill.a !== 0) {
|
118
|
+
this.ctx.fillStyle = style.renderingStyle.fill.toHexString();
|
119
|
+
this.ctx.fillText(text, 0, 0);
|
120
|
+
}
|
121
|
+
if (style.renderingStyle.stroke) {
|
122
|
+
this.ctx.strokeStyle = style.renderingStyle.stroke.color.toHexString();
|
123
|
+
this.ctx.lineWidth = style.renderingStyle.stroke.width;
|
124
|
+
this.ctx.strokeText(text, 0, 0);
|
125
|
+
}
|
126
|
+
this.ctx.restore();
|
127
|
+
}
|
108
128
|
startObject(boundingBox, clip) {
|
109
129
|
if (this.isTooSmallToRender(boundingBox)) {
|
110
130
|
this.ignoreObjectsAboveLevel = this.getNestingLevel();
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { TextStyle } from '../../components/Text';
|
1
2
|
import Mat33 from '../../geometry/Mat33';
|
2
3
|
import Rect2 from '../../geometry/Rect2';
|
3
4
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
@@ -10,6 +11,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
10
11
|
lastFillStyle: RenderingStyle | null;
|
11
12
|
lastPoint: Point2 | null;
|
12
13
|
objectNestingLevel: number;
|
14
|
+
lastText: string | null;
|
13
15
|
pointBuffer: Point2[];
|
14
16
|
constructor(viewport: Viewport);
|
15
17
|
displaySize(): Vec2;
|
@@ -21,6 +23,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
21
23
|
protected traceCubicBezierCurve(p1: Vec3, p2: Vec3, p3: Vec3): void;
|
22
24
|
protected traceQuadraticBezierCurve(controlPoint: Vec3, endPoint: Vec3): void;
|
23
25
|
drawPoints(..._points: Vec3[]): void;
|
26
|
+
drawText(text: string, _transform: Mat33, _style: TextStyle): void;
|
24
27
|
startObject(boundingBox: Rect2, _clip: boolean): void;
|
25
28
|
endObject(): void;
|
26
29
|
isTooSmallToRender(_rect: Rect2): boolean;
|
@@ -10,6 +10,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
10
10
|
this.lastFillStyle = null;
|
11
11
|
this.lastPoint = null;
|
12
12
|
this.objectNestingLevel = 0;
|
13
|
+
this.lastText = null;
|
13
14
|
// List of points drawn since the last clear.
|
14
15
|
this.pointBuffer = [];
|
15
16
|
}
|
@@ -28,6 +29,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
28
29
|
this.clearedCount++;
|
29
30
|
this.renderedPathCount = 0;
|
30
31
|
this.pointBuffer = [];
|
32
|
+
this.lastText = null;
|
31
33
|
// Ensure all objects finished rendering
|
32
34
|
if (this.objectNestingLevel > 0) {
|
33
35
|
throw new Error(`Within an object while clearing! Nesting level: ${this.objectNestingLevel}`);
|
@@ -68,6 +70,9 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
68
70
|
// drawPoints is intended for debugging.
|
69
71
|
// As such, it is unlikely to be the target of automated tests.
|
70
72
|
}
|
73
|
+
drawText(text, _transform, _style) {
|
74
|
+
this.lastText = text;
|
75
|
+
}
|
71
76
|
startObject(boundingBox, _clip) {
|
72
77
|
super.startObject(boundingBox);
|
73
78
|
this.objectNestingLevel += 1;
|
@@ -1,4 +1,6 @@
|
|
1
1
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
|
+
import { TextStyle } from '../../components/Text';
|
3
|
+
import Mat33 from '../../geometry/Mat33';
|
2
4
|
import Rect2 from '../../geometry/Rect2';
|
3
5
|
import { Point2, Vec2 } from '../../geometry/Vec2';
|
4
6
|
import Viewport from '../../Viewport';
|
@@ -19,6 +21,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
19
21
|
protected beginPath(startPoint: Point2): void;
|
20
22
|
protected endPath(style: RenderingStyle): void;
|
21
23
|
private addPathToSVG;
|
24
|
+
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
22
25
|
startObject(boundingBox: Rect2): void;
|
23
26
|
endObject(loaderData?: LoadSaveDataTable): void;
|
24
27
|
protected lineTo(point: Point2): void;
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Path, { PathCommandType } from '../../geometry/Path';
|
2
2
|
import { Vec2 } from '../../geometry/Vec2';
|
3
|
-
import { svgAttributesDataKey } from '../../SVGLoader';
|
3
|
+
import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
|
4
4
|
import AbstractRenderer from './AbstractRenderer';
|
5
5
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
6
6
|
export default class SVGRenderer extends AbstractRenderer {
|
@@ -86,6 +86,29 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
86
86
|
this.elem.appendChild(pathElem);
|
87
87
|
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(pathElem);
|
88
88
|
}
|
89
|
+
drawText(text, transform, style) {
|
90
|
+
var _a, _b, _c;
|
91
|
+
transform = this.getCanvasToScreenTransform().rightMul(transform);
|
92
|
+
const textElem = document.createElementNS(svgNameSpace, 'text');
|
93
|
+
textElem.appendChild(document.createTextNode(text));
|
94
|
+
textElem.style.transform = `matrix(
|
95
|
+
${transform.a1}, ${transform.b1},
|
96
|
+
${transform.a2}, ${transform.b2},
|
97
|
+
${transform.a3}, ${transform.b3}
|
98
|
+
)`;
|
99
|
+
textElem.style.fontFamily = style.fontFamily;
|
100
|
+
textElem.style.fontVariant = (_a = style.fontVariant) !== null && _a !== void 0 ? _a : '';
|
101
|
+
textElem.style.fontWeight = (_b = style.fontWeight) !== null && _b !== void 0 ? _b : '';
|
102
|
+
textElem.style.fontSize = style.size + 'px';
|
103
|
+
textElem.style.fill = style.renderingStyle.fill.toHexString();
|
104
|
+
if (style.renderingStyle.stroke) {
|
105
|
+
const strokeStyle = style.renderingStyle.stroke;
|
106
|
+
textElem.style.stroke = strokeStyle.color.toHexString();
|
107
|
+
textElem.style.strokeWidth = strokeStyle.width + 'px';
|
108
|
+
}
|
109
|
+
this.elem.appendChild(textElem);
|
110
|
+
(_c = this.objectElems) === null || _c === void 0 ? void 0 : _c.push(textElem);
|
111
|
+
}
|
89
112
|
startObject(boundingBox) {
|
90
113
|
super.startObject(boundingBox);
|
91
114
|
// Only accumulate a path within an object
|
@@ -103,11 +126,17 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
103
126
|
// Restore any attributes unsupported by the app.
|
104
127
|
for (const elem of (_a = this.objectElems) !== null && _a !== void 0 ? _a : []) {
|
105
128
|
const attrs = loaderData[svgAttributesDataKey];
|
129
|
+
const styleAttrs = loaderData[svgStyleAttributesDataKey];
|
106
130
|
if (attrs) {
|
107
131
|
for (const [attr, value] of attrs) {
|
108
132
|
elem.setAttribute(attr, value);
|
109
133
|
}
|
110
134
|
}
|
135
|
+
if (styleAttrs) {
|
136
|
+
for (const attr of styleAttrs) {
|
137
|
+
elem.style.setProperty(attr.key, attr.value, attr.priority);
|
138
|
+
}
|
139
|
+
}
|
111
140
|
}
|
112
141
|
}
|
113
142
|
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { TextStyle } from '../../components/Text';
|
2
|
+
import Mat33 from '../../geometry/Mat33';
|
3
|
+
import Rect2 from '../../geometry/Rect2';
|
4
|
+
import Vec3 from '../../geometry/Vec3';
|
5
|
+
import Viewport from '../../Viewport';
|
6
|
+
import { TextRendererLocalization } from '../localization';
|
7
|
+
import AbstractRenderer, { RenderingStyle } from './AbstractRenderer';
|
8
|
+
export default class TextOnlyRenderer extends AbstractRenderer {
|
9
|
+
private localizationTable;
|
10
|
+
private descriptionBuilder;
|
11
|
+
constructor(viewport: Viewport, localizationTable: TextRendererLocalization);
|
12
|
+
displaySize(): Vec3;
|
13
|
+
clear(): void;
|
14
|
+
getDescription(): string;
|
15
|
+
protected beginPath(_startPoint: Vec3): void;
|
16
|
+
protected endPath(_style: RenderingStyle): void;
|
17
|
+
protected lineTo(_point: Vec3): void;
|
18
|
+
protected moveTo(_point: Vec3): void;
|
19
|
+
protected traceCubicBezierCurve(_p1: Vec3, _p2: Vec3, _p3: Vec3): void;
|
20
|
+
protected traceQuadraticBezierCurve(_controlPoint: Vec3, _endPoint: Vec3): void;
|
21
|
+
drawText(text: string, _transform: Mat33, _style: TextStyle): void;
|
22
|
+
isTooSmallToRender(rect: Rect2): boolean;
|
23
|
+
drawPoints(..._points: Vec3[]): void;
|
24
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { Vec2 } from '../../geometry/Vec2';
|
2
|
+
import AbstractRenderer from './AbstractRenderer';
|
3
|
+
// Outputs a description of what was rendered.
|
4
|
+
export default class TextOnlyRenderer extends AbstractRenderer {
|
5
|
+
constructor(viewport, localizationTable) {
|
6
|
+
super(viewport);
|
7
|
+
this.localizationTable = localizationTable;
|
8
|
+
this.descriptionBuilder = [];
|
9
|
+
}
|
10
|
+
displaySize() {
|
11
|
+
// We don't have a graphical display, export a reasonable size.
|
12
|
+
return Vec2.of(500, 500);
|
13
|
+
}
|
14
|
+
clear() {
|
15
|
+
this.descriptionBuilder = [];
|
16
|
+
}
|
17
|
+
getDescription() {
|
18
|
+
return this.descriptionBuilder.join('\n');
|
19
|
+
}
|
20
|
+
beginPath(_startPoint) {
|
21
|
+
}
|
22
|
+
endPath(_style) {
|
23
|
+
}
|
24
|
+
lineTo(_point) {
|
25
|
+
}
|
26
|
+
moveTo(_point) {
|
27
|
+
}
|
28
|
+
traceCubicBezierCurve(_p1, _p2, _p3) {
|
29
|
+
}
|
30
|
+
traceQuadraticBezierCurve(_controlPoint, _endPoint) {
|
31
|
+
}
|
32
|
+
drawText(text, _transform, _style) {
|
33
|
+
this.descriptionBuilder.push(this.localizationTable.textNode(text));
|
34
|
+
}
|
35
|
+
isTooSmallToRender(rect) {
|
36
|
+
return rect.maxDimension < 10 / this.getSizeOfCanvasPixelOnScreen();
|
37
|
+
}
|
38
|
+
drawPoints(..._points) {
|
39
|
+
}
|
40
|
+
}
|
@@ -15,10 +15,7 @@ export const loadExpectExtensions = () => {
|
|
15
15
|
return {
|
16
16
|
pass,
|
17
17
|
message: () => {
|
18
|
-
|
19
|
-
return `Expected ${expected} not to .eq ${actual}. Options(${eqArgs})`;
|
20
|
-
}
|
21
|
-
return `Expected ${expected} to .eq ${actual}. Options(${eqArgs})`;
|
18
|
+
return `Expected ${pass ? '!' : ''}(${actual}).eq(${expected}). Options(${eqArgs})`;
|
22
19
|
},
|
23
20
|
};
|
24
21
|
},
|
@@ -10,10 +10,11 @@ import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
|
|
10
10
|
import { makeLineBuilder } from '../components/builders/LineBuilder';
|
11
11
|
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
|
12
12
|
import { defaultToolbarLocalization } from './localization';
|
13
|
-
import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon } from './icons';
|
13
|
+
import { makeDropdownIcon, makeEraserIcon, makeIconFromFactory, makePenIcon, makeRedoIcon, makeSelectionIcon, makeHandToolIcon, makeUndoIcon, makeTextIcon } from './icons';
|
14
14
|
import PanZoom, { PanZoomMode } from '../tools/PanZoom';
|
15
15
|
import Mat33 from '../geometry/Mat33';
|
16
16
|
import Viewport from '../Viewport';
|
17
|
+
import TextTool from '../tools/TextTool';
|
17
18
|
const toolbarCSSPrefix = 'toolbar-';
|
18
19
|
class ToolbarWidget {
|
19
20
|
constructor(editor, targetTool, localizationTable) {
|
@@ -316,6 +317,76 @@ class HandToolWidget extends ToolbarWidget {
|
|
316
317
|
this.setDropdownVisible(!this.isDropdownVisible());
|
317
318
|
}
|
318
319
|
}
|
320
|
+
class TextToolWidget extends ToolbarWidget {
|
321
|
+
constructor(editor, tool, localization) {
|
322
|
+
super(editor, tool, localization);
|
323
|
+
this.tool = tool;
|
324
|
+
this.updateDropdownInputs = null;
|
325
|
+
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
326
|
+
var _a;
|
327
|
+
if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
|
328
|
+
this.updateIcon();
|
329
|
+
(_a = this.updateDropdownInputs) === null || _a === void 0 ? void 0 : _a.call(this);
|
330
|
+
}
|
331
|
+
});
|
332
|
+
}
|
333
|
+
getTitle() {
|
334
|
+
return this.targetTool.description;
|
335
|
+
}
|
336
|
+
createIcon() {
|
337
|
+
const textStyle = this.tool.getTextStyle();
|
338
|
+
return makeTextIcon(textStyle);
|
339
|
+
}
|
340
|
+
fillDropdown(dropdown) {
|
341
|
+
const fontRow = document.createElement('div');
|
342
|
+
const colorRow = document.createElement('div');
|
343
|
+
const fontInput = document.createElement('select');
|
344
|
+
const fontLabel = document.createElement('label');
|
345
|
+
const colorInput = document.createElement('input');
|
346
|
+
const colorLabel = document.createElement('label');
|
347
|
+
const fontsInInput = new Set();
|
348
|
+
const addFontToInput = (fontName) => {
|
349
|
+
const option = document.createElement('option');
|
350
|
+
option.value = fontName;
|
351
|
+
option.textContent = fontName;
|
352
|
+
fontInput.appendChild(option);
|
353
|
+
fontsInInput.add(fontName);
|
354
|
+
};
|
355
|
+
fontLabel.innerText = this.localizationTable.fontLabel;
|
356
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
357
|
+
colorInput.classList.add('coloris_input');
|
358
|
+
colorInput.type = 'button';
|
359
|
+
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
360
|
+
colorLabel.setAttribute('for', colorInput.id);
|
361
|
+
addFontToInput('monospace');
|
362
|
+
addFontToInput('serif');
|
363
|
+
addFontToInput('sans-serif');
|
364
|
+
fontInput.id = `${toolbarCSSPrefix}-text-font-input-${TextToolWidget.idCounter++}`;
|
365
|
+
fontLabel.setAttribute('for', fontInput.id);
|
366
|
+
fontInput.onchange = () => {
|
367
|
+
this.tool.setFontFamily(fontInput.value);
|
368
|
+
};
|
369
|
+
colorInput.oninput = () => {
|
370
|
+
this.tool.setColor(Color4.fromString(colorInput.value));
|
371
|
+
};
|
372
|
+
colorRow.appendChild(colorLabel);
|
373
|
+
colorRow.appendChild(colorInput);
|
374
|
+
fontRow.appendChild(fontLabel);
|
375
|
+
fontRow.appendChild(fontInput);
|
376
|
+
this.updateDropdownInputs = () => {
|
377
|
+
const style = this.tool.getTextStyle();
|
378
|
+
colorInput.value = style.renderingStyle.fill.toHexString();
|
379
|
+
if (!fontsInInput.has(style.fontFamily)) {
|
380
|
+
addFontToInput(style.fontFamily);
|
381
|
+
}
|
382
|
+
fontInput.value = style.fontFamily;
|
383
|
+
};
|
384
|
+
this.updateDropdownInputs();
|
385
|
+
dropdown.replaceChildren(colorRow, fontRow);
|
386
|
+
return true;
|
387
|
+
}
|
388
|
+
}
|
389
|
+
TextToolWidget.idCounter = 0;
|
319
390
|
class PenWidget extends ToolbarWidget {
|
320
391
|
constructor(editor, tool, localization, penTypes) {
|
321
392
|
super(editor, tool, localization);
|
@@ -559,6 +630,12 @@ export default class HTMLToolbar {
|
|
559
630
|
}
|
560
631
|
(new SelectionWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
561
632
|
}
|
633
|
+
for (const tool of toolController.getMatchingTools(ToolType.Text)) {
|
634
|
+
if (!(tool instanceof TextTool)) {
|
635
|
+
throw new Error('All text tools must have kind === ToolType.Text');
|
636
|
+
}
|
637
|
+
(new TextToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
638
|
+
}
|
562
639
|
for (const tool of toolController.getMatchingTools(ToolType.PanZoom)) {
|
563
640
|
if (!(tool instanceof PanZoom)) {
|
564
641
|
throw new Error('All SelectionTools must have kind === ToolType.PanZoom');
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
2
|
+
import { TextStyle } from '../components/Text';
|
2
3
|
import Pen from '../tools/Pen';
|
3
4
|
export declare const makeUndoIcon: () => SVGSVGElement;
|
4
5
|
export declare const makeRedoIcon: (mirror?: boolean) => SVGSVGElement;
|
@@ -6,5 +7,6 @@ export declare const makeDropdownIcon: () => SVGSVGElement;
|
|
6
7
|
export declare const makeEraserIcon: () => SVGSVGElement;
|
7
8
|
export declare const makeSelectionIcon: () => SVGSVGElement;
|
8
9
|
export declare const makeHandToolIcon: () => SVGSVGElement;
|
10
|
+
export declare const makeTextIcon: (textStyle: TextStyle) => SVGSVGElement;
|
9
11
|
export declare const makePenIcon: (tipThickness: number, color: string) => SVGSVGElement;
|
10
12
|
export declare const makeIconFromFactory: (pen: Pen, factory: ComponentBuilderFactory) => SVGSVGElement;
|
@@ -111,6 +111,24 @@ export const makeHandToolIcon = () => {
|
|
111
111
|
icon.setAttribute('viewBox', '0 0 100 100');
|
112
112
|
return icon;
|
113
113
|
};
|
114
|
+
export const makeTextIcon = (textStyle) => {
|
115
|
+
var _a, _b;
|
116
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
117
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
118
|
+
const textNode = document.createElementNS(svgNamespace, 'text');
|
119
|
+
textNode.appendChild(document.createTextNode('T'));
|
120
|
+
textNode.style.fontFamily = textStyle.fontFamily;
|
121
|
+
textNode.style.fontWeight = (_a = textStyle.fontWeight) !== null && _a !== void 0 ? _a : '';
|
122
|
+
textNode.style.fontVariant = (_b = textStyle.fontVariant) !== null && _b !== void 0 ? _b : '';
|
123
|
+
textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
|
124
|
+
textNode.style.textAnchor = 'middle';
|
125
|
+
textNode.setAttribute('x', '50');
|
126
|
+
textNode.setAttribute('y', '75');
|
127
|
+
textNode.style.fontSize = '65px';
|
128
|
+
textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
|
129
|
+
icon.appendChild(textNode);
|
130
|
+
return icon;
|
131
|
+
};
|
114
132
|
export const makePenIcon = (tipThickness, color) => {
|
115
133
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
116
134
|
icon.setAttribute('viewBox', '0 0 100 100');
|
@@ -396,7 +396,7 @@ export default class SelectionTool extends BaseTool {
|
|
396
396
|
if (hasSelection) {
|
397
397
|
this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
|
398
398
|
const selectionRect = this.selectionBox.region;
|
399
|
-
this.editor.viewport.zoomTo(selectionRect).apply(this.editor);
|
399
|
+
this.editor.viewport.zoomTo(selectionRect, false).apply(this.editor);
|
400
400
|
}
|
401
401
|
}
|
402
402
|
onPointerUp(event) {
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import { TextStyle } from '../components/Text';
|
3
|
+
import Editor from '../Editor';
|
4
|
+
import { PointerEvt } from '../types';
|
5
|
+
import BaseTool from './BaseTool';
|
6
|
+
import { ToolLocalization } from './localization';
|
7
|
+
import { ToolType } from './ToolController';
|
8
|
+
export default class TextTool extends BaseTool {
|
9
|
+
private editor;
|
10
|
+
private localizationTable;
|
11
|
+
kind: ToolType;
|
12
|
+
private textStyle;
|
13
|
+
private textEditOverlay;
|
14
|
+
private textInputElem;
|
15
|
+
private textTargetPosition;
|
16
|
+
private textMeasuringCtx;
|
17
|
+
private textRotation;
|
18
|
+
constructor(editor: Editor, description: string, localizationTable: ToolLocalization);
|
19
|
+
private getTextAscent;
|
20
|
+
private flushInput;
|
21
|
+
private updateTextInput;
|
22
|
+
private startTextInput;
|
23
|
+
setEnabled(enabled: boolean): void;
|
24
|
+
onPointerDown({ current, allPointers }: PointerEvt): boolean;
|
25
|
+
onGestureCancel(): void;
|
26
|
+
private dispatchUpdateEvent;
|
27
|
+
setFontFamily(fontFamily: string): void;
|
28
|
+
setColor(color: Color4): void;
|
29
|
+
setFontSize(size: number): void;
|
30
|
+
getTextStyle(): TextStyle;
|
31
|
+
}
|