js-draw 0.18.2 → 0.20.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 +10 -0
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -0
- package/dist/cjs/src/Color4.d.ts +8 -0
- package/dist/cjs/src/Color4.js +67 -0
- package/dist/cjs/src/Editor.d.ts +2 -2
- package/dist/cjs/src/Editor.js +7 -7
- package/dist/cjs/src/SVGLoader.js +77 -12
- 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 +2 -2
- package/dist/cjs/src/components/AbstractComponent.js +3 -3
- 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/RestylableComponent.d.ts +21 -2
- package/dist/cjs/src/components/Stroke.d.ts +35 -0
- package/dist/cjs/src/components/Stroke.js +37 -3
- package/dist/cjs/src/components/TextComponent.d.ts +27 -17
- package/dist/cjs/src/components/TextComponent.js +23 -1
- package/dist/cjs/src/components/lib.d.ts +4 -3
- package/dist/cjs/src/components/lib.js +2 -2
- package/dist/cjs/src/components/util/StrokeSmoother.js +25 -15
- package/dist/cjs/src/lib.d.ts +30 -0
- package/dist/cjs/src/lib.js +30 -0
- package/dist/cjs/src/localizations/de.js +1 -1
- package/dist/cjs/src/localizations/es.js +1 -1
- package/dist/cjs/src/math/Path.js +1 -1
- package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
- package/dist/cjs/src/math/polynomial/QuadraticBezier.js +115 -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/RenderingStyle.d.ts +4 -4
- package/dist/cjs/src/rendering/TextRenderingStyle.d.ts +10 -10
- package/dist/cjs/src/rendering/lib.d.ts +2 -0
- package/dist/cjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
- package/dist/cjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
- package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +5 -3
- package/dist/cjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
- package/dist/cjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
- package/dist/cjs/src/rendering/renderers/SVGRenderer.js +15 -6
- package/dist/cjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
- package/dist/cjs/src/toolbar/IconProvider.d.ts +2 -2
- package/dist/cjs/src/toolbar/localization.d.ts +2 -1
- package/dist/cjs/src/toolbar/localization.js +3 -2
- package/dist/cjs/src/toolbar/widgets/BaseWidget.js +1 -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.d.ts +24 -0
- package/dist/cjs/src/tools/SoundUITool.js +164 -0
- package/dist/cjs/src/tools/TextTool.d.ts +2 -2
- package/dist/cjs/src/tools/ToolController.js +6 -1
- package/dist/cjs/src/tools/lib.d.ts +1 -0
- package/dist/cjs/src/tools/lib.js +3 -1
- package/dist/cjs/src/tools/localization.d.ts +3 -0
- package/dist/cjs/src/tools/localization.js +3 -0
- package/dist/mjs/src/Color4.d.ts +8 -0
- package/dist/mjs/src/Color4.mjs +64 -0
- package/dist/mjs/src/Editor.d.ts +2 -2
- package/dist/mjs/src/Editor.mjs +6 -6
- package/dist/mjs/src/SVGLoader.mjs +76 -11
- 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 +2 -2
- package/dist/mjs/src/components/AbstractComponent.mjs +3 -3
- 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/RestylableComponent.d.ts +21 -2
- package/dist/mjs/src/components/Stroke.d.ts +35 -0
- package/dist/mjs/src/components/Stroke.mjs +37 -3
- package/dist/mjs/src/components/TextComponent.d.ts +27 -17
- package/dist/mjs/src/components/TextComponent.mjs +23 -1
- package/dist/mjs/src/components/lib.d.ts +4 -3
- package/dist/mjs/src/components/lib.mjs +2 -2
- package/dist/mjs/src/components/util/StrokeSmoother.mjs +25 -15
- package/dist/mjs/src/lib.d.ts +30 -0
- package/dist/mjs/src/lib.mjs +30 -0
- package/dist/mjs/src/localizations/de.mjs +1 -1
- package/dist/mjs/src/localizations/es.mjs +1 -1
- package/dist/mjs/src/math/Path.mjs +1 -1
- package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
- package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +109 -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/RenderingStyle.d.ts +4 -4
- package/dist/mjs/src/rendering/TextRenderingStyle.d.ts +10 -10
- package/dist/mjs/src/rendering/lib.d.ts +2 -0
- package/dist/mjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
- package/dist/mjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
- package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +5 -3
- package/dist/mjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
- package/dist/mjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
- package/dist/mjs/src/rendering/renderers/SVGRenderer.mjs +15 -6
- package/dist/mjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
- package/dist/mjs/src/toolbar/IconProvider.d.ts +2 -2
- package/dist/mjs/src/toolbar/localization.d.ts +2 -1
- package/dist/mjs/src/toolbar/localization.mjs +3 -2
- package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +1 -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.d.ts +24 -0
- package/dist/mjs/src/tools/SoundUITool.mjs +158 -0
- package/dist/mjs/src/tools/TextTool.d.ts +2 -2
- package/dist/mjs/src/tools/ToolController.mjs +6 -1
- package/dist/mjs/src/tools/lib.d.ts +1 -0
- package/dist/mjs/src/tools/lib.mjs +1 -0
- package/dist/mjs/src/tools/localization.d.ts +3 -0
- package/dist/mjs/src/tools/localization.mjs +3 -0
- package/jest.config.js +1 -1
- package/package.json +19 -17
- package/src/Editor.css +2 -2
- package/src/tools/SoundUITool.css +15 -0
- package/src/tools/tools.css +4 -0
- package/dist/cjs/src/components/ImageBackground.js +0 -146
- package/dist/mjs/src/components/ImageBackground.mjs +0 -139
- package/src/Color4.test.ts +0 -40
- package/src/Color4.ts +0 -236
- package/src/Editor.loadFrom.test.ts +0 -24
- package/src/Editor.toSVG.test.ts +0 -111
- package/src/Editor.ts +0 -1122
- package/src/EditorImage.test.ts +0 -120
- package/src/EditorImage.ts +0 -603
- package/src/EventDispatcher.test.ts +0 -123
- package/src/EventDispatcher.ts +0 -71
- package/src/Pointer.ts +0 -127
- package/src/SVGLoader.test.ts +0 -114
- package/src/SVGLoader.ts +0 -511
- package/src/UndoRedoHistory.test.ts +0 -33
- package/src/UndoRedoHistory.ts +0 -102
- package/src/Viewport.ts +0 -319
- package/src/bundle/bundled.ts +0 -7
- package/src/commands/Command.ts +0 -45
- package/src/commands/Duplicate.ts +0 -48
- package/src/commands/Erase.ts +0 -74
- package/src/commands/SerializableCommand.ts +0 -49
- package/src/commands/UnresolvedCommand.ts +0 -37
- package/src/commands/invertCommand.ts +0 -51
- package/src/commands/lib.ts +0 -16
- package/src/commands/localization.ts +0 -47
- package/src/commands/uniteCommands.test.ts +0 -23
- package/src/commands/uniteCommands.ts +0 -135
- package/src/components/AbstractComponent.transformBy.test.ts +0 -22
- package/src/components/AbstractComponent.ts +0 -364
- package/src/components/ImageBackground.test.ts +0 -35
- package/src/components/ImageBackground.ts +0 -176
- package/src/components/ImageComponent.ts +0 -171
- package/src/components/RestylableComponent.ts +0 -142
- package/src/components/SVGGlobalAttributesObject.ts +0 -81
- package/src/components/Stroke.test.ts +0 -139
- package/src/components/Stroke.ts +0 -245
- package/src/components/TextComponent.test.ts +0 -99
- package/src/components/TextComponent.ts +0 -315
- package/src/components/UnknownSVGObject.test.ts +0 -10
- package/src/components/UnknownSVGObject.ts +0 -60
- package/src/components/builders/ArrowBuilder.ts +0 -107
- package/src/components/builders/FreehandLineBuilder.ts +0 -212
- package/src/components/builders/LineBuilder.ts +0 -77
- package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -454
- package/src/components/builders/RectangleBuilder.ts +0 -74
- package/src/components/builders/types.ts +0 -15
- package/src/components/lib.ts +0 -25
- package/src/components/localization.ts +0 -22
- package/src/components/util/StrokeSmoother.ts +0 -293
- package/src/components/util/describeComponentList.ts +0 -18
- package/src/lib.ts +0 -37
- package/src/localization.ts +0 -34
- package/src/localizations/de.ts +0 -98
- package/src/localizations/en.ts +0 -8
- package/src/localizations/es.ts +0 -74
- package/src/localizations/getLocalizationTable.test.ts +0 -27
- package/src/localizations/getLocalizationTable.ts +0 -55
- package/src/math/LineSegment2.test.ts +0 -99
- package/src/math/LineSegment2.ts +0 -160
- package/src/math/Mat33.test.ts +0 -244
- package/src/math/Mat33.ts +0 -437
- package/src/math/Path.fromString.test.ts +0 -223
- package/src/math/Path.test.ts +0 -198
- package/src/math/Path.toString.test.ts +0 -77
- package/src/math/Path.ts +0 -790
- package/src/math/Rect2.test.ts +0 -204
- package/src/math/Rect2.ts +0 -315
- package/src/math/Triangle.ts +0 -29
- package/src/math/Vec2.test.ts +0 -30
- package/src/math/Vec2.ts +0 -18
- package/src/math/Vec3.test.ts +0 -44
- package/src/math/Vec3.ts +0 -218
- package/src/math/lib.ts +0 -15
- package/src/math/rounding.test.ts +0 -65
- package/src/math/rounding.ts +0 -156
- package/src/rendering/Display.ts +0 -249
- package/src/rendering/RenderingStyle.test.ts +0 -68
- package/src/rendering/RenderingStyle.ts +0 -55
- package/src/rendering/TextRenderingStyle.ts +0 -45
- package/src/rendering/caching/CacheRecord.test.ts +0 -49
- package/src/rendering/caching/CacheRecord.ts +0 -77
- package/src/rendering/caching/CacheRecordManager.ts +0 -71
- package/src/rendering/caching/RenderingCache.test.ts +0 -44
- package/src/rendering/caching/RenderingCache.ts +0 -66
- package/src/rendering/caching/RenderingCacheNode.ts +0 -405
- package/src/rendering/caching/testUtils.ts +0 -35
- package/src/rendering/caching/types.ts +0 -34
- package/src/rendering/lib.ts +0 -6
- package/src/rendering/localization.ts +0 -20
- package/src/rendering/renderers/AbstractRenderer.ts +0 -222
- package/src/rendering/renderers/CanvasRenderer.ts +0 -296
- package/src/rendering/renderers/DummyRenderer.test.ts +0 -42
- package/src/rendering/renderers/DummyRenderer.ts +0 -136
- package/src/rendering/renderers/SVGRenderer.ts +0 -354
- package/src/rendering/renderers/TextOnlyRenderer.ts +0 -70
- package/src/testing/beforeEachFile.ts +0 -8
- package/src/testing/createEditor.ts +0 -11
- package/src/testing/global.d.ts +0 -17
- package/src/testing/lib.ts +0 -3
- package/src/testing/loadExpectExtensions.ts +0 -25
- package/src/testing/sendPenEvent.ts +0 -31
- package/src/testing/sendTouchEvent.ts +0 -78
- package/src/toolbar/HTMLToolbar.ts +0 -492
- package/src/toolbar/IconProvider.ts +0 -736
- package/src/toolbar/lib.ts +0 -4
- package/src/toolbar/localization.ts +0 -106
- package/src/toolbar/makeColorInput.ts +0 -145
- package/src/toolbar/types.ts +0 -5
- package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
- package/src/toolbar/widgets/BaseToolWidget.ts +0 -56
- package/src/toolbar/widgets/BaseWidget.ts +0 -377
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -167
- package/src/toolbar/widgets/EraserToolWidget.ts +0 -85
- package/src/toolbar/widgets/HandToolWidget.ts +0 -250
- package/src/toolbar/widgets/InsertImageWidget.ts +0 -223
- package/src/toolbar/widgets/OverflowWidget.ts +0 -92
- package/src/toolbar/widgets/PenToolWidget.ts +0 -288
- package/src/toolbar/widgets/SelectionToolWidget.ts +0 -190
- package/src/toolbar/widgets/TextToolWidget.ts +0 -145
- package/src/toolbar/widgets/lib.ts +0 -13
- package/src/tools/BaseTool.ts +0 -76
- package/src/tools/Eraser.test.ts +0 -103
- package/src/tools/Eraser.ts +0 -139
- package/src/tools/FindTool.ts +0 -152
- package/src/tools/PanZoom.test.ts +0 -310
- package/src/tools/PanZoom.ts +0 -520
- package/src/tools/PasteHandler.ts +0 -95
- package/src/tools/Pen.test.ts +0 -194
- package/src/tools/Pen.ts +0 -226
- package/src/tools/PipetteTool.ts +0 -55
- package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -28
- package/src/tools/SelectionTool/Selection.ts +0 -607
- package/src/tools/SelectionTool/SelectionHandle.ts +0 -108
- package/src/tools/SelectionTool/SelectionTool.test.ts +0 -261
- package/src/tools/SelectionTool/SelectionTool.ts +0 -480
- package/src/tools/SelectionTool/TransformMode.ts +0 -114
- package/src/tools/SelectionTool/types.ts +0 -11
- package/src/tools/TextTool.ts +0 -326
- package/src/tools/ToolController.ts +0 -178
- package/src/tools/ToolEnabledGroup.ts +0 -14
- package/src/tools/ToolSwitcherShortcut.ts +0 -39
- package/src/tools/ToolbarShortcutHandler.ts +0 -34
- package/src/tools/UndoRedoShortcut.test.ts +0 -56
- package/src/tools/UndoRedoShortcut.ts +0 -25
- package/src/tools/lib.ts +0 -21
- package/src/tools/localization.ts +0 -66
- package/src/types.ts +0 -234
- package/src/util/assertions.ts +0 -55
- package/src/util/fileToBase64.ts +0 -18
- package/src/util/untilNextAnimationFrame.ts +0 -9
- package/src/util/waitForTimeout.ts +0 -9
@@ -3,12 +3,43 @@ import Rect2 from '../math/Rect2.mjs';
|
|
3
3
|
import { styleFromJSON, styleToJSON } from '../rendering/RenderingStyle.mjs';
|
4
4
|
import AbstractComponent from './AbstractComponent.mjs';
|
5
5
|
import { createRestyleComponentCommand } from './RestylableComponent.mjs';
|
6
|
+
/**
|
7
|
+
* Represents an {@link AbstractComponent} made up of one or more {@link Path}s.
|
8
|
+
*
|
9
|
+
* @example
|
10
|
+
* For some {@link Editor} editor and `Stroke` stroke,
|
11
|
+
*
|
12
|
+
* **Restyling**:
|
13
|
+
* ```ts
|
14
|
+
* editor.dispatch(stroke.updateStyle({ color: Color4.red }));
|
15
|
+
* ```
|
16
|
+
*
|
17
|
+
* **Transforming**:
|
18
|
+
* ```ts
|
19
|
+
* editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
|
20
|
+
* ```
|
21
|
+
*/
|
6
22
|
export default class Stroke extends AbstractComponent {
|
7
|
-
|
8
|
-
|
23
|
+
/**
|
24
|
+
* Creates a `Stroke` from the given `parts`. All parts should have the
|
25
|
+
* same color.
|
26
|
+
*
|
27
|
+
* @example
|
28
|
+
* ```ts
|
29
|
+
* // A path that starts at (1,1), moves to the right by (2, 0),
|
30
|
+
* // then moves down and right by (3, 3)
|
31
|
+
* const path = Path.fromString('m1,1 2,0 3,3');
|
32
|
+
*
|
33
|
+
* const stroke = new Stroke([
|
34
|
+
* // Fill with red
|
35
|
+
* path.toRenderable({ fill: Color4.red })
|
36
|
+
* ]);
|
37
|
+
* ```
|
38
|
+
*/
|
9
39
|
constructor(parts) {
|
10
40
|
var _a;
|
11
41
|
super('stroke');
|
42
|
+
// @internal
|
12
43
|
// eslint-disable-next-line @typescript-eslint/prefer-as-const
|
13
44
|
this.isRestylableComponent = true;
|
14
45
|
this.approximateRenderingTime = 0;
|
@@ -94,7 +125,7 @@ export default class Stroke extends AbstractComponent {
|
|
94
125
|
if (!bbox.intersects(visibleRect)) {
|
95
126
|
continue;
|
96
127
|
}
|
97
|
-
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x *
|
128
|
+
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 3 || bbox.size.y > visibleRect.size.y * 3;
|
98
129
|
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)) {
|
99
130
|
continue;
|
100
131
|
}
|
@@ -141,6 +172,9 @@ export default class Stroke extends AbstractComponent {
|
|
141
172
|
};
|
142
173
|
});
|
143
174
|
}
|
175
|
+
/**
|
176
|
+
* @returns the {@link Path.union} of all paths that make up this stroke.
|
177
|
+
*/
|
144
178
|
getPath() {
|
145
179
|
let result = null;
|
146
180
|
for (const part of this.parts) {
|
@@ -4,18 +4,26 @@ import Mat33 from '../math/Mat33';
|
|
4
4
|
import Rect2 from '../math/Rect2';
|
5
5
|
import Editor from '../Editor';
|
6
6
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
7
|
-
import {
|
7
|
+
import { TextRenderingStyle } from '../rendering/TextRenderingStyle';
|
8
8
|
import AbstractComponent from './AbstractComponent';
|
9
9
|
import { ImageComponentLocalization } from './localization';
|
10
10
|
import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
|
11
|
+
/**
|
12
|
+
* Displays text.
|
13
|
+
*/
|
11
14
|
export default class TextComponent extends AbstractComponent implements RestyleableComponent {
|
12
15
|
protected readonly textObjects: Array<string | TextComponent>;
|
13
16
|
private transform;
|
14
17
|
private style;
|
15
18
|
protected contentBBox: Rect2;
|
16
19
|
readonly isRestylableComponent: true;
|
17
|
-
|
18
|
-
|
20
|
+
/**
|
21
|
+
* Creates a new text object from a list of component text or child TextComponents.
|
22
|
+
*
|
23
|
+
* @see {@link fromLines}
|
24
|
+
*/
|
25
|
+
constructor(textObjects: Array<string | TextComponent>, transform: Mat33, style: TextRenderingStyle);
|
26
|
+
static applyTextStyles(ctx: CanvasRenderingContext2D, style: TextRenderingStyle): void;
|
19
27
|
private static textMeasuringCtx;
|
20
28
|
private static estimateTextDimens;
|
21
29
|
private static getTextDimens;
|
@@ -28,19 +36,7 @@ export default class TextComponent extends AbstractComponent implements Restylea
|
|
28
36
|
getStyle(): ComponentStyle;
|
29
37
|
updateStyle(style: ComponentStyle): SerializableCommand;
|
30
38
|
forceStyle(style: ComponentStyle, editor: Editor | null): void;
|
31
|
-
getTextStyle():
|
32
|
-
renderingStyle: {
|
33
|
-
fill: import("../Color4").default;
|
34
|
-
stroke: {
|
35
|
-
color: import("../Color4").default;
|
36
|
-
width: number;
|
37
|
-
} | undefined;
|
38
|
-
};
|
39
|
-
size: number;
|
40
|
-
fontFamily: string;
|
41
|
-
fontWeight?: string | undefined;
|
42
|
-
fontVariant?: string | undefined;
|
43
|
-
};
|
39
|
+
getTextStyle(): TextRenderingStyle;
|
44
40
|
getBaselinePos(): import("../lib").Vec3;
|
45
41
|
getTransform(): Mat33;
|
46
42
|
protected applyTransformation(affineTransfm: Mat33): void;
|
@@ -49,5 +45,19 @@ export default class TextComponent extends AbstractComponent implements Restylea
|
|
49
45
|
description(localizationTable: ImageComponentLocalization): string;
|
50
46
|
protected serializeToJSON(): Record<string, any>;
|
51
47
|
static deserializeFromString(json: any): TextComponent;
|
52
|
-
|
48
|
+
/**
|
49
|
+
* Creates a `TextComponent` from `lines`.
|
50
|
+
*
|
51
|
+
* @example
|
52
|
+
* ```ts
|
53
|
+
* const textStyle = {
|
54
|
+
* size: 12,
|
55
|
+
* fontFamily: 'serif',
|
56
|
+
* renderingStyle: { fill: Color4.black },
|
57
|
+
* };
|
58
|
+
*
|
59
|
+
* const text = TextComponent.fromLines('foo\nbar'.split('\n'), Mat33.identity, textStyle);
|
60
|
+
* ```
|
61
|
+
*/
|
62
|
+
static fromLines(lines: string[], transform: Mat33, style: TextRenderingStyle): AbstractComponent;
|
53
63
|
}
|
@@ -6,7 +6,15 @@ import { cloneTextStyle, textStyleFromJSON, textStyleToJSON } from '../renderi
|
|
6
6
|
import AbstractComponent from './AbstractComponent.mjs';
|
7
7
|
import { createRestyleComponentCommand } from './RestylableComponent.mjs';
|
8
8
|
const componentTypeId = 'text';
|
9
|
+
/**
|
10
|
+
* Displays text.
|
11
|
+
*/
|
9
12
|
export default class TextComponent extends AbstractComponent {
|
13
|
+
/**
|
14
|
+
* Creates a new text object from a list of component text or child TextComponents.
|
15
|
+
*
|
16
|
+
* @see {@link fromLines}
|
17
|
+
*/
|
10
18
|
constructor(textObjects, transform, style) {
|
11
19
|
super(componentTypeId);
|
12
20
|
this.textObjects = textObjects;
|
@@ -150,7 +158,7 @@ export default class TextComponent extends AbstractComponent {
|
|
150
158
|
editor.queueRerender();
|
151
159
|
}
|
152
160
|
}
|
153
|
-
// See
|
161
|
+
// See {@link getStyle}
|
154
162
|
getTextStyle() {
|
155
163
|
return cloneTextStyle(this.style);
|
156
164
|
}
|
@@ -232,6 +240,20 @@ export default class TextComponent extends AbstractComponent {
|
|
232
240
|
const transform = new Mat33(...transformData);
|
233
241
|
return new TextComponent(textObjects, transform, style);
|
234
242
|
}
|
243
|
+
/**
|
244
|
+
* Creates a `TextComponent` from `lines`.
|
245
|
+
*
|
246
|
+
* @example
|
247
|
+
* ```ts
|
248
|
+
* const textStyle = {
|
249
|
+
* size: 12,
|
250
|
+
* fontFamily: 'serif',
|
251
|
+
* renderingStyle: { fill: Color4.black },
|
252
|
+
* };
|
253
|
+
*
|
254
|
+
* const text = TextComponent.fromLines('foo\nbar'.split('\n'), Mat33.identity, textStyle);
|
255
|
+
* ```
|
256
|
+
*/
|
235
257
|
static fromLines(lines, transform, style) {
|
236
258
|
let lastComponent = null;
|
237
259
|
const components = [];
|
@@ -7,6 +7,7 @@ export { default as AbstractComponent } from './AbstractComponent';
|
|
7
7
|
import Stroke from './Stroke';
|
8
8
|
import TextComponent from './TextComponent';
|
9
9
|
import ImageComponent from './ImageComponent';
|
10
|
-
import RestyleableComponent
|
11
|
-
import
|
12
|
-
|
10
|
+
import RestyleableComponent from './RestylableComponent';
|
11
|
+
import { createRestyleComponentCommand, isRestylableComponent, ComponentStyle as RestyleableComponentStyle } from './RestylableComponent';
|
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.
|
@@ -176,19 +176,29 @@ 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
|
+
let nonMatching = 0;
|
188
|
+
const maxNonMatching = 2;
|
189
|
+
const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 3, this.minFitAllowed);
|
187
190
|
for (const point of this.buffer) {
|
188
|
-
|
189
|
-
const dist = proj.minus(point).magnitude();
|
190
|
-
const minFit = Math.max(Math.min(this.curveStartWidth, this.curveEndWidth) / 3, this.minFitAllowed);
|
191
|
+
let dist = curve.approximateDistance(point);
|
191
192
|
if (dist > minFit || dist > this.maxFitAllowed) {
|
193
|
+
// Avoid using the slower .distance
|
194
|
+
if (nonMatching >= maxNonMatching - 1) {
|
195
|
+
dist = curve.distance(point);
|
196
|
+
}
|
197
|
+
if (dist > minFit || dist > this.maxFitAllowed) {
|
198
|
+
nonMatching++;
|
199
|
+
}
|
200
|
+
}
|
201
|
+
if (nonMatching >= maxNonMatching) {
|
192
202
|
return false;
|
193
203
|
}
|
194
204
|
}
|
package/dist/mjs/src/lib.d.ts
CHANGED
@@ -5,10 +5,40 @@
|
|
5
5
|
* @example
|
6
6
|
* ```
|
7
7
|
* import { Editor, Vec3, Mat33 } from 'js-draw';
|
8
|
+
*
|
9
|
+
* // Apply js-draw CSS
|
10
|
+
* import 'js-draw/styles';
|
11
|
+
* // If your bundler doesn't support the above, try
|
12
|
+
* // import 'js-draw/bundledStyles';
|
13
|
+
*
|
14
|
+
* (async () => {
|
15
|
+
* const editor = new Editor(document.body);
|
16
|
+
* const toolbar = editor.addToolbar();
|
17
|
+
* editor.getRootElement().style.height = '600px';
|
18
|
+
*
|
19
|
+
* await editor.loadFromSVG(`
|
20
|
+
* <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
21
|
+
* <style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style>
|
22
|
+
* <path d="M500,500L500,0L0,0L0,500L500,500" fill="#423131bf" class="js-draw-image-background"></path>
|
23
|
+
* <text style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: rgb(204, 102, 51);">Testing...</text>
|
24
|
+
* </svg>
|
25
|
+
* `);
|
26
|
+
*
|
27
|
+
* toolbar.addActionButton({
|
28
|
+
* label: 'Save',
|
29
|
+
* icon: editor.icons.makeSaveIcon(),
|
30
|
+
* }, () => {
|
31
|
+
* const saveData = editor.toSVG().outerHTML;
|
32
|
+
*
|
33
|
+
* // Do something with saveData
|
34
|
+
* });
|
35
|
+
* })();
|
8
36
|
* ```
|
9
37
|
*
|
10
38
|
* @see
|
11
39
|
* {@link Editor}
|
40
|
+
* {@link Editor.loadFromSVG}
|
41
|
+
* {@link HTMLToolbar.addActionButton }
|
12
42
|
*
|
13
43
|
* @packageDocumentation
|
14
44
|
*/
|
package/dist/mjs/src/lib.mjs
CHANGED
@@ -5,10 +5,40 @@
|
|
5
5
|
* @example
|
6
6
|
* ```
|
7
7
|
* import { Editor, Vec3, Mat33 } from 'js-draw';
|
8
|
+
*
|
9
|
+
* // Apply js-draw CSS
|
10
|
+
* import 'js-draw/styles';
|
11
|
+
* // If your bundler doesn't support the above, try
|
12
|
+
* // import 'js-draw/bundledStyles';
|
13
|
+
*
|
14
|
+
* (async () => {
|
15
|
+
* const editor = new Editor(document.body);
|
16
|
+
* const toolbar = editor.addToolbar();
|
17
|
+
* editor.getRootElement().style.height = '600px';
|
18
|
+
*
|
19
|
+
* await editor.loadFromSVG(`
|
20
|
+
* <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
|
21
|
+
* <style id="js-draw-style-sheet">path{stroke-linecap:round;stroke-linejoin:round;}text{white-space:pre;}</style>
|
22
|
+
* <path d="M500,500L500,0L0,0L0,500L500,500" fill="#423131bf" class="js-draw-image-background"></path>
|
23
|
+
* <text style="transform: matrix(1, 0, 0, 1, 57, 192); font-family: serif; font-size: 32px; fill: rgb(204, 102, 51);">Testing...</text>
|
24
|
+
* </svg>
|
25
|
+
* `);
|
26
|
+
*
|
27
|
+
* toolbar.addActionButton({
|
28
|
+
* label: 'Save',
|
29
|
+
* icon: editor.icons.makeSaveIcon(),
|
30
|
+
* }, () => {
|
31
|
+
* const saveData = editor.toSVG().outerHTML;
|
32
|
+
*
|
33
|
+
* // Do something with saveData
|
34
|
+
* });
|
35
|
+
* })();
|
8
36
|
* ```
|
9
37
|
*
|
10
38
|
* @see
|
11
39
|
* {@link Editor}
|
40
|
+
* {@link Editor.loadFromSVG}
|
41
|
+
* {@link HTMLToolbar.addActionButton }
|
12
42
|
*
|
13
43
|
* @packageDocumentation
|
14
44
|
*/
|
@@ -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`;
|
@@ -341,7 +341,7 @@ export default class Path {
|
|
341
341
|
const onlyStroked = strokeWidth > 0 && renderablePath.style.fill.a === 0;
|
342
342
|
// Scale the expanded rect --- the visual equivalent is only close for huge strokes.
|
343
343
|
const expandedRect = visibleRect.grownBy(strokeWidth)
|
344
|
-
.transformedBoundingBox(Mat33.scaling2D(
|
344
|
+
.transformedBoundingBox(Mat33.scaling2D(4, visibleRect.center));
|
345
345
|
// TODO: Handle simplifying very small paths.
|
346
346
|
if (expandedRect.containsRect(path.bbox.grownBy(strokeWidth))) {
|
347
347
|
return renderablePath;
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { Point2, Vec2 } from '../Vec2';
|
2
|
+
export default class QuadraticBezier {
|
3
|
+
readonly p0: Point2;
|
4
|
+
readonly p1: Point2;
|
5
|
+
readonly p2: Point2;
|
6
|
+
private bezierJs;
|
7
|
+
constructor(p0: Point2, p1: Point2, p2: Point2);
|
8
|
+
/**
|
9
|
+
* Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
|
10
|
+
* all y components of the target curve.
|
11
|
+
*/
|
12
|
+
private static componentAt;
|
13
|
+
private static derivativeComponentAt;
|
14
|
+
/**
|
15
|
+
* @returns the curve evaluated at `t`.
|
16
|
+
*/
|
17
|
+
at(t: number): Point2;
|
18
|
+
derivativeAt(t: number): Point2;
|
19
|
+
/**
|
20
|
+
* @returns the approximate distance from `point` to this curve.
|
21
|
+
*/
|
22
|
+
approximateDistance(point: Point2): number;
|
23
|
+
/**
|
24
|
+
* @returns the exact distance from `point` to this.
|
25
|
+
*/
|
26
|
+
distance(point: Point2): number;
|
27
|
+
normal(t: number): Vec2;
|
28
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
import { Bezier } from 'bezier-js';
|
2
|
+
import { Vec2 } from '../Vec2.mjs';
|
3
|
+
import solveQuadratic from './solveQuadratic.mjs';
|
4
|
+
export default class QuadraticBezier {
|
5
|
+
constructor(p0, p1, p2) {
|
6
|
+
this.p0 = p0;
|
7
|
+
this.p1 = p1;
|
8
|
+
this.p2 = p2;
|
9
|
+
this.bezierJs = null;
|
10
|
+
}
|
11
|
+
/**
|
12
|
+
* Returns a component of a quadratic Bézier curve at t, where p0,p1,p2 are either all x or
|
13
|
+
* all y components of the target curve.
|
14
|
+
*/
|
15
|
+
static componentAt(t, p0, p1, p2) {
|
16
|
+
return p0 + t * (-2 * p0 + 2 * p1) + t * t * (p0 - 2 * p1 + p2);
|
17
|
+
}
|
18
|
+
static derivativeComponentAt(t, p0, p1, p2) {
|
19
|
+
return -2 * p0 + 2 * p1 + 2 * t * (p0 - 2 * p1 + p2);
|
20
|
+
}
|
21
|
+
/**
|
22
|
+
* @returns the curve evaluated at `t`.
|
23
|
+
*/
|
24
|
+
at(t) {
|
25
|
+
const p0 = this.p0;
|
26
|
+
const p1 = this.p1;
|
27
|
+
const p2 = this.p2;
|
28
|
+
return Vec2.of(QuadraticBezier.componentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.componentAt(t, p0.y, p1.y, p2.y));
|
29
|
+
}
|
30
|
+
derivativeAt(t) {
|
31
|
+
const p0 = this.p0;
|
32
|
+
const p1 = this.p1;
|
33
|
+
const p2 = this.p2;
|
34
|
+
return Vec2.of(QuadraticBezier.derivativeComponentAt(t, p0.x, p1.x, p2.x), QuadraticBezier.derivativeComponentAt(t, p0.y, p1.y, p2.y));
|
35
|
+
}
|
36
|
+
/**
|
37
|
+
* @returns the approximate distance from `point` to this curve.
|
38
|
+
*/
|
39
|
+
approximateDistance(point) {
|
40
|
+
// We want to minimize f(t) = |B(t) - p|².
|
41
|
+
// Expanding,
|
42
|
+
// f(t) = (Bₓ(t) - pₓ)² + (Bᵧ(t) - pᵧ)²
|
43
|
+
// ⇒ f'(t) = Dₜ(Bₓ(t) - pₓ)² + Dₜ(Bᵧ(t) - pᵧ)²
|
44
|
+
//
|
45
|
+
// Considering just one component,
|
46
|
+
// Dₜ(Bₓ(t) - pₓ)² = 2(Bₓ(t) - pₓ)(DₜBₓ(t))
|
47
|
+
// = 2(Bₓ(t)DₜBₓ(t) - pₓBₓ(t))
|
48
|
+
// = 2(p0ₓ + (t)(-2p0ₓ + 2p1ₓ) + (t²)(p0ₓ - 2p1ₓ + p2ₓ) - pₓ)((-2p0ₓ + 2p1ₓ) + 2(t)(p0ₓ - 2p1ₓ + p2ₓ))
|
49
|
+
// - (pₓ)((-2p0ₓ + 2p1ₓ) + (t)(p0ₓ - 2p1ₓ + p2ₓ))
|
50
|
+
const A = this.p0.x - point.x;
|
51
|
+
const B = -2 * this.p0.x + 2 * this.p1.x;
|
52
|
+
const C = this.p0.x - 2 * this.p1.x + this.p2.x;
|
53
|
+
// Let A = p0ₓ - pₓ, B = -2p0ₓ + 2p1ₓ, C = p0ₓ - 2p1ₓ + p2ₓ. We then have,
|
54
|
+
// Dₜ(Bₓ(t) - pₓ)²
|
55
|
+
// = 2(A + tB + t²C)(B + 2tC) - (pₓ)(B + 2tC)
|
56
|
+
// = 2(AB + tB² + t²BC + 2tCA + 2tCtB + 2tCt²C) - pₓB - pₓ2tC
|
57
|
+
// = 2(AB + tB² + 2tCA + t²BC + 2t²CB + 2C²t³) - pₓB - pₓ2tC
|
58
|
+
// = 2AB + 2t(B² + 2CA) + 2t²(BC + 2CB) + 4C²t³ - pₓB - pₓ2tC
|
59
|
+
// = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
60
|
+
//
|
61
|
+
const D = this.p0.y - point.y;
|
62
|
+
const E = -2 * this.p0.y + 2 * this.p1.y;
|
63
|
+
const F = this.p0.y - 2 * this.p1.y + this.p2.y;
|
64
|
+
// Using D = p0ᵧ - pᵧ, E = -2p0ᵧ + 2p1ᵧ, F = p0ᵧ - 2p1ᵧ + p2ᵧ, we thus have,
|
65
|
+
// f'(t) = 2AB + 2t(B² + 2CA - pₓC) + 2t²(BC + 2CB) + 4C²t³ - pₓB
|
66
|
+
// + 2DE + 2t(E² + 2FD - pᵧF) + 2t²(EF + 2FE) + 4F²t³ - pᵧE
|
67
|
+
const a = 2 * A * B + 2 * D * E - point.x * B - point.y * E;
|
68
|
+
const b = 2 * B * B + 2 * E * E + 2 * C * A + 2 * F * D - point.x * C - point.y * F;
|
69
|
+
const c = 2 * E * F + 2 * B * C + 2 * C * B + 2 * F * E;
|
70
|
+
//const d = 4 * C * C + 4 * F * F;
|
71
|
+
// Thus,
|
72
|
+
// f'(t) = a + bt + ct² + dt³
|
73
|
+
const fDerivAtZero = a;
|
74
|
+
const f2ndDerivAtZero = b;
|
75
|
+
const f3rdDerivAtZero = 2 * c;
|
76
|
+
// Using the first few terms of a Maclaurin series to approximate f'(t),
|
77
|
+
// f'(t) ≈ f'(0) + t f''(0) + t² f'''(0) / 2
|
78
|
+
let [min1, min2] = solveQuadratic(f3rdDerivAtZero / 2, f2ndDerivAtZero, fDerivAtZero);
|
79
|
+
// If the quadratic has no solutions, approximate.
|
80
|
+
if (isNaN(min1)) {
|
81
|
+
min1 = 0.25;
|
82
|
+
}
|
83
|
+
if (isNaN(min2)) {
|
84
|
+
min2 = 0.75;
|
85
|
+
}
|
86
|
+
const at1 = this.at(min1);
|
87
|
+
const at2 = this.at(min2);
|
88
|
+
const sqrDist1 = at1.minus(point).magnitudeSquared();
|
89
|
+
const sqrDist2 = at2.minus(point).magnitudeSquared();
|
90
|
+
const sqrDist3 = this.at(0).minus(point).magnitudeSquared();
|
91
|
+
const sqrDist4 = this.at(1).minus(point).magnitudeSquared();
|
92
|
+
return Math.sqrt(Math.min(sqrDist1, sqrDist2, sqrDist3, sqrDist4));
|
93
|
+
}
|
94
|
+
/**
|
95
|
+
* @returns the exact distance from `point` to this.
|
96
|
+
*/
|
97
|
+
distance(point) {
|
98
|
+
if (!this.bezierJs) {
|
99
|
+
this.bezierJs = new Bezier([this.p0.xy, this.p1.xy, this.p2.xy]);
|
100
|
+
}
|
101
|
+
const proj = Vec2.ofXY(this.bezierJs.project(point.xy));
|
102
|
+
const dist = proj.minus(point).magnitude();
|
103
|
+
return dist;
|
104
|
+
}
|
105
|
+
normal(t) {
|
106
|
+
const tangent = this.derivativeAt(t);
|
107
|
+
return tangent.orthog().normalized();
|
108
|
+
}
|
109
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
/**
|
2
|
+
* Solves an equation of the form ax² + bx + c = 0.
|
3
|
+
* The larger solution is returned first.
|
4
|
+
*/
|
5
|
+
const solveQuadratic = (a, b, c) => {
|
6
|
+
// See also https://en.wikipedia.org/wiki/Quadratic_formula
|
7
|
+
if (a === 0) {
|
8
|
+
let solution;
|
9
|
+
if (b === 0) {
|
10
|
+
solution = c === 0 ? 0 : NaN;
|
11
|
+
}
|
12
|
+
else {
|
13
|
+
// Then we have bx + c = 0
|
14
|
+
// which implies bx = -c.
|
15
|
+
// Thus, x = -c/b
|
16
|
+
solution = -c / b;
|
17
|
+
}
|
18
|
+
return [solution, solution];
|
19
|
+
}
|
20
|
+
const discriminant = b * b - 4 * a * c;
|
21
|
+
if (discriminant < 0) {
|
22
|
+
return [NaN, NaN];
|
23
|
+
}
|
24
|
+
const rootDiscriminant = Math.sqrt(discriminant);
|
25
|
+
const solution1 = (-b + rootDiscriminant) / (2 * a);
|
26
|
+
const solution2 = (-b - rootDiscriminant) / (2 * a);
|
27
|
+
if (solution1 > solution2) {
|
28
|
+
return [solution1, solution2];
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
return [solution2, solution1];
|
32
|
+
}
|
33
|
+
};
|
34
|
+
export default solveQuadratic;
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
interface RenderingStyle {
|
3
|
-
fill: Color4;
|
4
|
-
stroke?: {
|
5
|
-
color: Color4;
|
6
|
-
width: number;
|
3
|
+
readonly fill: Color4;
|
4
|
+
readonly stroke?: {
|
5
|
+
readonly color: Color4;
|
6
|
+
readonly width: number;
|
7
7
|
};
|
8
8
|
}
|
9
9
|
export default RenderingStyle;
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import RenderingStyle from './RenderingStyle';
|
2
|
-
export interface
|
3
|
-
size: number;
|
4
|
-
fontFamily: string;
|
5
|
-
fontWeight?: string;
|
6
|
-
fontVariant?: string;
|
7
|
-
renderingStyle: RenderingStyle;
|
2
|
+
export interface TextRenderingStyle {
|
3
|
+
readonly size: number;
|
4
|
+
readonly fontFamily: string;
|
5
|
+
readonly fontWeight?: string;
|
6
|
+
readonly fontVariant?: string;
|
7
|
+
readonly renderingStyle: RenderingStyle;
|
8
8
|
}
|
9
|
-
export default
|
10
|
-
export declare const cloneTextStyle: (style:
|
9
|
+
export default TextRenderingStyle;
|
10
|
+
export declare const cloneTextStyle: (style: TextRenderingStyle) => {
|
11
11
|
renderingStyle: {
|
12
12
|
fill: import("../Color4").default;
|
13
13
|
stroke: {
|
@@ -20,8 +20,8 @@ export declare const cloneTextStyle: (style: TextStyle) => {
|
|
20
20
|
fontWeight?: string | undefined;
|
21
21
|
fontVariant?: string | undefined;
|
22
22
|
};
|
23
|
-
export declare const textStyleFromJSON: (json: any) =>
|
24
|
-
export declare const textStyleToJSON: (style:
|
23
|
+
export declare const textStyleFromJSON: (json: any) => TextRenderingStyle;
|
24
|
+
export declare const textStyleToJSON: (style: TextRenderingStyle) => {
|
25
25
|
renderingStyle: {
|
26
26
|
fill: string;
|
27
27
|
stroke: {
|