js-draw 0.19.0 → 0.21.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/.eslintrc.js +1 -0
- package/CHANGELOG.md +11 -0
- package/README.md +4 -4
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/src/Color4.js +3 -3
- package/dist/cjs/src/Editor.d.ts +4 -1
- package/dist/cjs/src/Editor.js +30 -12
- package/dist/cjs/src/SVGLoader.js +69 -7
- package/dist/cjs/src/Viewport.d.ts +2 -0
- package/dist/cjs/src/Viewport.js +6 -2
- package/dist/cjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/cjs/src/components/AbstractComponent.js +19 -9
- package/dist/cjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
- package/dist/cjs/src/components/BackgroundComponent.js +309 -0
- package/dist/cjs/src/components/Stroke.d.ts +1 -0
- package/dist/cjs/src/components/Stroke.js +15 -2
- package/dist/cjs/src/components/TextComponent.d.ts +1 -13
- package/dist/cjs/src/components/TextComponent.js +1 -1
- package/dist/cjs/src/components/lib.d.ts +2 -2
- package/dist/cjs/src/components/lib.js +2 -2
- package/dist/cjs/src/components/util/StrokeSmoother.js +26 -18
- package/dist/cjs/src/localizations/de.js +1 -1
- package/dist/cjs/src/localizations/es.js +1 -1
- package/dist/cjs/src/math/LineSegment2.d.ts +2 -0
- package/dist/cjs/src/math/LineSegment2.js +4 -0
- package/dist/cjs/src/math/Path.d.ts +24 -3
- package/dist/cjs/src/math/Path.js +225 -4
- package/dist/cjs/src/math/Rect2.js +4 -3
- package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
- package/dist/cjs/src/math/polynomial/QuadraticBezier.js +114 -0
- package/dist/cjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
- package/dist/cjs/src/math/polynomial/solveQuadratic.js +36 -0
- package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +5 -3
- package/dist/cjs/src/rendering/renderers/SVGRenderer.js +15 -6
- package/dist/cjs/src/toolbar/HTMLToolbar.js +7 -0
- package/dist/cjs/src/toolbar/localization.d.ts +2 -1
- package/dist/cjs/src/toolbar/localization.js +2 -1
- package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
- package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +77 -2
- package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
- package/dist/cjs/src/tools/FindTool.js +1 -1
- package/dist/cjs/src/tools/SoundUITool.js +1 -1
- package/dist/mjs/src/Color4.mjs +3 -3
- package/dist/mjs/src/Editor.d.ts +4 -1
- package/dist/mjs/src/Editor.mjs +29 -11
- package/dist/mjs/src/SVGLoader.mjs +68 -6
- package/dist/mjs/src/Viewport.d.ts +2 -0
- package/dist/mjs/src/Viewport.mjs +6 -2
- package/dist/mjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/mjs/src/components/AbstractComponent.mjs +19 -9
- package/dist/mjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
- package/dist/mjs/src/components/BackgroundComponent.mjs +279 -0
- package/dist/mjs/src/components/Stroke.d.ts +1 -0
- package/dist/mjs/src/components/Stroke.mjs +15 -2
- package/dist/mjs/src/components/TextComponent.d.ts +1 -13
- package/dist/mjs/src/components/TextComponent.mjs +1 -1
- package/dist/mjs/src/components/lib.d.ts +2 -2
- package/dist/mjs/src/components/lib.mjs +2 -2
- package/dist/mjs/src/components/util/StrokeSmoother.mjs +26 -18
- package/dist/mjs/src/localizations/de.mjs +1 -1
- package/dist/mjs/src/localizations/es.mjs +1 -1
- package/dist/mjs/src/math/LineSegment2.d.ts +2 -0
- package/dist/mjs/src/math/LineSegment2.mjs +4 -0
- package/dist/mjs/src/math/Path.d.ts +24 -3
- package/dist/mjs/src/math/Path.mjs +225 -4
- package/dist/mjs/src/math/Rect2.mjs +4 -3
- package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
- package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +108 -0
- package/dist/mjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
- package/dist/mjs/src/math/polynomial/solveQuadratic.mjs +34 -0
- package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +5 -3
- package/dist/mjs/src/rendering/renderers/SVGRenderer.mjs +15 -6
- package/dist/mjs/src/toolbar/HTMLToolbar.mjs +8 -1
- package/dist/mjs/src/toolbar/localization.d.ts +2 -1
- package/dist/mjs/src/toolbar/localization.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
- package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -2
- package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +1 -1
- package/dist/mjs/src/tools/FindTool.mjs +1 -1
- package/dist/mjs/src/tools/SoundUITool.mjs +1 -1
- package/jest.config.js +1 -1
- package/package.json +14 -14
- package/src/Coloris.css +52 -0
- package/src/Editor.css +12 -0
- package/src/toolbar/toolbar.css +9 -0
- package/dist/cjs/src/components/ImageBackground.js +0 -146
- package/dist/mjs/src/components/ImageBackground.mjs +0 -139
@@ -0,0 +1,279 @@
|
|
1
|
+
import Color4 from '../Color4.mjs';
|
2
|
+
import { EditorImageEventType } from '../EditorImage.mjs';
|
3
|
+
import Rect2 from '../math/Rect2.mjs';
|
4
|
+
import AbstractComponent from './AbstractComponent.mjs';
|
5
|
+
import { createRestyleComponentCommand } from './RestylableComponent.mjs';
|
6
|
+
import Path, { PathCommandType } from '../math/Path.mjs';
|
7
|
+
import { Vec2 } from '../math/Vec2.mjs';
|
8
|
+
import Viewport from '../Viewport.mjs';
|
9
|
+
import { toRoundedString } from '../math/rounding.mjs';
|
10
|
+
export var BackgroundType;
|
11
|
+
(function (BackgroundType) {
|
12
|
+
BackgroundType[BackgroundType["SolidColor"] = 0] = "SolidColor";
|
13
|
+
BackgroundType[BackgroundType["Grid"] = 1] = "Grid";
|
14
|
+
BackgroundType[BackgroundType["None"] = 2] = "None";
|
15
|
+
})(BackgroundType || (BackgroundType = {}));
|
16
|
+
export const imageBackgroundCSSClassName = 'js-draw-image-background';
|
17
|
+
// Class name prefix indicating the size of the background's grid cells (if present).
|
18
|
+
export const imageBackgroundGridSizeCSSPrefix = 'js-draw-image-background-grid-';
|
19
|
+
// Flag included in rendered SVGs (etc) that indicates that the secondary color of the
|
20
|
+
// background has been manually set.
|
21
|
+
export const imageBackgroundNonAutomaticSecondaryColorCSSClassName = 'js-draw-image-background-non-automatic-secondary-color';
|
22
|
+
export const backgroundTypeToClassNameMap = {
|
23
|
+
[BackgroundType.Grid]: 'js-draw-image-background-grid',
|
24
|
+
[BackgroundType.SolidColor]: imageBackgroundCSSClassName,
|
25
|
+
[BackgroundType.None]: '',
|
26
|
+
};
|
27
|
+
// Represents the background of the editor's canvas.
|
28
|
+
export default class BackgroundComponent extends AbstractComponent {
|
29
|
+
constructor(backgroundType, mainColor) {
|
30
|
+
super('image-background', 0);
|
31
|
+
this.backgroundType = backgroundType;
|
32
|
+
this.mainColor = mainColor;
|
33
|
+
this.viewportSizeChangeListener = null;
|
34
|
+
this.gridSize = Viewport.getGridSize(2);
|
35
|
+
this.gridStrokeWidth = 0.7;
|
36
|
+
this.secondaryColor = null;
|
37
|
+
// eslint-disable-next-line @typescript-eslint/prefer-as-const
|
38
|
+
this.isRestylableComponent = true;
|
39
|
+
this.contentBBox = Rect2.empty;
|
40
|
+
}
|
41
|
+
static ofGrid(backgroundColor, gridSize, gridColor, gridStrokeWidth) {
|
42
|
+
const background = new BackgroundComponent(BackgroundType.Grid, backgroundColor);
|
43
|
+
if (gridSize !== undefined) {
|
44
|
+
background.gridSize = gridSize;
|
45
|
+
}
|
46
|
+
if (gridColor !== undefined) {
|
47
|
+
background.secondaryColor = gridColor;
|
48
|
+
}
|
49
|
+
if (gridStrokeWidth !== undefined) {
|
50
|
+
background.gridStrokeWidth = gridStrokeWidth;
|
51
|
+
}
|
52
|
+
return background;
|
53
|
+
}
|
54
|
+
getBackgroundType() {
|
55
|
+
return this.backgroundType;
|
56
|
+
}
|
57
|
+
// @internal
|
58
|
+
getMainColor() {
|
59
|
+
return this.mainColor;
|
60
|
+
}
|
61
|
+
// @internal
|
62
|
+
getSecondaryColor() {
|
63
|
+
return this.secondaryColor;
|
64
|
+
}
|
65
|
+
// @internal
|
66
|
+
getGridSize() {
|
67
|
+
return this.gridSize;
|
68
|
+
}
|
69
|
+
getStyle() {
|
70
|
+
let color = this.mainColor;
|
71
|
+
if (this.backgroundType === BackgroundType.None) {
|
72
|
+
color = undefined;
|
73
|
+
}
|
74
|
+
return {
|
75
|
+
color,
|
76
|
+
};
|
77
|
+
}
|
78
|
+
updateStyle(style) {
|
79
|
+
return createRestyleComponentCommand(this.getStyle(), style, this);
|
80
|
+
}
|
81
|
+
// @internal
|
82
|
+
forceStyle(style, editor) {
|
83
|
+
const fill = style.color;
|
84
|
+
if (!fill) {
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
this.mainColor = fill;
|
88
|
+
// A solid background and transparent fill is equivalent to no background.
|
89
|
+
if (fill.eq(Color4.transparent) && this.backgroundType === BackgroundType.SolidColor) {
|
90
|
+
this.backgroundType = BackgroundType.None;
|
91
|
+
}
|
92
|
+
else if (this.backgroundType === BackgroundType.None) {
|
93
|
+
this.backgroundType = BackgroundType.SolidColor;
|
94
|
+
}
|
95
|
+
if (editor) {
|
96
|
+
editor.image.queueRerenderOf(this);
|
97
|
+
editor.queueRerender();
|
98
|
+
}
|
99
|
+
}
|
100
|
+
onAddToImage(image) {
|
101
|
+
if (this.viewportSizeChangeListener) {
|
102
|
+
console.warn('onAddToImage called when background is already in an image');
|
103
|
+
this.onRemoveFromImage();
|
104
|
+
}
|
105
|
+
this.viewportSizeChangeListener = image.notifier.on(EditorImageEventType.ExportViewportChanged, () => {
|
106
|
+
this.recomputeBBox(image);
|
107
|
+
});
|
108
|
+
this.recomputeBBox(image);
|
109
|
+
}
|
110
|
+
onRemoveFromImage() {
|
111
|
+
var _a;
|
112
|
+
(_a = this.viewportSizeChangeListener) === null || _a === void 0 ? void 0 : _a.remove();
|
113
|
+
this.viewportSizeChangeListener = null;
|
114
|
+
}
|
115
|
+
recomputeBBox(image) {
|
116
|
+
const importExportRect = image.getImportExportViewport().visibleRect;
|
117
|
+
if (!this.contentBBox.eq(importExportRect)) {
|
118
|
+
this.contentBBox = importExportRect;
|
119
|
+
// Re-render this if already added to the EditorImage.
|
120
|
+
image.queueRerenderOf(this);
|
121
|
+
}
|
122
|
+
}
|
123
|
+
generateGridPath(visibleRect) {
|
124
|
+
var _a, _b;
|
125
|
+
const targetRect = (_b = (_a = visibleRect === null || visibleRect === void 0 ? void 0 : visibleRect.grownBy(this.gridStrokeWidth)) === null || _a === void 0 ? void 0 : _a.intersection(this.contentBBox)) !== null && _b !== void 0 ? _b : this.contentBBox;
|
126
|
+
const roundDownToGrid = (coord) => Math.floor(coord / this.gridSize) * this.gridSize;
|
127
|
+
const roundUpToGrid = (coord) => Math.ceil(coord / this.gridSize) * this.gridSize;
|
128
|
+
const startY = roundUpToGrid(targetRect.y);
|
129
|
+
const endY = roundDownToGrid(targetRect.y + targetRect.h);
|
130
|
+
const startX = roundUpToGrid(targetRect.x);
|
131
|
+
const endX = roundDownToGrid(targetRect.x + targetRect.w);
|
132
|
+
const result = [];
|
133
|
+
// Don't generate grids with a huge number of rows/columns -- such grids
|
134
|
+
// take a long time to render and are likely invisible due to the number of
|
135
|
+
// cells.
|
136
|
+
const rowCount = (endY - startY) / this.gridSize;
|
137
|
+
const colCount = (endX - startX) / this.gridSize;
|
138
|
+
const maxGridCols = 1000;
|
139
|
+
const maxGridRows = 1000;
|
140
|
+
if (rowCount > maxGridRows || colCount > maxGridCols) {
|
141
|
+
return Path.empty;
|
142
|
+
}
|
143
|
+
const startPoint = Vec2.of(targetRect.x, startY);
|
144
|
+
for (let y = startY; y <= endY; y += this.gridSize) {
|
145
|
+
result.push({
|
146
|
+
kind: PathCommandType.MoveTo,
|
147
|
+
point: Vec2.of(targetRect.x, y),
|
148
|
+
});
|
149
|
+
result.push({
|
150
|
+
kind: PathCommandType.LineTo,
|
151
|
+
point: Vec2.of(targetRect.x + targetRect.w, y),
|
152
|
+
});
|
153
|
+
}
|
154
|
+
for (let x = startX; x <= endX; x += this.gridSize) {
|
155
|
+
result.push({
|
156
|
+
kind: PathCommandType.MoveTo,
|
157
|
+
point: Vec2.of(x, targetRect.y),
|
158
|
+
});
|
159
|
+
result.push({
|
160
|
+
kind: PathCommandType.LineTo,
|
161
|
+
point: Vec2.of(x, targetRect.y + targetRect.h)
|
162
|
+
});
|
163
|
+
}
|
164
|
+
return new Path(startPoint, result);
|
165
|
+
}
|
166
|
+
render(canvas, visibleRect) {
|
167
|
+
if (this.backgroundType === BackgroundType.None) {
|
168
|
+
return;
|
169
|
+
}
|
170
|
+
const clip = true;
|
171
|
+
canvas.startObject(this.contentBBox, clip);
|
172
|
+
if (this.backgroundType === BackgroundType.SolidColor || this.backgroundType === BackgroundType.Grid) {
|
173
|
+
// If the rectangle for this region contains the visible rect,
|
174
|
+
// we can fill the entire visible rectangle (which may be more efficient than
|
175
|
+
// filling the entire region for this.)
|
176
|
+
if (visibleRect) {
|
177
|
+
const intersection = visibleRect.intersection(this.contentBBox);
|
178
|
+
if (intersection) {
|
179
|
+
canvas.fillRect(intersection, this.mainColor);
|
180
|
+
}
|
181
|
+
}
|
182
|
+
else {
|
183
|
+
canvas.fillRect(this.contentBBox, this.mainColor);
|
184
|
+
}
|
185
|
+
}
|
186
|
+
if (this.backgroundType === BackgroundType.Grid) {
|
187
|
+
let gridColor = this.secondaryColor;
|
188
|
+
gridColor !== null && gridColor !== void 0 ? gridColor : (gridColor = Color4.ofRGBA(1 - this.mainColor.r, 1 - this.mainColor.g, 1 - this.mainColor.b, 0.2));
|
189
|
+
// If the background fill is completely transparent, ensure visibility on otherwise light
|
190
|
+
// and dark backgrounds.
|
191
|
+
if (this.mainColor.a === 0) {
|
192
|
+
gridColor = Color4.ofRGBA(0.5, 0.5, 0.5, 0.2);
|
193
|
+
}
|
194
|
+
const style = {
|
195
|
+
fill: Color4.transparent,
|
196
|
+
stroke: { width: this.gridStrokeWidth, color: gridColor }
|
197
|
+
};
|
198
|
+
canvas.drawPath(this.generateGridPath(visibleRect).toRenderable(style));
|
199
|
+
}
|
200
|
+
const backgroundTypeCSSClass = backgroundTypeToClassNameMap[this.backgroundType];
|
201
|
+
const classNames = [imageBackgroundCSSClassName];
|
202
|
+
if (backgroundTypeCSSClass !== imageBackgroundCSSClassName) {
|
203
|
+
classNames.push(backgroundTypeCSSClass);
|
204
|
+
const gridSizeStr = toRoundedString(this.gridSize).replace(/[.]/g, 'p');
|
205
|
+
classNames.push(imageBackgroundGridSizeCSSPrefix + gridSizeStr);
|
206
|
+
}
|
207
|
+
if (this.secondaryColor !== null) {
|
208
|
+
classNames.push(imageBackgroundNonAutomaticSecondaryColorCSSClassName);
|
209
|
+
}
|
210
|
+
canvas.endObject(this.getLoadSaveData(), classNames);
|
211
|
+
}
|
212
|
+
intersects(lineSegment) {
|
213
|
+
return this.contentBBox.getEdges().some(edge => edge.intersects(lineSegment));
|
214
|
+
}
|
215
|
+
isSelectable() {
|
216
|
+
return false;
|
217
|
+
}
|
218
|
+
isBackground() {
|
219
|
+
return true;
|
220
|
+
}
|
221
|
+
serializeToJSON() {
|
222
|
+
var _a;
|
223
|
+
return {
|
224
|
+
mainColor: this.mainColor.toHexString(),
|
225
|
+
secondaryColor: (_a = this.secondaryColor) === null || _a === void 0 ? void 0 : _a.toHexString(),
|
226
|
+
backgroundType: this.backgroundType,
|
227
|
+
gridSize: this.gridSize,
|
228
|
+
gridStrokeWidth: this.gridStrokeWidth,
|
229
|
+
};
|
230
|
+
}
|
231
|
+
applyTransformation(_affineTransfm) {
|
232
|
+
// Do nothing — it doesn't make sense to transform the background.
|
233
|
+
}
|
234
|
+
description(localizationTable) {
|
235
|
+
if (this.backgroundType === BackgroundType.SolidColor) {
|
236
|
+
return localizationTable.filledBackgroundWithColor(this.mainColor.toString());
|
237
|
+
}
|
238
|
+
else {
|
239
|
+
return localizationTable.emptyBackground;
|
240
|
+
}
|
241
|
+
}
|
242
|
+
createClone() {
|
243
|
+
return new BackgroundComponent(this.backgroundType, this.mainColor);
|
244
|
+
}
|
245
|
+
// @internal
|
246
|
+
static deserializeFromJSON(json) {
|
247
|
+
var _a, _b;
|
248
|
+
if (typeof json === 'string') {
|
249
|
+
json = JSON.parse(json);
|
250
|
+
}
|
251
|
+
if (typeof json.mainColor !== 'string') {
|
252
|
+
throw new Error('Error deserializing — mainColor must be of type string.');
|
253
|
+
}
|
254
|
+
let backgroundType;
|
255
|
+
const jsonBackgroundType = json.backgroundType;
|
256
|
+
if (jsonBackgroundType === BackgroundType.None || jsonBackgroundType === BackgroundType.Grid
|
257
|
+
|| jsonBackgroundType === BackgroundType.SolidColor) {
|
258
|
+
backgroundType = jsonBackgroundType;
|
259
|
+
}
|
260
|
+
else {
|
261
|
+
const exhaustivenessCheck = jsonBackgroundType;
|
262
|
+
return exhaustivenessCheck;
|
263
|
+
}
|
264
|
+
const mainColor = Color4.fromHex(json.mainColor);
|
265
|
+
const secondaryColor = json.secondaryColor ? Color4.fromHex(json.secondaryColor) : null;
|
266
|
+
const gridSize = (_a = json.gridSize) !== null && _a !== void 0 ? _a : undefined;
|
267
|
+
const gridStrokeWidth = (_b = json.gridStrokeWidth) !== null && _b !== void 0 ? _b : undefined;
|
268
|
+
const result = new BackgroundComponent(backgroundType, mainColor);
|
269
|
+
result.secondaryColor = secondaryColor;
|
270
|
+
if (gridSize) {
|
271
|
+
result.gridSize = gridSize;
|
272
|
+
}
|
273
|
+
if (gridStrokeWidth) {
|
274
|
+
result.gridStrokeWidth = gridStrokeWidth;
|
275
|
+
}
|
276
|
+
return result;
|
277
|
+
}
|
278
|
+
}
|
279
|
+
AbstractComponent.registerComponent('image-background', BackgroundComponent.deserializeFromJSON);
|
@@ -53,6 +53,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
53
53
|
render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
54
54
|
getProportionalRenderingTime(): number;
|
55
55
|
private bboxForPart;
|
56
|
+
getExactBBox(): Rect2;
|
56
57
|
protected applyTransformation(affineTransfm: Mat33): void;
|
57
58
|
/**
|
58
59
|
* @returns the {@link Path.union} of all paths that make up this stroke.
|
@@ -109,8 +109,11 @@ export default class Stroke extends AbstractComponent {
|
|
109
109
|
}
|
110
110
|
}
|
111
111
|
intersects(line) {
|
112
|
+
var _a;
|
112
113
|
for (const part of this.parts) {
|
113
|
-
|
114
|
+
const strokeWidth = (_a = part.style.stroke) === null || _a === void 0 ? void 0 : _a.width;
|
115
|
+
const strokeRadius = strokeWidth ? strokeWidth / 2 : undefined;
|
116
|
+
if (part.path.intersection(line, strokeRadius).length > 0) {
|
114
117
|
return true;
|
115
118
|
}
|
116
119
|
}
|
@@ -125,7 +128,7 @@ export default class Stroke extends AbstractComponent {
|
|
125
128
|
if (!bbox.intersects(visibleRect)) {
|
126
129
|
continue;
|
127
130
|
}
|
128
|
-
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x *
|
131
|
+
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 3 || bbox.size.y > visibleRect.size.y * 3;
|
129
132
|
if (muchBiggerThanVisible && !part.path.roughlyIntersects(visibleRect, (_b = (_a = part.style.stroke) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0)) {
|
130
133
|
continue;
|
131
134
|
}
|
@@ -144,6 +147,16 @@ export default class Stroke extends AbstractComponent {
|
|
144
147
|
}
|
145
148
|
return origBBox.grownBy(style.stroke.width / 2);
|
146
149
|
}
|
150
|
+
getExactBBox() {
|
151
|
+
let bbox = null;
|
152
|
+
for (const { path, style } of this.parts) {
|
153
|
+
// Paths' default .bbox can be
|
154
|
+
const partBBox = this.bboxForPart(path.getExactBBox(), style);
|
155
|
+
bbox !== null && bbox !== void 0 ? bbox : (bbox = partBBox);
|
156
|
+
bbox = bbox.union(partBBox);
|
157
|
+
}
|
158
|
+
return bbox !== null && bbox !== void 0 ? bbox : Rect2.empty;
|
159
|
+
}
|
147
160
|
applyTransformation(affineTransfm) {
|
148
161
|
this.contentBBox = Rect2.empty;
|
149
162
|
let isFirstPart = true;
|
@@ -36,19 +36,7 @@ export default class TextComponent extends AbstractComponent implements Restylea
|
|
36
36
|
getStyle(): ComponentStyle;
|
37
37
|
updateStyle(style: ComponentStyle): SerializableCommand;
|
38
38
|
forceStyle(style: ComponentStyle, editor: Editor | null): void;
|
39
|
-
getTextStyle():
|
40
|
-
renderingStyle: {
|
41
|
-
fill: import("../Color4").default;
|
42
|
-
stroke: {
|
43
|
-
color: import("../Color4").default;
|
44
|
-
width: number;
|
45
|
-
} | undefined;
|
46
|
-
};
|
47
|
-
size: number;
|
48
|
-
fontFamily: string;
|
49
|
-
fontWeight?: string | undefined;
|
50
|
-
fontVariant?: string | undefined;
|
51
|
-
};
|
39
|
+
getTextStyle(): TextRenderingStyle;
|
52
40
|
getBaselinePos(): import("../lib").Vec3;
|
53
41
|
getTransform(): Mat33;
|
54
42
|
protected applyTransformation(affineTransfm: Mat33): void;
|
@@ -9,5 +9,5 @@ import TextComponent from './TextComponent';
|
|
9
9
|
import ImageComponent from './ImageComponent';
|
10
10
|
import RestyleableComponent from './RestylableComponent';
|
11
11
|
import { createRestyleComponentCommand, isRestylableComponent, ComponentStyle as RestyleableComponentStyle } from './RestylableComponent';
|
12
|
-
import
|
13
|
-
export { Stroke, TextComponent as Text, RestyleableComponent, createRestyleComponentCommand, isRestylableComponent, RestyleableComponentStyle, TextComponent, Stroke as StrokeComponent,
|
12
|
+
import BackgroundComponent from './BackgroundComponent';
|
13
|
+
export { Stroke, TextComponent as Text, RestyleableComponent, createRestyleComponentCommand, isRestylableComponent, RestyleableComponentStyle, TextComponent, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
@@ -8,5 +8,5 @@ import Stroke from './Stroke.mjs';
|
|
8
8
|
import TextComponent from './TextComponent.mjs';
|
9
9
|
import ImageComponent from './ImageComponent.mjs';
|
10
10
|
import { createRestyleComponentCommand, isRestylableComponent } from './RestylableComponent.mjs';
|
11
|
-
import
|
12
|
-
export { Stroke, TextComponent as Text, createRestyleComponentCommand, isRestylableComponent, TextComponent, Stroke as StrokeComponent,
|
11
|
+
import BackgroundComponent from './BackgroundComponent.mjs';
|
12
|
+
export { Stroke, TextComponent as Text, createRestyleComponentCommand, isRestylableComponent, TextComponent, Stroke as StrokeComponent, BackgroundComponent, ImageComponent, };
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import { Bezier } from 'bezier-js';
|
2
1
|
import { Vec2 } from '../../math/Vec2.mjs';
|
3
2
|
import Rect2 from '../../math/Rect2.mjs';
|
4
3
|
import LineSegment2 from '../../math/LineSegment2.mjs';
|
4
|
+
import QuadraticBezier from '../../math/polynomial/QuadraticBezier.mjs';
|
5
5
|
// Handles stroke smoothing
|
6
6
|
export class StrokeSmoother {
|
7
7
|
constructor(startPoint,
|
@@ -38,9 +38,9 @@ export class StrokeSmoother {
|
|
38
38
|
if (!this.currentCurve) {
|
39
39
|
return 0;
|
40
40
|
}
|
41
|
-
const startPt =
|
42
|
-
const controlPt =
|
43
|
-
const endPt =
|
41
|
+
const startPt = this.currentCurve.p0;
|
42
|
+
const controlPt = this.currentCurve.p1;
|
43
|
+
const endPt = this.currentCurve.p2;
|
44
44
|
const toControlDist = startPt.minus(controlPt).length();
|
45
45
|
const toEndDist = endPt.minus(controlPt).length();
|
46
46
|
return toControlDist + toEndDist;
|
@@ -52,7 +52,7 @@ export class StrokeSmoother {
|
|
52
52
|
}
|
53
53
|
this.onCurveAdded(this.currentSegmentToPath());
|
54
54
|
const lastPoint = this.buffer[this.buffer.length - 1];
|
55
|
-
this.lastExitingVec =
|
55
|
+
this.lastExitingVec = this.currentCurve.p2.minus(this.currentCurve.p1);
|
56
56
|
console.assert(this.lastExitingVec.magnitude() !== 0, 'lastExitingVec has zero length!');
|
57
57
|
// Use the last two points to start a new curve (the last point isn't used
|
58
58
|
// in the current curve and we want connected curves to share end points)
|
@@ -67,13 +67,13 @@ export class StrokeSmoother {
|
|
67
67
|
if (this.currentCurve == null) {
|
68
68
|
throw new Error('Invalid State: currentCurve is null!');
|
69
69
|
}
|
70
|
-
const startVec =
|
70
|
+
const startVec = this.currentCurve.normal(0).normalized();
|
71
71
|
if (!isFinite(startVec.magnitude())) {
|
72
72
|
throw new Error(`startVec(${startVec}) is NaN or ∞`);
|
73
73
|
}
|
74
|
-
const startPt =
|
75
|
-
const endPt =
|
76
|
-
const controlPoint =
|
74
|
+
const startPt = this.currentCurve.at(0);
|
75
|
+
const endPt = this.currentCurve.at(1);
|
76
|
+
const controlPoint = this.currentCurve.p1;
|
77
77
|
return {
|
78
78
|
startPoint: startPt,
|
79
79
|
controlPoint,
|
@@ -125,7 +125,7 @@ export class StrokeSmoother {
|
|
125
125
|
const p2 = lastPoint.pos.plus((_b = this.lastExitingVec) !== null && _b !== void 0 ? _b : Vec2.unitX);
|
126
126
|
const p3 = newPoint.pos;
|
127
127
|
// Quadratic Bézier curve
|
128
|
-
this.currentCurve = new
|
128
|
+
this.currentCurve = new QuadraticBezier(p1, p2, p3);
|
129
129
|
console.assert(!isNaN(p1.magnitude()) && !isNaN(p2.magnitude()) && !isNaN(p3.magnitude()), 'Expected !NaN');
|
130
130
|
if (this.isFirstSegment) {
|
131
131
|
// The start of a curve often lacks accurate pressure information. Update it.
|
@@ -146,7 +146,7 @@ export class StrokeSmoother {
|
|
146
146
|
}
|
147
147
|
let exitingVec = this.computeExitingVec();
|
148
148
|
// Find the intersection between the entering vector and the exiting vector
|
149
|
-
const maxRelativeLength = 2
|
149
|
+
const maxRelativeLength = 2;
|
150
150
|
const segmentStart = this.buffer[0];
|
151
151
|
const segmentEnd = newPoint.pos;
|
152
152
|
const startEndDist = segmentEnd.minus(segmentStart).magnitude();
|
@@ -176,20 +176,28 @@ export class StrokeSmoother {
|
|
176
176
|
console.assert(!segmentStart.eq(controlPoint, 1e-11), 'Start and control points are equal!');
|
177
177
|
console.assert(!controlPoint.eq(segmentEnd, 1e-11), 'Control and end points are equal!');
|
178
178
|
const prevCurve = this.currentCurve;
|
179
|
-
this.currentCurve = new
|
180
|
-
if (isNaN(
|
179
|
+
this.currentCurve = new QuadraticBezier(segmentStart, controlPoint, segmentEnd);
|
180
|
+
if (isNaN(this.currentCurve.normal(0).magnitude())) {
|
181
181
|
console.error('NaN normal at 0. Curve:', this.currentCurve);
|
182
182
|
this.currentCurve = prevCurve;
|
183
183
|
}
|
184
184
|
// Should we start making a new curve? Check whether all buffer points are within
|
185
185
|
// ±strokeWidth of the curve.
|
186
186
|
const curveMatchesPoints = (curve) => {
|
187
|
+
const minFit = Math.min(Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 4, this.minFitAllowed), this.maxFitAllowed);
|
188
|
+
// The sum of distances greater than minFit must not exceed this:
|
189
|
+
const maxNonMatchingDistSum = minFit;
|
190
|
+
// Sum of distances greater than minFit.
|
191
|
+
let nonMatchingDistSum = 0;
|
187
192
|
for (const point of this.buffer) {
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
+
let dist = curve.approximateDistance(point);
|
194
|
+
if (dist > minFit) {
|
195
|
+
// Use the more accurate distance function
|
196
|
+
dist = curve.distance(point);
|
197
|
+
nonMatchingDistSum += Math.max(0, dist - minFit);
|
198
|
+
if (nonMatchingDistSum > maxNonMatchingDistSum) {
|
199
|
+
return false; // false: Curve doesn't match points well enough.
|
200
|
+
}
|
193
201
|
}
|
194
202
|
}
|
195
203
|
return true;
|
@@ -1,4 +1,4 @@
|
|
1
1
|
import { defaultEditorLocalization } from '../localization.mjs';
|
2
2
|
// German localization
|
3
|
-
const localization = Object.assign(Object.assign({}, defaultEditorLocalization), { pen: 'Stift', eraser: 'Radierer', select: 'Auswahl', handTool: 'Verschieben', zoom: 'Vergrößerung', resetView: 'Ansicht zurücksetzen', thicknessLabel: 'Dicke: ', colorLabel: 'Farbe: ', fontLabel: 'Schriftart: ', resizeImageToSelection: 'Bildgröße an Auswahl anpassen', deleteSelection: 'Auswahl löschen', duplicateSelection: 'Auswahl duplizieren', undo: 'Rückgängig', redo: 'Wiederholen', pickColorFromScreen: 'Farbe von Bildschirm auswählen', clickToPickColorAnnouncement: 'Klicke auf den Bildschirm, um eine Farbe auszuwählen', selectionToolKeyboardShortcuts: 'Auswahl-Werkzeug: Verwende die Pfeiltasten, um ausgewählte Elemente zu verschieben und ‚i‘ und ‚o‘, um ihre Größe zu ändern.', touchPanning: 'Ansicht mit Touchscreen verschieben', anyDevicePanning: 'Ansicht mit jedem Eingabegerät verschieben',
|
3
|
+
const localization = Object.assign(Object.assign({}, defaultEditorLocalization), { pen: 'Stift', eraser: 'Radierer', select: 'Auswahl', handTool: 'Verschieben', zoom: 'Vergrößerung', resetView: 'Ansicht zurücksetzen', thicknessLabel: 'Dicke: ', colorLabel: 'Farbe: ', fontLabel: 'Schriftart: ', resizeImageToSelection: 'Bildgröße an Auswahl anpassen', deleteSelection: 'Auswahl löschen', duplicateSelection: 'Auswahl duplizieren', undo: 'Rückgängig', redo: 'Wiederholen', pickColorFromScreen: 'Farbe von Bildschirm auswählen', clickToPickColorAnnouncement: 'Klicke auf den Bildschirm, um eine Farbe auszuwählen', selectionToolKeyboardShortcuts: 'Auswahl-Werkzeug: Verwende die Pfeiltasten, um ausgewählte Elemente zu verschieben und ‚i‘ und ‚o‘, um ihre Größe zu ändern.', touchPanning: 'Ansicht mit Touchscreen verschieben', anyDevicePanning: 'Ansicht mit jedem Eingabegerät verschieben', selectPenType: 'Objekt-Typ: ', freehandPen: 'Freihand', arrowPen: 'Pfeil', linePen: 'Linie', outlinedRectanglePen: 'Umrissenes Rechteck', filledRectanglePen: 'Ausgefülltes Rechteck', dropdownShown: t => `Dropdown-Menü für ${t} angezeigt`, dropdownHidden: t => `Dropdown-Menü für ${t} versteckt`, zoomLevel: t => `Vergößerung: ${t}%`, colorChangedAnnouncement: t => `Farbe zu ${t} geändert`, penTool: t => `Stift ${t}`, selectionTool: 'Auswahl', eraserTool: 'Radiergummi', touchPanTool: 'Ansicht mit Touchscreen verschieben', twoFingerPanZoomTool: 'Ansicht verschieben und vergrößern', undoRedoTool: 'Rückgängig/Wiederholen', rightClickDragPanTool: 'Rechtsklick-Ziehen', pipetteTool: 'Farbe von Bildschirm auswählen', keyboardPanZoom: 'Tastaturkürzel zum Verschieben/Vergrößern der Ansicht', textTool: 'Text', enterTextToInsert: 'Einzufügender Text', toolEnabledAnnouncement: t => `${t} aktiviert`, toolDisabledAnnouncement: t => `${t} deaktiviert`, updatedViewport: 'Transformierte Ansicht', transformedElements: t => `${t} Element${1 === t ? '' : 'e'} transformiert`, resizeOutputCommand: t => `Bildgröße auf ${t.w}x${t.h} geändert`, addElementAction: t => `${t} hinzugefügt`, eraseAction: (t, e) => `${e} ${t} gelöscht`, duplicateAction: (t, e) => `${e} ${t} dupliziert`, inverseOf: t => `Umkehrung von ${t}`, elements: 'Elemente', erasedNoElements: 'Nichts entfernt', duplicatedNoElements: 'Nichts dupliziert', rotatedBy: t => `${Math.abs(t)} Grad ${t < 0 ? 'im Uhrzeigersinn' : 'gegen den Uhrzeigersinn'} gedreht`, movedLeft: 'Nacht links bewegt', movedUp: 'Nacht oben bewegt', movedDown: 'Nacht unten bewegt', movedRight: 'Nacht rechts bewegt', zoomedOut: 'Ansicht verkleinert', zoomedIn: 'Ansicht vergrößert', selectedElements: t => `${t} Element${1 === t ? '' : 'e'} ausgewählt`, stroke: 'Strich', svgObject: 'SVG-Objekt', text: t => `Text-Objekt: ${t}`, pathNodeCount: t => `Es gibt ${t} sichtbare Pfad-Objekte.`, textNodeCount: t => `Es gibt ${t} sichtbare Text-Knotenpunkte.`, textNode: t => `Text: ${t}`, rerenderAsText: 'Als Text darstellen', accessibilityInputInstructions: 'Drücke ‚t‘, um den Inhalt des Ansichtsfensters als Text zu lesen. Verwende die Pfeiltasten, um die Ansicht zu verschieben, und klicke und ziehe, um Striche zu zeichnen. Drücke ‚w‘ zum Vergrößern und ‚s‘ zum Verkleinern der Ansicht.', loading: t => `Laden ${t}%...`, doneLoading: 'Laden fertig', imageEditor: 'Bild-Editor', undoAnnouncement: t => `Rückgangig gemacht ${t}`, redoAnnouncement: t => `Wiederholt ${t}` });
|
4
4
|
export default localization;
|
@@ -6,7 +6,7 @@ const localization = Object.assign(Object.assign({}, defaultEditorLocalization),
|
|
6
6
|
loading: (percentage) => `Cargando: ${percentage}%...`, imageEditor: 'Editor de dibujos', undoAnnouncement: (commandDescription) => `${commandDescription} fue deshecho`, redoAnnouncement: (commandDescription) => `${commandDescription} fue rehecho`, undo: 'Deshace', redo: 'Rehace',
|
7
7
|
// Strings for the toolbar
|
8
8
|
// (see src/toolbar/localization.ts)
|
9
|
-
pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma',
|
9
|
+
pen: 'Lapiz', eraser: 'Borrador', select: 'Selecciona', thicknessLabel: 'Tamaño: ', colorLabel: 'Color: ', doneLoading: 'El cargado terminó', fontLabel: 'Fuente: ', anyDevicePanning: 'Mover la pantalla con todo dispotivo', touchPanning: 'Mover la pantalla con un dedo', touchPanTool: 'Instrumento de mover la pantalla con un dedo', outlinedRectanglePen: 'Rectángulo con nada más que un borde', filledRectanglePen: 'Rectángulo sin borde', linePen: 'Línea', arrowPen: 'Flecha', freehandPen: 'Dibuja sin restricción de forma', selectPenType: 'Forma de dibuja:', handTool: 'Mover', zoom: 'Zoom', resetView: 'Reiniciar vista', resizeImageToSelection: 'Redimensionar la imagen a lo que está seleccionado', deleteSelection: 'Borra la selección', duplicateSelection: 'Duplica la selección', pickColorFromScreen: 'Selecciona un color de la pantalla', clickToPickColorAnnouncement: 'Haga un clic en la pantalla para seleccionar un color', dropdownShown(toolName) {
|
10
10
|
return `Menú por ${toolName} es visible`;
|
11
11
|
}, dropdownHidden: function (toolName) {
|
12
12
|
return `Menú por ${toolName} fue ocultado`;
|
@@ -18,6 +18,8 @@ export default class LineSegment2 {
|
|
18
18
|
intersection(other: LineSegment2): IntersectionResult | null;
|
19
19
|
intersects(other: LineSegment2): boolean;
|
20
20
|
closestPointTo(target: Point2): import("./Vec3").default;
|
21
|
+
/** Returns the distance from this line segment to `target`. */
|
22
|
+
distance(target: Point2): number;
|
21
23
|
transformedBy(affineTransfm: Mat33): LineSegment2;
|
22
24
|
toString(): string;
|
23
25
|
}
|
@@ -116,6 +116,10 @@ export default class LineSegment2 {
|
|
116
116
|
return this.p1;
|
117
117
|
}
|
118
118
|
}
|
119
|
+
/** Returns the distance from this line segment to `target`. */
|
120
|
+
distance(target) {
|
121
|
+
return this.closestPointTo(target).minus(target).magnitude();
|
122
|
+
}
|
119
123
|
transformedBy(affineTransfm) {
|
120
124
|
return new LineSegment2(affineTransfm.transformVec2(this.p1), affineTransfm.transformVec2(this.p2));
|
121
125
|
}
|
@@ -32,21 +32,42 @@ export interface MoveToPathCommand {
|
|
32
32
|
}
|
33
33
|
export type PathCommand = CubicBezierPathCommand | LinePathCommand | QuadraticBezierPathCommand | MoveToPathCommand;
|
34
34
|
interface IntersectionResult {
|
35
|
-
curve: LineSegment2 | Bezier;
|
35
|
+
curve: LineSegment2 | Bezier | Point2;
|
36
36
|
parameterValue: number;
|
37
37
|
point: Point2;
|
38
38
|
}
|
39
|
+
type GeometryType = LineSegment2 | Bezier | Point2;
|
40
|
+
type GeometryArrayType = Array<GeometryType>;
|
39
41
|
export default class Path {
|
40
42
|
readonly startPoint: Point2;
|
41
43
|
readonly parts: PathCommand[];
|
44
|
+
/**
|
45
|
+
* A rough estimate of the bounding box of the path.
|
46
|
+
* A slight overestimate.
|
47
|
+
* See {@link getExactBBox}
|
48
|
+
*/
|
42
49
|
readonly bbox: Rect2;
|
43
50
|
constructor(startPoint: Point2, parts: PathCommand[]);
|
51
|
+
getExactBBox(): Rect2;
|
44
52
|
private cachedGeometry;
|
45
|
-
get geometry():
|
53
|
+
get geometry(): GeometryArrayType;
|
46
54
|
private cachedPolylineApproximation;
|
47
55
|
polylineApproximation(): LineSegment2[];
|
48
56
|
static computeBBoxForSegment(startPoint: Point2, part: PathCommand): Rect2;
|
49
|
-
|
57
|
+
/**
|
58
|
+
* Let `S` be a closed path a distance `strokeRadius` from this path.
|
59
|
+
*
|
60
|
+
* @returns Approximate intersections of `line` with `S` using ray marching, starting from
|
61
|
+
* both end points of `line` and each point in `additionalRaymarchStartPoints`.
|
62
|
+
*/
|
63
|
+
private raymarchIntersectionWith;
|
64
|
+
/**
|
65
|
+
* Returns a list of intersections with this path. If `strokeRadius` is given,
|
66
|
+
* intersections are approximated with the surface `strokeRadius` away from this.
|
67
|
+
*
|
68
|
+
* If `strokeRadius > 0`, the resultant `parameterValue` has no defined value.
|
69
|
+
*/
|
70
|
+
intersection(line: LineSegment2, strokeRadius?: number): IntersectionResult[];
|
50
71
|
private static mapPathCommand;
|
51
72
|
mapPoints(mapping: (point: Point2) => Point2): Path;
|
52
73
|
transformedBy(affineTransfm: Mat33): Path;
|