js-draw 0.7.1 → 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 +10 -0
- package/CONTRIBUTING.md +75 -0
- package/dist/bundle.js +1 -1
- package/dist/src/SVGLoader.js +1 -0
- 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 +39 -7
- 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/TextTool.js +4 -2
- package/dist/src/tools/ToolController.js +2 -1
- package/package.json +1 -1
- package/src/SVGLoader.ts +1 -0
- 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 +47 -7
- 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/TextTool.ts +5 -2
- package/src/tools/ToolController.ts +2 -1
package/dist/src/math/Path.js
CHANGED
@@ -195,6 +195,45 @@ export default class Path {
|
|
195
195
|
...other.parts,
|
196
196
|
]);
|
197
197
|
}
|
198
|
+
getEndPoint() {
|
199
|
+
if (this.parts.length === 0) {
|
200
|
+
return this.startPoint;
|
201
|
+
}
|
202
|
+
const lastPart = this.parts[this.parts.length - 1];
|
203
|
+
if (lastPart.kind === PathCommandType.QuadraticBezierTo || lastPart.kind === PathCommandType.CubicBezierTo) {
|
204
|
+
return lastPart.endPoint;
|
205
|
+
}
|
206
|
+
else {
|
207
|
+
return lastPart.point;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
roughlyIntersects(rect, strokeWidth = 0) {
|
211
|
+
if (this.parts.length === 0) {
|
212
|
+
return rect.containsPoint(this.startPoint);
|
213
|
+
}
|
214
|
+
const isClosed = this.startPoint.eq(this.getEndPoint());
|
215
|
+
if (isClosed && strokeWidth == 0) {
|
216
|
+
return this.closedRoughlyIntersects(rect);
|
217
|
+
}
|
218
|
+
if (rect.containsRect(this.bbox)) {
|
219
|
+
return true;
|
220
|
+
}
|
221
|
+
// Does the rectangle intersect the bounding boxes of any of this' parts?
|
222
|
+
let startPoint = this.startPoint;
|
223
|
+
for (const part of this.parts) {
|
224
|
+
const bbox = Path.computeBBoxForSegment(startPoint, part).grownBy(strokeWidth);
|
225
|
+
if (part.kind === PathCommandType.LineTo || part.kind === PathCommandType.MoveTo) {
|
226
|
+
startPoint = part.point;
|
227
|
+
}
|
228
|
+
else {
|
229
|
+
startPoint = part.endPoint;
|
230
|
+
}
|
231
|
+
if (rect.intersects(bbox)) {
|
232
|
+
return true;
|
233
|
+
}
|
234
|
+
}
|
235
|
+
return false;
|
236
|
+
}
|
198
237
|
// Treats this as a closed path and returns true if part of `rect` is roughly within
|
199
238
|
// this path's interior.
|
200
239
|
//
|
@@ -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) {
|
@@ -83,11 +99,14 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
83
99
|
}
|
84
100
|
this.lastPathString.push(path.toString());
|
85
101
|
}
|
86
|
-
// Apply [elemTransform] to [elem].
|
87
|
-
|
102
|
+
// Apply [elemTransform] to [elem]. Uses both a `matrix` and `.x`, `.y` properties if `setXY` is true.
|
103
|
+
// Otherwise, just uses a `matrix`.
|
104
|
+
transformFrom(elemTransform, elem, inCanvasSpace = false, setXY = true) {
|
88
105
|
let transform = !inCanvasSpace ? this.getCanvasToScreenTransform().rightMul(elemTransform) : elemTransform;
|
89
106
|
const translation = transform.transformVec2(Vec2.zero);
|
90
|
-
|
107
|
+
if (setXY) {
|
108
|
+
transform = transform.rightMul(Mat33.translation(translation.times(-1)));
|
109
|
+
}
|
91
110
|
if (!transform.eq(Mat33.identity)) {
|
92
111
|
elem.style.transform = `matrix(
|
93
112
|
${transform.a1}, ${transform.b1},
|
@@ -98,8 +117,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
98
117
|
else {
|
99
118
|
elem.style.transform = '';
|
100
119
|
}
|
101
|
-
|
102
|
-
|
120
|
+
if (setXY) {
|
121
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
122
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
123
|
+
}
|
103
124
|
}
|
104
125
|
drawText(text, transform, style) {
|
105
126
|
var _a;
|
@@ -120,7 +141,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
120
141
|
if (!this.textContainer) {
|
121
142
|
const container = document.createElementNS(svgNameSpace, 'text');
|
122
143
|
container.appendChild(document.createTextNode(text));
|
123
|
-
|
144
|
+
// Don't set .x/.y properties (just use .style.transform).
|
145
|
+
// Child nodes aren't translated by .x/.y properties, but are by .style.transform.
|
146
|
+
const setXY = false;
|
147
|
+
this.transformFrom(transform, container, true, setXY);
|
124
148
|
applyTextStyles(container, style);
|
125
149
|
this.elem.appendChild(container);
|
126
150
|
(_a = this.objectElems) === null || _a === void 0 ? void 0 : _a.push(container);
|
@@ -133,8 +157,12 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
133
157
|
const elem = document.createElementNS(svgNameSpace, 'tspan');
|
134
158
|
elem.appendChild(document.createTextNode(text));
|
135
159
|
this.textContainer.appendChild(elem);
|
160
|
+
// Make .x/.y relative to the parent.
|
136
161
|
transform = this.textContainerTransform.inverse().rightMul(transform);
|
137
|
-
|
162
|
+
// .style.transform does nothing to tspan elements. As such, we need to set x/y:
|
163
|
+
const translation = transform.transformVec2(Vec2.zero);
|
164
|
+
elem.setAttribute('x', `${toRoundedString(translation.x)}`);
|
165
|
+
elem.setAttribute('y', `${toRoundedString(translation.y)}`);
|
138
166
|
applyTextStyles(elem, style);
|
139
167
|
}
|
140
168
|
}
|
@@ -202,6 +230,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
202
230
|
if (this.sanitize) {
|
203
231
|
return;
|
204
232
|
}
|
233
|
+
// Don't add multiple copies of the default stylesheet.
|
234
|
+
if (elem.tagName.toLowerCase() === 'style' && elem.getAttribute('id') === renderedStylesheetId) {
|
235
|
+
return;
|
236
|
+
}
|
205
237
|
this.elem.appendChild(elem.cloneNode(true));
|
206
238
|
}
|
207
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;
|
@@ -119,7 +119,7 @@ export default class TextTool extends BaseTool {
|
|
119
119
|
this.textInputElem = document.createElement('textarea');
|
120
120
|
this.textInputElem.value = initialText;
|
121
121
|
this.textInputElem.style.display = 'inline-block';
|
122
|
-
this.textTargetPosition = textCanvasPos;
|
122
|
+
this.textTargetPosition = this.editor.viewport.roundPoint(textCanvasPos);
|
123
123
|
this.textRotation = -this.editor.viewport.getRotationAngle();
|
124
124
|
this.textScale = Vec2.of(1, 1).times(this.editor.viewport.getSizeOfPixelOnCanvas());
|
125
125
|
this.updateTextInput();
|
@@ -172,6 +172,8 @@ export default class TextTool extends BaseTool {
|
|
172
172
|
const testRegion = Rect2.fromCorners(canvasPos.minus(halfTestRegionSize), canvasPos.plus(halfTestRegionSize));
|
173
173
|
const targetNodes = this.editor.image.getElementsIntersectingRegion(testRegion);
|
174
174
|
const targetTextNodes = targetNodes.filter(node => node instanceof TextComponent);
|
175
|
+
// End any TextNodes we're currently editing.
|
176
|
+
this.flushInput();
|
175
177
|
if (targetTextNodes.length > 0) {
|
176
178
|
const targetNode = targetTextNodes[targetTextNodes.length - 1];
|
177
179
|
this.setTextStyle(targetNode.getTextStyle());
|
@@ -227,7 +229,7 @@ export default class TextTool extends BaseTool {
|
|
227
229
|
}
|
228
230
|
setTextStyle(style) {
|
229
231
|
// Copy the style — we may change parts of it.
|
230
|
-
this.textStyle = Object.assign({}, style);
|
232
|
+
this.textStyle = Object.assign(Object.assign({}, style), { renderingStyle: Object.assign({}, style.renderingStyle) });
|
231
233
|
this.dispatchUpdateEvent();
|
232
234
|
}
|
233
235
|
}
|
@@ -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/SVGLoader.ts
CHANGED
@@ -218,6 +218,7 @@ export default class SVGLoader implements ImageLoader {
|
|
218
218
|
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
219
219
|
const subElem = child as SVGElement;
|
220
220
|
if (subElem.tagName.toLowerCase() === 'tspan') {
|
221
|
+
// FIXME: tspan's (x, y) components are absolute, not relative to the parent.
|
221
222
|
contentList.push(this.makeText(subElem as SVGTSpanElement));
|
222
223
|
} else {
|
223
224
|
throw new Error(`Unrecognized text child element: ${subElem}`);
|
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
|
}
|