js-draw 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +9 -6
- package/dist/src/Editor.js +9 -3
- package/dist/src/EditorImage.d.ts +3 -0
- package/dist/src/EditorImage.js +7 -0
- package/dist/src/SVGLoader.js +5 -6
- package/dist/src/components/AbstractComponent.d.ts +1 -0
- package/dist/src/components/AbstractComponent.js +4 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +3 -0
- package/dist/src/components/Text.d.ts +3 -5
- package/dist/src/components/Text.js +19 -10
- package/dist/src/components/UnknownSVGObject.d.ts +1 -0
- package/dist/src/components/UnknownSVGObject.js +3 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/src/rendering/renderers/SVGRenderer.js +1 -1
- package/dist/src/testing/beforeEachFile.js +4 -0
- package/dist/src/toolbar/HTMLToolbar.js +2 -3
- package/dist/src/toolbar/IconProvider.d.ts +24 -0
- package/dist/src/toolbar/IconProvider.js +415 -0
- package/dist/src/toolbar/lib.d.ts +1 -1
- package/dist/src/toolbar/lib.js +1 -2
- package/dist/src/toolbar/localization.d.ts +0 -1
- package/dist/src/toolbar/localization.js +0 -1
- package/dist/src/toolbar/makeColorInput.js +1 -2
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +2 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +16 -2
- package/dist/src/toolbar/widgets/EraserToolWidget.js +1 -2
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +5 -3
- package/dist/src/toolbar/widgets/HandToolWidget.js +35 -12
- package/dist/src/toolbar/widgets/PenToolWidget.d.ts +2 -0
- package/dist/src/toolbar/widgets/PenToolWidget.js +16 -3
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/SelectionToolWidget.js +20 -7
- package/dist/src/toolbar/widgets/TextToolWidget.js +1 -2
- package/dist/src/tools/PanZoom.d.ts +1 -1
- package/dist/src/tools/PanZoom.js +4 -1
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +66 -3
- package/dist/src/tools/ToolController.js +3 -0
- package/dist/src/tools/ToolbarShortcutHandler.d.ts +12 -0
- package/dist/src/tools/ToolbarShortcutHandler.js +23 -0
- package/dist/src/tools/lib.d.ts +1 -0
- package/dist/src/tools/lib.js +1 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +4 -2
- package/package.json +1 -1
- package/src/Editor.ts +17 -7
- package/src/EditorImage.ts +9 -0
- package/src/SVGLoader.test.ts +37 -0
- package/src/SVGLoader.ts +5 -6
- package/src/components/AbstractComponent.ts +5 -0
- package/src/components/SVGGlobalAttributesObject.ts +4 -0
- package/src/components/Text.test.ts +1 -16
- package/src/components/Text.ts +21 -11
- package/src/components/UnknownSVGObject.ts +4 -0
- package/src/components/builders/FreehandLineBuilder.ts +3 -3
- package/src/rendering/renderers/SVGRenderer.ts +1 -1
- package/src/testing/beforeEachFile.ts +6 -1
- package/src/toolbar/HTMLToolbar.ts +2 -3
- package/src/toolbar/IconProvider.ts +476 -0
- package/src/toolbar/lib.ts +1 -1
- package/src/toolbar/localization.ts +0 -2
- package/src/toolbar/makeColorInput.ts +1 -2
- package/src/toolbar/widgets/BaseWidget.ts +20 -3
- package/src/toolbar/widgets/EraserToolWidget.ts +1 -2
- package/src/toolbar/widgets/HandToolWidget.ts +42 -20
- package/src/toolbar/widgets/PenToolWidget.ts +20 -4
- package/src/toolbar/widgets/SelectionToolWidget.ts +24 -8
- package/src/toolbar/widgets/TextToolWidget.ts +1 -2
- package/src/tools/PanZoom.ts +4 -1
- package/src/tools/SelectionTool/SelectionTool.css +2 -1
- package/src/tools/SelectionTool/SelectionTool.test.ts +40 -0
- package/src/tools/SelectionTool/SelectionTool.ts +73 -4
- package/src/tools/ToolController.ts +3 -0
- package/src/tools/ToolbarShortcutHandler.ts +34 -0
- package/src/tools/UndoRedoShortcut.test.ts +3 -0
- package/src/tools/lib.ts +1 -0
- package/src/tools/localization.ts +4 -0
- package/src/types.ts +13 -8
- package/typedoc.json +5 -1
- package/dist/src/toolbar/icons.d.ts +0 -20
- package/dist/src/toolbar/icons.js +0 -385
- package/src/toolbar/icons.ts +0 -443
package/src/components/Text.ts
CHANGED
@@ -14,8 +14,6 @@ export interface TextStyle {
|
|
14
14
|
renderingStyle: RenderingStyle;
|
15
15
|
}
|
16
16
|
|
17
|
-
type GetTextDimensCallback = (text: string, style: TextStyle) => Rect2;
|
18
|
-
|
19
17
|
const componentTypeId = 'text';
|
20
18
|
export default class Text extends AbstractComponent {
|
21
19
|
protected contentBBox: Rect2;
|
@@ -24,10 +22,6 @@ export default class Text extends AbstractComponent {
|
|
24
22
|
protected readonly textObjects: Array<string|Text>,
|
25
23
|
private transform: Mat33,
|
26
24
|
private readonly style: TextStyle,
|
27
|
-
|
28
|
-
// If not given, an HtmlCanvasElement is used to determine text boundaries.
|
29
|
-
// @internal
|
30
|
-
private readonly getTextDimens: GetTextDimensCallback = Text.getTextDimens,
|
31
25
|
) {
|
32
26
|
super(componentTypeId);
|
33
27
|
this.recomputeBBox();
|
@@ -47,9 +41,25 @@ export default class Text extends AbstractComponent {
|
|
47
41
|
ctx.textAlign = 'left';
|
48
42
|
}
|
49
43
|
|
50
|
-
private static textMeasuringCtx: CanvasRenderingContext2D;
|
44
|
+
private static textMeasuringCtx: CanvasRenderingContext2D|null = null;
|
45
|
+
|
46
|
+
// Roughly estimate the bounding box of `text`. Use if no CanvasRenderingContext2D is available.
|
47
|
+
private static estimateTextDimens(text: string, style: TextStyle): Rect2 {
|
48
|
+
const widthEst = text.length * style.size;
|
49
|
+
const heightEst = style.size;
|
50
|
+
|
51
|
+
// Text is drawn with (0, 0) as its baseline. As such, the majority of the text's height should
|
52
|
+
// be above (0, 0).
|
53
|
+
return new Rect2(0, -heightEst * 2/3, widthEst, heightEst);
|
54
|
+
}
|
55
|
+
|
56
|
+
// Returns the bounding box of `text`. This is approximate if no Canvas is available.
|
51
57
|
private static getTextDimens(text: string, style: TextStyle): Rect2 {
|
52
|
-
Text.textMeasuringCtx ??= document.createElement('canvas').getContext('2d')
|
58
|
+
Text.textMeasuringCtx ??= document.createElement('canvas').getContext('2d') ?? null;
|
59
|
+
if (!Text.textMeasuringCtx) {
|
60
|
+
return this.estimateTextDimens(text, style);
|
61
|
+
}
|
62
|
+
|
53
63
|
const ctx = Text.textMeasuringCtx;
|
54
64
|
Text.applyTextStyles(ctx, style);
|
55
65
|
|
@@ -63,7 +73,7 @@ export default class Text extends AbstractComponent {
|
|
63
73
|
|
64
74
|
private computeBBoxOfPart(part: string|Text) {
|
65
75
|
if (typeof part === 'string') {
|
66
|
-
const textBBox =
|
76
|
+
const textBBox = Text.getTextDimens(part, this.style);
|
67
77
|
return textBBox.transformedBoundingBox(this.transform);
|
68
78
|
} else {
|
69
79
|
const bbox = part.contentBBox.transformedBoundingBox(this.transform);
|
@@ -178,7 +188,7 @@ export default class Text extends AbstractComponent {
|
|
178
188
|
};
|
179
189
|
}
|
180
190
|
|
181
|
-
public static deserializeFromString(json: any
|
191
|
+
public static deserializeFromString(json: any): Text {
|
182
192
|
const style: TextStyle = {
|
183
193
|
renderingStyle: styleFromJSON(json.style.renderingStyle),
|
184
194
|
size: json.style.size,
|
@@ -203,7 +213,7 @@ export default class Text extends AbstractComponent {
|
|
203
213
|
const transformData = json.transform as Mat33Array;
|
204
214
|
const transform = new Mat33(...transformData);
|
205
215
|
|
206
|
-
return new Text(textObjects, transform, style
|
216
|
+
return new Text(textObjects, transform, style);
|
207
217
|
}
|
208
218
|
}
|
209
219
|
|
@@ -37,6 +37,10 @@ export default class UnknownSVGObject extends AbstractComponent {
|
|
37
37
|
protected applyTransformation(_affineTransfm: Mat33): void {
|
38
38
|
}
|
39
39
|
|
40
|
+
public isSelectable() {
|
41
|
+
return false;
|
42
|
+
}
|
43
|
+
|
40
44
|
protected createClone(): AbstractComponent {
|
41
45
|
return new UnknownSVGObject(this.svgObject.cloneNode(true) as SVGElement);
|
42
46
|
}
|
@@ -415,8 +415,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
415
415
|
|
416
416
|
let enteringVec = this.lastExitingVec;
|
417
417
|
if (!enteringVec) {
|
418
|
-
let sampleIdx = Math.ceil(this.buffer.length /
|
419
|
-
if (sampleIdx === 0) {
|
418
|
+
let sampleIdx = Math.ceil(this.buffer.length / 2);
|
419
|
+
if (sampleIdx === 0 || sampleIdx >= this.buffer.length) {
|
420
420
|
sampleIdx = this.buffer.length - 1;
|
421
421
|
}
|
422
422
|
|
@@ -426,7 +426,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
426
426
|
let exitingVec = this.computeExitingVec();
|
427
427
|
|
428
428
|
// Find the intersection between the entering vector and the exiting vector
|
429
|
-
const maxRelativeLength =
|
429
|
+
const maxRelativeLength = 2;
|
430
430
|
const segmentStart = this.buffer[0];
|
431
431
|
const segmentEnd = newPoint.pos;
|
432
432
|
const startEndDist = segmentEnd.minus(segmentStart).magnitude();
|
@@ -88,7 +88,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
88
88
|
|
89
89
|
public drawPath(pathSpec: RenderablePathSpec) {
|
90
90
|
const style = pathSpec.style;
|
91
|
-
const path = Path.fromRenderable(pathSpec);
|
91
|
+
const path = Path.fromRenderable(pathSpec).transformedBy(this.getCanvasToScreenTransform());
|
92
92
|
|
93
93
|
// Try to extend the previous path, if possible
|
94
94
|
if (!style.fill.eq(this.lastPathStyle?.fill) || this.lastPathString.length === 0) {
|
@@ -1,3 +1,8 @@
|
|
1
1
|
import loadExpectExtensions from './loadExpectExtensions';
|
2
2
|
loadExpectExtensions();
|
3
|
-
jest.useFakeTimers();
|
3
|
+
jest.useFakeTimers();
|
4
|
+
|
5
|
+
// jsdom doesn't support HTMLCanvasElement#getContext — it logs an error
|
6
|
+
// to the console. Make it return null so we can handle a non-existent Canvas
|
7
|
+
// at runtime (e.g. use something else, if available).
|
8
|
+
HTMLCanvasElement.prototype.getContext = () => null;
|
@@ -5,7 +5,6 @@ import { coloris, init as colorisInit } from '@melloware/coloris';
|
|
5
5
|
import Color4 from '../Color4';
|
6
6
|
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
7
7
|
import { ActionButtonIcon } from './types';
|
8
|
-
import { makeRedoIcon, makeUndoIcon } from './icons';
|
9
8
|
import SelectionTool from '../tools/SelectionTool/SelectionTool';
|
10
9
|
import PanZoomTool from '../tools/PanZoom';
|
11
10
|
import TextTool from '../tools/TextTool';
|
@@ -156,13 +155,13 @@ export default class HTMLToolbar {
|
|
156
155
|
|
157
156
|
const undoButton = this.addActionButton({
|
158
157
|
label: this.localizationTable.undo,
|
159
|
-
icon: makeUndoIcon()
|
158
|
+
icon: this.editor.icons.makeUndoIcon()
|
160
159
|
}, () => {
|
161
160
|
this.editor.history.undo();
|
162
161
|
}, undoRedoGroup);
|
163
162
|
const redoButton = this.addActionButton({
|
164
163
|
label: this.localizationTable.redo,
|
165
|
-
icon: makeRedoIcon(),
|
164
|
+
icon: this.editor.icons.makeRedoIcon(),
|
166
165
|
}, () => {
|
167
166
|
this.editor.history.redo();
|
168
167
|
}, undoRedoGroup);
|
@@ -0,0 +1,476 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
|
+
import { TextStyle } from '../components/Text';
|
4
|
+
import EventDispatcher from '../EventDispatcher';
|
5
|
+
import { Vec2 } from '../math/Vec2';
|
6
|
+
import SVGRenderer from '../rendering/renderers/SVGRenderer';
|
7
|
+
import Pen from '../tools/Pen';
|
8
|
+
import { StrokeDataPoint } from '../types';
|
9
|
+
import Viewport from '../Viewport';
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
const svgNamespace = 'http://www.w3.org/2000/svg';
|
14
|
+
const iconColorFill = `
|
15
|
+
style='fill: var(--icon-color);'
|
16
|
+
`;
|
17
|
+
const iconColorStrokeFill = `
|
18
|
+
style='fill: var(--icon-color); stroke: var(--icon-color);'
|
19
|
+
`;
|
20
|
+
const checkerboardPatternDef = `
|
21
|
+
<pattern
|
22
|
+
id='checkerboard'
|
23
|
+
viewBox='0,0,10,10'
|
24
|
+
width='20%'
|
25
|
+
height='20%'
|
26
|
+
patternUnits='userSpaceOnUse'
|
27
|
+
>
|
28
|
+
<rect x=0 y=0 width=10 height=10 fill='white'/>
|
29
|
+
<rect x=0 y=0 width=5 height=5 fill='gray'/>
|
30
|
+
<rect x=5 y=5 width=5 height=5 fill='gray'/>
|
31
|
+
</pattern>
|
32
|
+
`;
|
33
|
+
const checkerboardPatternRef = 'url(#checkerboard)';
|
34
|
+
|
35
|
+
// Provides icons that can be used in the toolbar, etc.
|
36
|
+
// Extend this class and override methods to customize icons.
|
37
|
+
export default class IconProvider {
|
38
|
+
|
39
|
+
public makeUndoIcon() {
|
40
|
+
return this.makeRedoIcon(true);
|
41
|
+
}
|
42
|
+
|
43
|
+
// @param mirror - reflect across the x-axis @internal
|
44
|
+
// @returns a redo icon.
|
45
|
+
public makeRedoIcon(mirror: boolean = false) {
|
46
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
47
|
+
icon.innerHTML = `
|
48
|
+
<style>
|
49
|
+
.toolbar-svg-undo-redo-icon {
|
50
|
+
stroke: var(--icon-color);
|
51
|
+
stroke-width: 12;
|
52
|
+
stroke-linejoin: round;
|
53
|
+
stroke-linecap: round;
|
54
|
+
fill: none;
|
55
|
+
|
56
|
+
transform-origin: center;
|
57
|
+
}
|
58
|
+
</style>
|
59
|
+
<path
|
60
|
+
d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
|
61
|
+
class='toolbar-svg-undo-redo-icon'
|
62
|
+
style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
|
63
|
+
`;
|
64
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
65
|
+
return icon;
|
66
|
+
}
|
67
|
+
|
68
|
+
public makeDropdownIcon() {
|
69
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
70
|
+
icon.innerHTML = `
|
71
|
+
<g>
|
72
|
+
<path
|
73
|
+
d='M5,10 L50,90 L95,10 Z'
|
74
|
+
${iconColorFill}
|
75
|
+
/>
|
76
|
+
</g>
|
77
|
+
`;
|
78
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
79
|
+
return icon;
|
80
|
+
}
|
81
|
+
|
82
|
+
public makeEraserIcon() {
|
83
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
84
|
+
|
85
|
+
// Draw an eraser-like shape
|
86
|
+
icon.innerHTML = `
|
87
|
+
<g>
|
88
|
+
<rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
|
89
|
+
<rect
|
90
|
+
x=10 y=10 width=80 height=50
|
91
|
+
${iconColorFill}
|
92
|
+
/>
|
93
|
+
</g>
|
94
|
+
`;
|
95
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
96
|
+
return icon;
|
97
|
+
}
|
98
|
+
|
99
|
+
public makeSelectionIcon() {
|
100
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
101
|
+
|
102
|
+
// Draw a cursor-like shape
|
103
|
+
icon.innerHTML = `
|
104
|
+
<g>
|
105
|
+
<rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
|
106
|
+
<rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
|
107
|
+
</g>
|
108
|
+
`;
|
109
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
110
|
+
|
111
|
+
return icon;
|
112
|
+
}
|
113
|
+
|
114
|
+
protected makeIconFromPath(
|
115
|
+
pathData: string,
|
116
|
+
fill: string = 'var(--icon-color)',
|
117
|
+
strokeColor: string = 'none',
|
118
|
+
strokeWidth: string = '0px',
|
119
|
+
) {
|
120
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
121
|
+
const path = document.createElementNS(svgNamespace, 'path');
|
122
|
+
path.setAttribute('d', pathData);
|
123
|
+
path.style.fill = fill;
|
124
|
+
path.style.stroke = strokeColor;
|
125
|
+
path.style.strokeWidth = strokeWidth;
|
126
|
+
icon.appendChild(path);
|
127
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
128
|
+
|
129
|
+
return icon;
|
130
|
+
}
|
131
|
+
|
132
|
+
public makeHandToolIcon() {
|
133
|
+
const fill = 'none';
|
134
|
+
const strokeColor = 'var(--icon-color)';
|
135
|
+
const strokeWidth = '3';
|
136
|
+
|
137
|
+
// Draw a cursor-like shape (like some of the other icons, made with Inkscape)
|
138
|
+
return this.makeIconFromPath(`
|
139
|
+
m 10,60
|
140
|
+
5,30
|
141
|
+
H 90
|
142
|
+
V 30
|
143
|
+
C 90,20 75,20 75,30
|
144
|
+
V 60
|
145
|
+
20
|
146
|
+
C 75,10 60,10 60,20
|
147
|
+
V 60
|
148
|
+
15
|
149
|
+
C 60,5 45,5 45,15
|
150
|
+
V 60
|
151
|
+
25
|
152
|
+
C 45,15 30,15 30,25
|
153
|
+
V 60
|
154
|
+
75
|
155
|
+
L 25,60
|
156
|
+
C 20,45 10,50 10,60
|
157
|
+
Z
|
158
|
+
`, fill, strokeColor, strokeWidth);
|
159
|
+
}
|
160
|
+
|
161
|
+
public makeTouchPanningIcon() {
|
162
|
+
const fill = 'none';
|
163
|
+
const strokeColor = 'var(--icon-color)';
|
164
|
+
const strokeWidth = '3';
|
165
|
+
|
166
|
+
return this.makeIconFromPath(`
|
167
|
+
M 5,5.5
|
168
|
+
V 17.2
|
169
|
+
L 16.25,5.46
|
170
|
+
Z
|
171
|
+
|
172
|
+
m 33.75,0
|
173
|
+
L 50,17
|
174
|
+
V 5.5
|
175
|
+
Z
|
176
|
+
|
177
|
+
M 5,40.7
|
178
|
+
v 11.7
|
179
|
+
h 11.25
|
180
|
+
z
|
181
|
+
|
182
|
+
M 26,19
|
183
|
+
C 19.8,19.4 17.65,30.4 21.9,34.8
|
184
|
+
L 50,70
|
185
|
+
H 27.5
|
186
|
+
c -11.25,0 -11.25,17.6 0,17.6
|
187
|
+
H 61.25
|
188
|
+
C 94.9,87.8 95,87.6 95,40.7 78.125,23 67,29 55.6,46.5
|
189
|
+
L 33.1,23
|
190
|
+
C 30.3125,20.128192 27.9,19 25.830078,19.119756
|
191
|
+
Z
|
192
|
+
`, fill, strokeColor, strokeWidth);
|
193
|
+
}
|
194
|
+
|
195
|
+
public makeAllDevicePanningIcon() {
|
196
|
+
const fill = 'none';
|
197
|
+
const strokeColor = 'var(--icon-color)';
|
198
|
+
const strokeWidth = '3';
|
199
|
+
return this.makeIconFromPath(`
|
200
|
+
M 5 5
|
201
|
+
L 5 17.5
|
202
|
+
17.5 5
|
203
|
+
5 5
|
204
|
+
z
|
205
|
+
|
206
|
+
M 42.5 5
|
207
|
+
L 55 17.5
|
208
|
+
55 5
|
209
|
+
42.5 5
|
210
|
+
z
|
211
|
+
|
212
|
+
M 70 10
|
213
|
+
L 70 21
|
214
|
+
61 15
|
215
|
+
55.5 23
|
216
|
+
66 30
|
217
|
+
56 37
|
218
|
+
61 45
|
219
|
+
70 39
|
220
|
+
70 50
|
221
|
+
80 50
|
222
|
+
80 39
|
223
|
+
89 45
|
224
|
+
95 36
|
225
|
+
84 30
|
226
|
+
95 23
|
227
|
+
89 15
|
228
|
+
80 21
|
229
|
+
80 10
|
230
|
+
70 10
|
231
|
+
z
|
232
|
+
|
233
|
+
M 27.5 26.25
|
234
|
+
L 27.5 91.25
|
235
|
+
L 43.75 83.125
|
236
|
+
L 52 99
|
237
|
+
L 68 91
|
238
|
+
L 60 75
|
239
|
+
L 76.25 66.875
|
240
|
+
L 27.5 26.25
|
241
|
+
z
|
242
|
+
|
243
|
+
M 5 42.5
|
244
|
+
L 5 55
|
245
|
+
L 17.5 55
|
246
|
+
L 5 42.5
|
247
|
+
z
|
248
|
+
`, fill, strokeColor, strokeWidth);
|
249
|
+
}
|
250
|
+
|
251
|
+
public makeZoomIcon = () => {
|
252
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
253
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
254
|
+
|
255
|
+
const addTextNode = (text: string, x: number, y: number) => {
|
256
|
+
const textNode = document.createElementNS(svgNamespace, 'text');
|
257
|
+
textNode.appendChild(document.createTextNode(text));
|
258
|
+
textNode.setAttribute('x', x.toString());
|
259
|
+
textNode.setAttribute('y', y.toString());
|
260
|
+
textNode.style.textAlign = 'center';
|
261
|
+
textNode.style.textAnchor = 'middle';
|
262
|
+
textNode.style.fontSize = '55px';
|
263
|
+
textNode.style.fill = 'var(--icon-color)';
|
264
|
+
textNode.style.fontFamily = 'monospace';
|
265
|
+
|
266
|
+
icon.appendChild(textNode);
|
267
|
+
};
|
268
|
+
|
269
|
+
addTextNode('+', 40, 45);
|
270
|
+
addTextNode('-', 70, 75);
|
271
|
+
|
272
|
+
return icon;
|
273
|
+
};
|
274
|
+
|
275
|
+
public makeTextIcon(textStyle: TextStyle) {
|
276
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
277
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
278
|
+
|
279
|
+
const textNode = document.createElementNS(svgNamespace, 'text');
|
280
|
+
textNode.appendChild(document.createTextNode('T'));
|
281
|
+
|
282
|
+
textNode.style.fontFamily = textStyle.fontFamily;
|
283
|
+
textNode.style.fontWeight = textStyle.fontWeight ?? '';
|
284
|
+
textNode.style.fontVariant = textStyle.fontVariant ?? '';
|
285
|
+
textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
|
286
|
+
|
287
|
+
textNode.style.textAnchor = 'middle';
|
288
|
+
textNode.setAttribute('x', '50');
|
289
|
+
textNode.setAttribute('y', '75');
|
290
|
+
textNode.style.fontSize = '65px';
|
291
|
+
textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
|
292
|
+
|
293
|
+
icon.appendChild(textNode);
|
294
|
+
|
295
|
+
return icon;
|
296
|
+
}
|
297
|
+
|
298
|
+
public makePenIcon(tipThickness: number, color: string|Color4) {
|
299
|
+
if (color instanceof Color4) {
|
300
|
+
color = color.toHexString();
|
301
|
+
}
|
302
|
+
|
303
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
304
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
305
|
+
|
306
|
+
const halfThickness = tipThickness / 2;
|
307
|
+
|
308
|
+
// Draw a pen-like shape
|
309
|
+
const primaryStrokeTipPath = `M14,63 L${50 - halfThickness},95 L${50 + halfThickness},90 L88,60 Z`;
|
310
|
+
const backgroundStrokeTipPath = `M14,63 L${50 - halfThickness},85 L${50 + halfThickness},83 L88,60 Z`;
|
311
|
+
icon.innerHTML = `
|
312
|
+
<defs>
|
313
|
+
${checkerboardPatternDef}
|
314
|
+
</defs>
|
315
|
+
<g>
|
316
|
+
<!-- Pen grip -->
|
317
|
+
<path
|
318
|
+
d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
|
319
|
+
${iconColorStrokeFill}
|
320
|
+
/>
|
321
|
+
</g>
|
322
|
+
<g>
|
323
|
+
<!-- Checkerboard background for slightly transparent pens -->
|
324
|
+
<path d='${backgroundStrokeTipPath}' fill='${checkerboardPatternRef}'/>
|
325
|
+
|
326
|
+
<!-- Actual pen tip -->
|
327
|
+
<path
|
328
|
+
d='${primaryStrokeTipPath}'
|
329
|
+
fill='${color}'
|
330
|
+
stroke='${color}'
|
331
|
+
/>
|
332
|
+
</g>
|
333
|
+
`;
|
334
|
+
return icon;
|
335
|
+
}
|
336
|
+
|
337
|
+
public makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory) {
|
338
|
+
const toolThickness = pen.getThickness();
|
339
|
+
|
340
|
+
const nowTime = (new Date()).getTime();
|
341
|
+
const startPoint: StrokeDataPoint = {
|
342
|
+
pos: Vec2.of(10, 10),
|
343
|
+
width: toolThickness / 5,
|
344
|
+
color: pen.getColor(),
|
345
|
+
time: nowTime - 100,
|
346
|
+
};
|
347
|
+
const endPoint: StrokeDataPoint = {
|
348
|
+
pos: Vec2.of(90, 90),
|
349
|
+
width: toolThickness / 5,
|
350
|
+
color: pen.getColor(),
|
351
|
+
time: nowTime,
|
352
|
+
};
|
353
|
+
|
354
|
+
const viewport = new Viewport(new EventDispatcher());
|
355
|
+
const builder = factory(startPoint, viewport);
|
356
|
+
builder.addPoint(endPoint);
|
357
|
+
|
358
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
359
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
360
|
+
viewport.updateScreenSize(Vec2.of(100, 100));
|
361
|
+
|
362
|
+
const renderer = new SVGRenderer(icon, viewport);
|
363
|
+
builder.preview(renderer);
|
364
|
+
|
365
|
+
return icon;
|
366
|
+
}
|
367
|
+
|
368
|
+
public makePipetteIcon(color?: Color4) {
|
369
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
370
|
+
const pipette = document.createElementNS(svgNamespace, 'path');
|
371
|
+
|
372
|
+
pipette.setAttribute('d', `
|
373
|
+
M 47,6
|
374
|
+
C 35,5 25,15 35,30
|
375
|
+
c -9.2,1.3 -15,0 -15,3
|
376
|
+
0,2 5,5 15,7
|
377
|
+
V 81
|
378
|
+
L 40,90
|
379
|
+
h 6
|
380
|
+
L 40,80
|
381
|
+
V 40
|
382
|
+
h 15
|
383
|
+
v 40
|
384
|
+
l -6,10
|
385
|
+
h 6
|
386
|
+
l 5,-9.2
|
387
|
+
V 40
|
388
|
+
C 70,38 75,35 75,33
|
389
|
+
75,30 69.2,31.2 60,30
|
390
|
+
65,15 65,5 47,6
|
391
|
+
Z
|
392
|
+
`);
|
393
|
+
pipette.style.fill = 'var(--icon-color)';
|
394
|
+
|
395
|
+
if (color) {
|
396
|
+
const defs = document.createElementNS(svgNamespace, 'defs');
|
397
|
+
defs.innerHTML = checkerboardPatternDef;
|
398
|
+
icon.appendChild(defs);
|
399
|
+
|
400
|
+
const fluidBackground = document.createElementNS(svgNamespace, 'path');
|
401
|
+
const fluid = document.createElementNS(svgNamespace, 'path');
|
402
|
+
|
403
|
+
const fluidPathData = `
|
404
|
+
m 40,50 c 5,5 10,0 15,-5 V 80 L 50,90 H 45 L 40,80 Z
|
405
|
+
`;
|
406
|
+
|
407
|
+
fluid.setAttribute('d', fluidPathData);
|
408
|
+
fluidBackground.setAttribute('d', fluidPathData);
|
409
|
+
|
410
|
+
fluid.style.fill = color.toHexString();
|
411
|
+
fluidBackground.style.fill = checkerboardPatternRef;
|
412
|
+
|
413
|
+
icon.appendChild(fluidBackground);
|
414
|
+
icon.appendChild(fluid);
|
415
|
+
}
|
416
|
+
icon.appendChild(pipette);
|
417
|
+
|
418
|
+
icon.setAttribute('viewBox', '0 0 100 100');
|
419
|
+
return icon;
|
420
|
+
}
|
421
|
+
|
422
|
+
public makeResizeViewportIcon() {
|
423
|
+
return this.makeIconFromPath(`
|
424
|
+
M 75 5 75 10 90 10 90 25 95 25 95 5 75 5 z
|
425
|
+
M 15 15 15 30 20 30 20 20 30 20 30 15 15 15 z
|
426
|
+
M 84 15 82 17 81 16 81 20 85 20 84 19 86 17 84 15 z
|
427
|
+
M 26 24 24 26 26 28 25 29 29 29 29 25 28 26 26 24 z
|
428
|
+
M 25 71 26 72 24 74 26 76 28 74 29 75 29 71 25 71 z
|
429
|
+
M 15 75 15 85 25 85 25 80 20 80 20 75 15 75 z
|
430
|
+
M 90 75 90 90 75 90 75 95 95 95 95 75 90 75 z
|
431
|
+
M 81 81 81 85 82 84 84 86 86 84 84 82 85 81 81 81 z
|
432
|
+
`);
|
433
|
+
}
|
434
|
+
|
435
|
+
public makeDuplicateSelectionIcon() {
|
436
|
+
return this.makeIconFromPath(`
|
437
|
+
M 45,10 45,55 90,55 90,10 45,10 z
|
438
|
+
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
|
439
|
+
`);
|
440
|
+
}
|
441
|
+
|
442
|
+
public makeDeleteSelectionIcon() {
|
443
|
+
const strokeWidth = '5px';
|
444
|
+
const strokeColor = 'var(--icon-color)';
|
445
|
+
const fillColor = 'none';
|
446
|
+
|
447
|
+
return this.makeIconFromPath(`
|
448
|
+
M 10,10 90,90
|
449
|
+
M 10,90 90,10
|
450
|
+
`, fillColor, strokeColor, strokeWidth);
|
451
|
+
}
|
452
|
+
|
453
|
+
public makeSaveIcon() {
|
454
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
455
|
+
svg.innerHTML = `
|
456
|
+
<style>
|
457
|
+
.toolbar-save-icon {
|
458
|
+
stroke: var(--icon-color);
|
459
|
+
stroke-width: 10;
|
460
|
+
stroke-linejoin: round;
|
461
|
+
stroke-linecap: round;
|
462
|
+
fill: none;
|
463
|
+
}
|
464
|
+
</style>
|
465
|
+
<path
|
466
|
+
d='
|
467
|
+
M 15,55 30,70 85,20
|
468
|
+
'
|
469
|
+
class='toolbar-save-icon'
|
470
|
+
/>
|
471
|
+
`;
|
472
|
+
svg.setAttribute('viewBox', '0 0 100 100');
|
473
|
+
return svg;
|
474
|
+
}
|
475
|
+
|
476
|
+
}
|
package/src/toolbar/lib.ts
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
export interface ToolbarLocalization {
|
4
4
|
fontLabel: string;
|
5
|
-
anyDevicePanning: string;
|
6
5
|
touchPanning: string;
|
7
6
|
outlinedRectanglePen: string;
|
8
7
|
filledRectanglePen: string;
|
@@ -54,7 +53,6 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
54
53
|
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
55
54
|
|
56
55
|
touchPanning: 'Touchscreen panning',
|
57
|
-
anyDevicePanning: 'Any device panning',
|
58
56
|
|
59
57
|
freehandPen: 'Freehand',
|
60
58
|
arrowPen: 'Arrow',
|
@@ -2,7 +2,6 @@ import Color4 from '../Color4';
|
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import PipetteTool from '../tools/PipetteTool';
|
4
4
|
import { EditorEventType } from '../types';
|
5
|
-
import { makePipetteIcon } from './icons';
|
6
5
|
|
7
6
|
type OnColorChangeListener = (color: Color4)=>void;
|
8
7
|
|
@@ -72,7 +71,7 @@ const addPipetteTool = (editor: Editor, container: HTMLElement, onColorChange: O
|
|
72
71
|
pipetteButton.setAttribute('alt', pipetteButton.title);
|
73
72
|
|
74
73
|
const updatePipetteIcon = (color?: Color4) => {
|
75
|
-
pipetteButton.replaceChildren(makePipetteIcon(color));
|
74
|
+
pipetteButton.replaceChildren(editor.icons.makePipetteIcon(color));
|
76
75
|
};
|
77
76
|
updatePipetteIcon();
|
78
77
|
|