js-draw 1.16.0 → 1.17.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/Editor.css +11 -0
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +79 -6
- package/dist/cjs/Editor.js +114 -91
- package/dist/cjs/Pointer.d.ts +2 -1
- package/dist/cjs/Pointer.js +9 -2
- package/dist/cjs/commands/localization.d.ts +1 -0
- package/dist/cjs/commands/localization.js +1 -0
- package/dist/cjs/commands/uniteCommands.d.ts +5 -1
- package/dist/cjs/commands/uniteCommands.js +33 -7
- package/dist/cjs/components/TextComponent.d.ts +36 -1
- package/dist/cjs/components/TextComponent.js +39 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +1 -1
- package/dist/cjs/components/builders/PolylineBuilder.d.ts +35 -0
- package/dist/cjs/components/builders/PolylineBuilder.js +115 -0
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +1 -1
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +1 -1
- package/dist/cjs/components/lib.d.ts +1 -0
- package/dist/cjs/components/lib.js +3 -1
- package/dist/cjs/components/util/StrokeSmoother.js +4 -4
- package/dist/cjs/image/EditorImage.d.ts +4 -1
- package/dist/cjs/image/EditorImage.js +4 -1
- package/dist/cjs/inputEvents.d.ts +11 -1
- package/dist/cjs/localizations/comments.d.ts +3 -0
- package/dist/cjs/localizations/comments.js +3 -0
- package/dist/cjs/localizations/de.js +0 -2
- package/dist/cjs/localizations/es.js +2 -2
- package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +16 -0
- package/dist/cjs/rendering/renderers/SVGRenderer.js +1 -1
- package/dist/cjs/toolbar/IconProvider.d.ts +6 -3
- package/dist/cjs/toolbar/IconProvider.js +6 -4
- package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +24 -1
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +7 -1
- package/dist/cjs/tools/Eraser.js +1 -1
- package/dist/cjs/tools/InputFilter/InputStabilizer.js +3 -3
- package/dist/cjs/tools/PasteHandler.js +40 -10
- package/dist/cjs/tools/Pen.js +2 -2
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +23 -4
- package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.js +1 -1
- package/dist/cjs/tools/ToolController.d.ts +17 -1
- package/dist/cjs/tools/ToolController.js +21 -8
- package/dist/cjs/tools/localization.d.ts +2 -2
- package/dist/cjs/tools/localization.js +2 -2
- package/dist/cjs/util/ClipboardHandler.d.ts +27 -0
- package/dist/cjs/util/ClipboardHandler.js +205 -0
- package/dist/cjs/util/ClipboardHandler.test.d.ts +1 -0
- package/dist/cjs/version.d.ts +5 -0
- package/dist/cjs/version.js +6 -1
- package/dist/mjs/Editor.d.ts +79 -6
- package/dist/mjs/Editor.mjs +114 -91
- package/dist/mjs/Pointer.d.ts +2 -1
- package/dist/mjs/Pointer.mjs +9 -2
- package/dist/mjs/commands/localization.d.ts +1 -0
- package/dist/mjs/commands/localization.mjs +1 -0
- package/dist/mjs/commands/uniteCommands.d.ts +5 -1
- package/dist/mjs/commands/uniteCommands.mjs +33 -7
- package/dist/mjs/components/TextComponent.d.ts +36 -1
- package/dist/mjs/components/TextComponent.mjs +40 -2
- package/dist/mjs/components/builders/ArrowBuilder.mjs +1 -1
- package/dist/mjs/components/builders/PolylineBuilder.d.ts +35 -0
- package/dist/mjs/components/builders/PolylineBuilder.mjs +108 -0
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +1 -1
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +1 -1
- package/dist/mjs/components/lib.d.ts +1 -0
- package/dist/mjs/components/lib.mjs +1 -0
- package/dist/mjs/components/util/StrokeSmoother.mjs +4 -4
- package/dist/mjs/image/EditorImage.d.ts +4 -1
- package/dist/mjs/image/EditorImage.mjs +4 -1
- package/dist/mjs/inputEvents.d.ts +11 -1
- package/dist/mjs/localizations/comments.d.ts +3 -0
- package/dist/mjs/localizations/comments.mjs +3 -0
- package/dist/mjs/localizations/de.mjs +0 -2
- package/dist/mjs/localizations/es.mjs +2 -2
- package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +16 -0
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +1 -1
- package/dist/mjs/toolbar/IconProvider.d.ts +6 -3
- package/dist/mjs/toolbar/IconProvider.mjs +6 -4
- package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +24 -1
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +7 -1
- package/dist/mjs/tools/Eraser.mjs +1 -1
- package/dist/mjs/tools/InputFilter/InputStabilizer.mjs +3 -3
- package/dist/mjs/tools/PasteHandler.mjs +40 -10
- package/dist/mjs/tools/Pen.mjs +2 -2
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +23 -4
- package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.mjs +1 -1
- package/dist/mjs/tools/ToolController.d.ts +17 -1
- package/dist/mjs/tools/ToolController.mjs +21 -8
- package/dist/mjs/tools/localization.d.ts +2 -2
- package/dist/mjs/tools/localization.mjs +2 -2
- package/dist/mjs/util/ClipboardHandler.d.ts +27 -0
- package/dist/mjs/util/ClipboardHandler.mjs +200 -0
- package/dist/mjs/util/ClipboardHandler.test.d.ts +1 -0
- package/dist/mjs/version.d.ts +5 -0
- package/dist/mjs/version.mjs +6 -1
- package/package.json +6 -6
- package/src/Editor.scss +10 -0
- package/src/toolbar/EdgeToolbar.scss +2 -0
- package/src/tools/SoundUITool.scss +4 -1
@@ -0,0 +1,115 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.makePolylineBuilder = void 0;
|
7
|
+
const math_1 = require("@js-draw/math");
|
8
|
+
const Stroke_1 = __importDefault(require("../Stroke"));
|
9
|
+
const Viewport_1 = __importDefault(require("../../Viewport"));
|
10
|
+
const makeShapeFitAutocorrect_1 = __importDefault(require("./autocorrect/makeShapeFitAutocorrect"));
|
11
|
+
/**
|
12
|
+
* Creates strokes from line segments rather than Bézier curves.
|
13
|
+
*
|
14
|
+
* @beta Output behavior may change significantly between versions. For now, intended for debugging.
|
15
|
+
*/
|
16
|
+
exports.makePolylineBuilder = (0, makeShapeFitAutocorrect_1.default)((initialPoint, viewport) => {
|
17
|
+
const minFit = viewport.getSizeOfPixelOnCanvas();
|
18
|
+
return new PolylineBuilder(initialPoint, minFit, viewport);
|
19
|
+
});
|
20
|
+
class PolylineBuilder {
|
21
|
+
constructor(startPoint, minFitAllowed, viewport) {
|
22
|
+
this.minFitAllowed = minFitAllowed;
|
23
|
+
this.viewport = viewport;
|
24
|
+
this.parts = [];
|
25
|
+
this.widthAverageNumSamples = 1;
|
26
|
+
this.averageWidth = startPoint.width;
|
27
|
+
this.startPoint = {
|
28
|
+
...startPoint,
|
29
|
+
pos: this.roundPoint(startPoint.pos),
|
30
|
+
};
|
31
|
+
this.lastPoint = this.startPoint.pos;
|
32
|
+
this.bbox = new math_1.Rect2(this.startPoint.pos.x, this.startPoint.pos.y, 0, 0);
|
33
|
+
this.parts = [
|
34
|
+
{
|
35
|
+
kind: math_1.PathCommandType.MoveTo,
|
36
|
+
point: this.startPoint.pos,
|
37
|
+
},
|
38
|
+
];
|
39
|
+
}
|
40
|
+
getBBox() {
|
41
|
+
return this.bbox.grownBy(this.averageWidth);
|
42
|
+
}
|
43
|
+
getRenderingStyle() {
|
44
|
+
return {
|
45
|
+
fill: math_1.Color4.transparent,
|
46
|
+
stroke: {
|
47
|
+
color: this.startPoint.color,
|
48
|
+
width: this.roundDistance(this.averageWidth),
|
49
|
+
}
|
50
|
+
};
|
51
|
+
}
|
52
|
+
previewCurrentPath() {
|
53
|
+
const startPoint = this.startPoint.pos;
|
54
|
+
const commands = [...this.parts];
|
55
|
+
// TODO: For now, this is necesary for the path to be visible.
|
56
|
+
if (commands.length <= 1) {
|
57
|
+
commands.push({
|
58
|
+
kind: math_1.PathCommandType.LineTo,
|
59
|
+
point: startPoint,
|
60
|
+
});
|
61
|
+
}
|
62
|
+
return {
|
63
|
+
startPoint,
|
64
|
+
commands,
|
65
|
+
style: this.getRenderingStyle(),
|
66
|
+
};
|
67
|
+
}
|
68
|
+
previewFullPath() {
|
69
|
+
return [this.previewCurrentPath()];
|
70
|
+
}
|
71
|
+
preview(renderer) {
|
72
|
+
const paths = this.previewFullPath();
|
73
|
+
if (paths) {
|
74
|
+
const approxBBox = this.viewport.visibleRect;
|
75
|
+
renderer.startObject(approxBBox);
|
76
|
+
for (const path of paths) {
|
77
|
+
renderer.drawPath(path);
|
78
|
+
}
|
79
|
+
renderer.endObject();
|
80
|
+
}
|
81
|
+
}
|
82
|
+
build() {
|
83
|
+
return new Stroke_1.default(this.previewFullPath());
|
84
|
+
}
|
85
|
+
getMinFit() {
|
86
|
+
let minFit = Math.min(this.minFitAllowed, this.averageWidth / 3);
|
87
|
+
if (minFit < 1e-10) {
|
88
|
+
minFit = this.minFitAllowed;
|
89
|
+
}
|
90
|
+
return minFit;
|
91
|
+
}
|
92
|
+
roundPoint(point) {
|
93
|
+
const minFit = this.getMinFit();
|
94
|
+
return Viewport_1.default.roundPoint(point, minFit);
|
95
|
+
}
|
96
|
+
roundDistance(dist) {
|
97
|
+
const minFit = this.getMinFit();
|
98
|
+
return Viewport_1.default.roundPoint(dist, minFit);
|
99
|
+
}
|
100
|
+
addPoint(newPoint) {
|
101
|
+
this.widthAverageNumSamples++;
|
102
|
+
this.averageWidth =
|
103
|
+
this.averageWidth * (this.widthAverageNumSamples - 1) / this.widthAverageNumSamples
|
104
|
+
+ newPoint.width / this.widthAverageNumSamples;
|
105
|
+
const roundedPoint = this.roundPoint(newPoint.pos);
|
106
|
+
if (!roundedPoint.eq(this.lastPoint)) {
|
107
|
+
this.parts.push({
|
108
|
+
kind: math_1.PathCommandType.LineTo,
|
109
|
+
point: this.roundPoint(newPoint.pos),
|
110
|
+
});
|
111
|
+
this.bbox = this.bbox.grownToPoint(roundedPoint);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
exports.default = PolylineBuilder;
|
@@ -291,7 +291,7 @@ class PressureSensitiveFreehandLineBuilder {
|
|
291
291
|
// Approximate the normal at the location of the control point
|
292
292
|
let projectionT = bezier.project(controlPoint.xy).t;
|
293
293
|
if (!projectionT) {
|
294
|
-
if (startPt.
|
294
|
+
if (startPt.squareDistanceTo(controlPoint) < endPt.squareDistanceTo(controlPoint)) {
|
295
295
|
projectionT = 0.1;
|
296
296
|
}
|
297
297
|
else {
|
@@ -85,7 +85,7 @@ class ShapeFitBuilder {
|
|
85
85
|
// Find the closest point to the startPoint
|
86
86
|
for (let i = 0; i < templatePoints.length; i++) {
|
87
87
|
const current = templatePoints[i];
|
88
|
-
const currentSqrDist = current.
|
88
|
+
const currentSqrDist = current.squareDistanceTo(startPoint);
|
89
89
|
if (!closestToFirst || currentSqrDist < closestToFirstSqrDist) {
|
90
90
|
closestToFirstSqrDist = currentSqrDist;
|
91
91
|
closestToFirst = current;
|
@@ -1,5 +1,6 @@
|
|
1
1
|
export * from './builders/types';
|
2
2
|
export { makeFreehandLineBuilder } from './builders/FreehandLineBuilder';
|
3
|
+
export { makePolylineBuilder } from './builders/PolylineBuilder';
|
3
4
|
export { makePressureSensitiveFreehandLineBuilder } from './builders/PressureSensitiveFreehandLineBuilder';
|
4
5
|
export { makeOutlinedCircleBuilder } from './builders/CircleBuilder';
|
5
6
|
export { default as StrokeSmoother, Curve as StrokeSmootherCurve } from './util/StrokeSmoother';
|
@@ -29,10 +29,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
29
29
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
30
30
|
};
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
32
|
-
exports.ImageComponent = exports.BackgroundComponentBackgroundType = exports.BackgroundComponent = exports.StrokeComponent = exports.Text = exports.TextComponent = exports.isRestylableComponent = exports.createRestyleComponentCommand = exports.Stroke = exports.AbstractComponent = exports.StrokeSmoother = exports.makeOutlinedCircleBuilder = exports.makePressureSensitiveFreehandLineBuilder = exports.makeFreehandLineBuilder = void 0;
|
32
|
+
exports.ImageComponent = exports.BackgroundComponentBackgroundType = exports.BackgroundComponent = exports.StrokeComponent = exports.Text = exports.TextComponent = exports.isRestylableComponent = exports.createRestyleComponentCommand = exports.Stroke = exports.AbstractComponent = exports.StrokeSmoother = exports.makeOutlinedCircleBuilder = exports.makePressureSensitiveFreehandLineBuilder = exports.makePolylineBuilder = exports.makeFreehandLineBuilder = void 0;
|
33
33
|
__exportStar(require("./builders/types"), exports);
|
34
34
|
var FreehandLineBuilder_1 = require("./builders/FreehandLineBuilder");
|
35
35
|
Object.defineProperty(exports, "makeFreehandLineBuilder", { enumerable: true, get: function () { return FreehandLineBuilder_1.makeFreehandLineBuilder; } });
|
36
|
+
var PolylineBuilder_1 = require("./builders/PolylineBuilder");
|
37
|
+
Object.defineProperty(exports, "makePolylineBuilder", { enumerable: true, get: function () { return PolylineBuilder_1.makePolylineBuilder; } });
|
36
38
|
var PressureSensitiveFreehandLineBuilder_1 = require("./builders/PressureSensitiveFreehandLineBuilder");
|
37
39
|
Object.defineProperty(exports, "makePressureSensitiveFreehandLineBuilder", { enumerable: true, get: function () { return PressureSensitiveFreehandLineBuilder_1.makePressureSensitiveFreehandLineBuilder; } });
|
38
40
|
var CircleBuilder_1 = require("./builders/CircleBuilder");
|
@@ -41,8 +41,8 @@ class StrokeSmoother {
|
|
41
41
|
const startPt = this.currentCurve.p0;
|
42
42
|
const controlPt = this.currentCurve.p1;
|
43
43
|
const endPt = this.currentCurve.p2;
|
44
|
-
const toControlDist = startPt.
|
45
|
-
const toEndDist = endPt.
|
44
|
+
const toControlDist = startPt.distanceTo(controlPt);
|
45
|
+
const toEndDist = endPt.distanceTo(controlPt);
|
46
46
|
return toControlDist + toEndDist;
|
47
47
|
}
|
48
48
|
finalizeCurrentCurve() {
|
@@ -99,7 +99,7 @@ class StrokeSmoother {
|
|
99
99
|
return;
|
100
100
|
}
|
101
101
|
const threshold = Math.min(this.lastPoint.width, newPoint.width) / 3;
|
102
|
-
const shouldSnapToInitial = this.startPoint.pos.
|
102
|
+
const shouldSnapToInitial = this.startPoint.pos.distanceTo(newPoint.pos) < threshold
|
103
103
|
&& this.isFirstSegment;
|
104
104
|
// Snap to the starting point if the stroke is contained within a small ball centered
|
105
105
|
// at the starting point.
|
@@ -150,7 +150,7 @@ class StrokeSmoother {
|
|
150
150
|
const maxRelativeLength = 1.7;
|
151
151
|
const segmentStart = this.buffer[0];
|
152
152
|
const segmentEnd = newPoint.pos;
|
153
|
-
const startEndDist = segmentEnd.
|
153
|
+
const startEndDist = segmentEnd.distanceTo(segmentStart);
|
154
154
|
const maxControlPointDist = maxRelativeLength * startEndDist;
|
155
155
|
// Exit in cases where we would divide by zero
|
156
156
|
if (maxControlPointDist === 0 || exitingVec.magnitude() === 0 || !isFinite(exitingVec.magnitude())) {
|
@@ -105,7 +105,10 @@ export default class EditorImage {
|
|
105
105
|
getImportExportRect(): Rect2;
|
106
106
|
/**
|
107
107
|
* Sets the import/export rectangle to the given `imageRect`. Disables
|
108
|
-
* autoresize
|
108
|
+
* autoresize if it was previously enabled.
|
109
|
+
*
|
110
|
+
* **Note**: The import/export rectangle is the same as the size of any
|
111
|
+
* {@link BackgroundComponent}s (and other components that auto-resize).
|
109
112
|
*/
|
110
113
|
setImportExportRect(imageRect: Rect2): SerializableCommand;
|
111
114
|
/** @see {@link setAutoresizeEnabled} */
|
@@ -235,7 +235,10 @@ class EditorImage {
|
|
235
235
|
}
|
236
236
|
/**
|
237
237
|
* Sets the import/export rectangle to the given `imageRect`. Disables
|
238
|
-
* autoresize
|
238
|
+
* autoresize if it was previously enabled.
|
239
|
+
*
|
240
|
+
* **Note**: The import/export rectangle is the same as the size of any
|
241
|
+
* {@link BackgroundComponent}s (and other components that auto-resize).
|
239
242
|
*/
|
240
243
|
setImportExportRect(imageRect) {
|
241
244
|
return _a.SetImportExportRectCommand.of(this, imageRect, false);
|
@@ -53,7 +53,7 @@ export interface KeyUpEvent extends BaseKeyEvent {
|
|
53
53
|
}
|
54
54
|
export interface CopyEvent {
|
55
55
|
readonly kind: InputEvtType.CopyEvent;
|
56
|
-
setData(mime: string, data: string): void;
|
56
|
+
setData(mime: string, data: string | Promise<Blob>): void;
|
57
57
|
}
|
58
58
|
export interface PasteEvent {
|
59
59
|
readonly kind: InputEvtType.PasteEvent;
|
@@ -76,7 +76,17 @@ export interface PointerMoveEvt extends PointerEvtBase {
|
|
76
76
|
export interface PointerUpEvt extends PointerEvtBase {
|
77
77
|
readonly kind: InputEvtType.PointerUpEvt;
|
78
78
|
}
|
79
|
+
/**
|
80
|
+
* An internal `js-draw` pointer event type.
|
81
|
+
*
|
82
|
+
* This **is not** the same as a DOM pointer event.
|
83
|
+
*/
|
79
84
|
export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
|
85
|
+
/**
|
86
|
+
* An internal `js-draw` input event type.
|
87
|
+
*
|
88
|
+
* These are not DOM events.
|
89
|
+
*/
|
80
90
|
export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
|
81
91
|
export declare const keyUpEventFromHTMLEvent: (event: KeyboardEvent) => KeyUpEvent;
|
82
92
|
export declare const keyPressEventFromHTMLEvent: (event: KeyboardEvent) => KeyPressEvent;
|
@@ -1,6 +1,9 @@
|
|
1
1
|
import { EditorLocalization } from '../localization';
|
2
2
|
/**
|
3
3
|
* Comments to help translators create translations.
|
4
|
+
*
|
5
|
+
* The key for each comment should be the same as is used in the
|
6
|
+
* translation and original source records.
|
4
7
|
*/
|
5
8
|
declare const comments: Partial<Record<keyof EditorLocalization, string>>;
|
6
9
|
export default comments;
|
@@ -2,6 +2,9 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
/**
|
4
4
|
* Comments to help translators create translations.
|
5
|
+
*
|
6
|
+
* The key for each comment should be the same as is used in the
|
7
|
+
* translation and original source records.
|
5
8
|
*/
|
6
9
|
const comments = {
|
7
10
|
pen: 'Likely unused',
|
@@ -111,8 +111,6 @@ const localization = {
|
|
111
111
|
soundExplorer: 'Klangbasierte Bilderkundung',
|
112
112
|
disableAccessibilityExploreTool: 'Deaktiviere klangbasierte Erkundung',
|
113
113
|
enableAccessibilityExploreTool: 'Aktiviere klangbasierte Erkundung',
|
114
|
-
copied: (count, description) => `${count} ${description} kopiert`,
|
115
|
-
pasted: (count, description) => `${count} ${description} eingefügt`,
|
116
114
|
unionOf: (actionDescription, actionCount) => `Vereinigung: ${actionCount} ${actionDescription}`,
|
117
115
|
emptyBackground: 'Leerer Hintergrund',
|
118
116
|
filledBackgroundWithColor: (color) => `Gefüllter Hintergrund (${color})`,
|
@@ -63,8 +63,8 @@ const localization = {
|
|
63
63
|
toNextMatch: 'Próxima',
|
64
64
|
closeDialog: 'Cerrar',
|
65
65
|
anyDevicePanning: 'Mover la pantalla con todo dispotivo',
|
66
|
-
copied: (count
|
67
|
-
pasted: (count
|
66
|
+
copied: (count) => `${count} cosas fueron copiados`,
|
67
|
+
pasted: (count) => count === 1 ? 'Pegado' : `${count} cosas fueron pegados`,
|
68
68
|
toolEnabledAnnouncement: (toolName) => `${toolName} fue activado`,
|
69
69
|
toolDisabledAnnouncement: (toolName) => `${toolName} fue desactivado`,
|
70
70
|
resizeOutputCommand: (newSize) => `Tamaño de imagen fue cambiado a ${newSize.w}x${newSize.h}`,
|
@@ -58,4 +58,11 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
58
58
|
endObject(): void;
|
59
59
|
drawPoints(...points: Point2[]): void;
|
60
60
|
isTooSmallToRender(rect: Rect2): boolean;
|
61
|
+
static fromViewport(exportViewport: Viewport, options?: {
|
62
|
+
canvasSize?: Vec2;
|
63
|
+
maxCanvasDimen?: number;
|
64
|
+
}): {
|
65
|
+
renderer: CanvasRenderer;
|
66
|
+
element: HTMLCanvasElement;
|
67
|
+
};
|
61
68
|
}
|
@@ -250,5 +250,21 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
250
250
|
const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
|
251
251
|
return bothTooSmall || anyTooSmall;
|
252
252
|
}
|
253
|
+
// @internal
|
254
|
+
static fromViewport(exportViewport, options = {}) {
|
255
|
+
const canvas = document.createElement('canvas');
|
256
|
+
const exportRectSize = exportViewport.getScreenRectSize();
|
257
|
+
let canvasSize = options.canvasSize ?? exportRectSize;
|
258
|
+
if (options.maxCanvasDimen && canvasSize.maximumEntryMagnitude() > options.maxCanvasDimen) {
|
259
|
+
canvasSize = canvasSize.times(options.maxCanvasDimen / canvasSize.maximumEntryMagnitude());
|
260
|
+
}
|
261
|
+
canvas.width = canvasSize.x;
|
262
|
+
canvas.height = canvasSize.y;
|
263
|
+
const ctx = canvas.getContext('2d');
|
264
|
+
// Scale to ensure that the entire output is visible.
|
265
|
+
const scaleFactor = Math.min(canvasSize.x / exportRectSize.x, canvasSize.y / exportRectSize.y);
|
266
|
+
ctx.scale(scaleFactor, scaleFactor);
|
267
|
+
return { renderer: new CanvasRenderer(ctx, exportViewport), element: canvas };
|
268
|
+
}
|
253
269
|
}
|
254
270
|
exports.default = CanvasRenderer;
|
@@ -116,7 +116,7 @@ class SVGRenderer extends AbstractRenderer_1.default {
|
|
116
116
|
}
|
117
117
|
if (style.stroke) {
|
118
118
|
pathElem.setAttribute('stroke', style.stroke.color.toHexString());
|
119
|
-
pathElem.setAttribute('stroke-width', (0, math_1.toRoundedString)(style.stroke.width));
|
119
|
+
pathElem.setAttribute('stroke-width', (0, math_1.toRoundedString)(style.stroke.width * this.getSizeOfCanvasPixelOnScreen()));
|
120
120
|
}
|
121
121
|
this.elem.appendChild(pathElem);
|
122
122
|
this.objectElems?.push(pathElem);
|
@@ -3,8 +3,9 @@ import TextRenderingStyle from '../rendering/TextRenderingStyle';
|
|
3
3
|
import { PenStyle } from '../tools/Pen';
|
4
4
|
export type IconElemType = HTMLImageElement | SVGElement;
|
5
5
|
/**
|
6
|
-
* Provides icons that can be used in the toolbar
|
7
|
-
*
|
6
|
+
* Provides icons that can be used in the toolbar and other locations.
|
7
|
+
*
|
8
|
+
* To customize the icons used by the editor, extend this class and override methods.
|
8
9
|
*
|
9
10
|
* @example
|
10
11
|
* ```ts,runnable
|
@@ -12,7 +13,7 @@ export type IconElemType = HTMLImageElement | SVGElement;
|
|
12
13
|
*
|
13
14
|
* class CustomIconProvider extends jsdraw.IconProvider {
|
14
15
|
* // Use '☺' instead of the default dropdown symbol.
|
15
|
-
* public makeDropdownIcon() {
|
16
|
+
* public override makeDropdownIcon() {
|
16
17
|
* const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
17
18
|
* icon.innerHTML = `
|
18
19
|
* <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
|
@@ -24,6 +25,8 @@ export type IconElemType = HTMLImageElement | SVGElement;
|
|
24
25
|
*
|
25
26
|
* const icons = new CustomIconProvider();
|
26
27
|
* const editor = new jsdraw.Editor(document.body, {
|
28
|
+
* // The icon pack to use is specified through the editor's initial
|
29
|
+
* // configuration object:
|
27
30
|
* iconProvider: icons,
|
28
31
|
* });
|
29
32
|
*
|
@@ -62,8 +62,9 @@ const makeRedoIcon = (mirror) => {
|
|
62
62
|
return icon;
|
63
63
|
};
|
64
64
|
/**
|
65
|
-
* Provides icons that can be used in the toolbar
|
66
|
-
*
|
65
|
+
* Provides icons that can be used in the toolbar and other locations.
|
66
|
+
*
|
67
|
+
* To customize the icons used by the editor, extend this class and override methods.
|
67
68
|
*
|
68
69
|
* @example
|
69
70
|
* ```ts,runnable
|
@@ -71,7 +72,7 @@ const makeRedoIcon = (mirror) => {
|
|
71
72
|
*
|
72
73
|
* class CustomIconProvider extends jsdraw.IconProvider {
|
73
74
|
* // Use '☺' instead of the default dropdown symbol.
|
74
|
-
* public makeDropdownIcon() {
|
75
|
+
* public override makeDropdownIcon() {
|
75
76
|
* const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
76
77
|
* icon.innerHTML = `
|
77
78
|
* <text x='5' y='55' style='fill: var(--icon-color); font-size: 50pt;'>☺</text>
|
@@ -83,6 +84,8 @@ const makeRedoIcon = (mirror) => {
|
|
83
84
|
*
|
84
85
|
* const icons = new CustomIconProvider();
|
85
86
|
* const editor = new jsdraw.Editor(document.body, {
|
87
|
+
* // The icon pack to use is specified through the editor's initial
|
88
|
+
* // configuration object:
|
86
89
|
* iconProvider: icons,
|
87
90
|
* });
|
88
91
|
*
|
@@ -97,7 +100,6 @@ class IconProvider {
|
|
97
100
|
makeUndoIcon() {
|
98
101
|
return makeRedoIcon(true);
|
99
102
|
}
|
100
|
-
// @param mirror - reflect across the x-axis. This parameter is internal.
|
101
103
|
// @returns a redo icon.
|
102
104
|
makeRedoIcon() {
|
103
105
|
return makeRedoIcon(false);
|
@@ -194,7 +194,30 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
|
|
194
194
|
row.replaceChildren(label, input);
|
195
195
|
return {
|
196
196
|
setValue: (value) => {
|
197
|
-
|
197
|
+
// Slightly improve the case where the user tries to change the
|
198
|
+
// first digit of a dimension like 600.
|
199
|
+
//
|
200
|
+
// As changing the value also gives the image zero size (which is unsupported,
|
201
|
+
// .setValue is called immediately). We work around this by trying to select
|
202
|
+
// the added/changed digits.
|
203
|
+
//
|
204
|
+
// See https://github.com/personalizedrefrigerator/js-draw/issues/58.
|
205
|
+
if (document.activeElement === input && input.value.match(/^0*$/)) {
|
206
|
+
// We need to switch to type="text" and back to type="number" because
|
207
|
+
// number inputs don't support selection.
|
208
|
+
//
|
209
|
+
// See https://stackoverflow.com/q/22381837
|
210
|
+
const originalValue = input.value;
|
211
|
+
input.type = 'text';
|
212
|
+
input.value = value.toString();
|
213
|
+
// Select the added digits
|
214
|
+
const lengthToSelect = Math.max(1, input.value.length - originalValue.length);
|
215
|
+
input.setSelectionRange(0, lengthToSelect);
|
216
|
+
input.type = 'number';
|
217
|
+
}
|
218
|
+
else {
|
219
|
+
input.value = value.toString();
|
220
|
+
}
|
198
221
|
},
|
199
222
|
setIsAutomaticSize: (automatic) => {
|
200
223
|
input.disabled = automatic;
|
@@ -15,7 +15,7 @@ export interface PenTypeRecord {
|
|
15
15
|
export default class PenToolWidget extends BaseToolWidget {
|
16
16
|
private tool;
|
17
17
|
private updateInputs;
|
18
|
-
protected penTypes: PenTypeRecord[];
|
18
|
+
protected penTypes: Readonly<PenTypeRecord>[];
|
19
19
|
protected shapelikeIDs: string[];
|
20
20
|
private static idCounter;
|
21
21
|
constructor(editor: Editor, tool: Pen, localization?: ToolbarLocalization);
|
@@ -24,8 +24,11 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
24
24
|
this.updateInputs = () => { };
|
25
25
|
// Pen types that correspond to
|
26
26
|
this.shapelikeIDs = ['pressure-sensitive-pen', 'freehand-pen'];
|
27
|
+
// Additional client-specified pens.
|
28
|
+
const additionalPens = editor.getCurrentSettings().pens?.additionalPenTypes ?? [];
|
27
29
|
// Default pen types
|
28
30
|
this.penTypes = [
|
31
|
+
// Non-shape pens
|
29
32
|
{
|
30
33
|
name: this.localizationTable.flatTipPen,
|
31
34
|
id: 'pressure-sensitive-pen',
|
@@ -36,6 +39,8 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
36
39
|
id: 'freehand-pen',
|
37
40
|
factory: FreehandLineBuilder_1.makeFreehandLineBuilder,
|
38
41
|
},
|
42
|
+
...(additionalPens.filter(pen => !pen.isShapeBuilder)),
|
43
|
+
// Shape pens
|
39
44
|
{
|
40
45
|
name: this.localizationTable.arrowPen,
|
41
46
|
id: 'arrow',
|
@@ -65,7 +70,8 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
65
70
|
id: 'outlined-circle',
|
66
71
|
isShapeBuilder: true,
|
67
72
|
factory: CircleBuilder_1.makeOutlinedCircleBuilder,
|
68
|
-
}
|
73
|
+
},
|
74
|
+
...(additionalPens.filter(pen => pen.isShapeBuilder)),
|
69
75
|
];
|
70
76
|
this.editor.notifier.on(types_1.EditorEventType.ToolUpdated, toolEvt => {
|
71
77
|
if (toolEvt.kind !== types_1.EditorEventType.ToolUpdated) {
|
package/dist/cjs/tools/Eraser.js
CHANGED
@@ -50,7 +50,7 @@ class Eraser extends BaseTool_1.default {
|
|
50
50
|
return math_1.Rect2.fromCorners(centerPoint.minus(halfSize), centerPoint.plus(halfSize));
|
51
51
|
}
|
52
52
|
eraseTo(currentPoint) {
|
53
|
-
if (!this.isFirstEraseEvt && currentPoint.
|
53
|
+
if (!this.isFirstEraseEvt && currentPoint.distanceTo(this.lastPoint) === 0) {
|
54
54
|
return;
|
55
55
|
}
|
56
56
|
this.isFirstEraseEvt = false;
|
@@ -13,10 +13,10 @@ var StabilizerType;
|
|
13
13
|
})(StabilizerType || (StabilizerType = {}));
|
14
14
|
const defaultOptions = {
|
15
15
|
kind: StabilizerType.IntertialStabilizer,
|
16
|
-
mass: 0.4,
|
17
|
-
springConstant: 100.0,
|
16
|
+
mass: 0.4, // kg
|
17
|
+
springConstant: 100.0, // N/m
|
18
18
|
frictionCoefficient: 0.28,
|
19
|
-
maxPointDist: 10,
|
19
|
+
maxPointDist: 10, // screen units
|
20
20
|
inertiaFraction: 0.75,
|
21
21
|
minSimilarityToFinalize: 0.0,
|
22
22
|
velocityDecayFactor: 0.1,
|
@@ -28,8 +28,29 @@ class PasteHandler extends BaseTool_1.default {
|
|
28
28
|
// @internal
|
29
29
|
onPaste(event) {
|
30
30
|
const mime = event.mime.toLowerCase();
|
31
|
-
|
32
|
-
|
31
|
+
const svgData = (() => {
|
32
|
+
if (mime === 'image/svg+xml') {
|
33
|
+
return event.data;
|
34
|
+
}
|
35
|
+
if (mime !== 'text/html') {
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
// text/html is sometimes handlable SVG data. Use a hueristic
|
39
|
+
// to determine if this is the case:
|
40
|
+
// We use [^] and not . so that newlines are included.
|
41
|
+
const match = event.data.match(/^[^]{0,200}<svg.*/i); // [^]{0,200} <- Allow for metadata near start
|
42
|
+
if (!match) {
|
43
|
+
return false;
|
44
|
+
}
|
45
|
+
// Extract the SVG element from the pasted data
|
46
|
+
let svgEnd = event.data.toLowerCase().lastIndexOf('</svg>');
|
47
|
+
if (svgEnd === -1)
|
48
|
+
svgEnd = event.data.length;
|
49
|
+
return event.data.substring(event.data.search(/<svg/i), svgEnd);
|
50
|
+
})();
|
51
|
+
if (svgData) {
|
52
|
+
console.log('svgpaste', svgData);
|
53
|
+
void this.doSVGPaste(svgData);
|
33
54
|
return true;
|
34
55
|
}
|
35
56
|
else if (mime === 'text/plain') {
|
@@ -43,16 +64,21 @@ class PasteHandler extends BaseTool_1.default {
|
|
43
64
|
return false;
|
44
65
|
}
|
45
66
|
async addComponentsFromPaste(components) {
|
46
|
-
await this.editor.addAndCenterComponents(components);
|
67
|
+
await this.editor.addAndCenterComponents(components, true, this.editor.localization.pasted(components.length));
|
47
68
|
}
|
48
69
|
async doSVGPaste(data) {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
70
|
+
this.editor.showLoadingWarning(0);
|
71
|
+
try {
|
72
|
+
const loader = SVGLoader_1.default.fromString(data, true);
|
73
|
+
const components = [];
|
74
|
+
await loader.start((component) => {
|
75
|
+
components.push(component);
|
76
|
+
}, (_countProcessed, _totalToProcess) => null);
|
77
|
+
await this.addComponentsFromPaste(components);
|
78
|
+
}
|
79
|
+
finally {
|
80
|
+
this.editor.hideLoadingWarning();
|
81
|
+
}
|
56
82
|
}
|
57
83
|
async doTextPaste(text) {
|
58
84
|
const textTools = this.editor.toolController.getMatchingTools(TextTool_1.default);
|
@@ -67,6 +93,10 @@ class PasteHandler extends BaseTool_1.default {
|
|
67
93
|
});
|
68
94
|
const defaultTextStyle = { size: 12, fontFamily: 'sans', renderingStyle: { fill: math_1.Color4.red } };
|
69
95
|
const pastedTextStyle = textTools[0]?.getTextStyle() ?? defaultTextStyle;
|
96
|
+
// Don't paste text that would be invisible.
|
97
|
+
if (text.trim() === '') {
|
98
|
+
return;
|
99
|
+
}
|
70
100
|
const lines = text.split('\n');
|
71
101
|
await this.addComponentsFromPaste([TextComponent_1.default.fromLines(lines, math_1.Mat33.identity, pastedTextStyle)]);
|
72
102
|
}
|
package/dist/cjs/tools/Pen.js
CHANGED
@@ -102,8 +102,8 @@ class Pen extends BaseTool_1.default {
|
|
102
102
|
this.currentDeviceType = current.device;
|
103
103
|
if (this.shapeAutocompletionEnabled) {
|
104
104
|
const stationaryDetectionConfig = {
|
105
|
-
maxSpeed: 8.5,
|
106
|
-
maxRadius: 11,
|
105
|
+
maxSpeed: 8.5, // screenPx/s
|
106
|
+
maxRadius: 11, // screenPx
|
107
107
|
minTimeSeconds: 0.5, // s
|
108
108
|
};
|
109
109
|
this.stationaryDetector = new StationaryPenDetector_1.default(current, stationaryDetectionConfig, pointer => this.autocorrectShape(pointer));
|
@@ -8,6 +8,7 @@ const math_1 = require("@js-draw/math");
|
|
8
8
|
const types_1 = require("../../types");
|
9
9
|
const Viewport_1 = __importDefault(require("../../Viewport"));
|
10
10
|
const BaseTool_1 = __importDefault(require("../BaseTool"));
|
11
|
+
const CanvasRenderer_1 = __importDefault(require("../../rendering/renderers/CanvasRenderer"));
|
11
12
|
const SVGRenderer_1 = __importDefault(require("../../rendering/renderers/SVGRenderer"));
|
12
13
|
const Selection_1 = __importDefault(require("./Selection"));
|
13
14
|
const TextComponent_1 = __importDefault(require("../../components/TextComponent"));
|
@@ -376,19 +377,37 @@ class SelectionTool extends BaseTool_1.default {
|
|
376
377
|
return false;
|
377
378
|
}
|
378
379
|
const exportViewport = new Viewport_1.default(() => { });
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
380
|
+
const selectionScreenSize = this.selectionBox.getScreenRegion().size.times(this.editor.display.getDevicePixelRatio());
|
381
|
+
// Update the viewport to have screen size roughly equal to the size of the selection box
|
382
|
+
let scaleFactor = selectionScreenSize.maximumEntryMagnitude() / (bbox.size.maximumEntryMagnitude() || 1);
|
383
|
+
// Round to a nearby power of two
|
384
|
+
scaleFactor = Math.pow(2, Math.ceil(Math.log2(scaleFactor)));
|
385
|
+
exportViewport.updateScreenSize(bbox.size.times(scaleFactor));
|
386
|
+
exportViewport.resetTransform(math_1.Mat33.scaling2D(scaleFactor)
|
387
|
+
// Move the selection onto the screen
|
388
|
+
.rightMul(math_1.Mat33.translation(bbox.topLeft.times(-1))));
|
389
|
+
const { element: svgExportElem, renderer: svgRenderer } = SVGRenderer_1.default.fromViewport(exportViewport, { sanitize: true, useViewBoxForPositioning: true });
|
390
|
+
const { element: canvas, renderer: canvasRenderer } = CanvasRenderer_1.default.fromViewport(exportViewport, { maxCanvasDimen: 4096 });
|
383
391
|
const text = [];
|
384
392
|
for (const elem of selectedElems) {
|
385
393
|
elem.render(svgRenderer);
|
394
|
+
elem.render(canvasRenderer);
|
386
395
|
if (elem instanceof TextComponent_1.default) {
|
387
396
|
text.push(elem.getText());
|
388
397
|
}
|
389
398
|
}
|
390
399
|
event.setData('image/svg+xml', svgExportElem.outerHTML);
|
391
400
|
event.setData('text/html', svgExportElem.outerHTML);
|
401
|
+
event.setData('image/png', new Promise((resolve, reject) => {
|
402
|
+
canvas.toBlob((blob) => {
|
403
|
+
if (blob) {
|
404
|
+
resolve(blob);
|
405
|
+
}
|
406
|
+
else {
|
407
|
+
reject('Failed to convert canvas to blob.');
|
408
|
+
}
|
409
|
+
}, 'image/png');
|
410
|
+
}));
|
392
411
|
if (text.length > 0) {
|
393
412
|
event.setData('text/plain', text.join('\n'));
|
394
413
|
}
|
@@ -28,7 +28,7 @@ class ToPointerAutoscroller {
|
|
28
28
|
return math_1.Vec2.zero;
|
29
29
|
}
|
30
30
|
const closestEdgePoint = autoscrollBoundary.getClosestPointOnBoundaryTo(screenPoint);
|
31
|
-
const distToEdge = closestEdgePoint.
|
31
|
+
const distToEdge = closestEdgePoint.distanceTo(screenPoint);
|
32
32
|
const toEdge = closestEdgePoint.minus(screenPoint);
|
33
33
|
// Go faster for points further away from the boundary.
|
34
34
|
const maximumScaleFactor = 1.25;
|