js-draw 0.20.0 → 0.22.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 +17 -0
- package/README.md +4 -4
- package/dist/bundle.js +1 -1
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/src/Color4.js +2 -2
- package/dist/cjs/src/Editor.d.ts +5 -4
- package/dist/cjs/src/Editor.js +25 -7
- package/dist/cjs/src/EditorImage.js +8 -2
- package/dist/cjs/src/Viewport.js +1 -1
- package/dist/cjs/src/commands/Command.js +1 -1
- package/dist/cjs/src/commands/SerializableCommand.js +1 -1
- package/dist/cjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/cjs/src/components/AbstractComponent.js +25 -10
- package/dist/cjs/src/components/Stroke.d.ts +1 -0
- package/dist/cjs/src/components/Stroke.js +14 -1
- package/dist/cjs/src/components/TextComponent.js +1 -1
- package/dist/cjs/src/components/util/StrokeSmoother.js +12 -14
- package/dist/cjs/src/localizations/de.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/Mat33.js +1 -1
- 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 +5 -4
- package/dist/cjs/src/math/Vec3.js +1 -1
- package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
- package/dist/cjs/src/math/polynomial/QuadraticBezier.js +3 -4
- package/dist/cjs/src/rendering/Display.d.ts +1 -1
- package/dist/cjs/src/rendering/Display.js +1 -2
- package/dist/cjs/src/toolbar/HTMLToolbar.d.ts +2 -0
- package/dist/cjs/src/toolbar/HTMLToolbar.js +71 -5
- package/dist/cjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
- package/dist/cjs/src/toolbar/widgets/BaseToolWidget.js +0 -1
- package/dist/cjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
- package/dist/cjs/src/toolbar/widgets/BaseWidget.js +17 -5
- package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/EraserToolWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/InsertImageWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
- package/dist/cjs/src/toolbar/widgets/TextToolWidget.js +1 -1
- package/dist/cjs/src/tools/SelectionTool/Selection.js +5 -2
- package/dist/cjs/src/tools/SelectionTool/SelectionTool.js +1 -1
- package/dist/cjs/src/types.d.ts +2 -0
- package/dist/mjs/src/Color4.mjs +2 -1
- package/dist/mjs/src/Editor.d.ts +5 -4
- package/dist/mjs/src/Editor.mjs +25 -7
- package/dist/mjs/src/EditorImage.mjs +10 -2
- package/dist/mjs/src/Viewport.mjs +2 -1
- package/dist/mjs/src/commands/Command.mjs +2 -1
- package/dist/mjs/src/commands/SerializableCommand.mjs +2 -1
- package/dist/mjs/src/components/AbstractComponent.d.ts +13 -1
- package/dist/mjs/src/components/AbstractComponent.mjs +26 -10
- package/dist/mjs/src/components/Stroke.d.ts +1 -0
- package/dist/mjs/src/components/Stroke.mjs +14 -1
- package/dist/mjs/src/components/TextComponent.mjs +2 -1
- package/dist/mjs/src/components/util/StrokeSmoother.mjs +12 -14
- package/dist/mjs/src/localizations/de.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/Mat33.mjs +2 -1
- package/dist/mjs/src/math/Path.d.ts +24 -3
- package/dist/mjs/src/math/Path.mjs +226 -4
- package/dist/mjs/src/math/Rect2.mjs +6 -4
- package/dist/mjs/src/math/Vec3.mjs +2 -1
- package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +1 -1
- package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +3 -4
- package/dist/mjs/src/rendering/Display.d.ts +1 -1
- package/dist/mjs/src/rendering/Display.mjs +1 -2
- package/dist/mjs/src/toolbar/HTMLToolbar.d.ts +2 -0
- package/dist/mjs/src/toolbar/HTMLToolbar.mjs +73 -6
- package/dist/mjs/src/toolbar/widgets/BaseToolWidget.d.ts +0 -1
- package/dist/mjs/src/toolbar/widgets/BaseToolWidget.mjs +0 -1
- package/dist/mjs/src/toolbar/widgets/BaseWidget.d.ts +5 -0
- package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +17 -5
- package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/EraserToolWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/InsertImageWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +2 -1
- package/dist/mjs/src/toolbar/widgets/TextToolWidget.mjs +2 -1
- package/dist/mjs/src/tools/SelectionTool/Selection.mjs +6 -2
- package/dist/mjs/src/tools/SelectionTool/SelectionTool.mjs +2 -1
- package/dist/mjs/src/types.d.ts +2 -0
- package/package.json +12 -12
- package/src/Coloris.css +52 -0
- package/src/Editor.css +12 -0
- package/src/toolbar/toolbar.css +16 -0
- package/tsconfig-typedoc.json +7 -0
- package/tsconfig.json +2 -0
- package/typedoc.json +1 -0
package/dist/cjs/src/types.d.ts
CHANGED
@@ -9,6 +9,8 @@ import Pointer from './Pointer';
|
|
9
9
|
import Color4 from './Color4';
|
10
10
|
import Command from './commands/Command';
|
11
11
|
import BaseWidget from './toolbar/widgets/BaseWidget';
|
12
|
+
export type HTMLPointerEventName = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
|
13
|
+
export type HTMLPointerEventFilter = (eventName: HTMLPointerEventName, event: PointerEvent) => boolean;
|
12
14
|
export interface PointerEvtListener {
|
13
15
|
onPointerDown(event: PointerEvt): boolean;
|
14
16
|
onPointerMove(event: PointerEvt): void;
|
package/dist/mjs/src/Color4.mjs
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import Vec3 from './math/Vec3.mjs';
|
2
|
-
|
2
|
+
class Color4 {
|
3
3
|
constructor(
|
4
4
|
/** Red component. Should be in the range [0, 1]. */
|
5
5
|
r,
|
@@ -254,4 +254,5 @@ Color4.clay = Color4.ofRGB(0.8, 0.4, 0.2);
|
|
254
254
|
Color4.black = Color4.ofRGB(0, 0, 0);
|
255
255
|
Color4.gray = Color4.ofRGB(0.5, 0.5, 0.5);
|
256
256
|
Color4.white = Color4.ofRGB(1, 1, 1);
|
257
|
+
export default Color4;
|
257
258
|
export { Color4 };
|
package/dist/mjs/src/Editor.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import EditorImage from './EditorImage';
|
2
2
|
import ToolController from './tools/ToolController';
|
3
|
-
import { InputEvtType, EditorNotifier, ImageLoader } from './types';
|
3
|
+
import { InputEvtType, EditorNotifier, ImageLoader, HTMLPointerEventFilter } from './types';
|
4
4
|
import Command from './commands/Command';
|
5
5
|
import UndoRedoHistory from './UndoRedoHistory';
|
6
6
|
import Viewport from './Viewport';
|
@@ -14,8 +14,6 @@ import Rect2 from './math/Rect2';
|
|
14
14
|
import { EditorLocalization } from './localization';
|
15
15
|
import IconProvider from './toolbar/IconProvider';
|
16
16
|
import AbstractComponent from './components/AbstractComponent';
|
17
|
-
type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
|
18
|
-
type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;
|
19
17
|
export interface EditorSettings {
|
20
18
|
/** Defaults to `RenderingMode.CanvasRenderer` */
|
21
19
|
renderingMode: RenderingMode;
|
@@ -191,7 +189,10 @@ export declare class Editor {
|
|
191
189
|
* });
|
192
190
|
* ```
|
193
191
|
*/
|
194
|
-
handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter):
|
192
|
+
handlePointerEventsFrom(elem: HTMLElement, filter?: HTMLPointerEventFilter): {
|
193
|
+
/** Remove all event listeners registered by this function. */
|
194
|
+
remove: () => void;
|
195
|
+
};
|
195
196
|
/** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
|
196
197
|
handleKeyEventsFrom(elem: HTMLElement): void;
|
197
198
|
/** `apply` a command. `command` will be announced for accessibility. */
|
package/dist/mjs/src/Editor.mjs
CHANGED
@@ -455,20 +455,38 @@ export class Editor {
|
|
455
455
|
handlePointerEventsFrom(elem, filter) {
|
456
456
|
// May be required to prevent text selection on iOS/Safari:
|
457
457
|
// See https://stackoverflow.com/a/70992717/17055750
|
458
|
-
|
459
|
-
|
458
|
+
const touchstartListener = (evt) => evt.preventDefault();
|
459
|
+
const contextmenuListener = (evt) => {
|
460
460
|
// Don't show a context menu
|
461
461
|
evt.preventDefault();
|
462
|
-
}
|
462
|
+
};
|
463
|
+
const listeners = {
|
464
|
+
'touchstart': touchstartListener,
|
465
|
+
'contextmenu': contextmenuListener,
|
466
|
+
};
|
463
467
|
const eventNames = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'];
|
464
468
|
for (const eventName of eventNames) {
|
465
|
-
|
466
|
-
|
469
|
+
listeners[eventName] = (evt) => {
|
470
|
+
// This listener will only be called in the context of PointerEvents.
|
471
|
+
const event = evt;
|
472
|
+
if (filter && !filter(eventName, event)) {
|
467
473
|
return true;
|
468
474
|
}
|
469
|
-
return this.handleHTMLPointerEvent(eventName,
|
470
|
-
}
|
475
|
+
return this.handleHTMLPointerEvent(eventName, event);
|
476
|
+
};
|
471
477
|
}
|
478
|
+
// Add all listeners.
|
479
|
+
for (const eventName in listeners) {
|
480
|
+
elem.addEventListener(eventName, listeners[eventName]);
|
481
|
+
}
|
482
|
+
return {
|
483
|
+
/** Remove all event listeners registered by this function. */
|
484
|
+
remove: () => {
|
485
|
+
for (const eventName in listeners) {
|
486
|
+
elem.removeEventListener(eventName, listeners[eventName]);
|
487
|
+
}
|
488
|
+
},
|
489
|
+
};
|
472
490
|
}
|
473
491
|
/** Adds event listners for keypresses to `elem` and forwards those events to the editor. */
|
474
492
|
handleKeyEventsFrom(elem) {
|
@@ -1,3 +1,7 @@
|
|
1
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
2
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
3
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
4
|
+
};
|
1
5
|
var _a, _b, _c;
|
2
6
|
import Viewport from './Viewport.mjs';
|
3
7
|
import AbstractComponent from './components/AbstractComponent.mjs';
|
@@ -16,7 +20,7 @@ export var EditorImageEventType;
|
|
16
20
|
EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
|
17
21
|
})(EditorImageEventType || (EditorImageEventType = {}));
|
18
22
|
// Handles lookup/storage of elements in the image
|
19
|
-
|
23
|
+
class EditorImage {
|
20
24
|
// @internal
|
21
25
|
constructor() {
|
22
26
|
this.root = new ImageNode();
|
@@ -194,6 +198,7 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
|
|
194
198
|
};
|
195
199
|
}
|
196
200
|
},
|
201
|
+
__setFunctionName(_b, "AddElementCommand"),
|
197
202
|
(() => {
|
198
203
|
SerializableCommand.register('add-element', (json, editor) => {
|
199
204
|
const id = json.elemData.id;
|
@@ -241,6 +246,7 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
241
246
|
};
|
242
247
|
}
|
243
248
|
},
|
249
|
+
__setFunctionName(_c, "SetImportExportRectCommand"),
|
244
250
|
_c.commandId = 'set-import-export-rect',
|
245
251
|
(() => {
|
246
252
|
const commandId = _c.commandId;
|
@@ -261,8 +267,9 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
261
267
|
});
|
262
268
|
})(),
|
263
269
|
_c);
|
270
|
+
export default EditorImage;
|
264
271
|
/** Part of the Editor's image. @internal */
|
265
|
-
|
272
|
+
class ImageNode {
|
266
273
|
constructor(parent = null) {
|
267
274
|
this.parent = parent;
|
268
275
|
this.targetChildCount = 30;
|
@@ -475,3 +482,4 @@ export class ImageNode {
|
|
475
482
|
}
|
476
483
|
}
|
477
484
|
ImageNode.idCounter = 0;
|
485
|
+
export { ImageNode };
|
@@ -17,7 +17,7 @@ import { Vec2 } from './math/Vec2.mjs';
|
|
17
17
|
import Vec3 from './math/Vec3.mjs';
|
18
18
|
export class ViewportTransform extends Command {
|
19
19
|
}
|
20
|
-
|
20
|
+
class Viewport {
|
21
21
|
// @internal
|
22
22
|
constructor(onTransformChangeCallback) {
|
23
23
|
this.onTransformChangeCallback = onTransformChangeCallback;
|
@@ -257,4 +257,5 @@ Viewport.ViewportTransform = (_a = class extends ViewportTransform {
|
|
257
257
|
},
|
258
258
|
_inverseTransform = new WeakMap(),
|
259
259
|
_a);
|
260
|
+
export { Viewport };
|
260
261
|
export default Viewport;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
class Command {
|
2
2
|
// Called when the command is being deleted
|
3
3
|
onDrop(_editor) { }
|
4
4
|
static union(a, b) {
|
@@ -27,4 +27,5 @@ Command.empty = new class extends Command {
|
|
27
27
|
apply(_editor) { }
|
28
28
|
unapply(_editor) { }
|
29
29
|
};
|
30
|
+
export { Command };
|
30
31
|
export default Command;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Command from './Command.mjs';
|
2
|
-
|
2
|
+
class SerializableCommand extends Command {
|
3
3
|
constructor(commandTypeId) {
|
4
4
|
super();
|
5
5
|
this.commandTypeId = commandTypeId;
|
@@ -34,3 +34,4 @@ export default class SerializableCommand extends Command {
|
|
34
34
|
}
|
35
35
|
}
|
36
36
|
SerializableCommand.deserializationCallbacks = {};
|
37
|
+
export default SerializableCommand;
|
@@ -32,8 +32,15 @@ export default abstract class AbstractComponent {
|
|
32
32
|
/** See {@link attachLoadSaveData} */
|
33
33
|
getLoadSaveData(): LoadSaveDataTable;
|
34
34
|
getZIndex(): number;
|
35
|
-
/**
|
35
|
+
/**
|
36
|
+
* @returns the bounding box of this. This can be a slight overestimate if doing so
|
37
|
+
* significantly improves performance.
|
38
|
+
*/
|
36
39
|
getBBox(): Rect2;
|
40
|
+
/**
|
41
|
+
* @returns the bounding box of this. Unlike `getBBox`, this should **not** be a rough estimate.
|
42
|
+
*/
|
43
|
+
getExactBBox(): Rect2;
|
37
44
|
/** Called when this component is added to the given image. */
|
38
45
|
onAddToImage(_image: EditorImage): void;
|
39
46
|
onRemoveFromImage(): void;
|
@@ -43,6 +50,11 @@ export default abstract class AbstractComponent {
|
|
43
50
|
/**
|
44
51
|
* @returns true if this component intersects `rect` -- it is entirely contained
|
45
52
|
* within the rectangle or one of the rectangle's edges intersects this component.
|
53
|
+
*
|
54
|
+
* The default implementation assumes that `this.getExactBBox()` returns a tight bounding box
|
55
|
+
* -- that any horiziontal/vertical line that intersects this' boounding box also
|
56
|
+
* intersects a point in this component. If this is not the case, components must override
|
57
|
+
* this function.
|
46
58
|
*/
|
47
59
|
intersectsRect(rect: Rect2): boolean;
|
48
60
|
protected abstract serializeToJSON(): any[] | Record<string, any> | number | string | null;
|
@@ -1,3 +1,7 @@
|
|
1
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
2
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
3
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
4
|
+
};
|
1
5
|
var _a;
|
2
6
|
import SerializableCommand from '../commands/SerializableCommand.mjs';
|
3
7
|
import EditorImage from '../EditorImage.mjs';
|
@@ -6,7 +10,7 @@ import UnresolvedSerializableCommand from '../commands/UnresolvedCommand.mjs';
|
|
6
10
|
/**
|
7
11
|
* A base class for everything that can be added to an {@link EditorImage}.
|
8
12
|
*/
|
9
|
-
|
13
|
+
class AbstractComponent {
|
10
14
|
constructor(
|
11
15
|
// A unique identifier for the type of component
|
12
16
|
componentKind, initialZIndex) {
|
@@ -55,30 +59,40 @@ export default class AbstractComponent {
|
|
55
59
|
getZIndex() {
|
56
60
|
return this.zIndex;
|
57
61
|
}
|
58
|
-
/**
|
62
|
+
/**
|
63
|
+
* @returns the bounding box of this. This can be a slight overestimate if doing so
|
64
|
+
* significantly improves performance.
|
65
|
+
*/
|
59
66
|
getBBox() {
|
60
67
|
return this.contentBBox;
|
61
68
|
}
|
69
|
+
/**
|
70
|
+
* @returns the bounding box of this. Unlike `getBBox`, this should **not** be a rough estimate.
|
71
|
+
*/
|
72
|
+
getExactBBox() {
|
73
|
+
return this.getBBox();
|
74
|
+
}
|
62
75
|
/** Called when this component is added to the given image. */
|
63
76
|
onAddToImage(_image) { }
|
64
77
|
onRemoveFromImage() { }
|
65
78
|
/**
|
66
79
|
* @returns true if this component intersects `rect` -- it is entirely contained
|
67
80
|
* within the rectangle or one of the rectangle's edges intersects this component.
|
81
|
+
*
|
82
|
+
* The default implementation assumes that `this.getExactBBox()` returns a tight bounding box
|
83
|
+
* -- that any horiziontal/vertical line that intersects this' boounding box also
|
84
|
+
* intersects a point in this component. If this is not the case, components must override
|
85
|
+
* this function.
|
68
86
|
*/
|
69
87
|
intersectsRect(rect) {
|
70
|
-
// If this component intersects
|
88
|
+
// If this component intersects the given rectangle,
|
71
89
|
// it is either contained entirely within rect or intersects one of rect's edges.
|
72
90
|
// If contained within,
|
73
|
-
if (rect.containsRect(this.
|
91
|
+
if (rect.containsRect(this.getExactBBox())) {
|
74
92
|
return true;
|
75
93
|
}
|
76
|
-
//
|
77
|
-
|
78
|
-
const testLines = [];
|
79
|
-
for (const subregion of rect.divideIntoGrid(2, 2)) {
|
80
|
-
testLines.push(...subregion.getEdges());
|
81
|
-
}
|
94
|
+
// Otherwise check if it intersects one of the rectangle's edges.
|
95
|
+
const testLines = rect.getEdges();
|
82
96
|
return testLines.some(edge => this.intersects(edge));
|
83
97
|
}
|
84
98
|
// Returns a command that, when applied, transforms this by [affineTransfm] and
|
@@ -239,6 +253,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
239
253
|
};
|
240
254
|
}
|
241
255
|
},
|
256
|
+
__setFunctionName(_a, "TransformElementCommand"),
|
242
257
|
(() => {
|
243
258
|
SerializableCommand.register(AbstractComponent.transformElementCommandId, (json, editor) => {
|
244
259
|
var _a, _b;
|
@@ -250,3 +265,4 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
250
265
|
});
|
251
266
|
})(),
|
252
267
|
_a);
|
268
|
+
export default AbstractComponent;
|
@@ -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
|
}
|
@@ -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;
|
@@ -9,7 +9,7 @@ const componentTypeId = 'text';
|
|
9
9
|
/**
|
10
10
|
* Displays text.
|
11
11
|
*/
|
12
|
-
|
12
|
+
class TextComponent extends AbstractComponent {
|
13
13
|
/**
|
14
14
|
* Creates a new text object from a list of component text or child TextComponents.
|
15
15
|
*
|
@@ -271,4 +271,5 @@ export default class TextComponent extends AbstractComponent {
|
|
271
271
|
}
|
272
272
|
}
|
273
273
|
TextComponent.textMeasuringCtx = null;
|
274
|
+
export default TextComponent;
|
274
275
|
AbstractComponent.registerComponent(componentTypeId, (data) => TextComponent.deserializeFromString(data));
|
@@ -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();
|
@@ -184,22 +184,20 @@ export class StrokeSmoother {
|
|
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
|
-
|
188
|
-
|
189
|
-
const
|
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;
|
190
192
|
for (const point of this.buffer) {
|
191
193
|
let dist = curve.approximateDistance(point);
|
192
|
-
if (dist > minFit
|
193
|
-
//
|
194
|
-
|
195
|
-
|
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.
|
196
200
|
}
|
197
|
-
if (dist > minFit || dist > this.maxFitAllowed) {
|
198
|
-
nonMatching++;
|
199
|
-
}
|
200
|
-
}
|
201
|
-
if (nonMatching >= maxNonMatching) {
|
202
|
-
return false;
|
203
201
|
}
|
204
202
|
}
|
205
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', selectPenType: 'Objekt-Typ: ', freehandPen: 'Freihand', arrowPen: 'Pfeil', linePen: 'Linie', outlinedRectanglePen: 'Umrissenes Rechteck', filledRectanglePen: 'Ausgefülltes Rechteck', dropdownShown:
|
3
|
+
const localization = Object.assign(Object.assign({}, defaultEditorLocalization), { pen: 'Stift', eraser: 'Radierer', select: 'Auswahl', handTool: 'Verschieben', zoom: 'Vergrößerung', image: 'Bild', inputAltText: 'Alt-Text: ', chooseFile: 'Wähle Datei: ', submit: 'Absenden', cancel: 'Abbrechen', resetView: 'Ansicht zurücksetzen', thicknessLabel: 'Dicke: ', colorLabel: 'Farbe: ', fontLabel: 'Schriftart: ', textSize: 'Größe: ', 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', pressureSensitiveFreehandPen: 'Stift (druckempfindlich)', arrowPen: 'Pfeil', linePen: 'Linie', outlinedRectanglePen: 'Umrissenes Rechteck', filledRectanglePen: 'Ausgefülltes Rechteck', lockRotation: 'Sperre Rotation', paste: 'Einfügen', dropdownShown: (toolName) => `Dropdown-Menü für ${toolName} angezeigt`, dropdownHidden: (toolName) => `Dropdown-Menü für ${toolName} versteckt`, zoomLevel: (zoomPercent) => `Vergößerung: ${zoomPercent}%`, colorChangedAnnouncement: (color) => `Farbe zu ${color} geändert`, imageSize: (size, units) => `Bild-Größe: ${size} ${units}`, imageLoadError: (message) => `Fehler beim Laden des Bildes: ${message}`, errorImageHasZeroSize: 'Fehler: Bild hat Größe Null', penTool: (penNumber) => `Stift ${penNumber}`, 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', changeTool: 'Wechsle Werkzeug', pasteHandler: 'Copy-Paste-Handler', findLabel: 'Finde', toNextMatch: 'Nächstes', closeFindDialog: 'Schließen', findDialogShown: 'Finde-Dialog angezeigt', findDialogHidden: 'Finde-Dialog versteckt', focusedFoundText: (matchIdx, totalMatches) => `Sieh Treffer ${matchIdx} von ${totalMatches} an`, toolEnabledAnnouncement: (toolName) => `${toolName} aktiviert`, toolDisabledAnnouncement: (toolName) => `${toolName} deaktiviert`, updatedViewport: 'Transformierte Ansicht', transformedElements: (elemCount) => `${elemCount} Element${1 === elemCount ? '' : 'e'} transformiert`, resizeOutputCommand: (newSize) => `Bildgröße auf ${newSize.w}x${newSize.h} geändert`, addElementAction: (componentDescription) => `${componentDescription} hinzugefügt`, eraseAction: (elemDescription, countErased) => `${countErased} ${elemDescription} gelöscht`, duplicateAction: (elemDescription, countErased) => `${countErased} ${elemDescription} dupliziert`, inverseOf: (actionDescription) => `${actionDescription} umgekehrt`, elements: 'Elemente', erasedNoElements: 'Nichts entfernt', duplicatedNoElements: 'Nichts dupliziert', rotatedBy: (degrees) => `${Math.abs(degrees)} Grad ${degrees < 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: (count) => `${count} Element${1 === count ? '' : 'e'} ausgewählt`, stroke: 'Strich', svgObject: 'SVG-Objekt', text: (text) => `Text-Objekt: ${text}`, pathNodeCount: (count) => `Es gibt ${count} sichtbare Pfad-Objekte.`, textNodeCount: (count) => `Es gibt ${count} sichtbare Text-Knotenpunkte.`, textNode: (content) => `Text: ${content}`, imageNodeCount: (nodeCount) => `Es gibt ${nodeCount} sichtbare Bild-Knoten.`, imageNode: label => `Bild: ${label}`, unlabeledImageNode: 'Bild ohne Label', 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: percentage => `Laden ${percentage}%...`, doneLoading: 'Laden fertig', imageEditor: 'Bild-Editor', undoAnnouncement: (commandDescription) => `${commandDescription} rückgängig gemacht`, redoAnnouncement: (commandDescription) => `${commandDescription} wiederholt`, reformatSelection: 'Formatiere Auswahl', documentProperties: 'Seite', backgroundColor: 'Hintergrundfarbe: ', imageWidthOption: 'Breite: ', imageHeightOption: 'Höhe: ', useGridOption: 'Gitter: ', toggleOverflow: 'Mehr', selectAllTool: 'Alle auswählen', soundExplorer: 'Klangbasierte Bilderkundung', disableAccessibilityExploreTool: 'Deaktiviere klangbasierte Erkundung', enableAccessibilityExploreTool: 'Aktiviere klangbasierte Erkundung', copied: (count, description) => `${count} ${description} kopiert`, pasted: (count, description) => `${count} ${description} eingefügt`, unionOf: (actionDescription, actionCount) => `Vereinigung: ${actionCount} ${actionDescription}`, emptyBackground: 'Leerer Hintergrund', filledBackgroundWithColor: (color) => `Gefüllter Hintergrund (${color})`, restyledElement: (elementDescription) => `${elementDescription} umgestaltet` });
|
4
4
|
export default localization;
|
@@ -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
|
}
|
@@ -5,7 +5,7 @@ import Vec3 from './Vec3.mjs';
|
|
5
5
|
* a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
6
6
|
* **and** translates while a linear transformation just scales/rotates/shears).
|
7
7
|
*/
|
8
|
-
|
8
|
+
class Mat33 {
|
9
9
|
/**
|
10
10
|
* Creates a matrix from inputs in the form,
|
11
11
|
* ```
|
@@ -324,3 +324,4 @@ export default class Mat33 {
|
|
324
324
|
}
|
325
325
|
}
|
326
326
|
Mat33.identity = new Mat33(1, 0, 0, 0, 1, 0, 0, 0, 1);
|
327
|
+
export default Mat33;
|
@@ -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;
|