js-draw 0.7.2 → 0.8.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/.github/ISSUE_TEMPLATE/translation.md +1 -0
- package/CHANGELOG.md +4 -0
- package/dist/bundle.js +1 -1
- package/dist/src/components/Stroke.js +10 -3
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +10 -23
- package/dist/src/components/builders/FreehandLineBuilder.js +70 -396
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +36 -0
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.js +339 -0
- package/dist/src/components/lib.d.ts +2 -0
- package/dist/src/components/lib.js +2 -0
- package/dist/src/components/util/StrokeSmoother.d.ts +35 -0
- package/dist/src/components/util/StrokeSmoother.js +206 -0
- package/dist/src/math/Mat33.d.ts +2 -0
- package/dist/src/math/Mat33.js +4 -0
- package/dist/src/math/Path.d.ts +2 -0
- package/dist/src/math/Path.js +39 -0
- package/dist/src/rendering/renderers/CanvasRenderer.js +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/renderers/SVGRenderer.js +20 -0
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/widgets/PenToolWidget.js +6 -1
- package/dist/src/tools/Pen.d.ts +2 -2
- package/dist/src/tools/Pen.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +1 -0
- package/dist/src/tools/SelectionTool/Selection.js +8 -1
- package/dist/src/tools/ToolController.js +2 -1
- package/package.json +1 -1
- package/src/components/Stroke.ts +16 -3
- package/src/components/builders/FreehandLineBuilder.ts +54 -495
- package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +454 -0
- package/src/components/lib.ts +3 -1
- package/src/components/util/StrokeSmoother.ts +290 -0
- package/src/math/Mat33.ts +5 -0
- package/src/math/Path.test.ts +25 -0
- package/src/math/Path.ts +45 -0
- package/src/rendering/renderers/CanvasRenderer.ts +2 -0
- package/src/rendering/renderers/SVGRenderer.ts +24 -0
- package/src/toolbar/localization.ts +2 -0
- package/src/toolbar/widgets/PenToolWidget.ts +6 -1
- package/src/tools/Pen.test.ts +2 -2
- package/src/tools/Pen.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +10 -1
- package/src/tools/ToolController.ts +2 -1
@@ -64,6 +64,8 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
64
64
|
if (style.stroke) {
|
65
65
|
this.ctx.strokeStyle = style.stroke.color.toHexString();
|
66
66
|
this.ctx.lineWidth = this.getSizeOfCanvasPixelOnScreen() * style.stroke.width;
|
67
|
+
this.ctx.lineCap = 'round';
|
68
|
+
this.ctx.lineJoin = 'round';
|
67
69
|
this.ctx.stroke();
|
68
70
|
}
|
69
71
|
this.ctx.closePath();
|
@@ -6,6 +6,7 @@ import { Point2, Vec2 } from '../../math/Vec2';
|
|
6
6
|
import Viewport from '../../Viewport';
|
7
7
|
import RenderingStyle from '../RenderingStyle';
|
8
8
|
import AbstractRenderer, { RenderableImage, RenderablePathSpec } from './AbstractRenderer';
|
9
|
+
export declare const renderedStylesheetId = "js-draw-style-sheet";
|
9
10
|
export default class SVGRenderer extends AbstractRenderer {
|
10
11
|
private elem;
|
11
12
|
private sanitize;
|
@@ -14,6 +15,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
14
15
|
private objectElems;
|
15
16
|
private overwrittenAttrs;
|
16
17
|
constructor(elem: SVGSVGElement, viewport: Viewport, sanitize?: boolean);
|
18
|
+
private addStyleSheet;
|
17
19
|
setRootSVGAttribute(name: string, value: string | null): void;
|
18
20
|
displaySize(): Vec2;
|
19
21
|
clear(): void;
|
@@ -4,6 +4,7 @@ import { toRoundedString } from '../../math/rounding';
|
|
4
4
|
import { Vec2 } from '../../math/Vec2';
|
5
5
|
import { svgAttributesDataKey, svgStyleAttributesDataKey } from '../../SVGLoader';
|
6
6
|
import AbstractRenderer from './AbstractRenderer';
|
7
|
+
export const renderedStylesheetId = 'js-draw-style-sheet';
|
7
8
|
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
8
9
|
export default class SVGRenderer extends AbstractRenderer {
|
9
10
|
// Renders onto `elem`. If `sanitize`, don't render potentially untrusted data.
|
@@ -18,6 +19,21 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
18
19
|
this.textContainer = null;
|
19
20
|
this.textContainerTransform = null;
|
20
21
|
this.clear();
|
22
|
+
this.addStyleSheet();
|
23
|
+
}
|
24
|
+
addStyleSheet() {
|
25
|
+
if (!this.elem.querySelector(`#${renderedStylesheetId}`)) {
|
26
|
+
// Default to rounded strokes.
|
27
|
+
const styleSheet = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
28
|
+
styleSheet.innerHTML = `
|
29
|
+
path {
|
30
|
+
stroke-linecap: round;
|
31
|
+
stroke-linejoin: round;
|
32
|
+
}
|
33
|
+
`.replace(/\s+/g, '');
|
34
|
+
styleSheet.setAttribute('id', renderedStylesheetId);
|
35
|
+
this.elem.appendChild(styleSheet);
|
36
|
+
}
|
21
37
|
}
|
22
38
|
// Sets an attribute on the root SVG element.
|
23
39
|
setRootSVGAttribute(name, value) {
|
@@ -214,6 +230,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
214
230
|
if (this.sanitize) {
|
215
231
|
return;
|
216
232
|
}
|
233
|
+
// Don't add multiple copies of the default stylesheet.
|
234
|
+
if (elem.tagName.toLowerCase() === 'style' && elem.getAttribute('id') === renderedStylesheetId) {
|
235
|
+
return;
|
236
|
+
}
|
217
237
|
this.elem.appendChild(elem.cloneNode(true));
|
218
238
|
}
|
219
239
|
isTooSmallToRender(_rect) {
|
@@ -19,6 +19,7 @@ export const defaultToolbarLocalization = {
|
|
19
19
|
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
20
20
|
touchPanning: 'Touchscreen panning',
|
21
21
|
freehandPen: 'Freehand',
|
22
|
+
pressureSensitiveFreehandPen: 'Freehand (pressure sensitive)',
|
22
23
|
arrowPen: 'Arrow',
|
23
24
|
linePen: 'Line',
|
24
25
|
outlinedRectanglePen: 'Outlined rectangle',
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
|
2
2
|
import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
|
3
|
+
import { makePressureSensitiveFreehandLineBuilder } from '../../components/builders/PressureSensitiveFreehandLineBuilder';
|
3
4
|
import { makeLineBuilder } from '../../components/builders/LineBuilder';
|
4
5
|
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../../components/builders/RectangleBuilder';
|
5
6
|
import { EditorEventType } from '../../types';
|
@@ -13,6 +14,10 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
13
14
|
this.updateInputs = () => { };
|
14
15
|
// Default pen types
|
15
16
|
this.penTypes = [
|
17
|
+
{
|
18
|
+
name: localization.pressureSensitiveFreehandPen,
|
19
|
+
factory: makePressureSensitiveFreehandLineBuilder,
|
20
|
+
},
|
16
21
|
{
|
17
22
|
name: localization.freehandPen,
|
18
23
|
factory: makeFreehandLineBuilder,
|
@@ -50,7 +55,7 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
50
55
|
}
|
51
56
|
createIcon() {
|
52
57
|
const strokeFactory = this.tool.getStrokeFactory();
|
53
|
-
if (strokeFactory === makeFreehandLineBuilder) {
|
58
|
+
if (strokeFactory === makeFreehandLineBuilder || strokeFactory === makePressureSensitiveFreehandLineBuilder) {
|
54
59
|
// Use a square-root scale to prevent the pen's tip from overflowing.
|
55
60
|
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
56
61
|
const color = this.tool.getColor();
|
package/dist/src/tools/Pen.d.ts
CHANGED
@@ -11,10 +11,10 @@ export interface PenStyle {
|
|
11
11
|
export default class Pen extends BaseTool {
|
12
12
|
private editor;
|
13
13
|
private style;
|
14
|
+
private builderFactory;
|
14
15
|
protected builder: ComponentBuilder | null;
|
15
|
-
protected builderFactory: ComponentBuilderFactory;
|
16
16
|
private lastPoint;
|
17
|
-
constructor(editor: Editor, description: string, style: PenStyle);
|
17
|
+
constructor(editor: Editor, description: string, style: PenStyle, builderFactory?: ComponentBuilderFactory);
|
18
18
|
private getPressureMultiplier;
|
19
19
|
protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
|
20
20
|
protected previewStroke(): void;
|
package/dist/src/tools/Pen.js
CHANGED
@@ -4,12 +4,12 @@ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuil
|
|
4
4
|
import { EditorEventType } from '../types';
|
5
5
|
import BaseTool from './BaseTool';
|
6
6
|
export default class Pen extends BaseTool {
|
7
|
-
constructor(editor, description, style) {
|
7
|
+
constructor(editor, description, style, builderFactory = makeFreehandLineBuilder) {
|
8
8
|
super(editor.notifier, description);
|
9
9
|
this.editor = editor;
|
10
10
|
this.style = style;
|
11
|
+
this.builderFactory = builderFactory;
|
11
12
|
this.builder = null;
|
12
|
-
this.builderFactory = makeFreehandLineBuilder;
|
13
13
|
this.lastPoint = null;
|
14
14
|
}
|
15
15
|
getPressureMultiplier() {
|
@@ -30,6 +30,7 @@ export default class Selection {
|
|
30
30
|
this.transform = Mat33.identity;
|
31
31
|
this.transformCommands = [];
|
32
32
|
this.selectedElems = [];
|
33
|
+
this.hasParent = true;
|
33
34
|
this.targetHandle = null;
|
34
35
|
this.backgroundDragging = false;
|
35
36
|
this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
|
@@ -96,7 +97,7 @@ export default class Selection {
|
|
96
97
|
// Applies, previews, but doesn't finalize the given transformation.
|
97
98
|
setTransform(transform, preview = true) {
|
98
99
|
this.transform = transform;
|
99
|
-
if (preview) {
|
100
|
+
if (preview && this.hasParent) {
|
100
101
|
this.previewTransformCmds();
|
101
102
|
this.scrollTo();
|
102
103
|
}
|
@@ -190,6 +191,10 @@ export default class Selection {
|
|
190
191
|
}
|
191
192
|
// @internal
|
192
193
|
updateUI() {
|
194
|
+
// Don't update old selections.
|
195
|
+
if (!this.hasParent) {
|
196
|
+
return;
|
197
|
+
}
|
193
198
|
// marginLeft, marginTop: Display relative to the top left of the selection overlay.
|
194
199
|
// left, top don't work for this.
|
195
200
|
this.backgroundElem.style.marginLeft = `${this.screenRegion.topLeft.x}px`;
|
@@ -267,6 +272,7 @@ export default class Selection {
|
|
267
272
|
this.container.remove();
|
268
273
|
}
|
269
274
|
elem.appendChild(this.container);
|
275
|
+
this.hasParent = true;
|
270
276
|
}
|
271
277
|
setToPoint(point) {
|
272
278
|
this.originalRegion = this.originalRegion.grownToPoint(point);
|
@@ -277,6 +283,7 @@ export default class Selection {
|
|
277
283
|
this.container.remove();
|
278
284
|
}
|
279
285
|
this.originalRegion = Rect2.empty;
|
286
|
+
this.hasParent = false;
|
280
287
|
}
|
281
288
|
setSelectedObjects(objects, bbox) {
|
282
289
|
this.originalRegion = bbox;
|
@@ -11,6 +11,7 @@ import PipetteTool from './PipetteTool';
|
|
11
11
|
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
12
12
|
import PasteHandler from './PasteHandler';
|
13
13
|
import ToolbarShortcutHandler from './ToolbarShortcutHandler';
|
14
|
+
import { makePressureSensitiveFreehandLineBuilder } from '../components/builders/PressureSensitiveFreehandLineBuilder';
|
14
15
|
export default class ToolController {
|
15
16
|
/** @internal */
|
16
17
|
constructor(editor, localization) {
|
@@ -25,7 +26,7 @@ export default class ToolController {
|
|
25
26
|
primaryPenTool,
|
26
27
|
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
27
28
|
// Highlighter-like pen with width=64
|
28
|
-
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
29
|
+
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }, makePressureSensitiveFreehandLineBuilder),
|
29
30
|
new Eraser(editor, localization.eraserTool),
|
30
31
|
new SelectionTool(editor, localization.selectionTool),
|
31
32
|
new TextTool(editor, localization.textTool, localization),
|
package/package.json
CHANGED
package/src/components/Stroke.ts
CHANGED
@@ -61,7 +61,7 @@ export default class Stroke extends AbstractComponent {
|
|
61
61
|
}
|
62
62
|
|
63
63
|
const muchBiggerThanVisible = bbox.size.x > visibleRect.size.x * 2 || bbox.size.y > visibleRect.size.y * 2;
|
64
|
-
if (muchBiggerThanVisible && !part.path.
|
64
|
+
if (muchBiggerThanVisible && !part.path.roughlyIntersects(visibleRect, part.style.stroke?.width)) {
|
65
65
|
continue;
|
66
66
|
}
|
67
67
|
}
|
@@ -87,7 +87,20 @@ export default class Stroke extends AbstractComponent {
|
|
87
87
|
// Update each part
|
88
88
|
this.parts = this.parts.map((part) => {
|
89
89
|
const newPath = part.path.transformedBy(affineTransfm);
|
90
|
-
const
|
90
|
+
const newStyle = {
|
91
|
+
...part.style,
|
92
|
+
stroke: part.style.stroke ? {
|
93
|
+
...part.style.stroke,
|
94
|
+
} : undefined,
|
95
|
+
};
|
96
|
+
|
97
|
+
// Approximate the scale factor.
|
98
|
+
if (newStyle.stroke) {
|
99
|
+
const scaleFactor = affineTransfm.getScaleFactor();
|
100
|
+
newStyle.stroke.width *= scaleFactor;
|
101
|
+
}
|
102
|
+
|
103
|
+
const newBBox = this.bboxForPart(newPath.bbox, newStyle);
|
91
104
|
|
92
105
|
if (isFirstPart) {
|
93
106
|
this.contentBBox = newBBox;
|
@@ -100,7 +113,7 @@ export default class Stroke extends AbstractComponent {
|
|
100
113
|
path: newPath,
|
101
114
|
startPoint: newPath.startPoint,
|
102
115
|
commands: newPath.parts,
|
103
|
-
style:
|
116
|
+
style: newStyle,
|
104
117
|
};
|
105
118
|
});
|
106
119
|
}
|