@teachinglab/omd 0.6.1 → 0.6.2
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/README.md +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +546 -546
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +969 -962
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -57
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
package/canvas/drawing/stroke.js
CHANGED
|
@@ -1,386 +1,386 @@
|
|
|
1
|
-
import { BoundingBox } from '../utils/boundingBox.js';
|
|
2
|
-
import { jsvgPath } from '@teachinglab/jsvg';
|
|
3
|
-
/**
|
|
4
|
-
* Represents a drawing stroke made up of connected points
|
|
5
|
-
*/
|
|
6
|
-
export class Stroke {
|
|
7
|
-
/**
|
|
8
|
-
* @param {Object} options - Stroke configuration
|
|
9
|
-
* @param {number} options.x - Starting X coordinate
|
|
10
|
-
* @param {number} options.y - Starting Y coordinate
|
|
11
|
-
* @param {number} [options.strokeWidth=5] - Stroke width
|
|
12
|
-
* @param {string} [options.strokeColor='#000000'] - Stroke color
|
|
13
|
-
* @param {number} [options.strokeOpacity=1] - Stroke opacity
|
|
14
|
-
* @param {string} [options.tool='pencil'] - Tool that created this stroke
|
|
15
|
-
*/
|
|
16
|
-
constructor(options = {}) {
|
|
17
|
-
this.id = options.id || this._generateId();
|
|
18
|
-
this.tool = options.tool || 'pencil';
|
|
19
|
-
|
|
20
|
-
// Stroke properties
|
|
21
|
-
this.strokeWidth = options.strokeWidth || 5;
|
|
22
|
-
this.strokeColor = options.strokeColor || '#000000';
|
|
23
|
-
this.strokeOpacity = options.strokeOpacity || 1;
|
|
24
|
-
|
|
25
|
-
// Drawing data
|
|
26
|
-
this.points = [];
|
|
27
|
-
this.isFinished = false;
|
|
28
|
-
this.isSelected = false;
|
|
29
|
-
|
|
30
|
-
// Bounding box for hit testing and selection
|
|
31
|
-
this.boundingBox = new BoundingBox();
|
|
32
|
-
|
|
33
|
-
// Create SVG element
|
|
34
|
-
this._createElement();
|
|
35
|
-
|
|
36
|
-
// Add starting point if provided
|
|
37
|
-
if (options.x !== undefined && options.y !== undefined) {
|
|
38
|
-
this.addPoint({
|
|
39
|
-
x: options.x,
|
|
40
|
-
y: options.y,
|
|
41
|
-
pressure: 0.5,
|
|
42
|
-
width: this.strokeWidth,
|
|
43
|
-
timestamp: Date.now()
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Create the SVG path element
|
|
50
|
-
* @private
|
|
51
|
-
*/
|
|
52
|
-
_createElement() {
|
|
53
|
-
this.jsvgPath = new jsvgPath();
|
|
54
|
-
this.element = this.jsvgPath.svgObject; // Get the underlying SVG element
|
|
55
|
-
this.jsvgPath.setFillColor('none');
|
|
56
|
-
this.jsvgPath.setStrokeColor(this.strokeColor);
|
|
57
|
-
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
58
|
-
this.element.setAttribute('stroke-opacity', this.strokeOpacity);
|
|
59
|
-
this.element.setAttribute('stroke-linecap', 'round');
|
|
60
|
-
this.element.setAttribute('stroke-linejoin', 'round');
|
|
61
|
-
this.element.setAttribute('data-stroke-id', this.id);
|
|
62
|
-
this.element.setAttribute('data-tool', this.tool);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Add a point to the stroke
|
|
67
|
-
* @param {Object} point - Point data
|
|
68
|
-
* @param {number} point.x - X coordinate
|
|
69
|
-
* @param {number} point.y - Y coordinate
|
|
70
|
-
* @param {number} [point.pressure=0.5] - Pressure value
|
|
71
|
-
* @param {number} [point.width] - Stroke width at this point
|
|
72
|
-
* @param {number} [point.timestamp] - Timestamp
|
|
73
|
-
*/
|
|
74
|
-
addPoint(point) {
|
|
75
|
-
const normalizedPoint = {
|
|
76
|
-
x: point.x,
|
|
77
|
-
y: point.y,
|
|
78
|
-
pressure: point.pressure || 0.5,
|
|
79
|
-
width: point.width || this.strokeWidth,
|
|
80
|
-
timestamp: point.timestamp || Date.now()
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
this.points.push(normalizedPoint);
|
|
84
|
-
this._updatePath();
|
|
85
|
-
this._updateBoundingBox();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Update the SVG path based on current points
|
|
90
|
-
* @private
|
|
91
|
-
*/
|
|
92
|
-
_updatePath() {
|
|
93
|
-
if (this.points.length === 0) {
|
|
94
|
-
this.jsvgPath.clearPoints();
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.jsvgPath.clearPoints(); // Clear existing points before adding new ones
|
|
99
|
-
|
|
100
|
-
if (this.points.length === 1) {
|
|
101
|
-
// Single point - draw a small circle
|
|
102
|
-
const point = this.points[0];
|
|
103
|
-
this.jsvgPath.addPoint(point.x, point.y);
|
|
104
|
-
this.jsvgPath.addPoint(point.x + 0.1, point.y); // Add a second point for a tiny line
|
|
105
|
-
} else {
|
|
106
|
-
// Multiple points - create smooth path
|
|
107
|
-
this._generateSmoothPath();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.jsvgPath.updatePath();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Generate smooth path using cubic Bézier curves
|
|
115
|
-
* @private
|
|
116
|
-
*/
|
|
117
|
-
_generateSmoothPath() {
|
|
118
|
-
if (this.points.length < 2) return '';
|
|
119
|
-
|
|
120
|
-
this.jsvgPath.addPoint(this.points[0].x, this.points[0].y);
|
|
121
|
-
|
|
122
|
-
if (this.points.length === 2) {
|
|
123
|
-
// Simple line for 2 points
|
|
124
|
-
this.jsvgPath.addPoint(this.points[1].x, this.points[1].y);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Use cubic Bézier curves for smoother paths
|
|
129
|
-
for (let i = 1; i < this.points.length - 1; i++) {
|
|
130
|
-
const prev = this.points[i - 1];
|
|
131
|
-
const curr = this.points[i];
|
|
132
|
-
const next = this.points[i + 1];
|
|
133
|
-
|
|
134
|
-
// Calculate control points for smooth curve
|
|
135
|
-
const cp1x = curr.x + (next.x - prev.x) * 0.25;
|
|
136
|
-
const cp1y = curr.y + (next.y - prev.y) * 0.25;
|
|
137
|
-
const cp2x = next.x - (next.x - curr.x) * 0.25;
|
|
138
|
-
const cp2y = next.y - (next.y - curr.y) * 0.25;
|
|
139
|
-
|
|
140
|
-
// jsvgPath doesn't directly support cubic bezier curves, so we'll approximate with more points
|
|
141
|
-
// For a true cubic bezier, we'd need to extend jsvgPath or use raw SVG path data.
|
|
142
|
-
// For now, we'll just add the next point.
|
|
143
|
-
this.jsvgPath.addPoint(next.x, next.y);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Update bounding box based on current points
|
|
149
|
-
* @private
|
|
150
|
-
*/
|
|
151
|
-
_updateBoundingBox() {
|
|
152
|
-
if (this.points.length === 0) return;
|
|
153
|
-
|
|
154
|
-
let minX = Infinity, minY = Infinity;
|
|
155
|
-
let maxX = -Infinity, maxY = -Infinity;
|
|
156
|
-
|
|
157
|
-
this.points.forEach(point => {
|
|
158
|
-
const radius = point.width / 2;
|
|
159
|
-
minX = Math.min(minX, point.x - radius);
|
|
160
|
-
minY = Math.min(minY, point.y - radius);
|
|
161
|
-
maxX = Math.max(maxX, point.x + radius);
|
|
162
|
-
maxY = Math.max(maxY, point.y + radius);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
this.boundingBox.set(minX, minY, maxX - minX, maxY - minY);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Finish the stroke (no more points will be added)
|
|
170
|
-
*/
|
|
171
|
-
finish() {
|
|
172
|
-
this.isFinished = true;
|
|
173
|
-
this.element.setAttribute('data-finished', 'true');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Set stroke selection state
|
|
178
|
-
* @param {boolean} selected - Whether stroke is selected
|
|
179
|
-
*/
|
|
180
|
-
setSelected(selected) {
|
|
181
|
-
this.isSelected = selected;
|
|
182
|
-
|
|
183
|
-
if (selected) {
|
|
184
|
-
this.element.setAttribute('stroke-dasharray', '5,5');
|
|
185
|
-
this.element.setAttribute('data-selected', 'true');
|
|
186
|
-
} else {
|
|
187
|
-
this.element.removeAttribute('stroke-dasharray');
|
|
188
|
-
this.element.removeAttribute('data-selected');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Update stroke configuration
|
|
194
|
-
* @param {Object} config - New configuration
|
|
195
|
-
*/
|
|
196
|
-
updateConfig(config) {
|
|
197
|
-
if (config.strokeColor !== undefined) {
|
|
198
|
-
this.strokeColor = config.strokeColor;
|
|
199
|
-
this.jsvgPath.setStrokeColor(this.strokeColor);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (config.strokeWidth !== undefined) {
|
|
203
|
-
this.strokeWidth = config.strokeWidth;
|
|
204
|
-
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (config.strokeOpacity !== undefined) {
|
|
208
|
-
this.strokeOpacity = config.strokeOpacity;
|
|
209
|
-
this.element.setAttribute('stroke-opacity', this.strokeOpacity);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Check if a point is near this stroke
|
|
215
|
-
* @param {number} x - X coordinate
|
|
216
|
-
* @param {number} y - Y coordinate
|
|
217
|
-
* @param {number} [tolerance=10] - Distance tolerance
|
|
218
|
-
* @returns {boolean} True if point is near stroke
|
|
219
|
-
*/
|
|
220
|
-
isNearPoint(x, y, tolerance = 10) {
|
|
221
|
-
// Quick bounding box check first
|
|
222
|
-
if (!this.boundingBox.containsPoint(x, y, tolerance)) {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Check distance to each line segment
|
|
227
|
-
for (let i = 1; i < this.points.length; i++) {
|
|
228
|
-
const p1 = this.points[i - 1];
|
|
229
|
-
const p2 = this.points[i];
|
|
230
|
-
|
|
231
|
-
const distance = this._distanceToLineSegment(x, y, p1.x, p1.y, p2.x, p2.y);
|
|
232
|
-
if (distance <= tolerance) {
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Calculate distance from point to line segment
|
|
242
|
-
* @private
|
|
243
|
-
*/
|
|
244
|
-
_distanceToLineSegment(px, py, x1, y1, x2, y2) {
|
|
245
|
-
const dx = x2 - x1;
|
|
246
|
-
const dy = y2 - y1;
|
|
247
|
-
const length = Math.sqrt(dx * dx + dy * dy);
|
|
248
|
-
|
|
249
|
-
if (length === 0) {
|
|
250
|
-
// Points are the same
|
|
251
|
-
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / (length * length)));
|
|
255
|
-
const projX = x1 + t * dx;
|
|
256
|
-
const projY = y1 + t * dy;
|
|
257
|
-
|
|
258
|
-
return Math.sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Get stroke length
|
|
263
|
-
* @returns {number} Total stroke length
|
|
264
|
-
*/
|
|
265
|
-
getLength() {
|
|
266
|
-
let length = 0;
|
|
267
|
-
|
|
268
|
-
for (let i = 1; i < this.points.length; i++) {
|
|
269
|
-
const p1 = this.points[i - 1];
|
|
270
|
-
const p2 = this.points[i];
|
|
271
|
-
const dx = p2.x - p1.x;
|
|
272
|
-
const dy = p2.y - p1.y;
|
|
273
|
-
length += Math.sqrt(dx * dx + dy * dy);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return length;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Get stroke data for serialization
|
|
281
|
-
* @returns {Object} Serializable stroke data
|
|
282
|
-
*/
|
|
283
|
-
toJSON() {
|
|
284
|
-
return {
|
|
285
|
-
id: this.id,
|
|
286
|
-
tool: this.tool,
|
|
287
|
-
strokeWidth: this.strokeWidth,
|
|
288
|
-
strokeColor: this.strokeColor,
|
|
289
|
-
strokeOpacity: this.strokeOpacity,
|
|
290
|
-
points: this.points,
|
|
291
|
-
isFinished: this.isFinished,
|
|
292
|
-
boundingBox: this.boundingBox.toJSON()
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Create stroke from serialized data
|
|
298
|
-
* @param {Object} data - Serialized stroke data
|
|
299
|
-
* @returns {Stroke} New stroke instance
|
|
300
|
-
*/
|
|
301
|
-
static fromJSON(data) {
|
|
302
|
-
const stroke = new Stroke({
|
|
303
|
-
id: data.id,
|
|
304
|
-
tool: data.tool,
|
|
305
|
-
strokeWidth: data.strokeWidth,
|
|
306
|
-
strokeColor: data.strokeColor,
|
|
307
|
-
strokeOpacity: data.strokeOpacity
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Add all points
|
|
311
|
-
data.points.forEach(point => stroke.addPoint(point));
|
|
312
|
-
|
|
313
|
-
if (data.isFinished) {
|
|
314
|
-
stroke.finish();
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return stroke;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Generate unique stroke ID
|
|
322
|
-
* @private
|
|
323
|
-
*/
|
|
324
|
-
_generateId() {
|
|
325
|
-
return `stroke_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Clone this stroke
|
|
330
|
-
* @returns {Stroke} New stroke instance
|
|
331
|
-
*/
|
|
332
|
-
clone() {
|
|
333
|
-
return Stroke.fromJSON(this.toJSON());
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Get point count
|
|
338
|
-
* @returns {number} Number of points in stroke
|
|
339
|
-
*/
|
|
340
|
-
getPointCount() {
|
|
341
|
-
return this.points.length;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Get bounding box
|
|
346
|
-
* @returns {BoundingBox} Stroke bounding box
|
|
347
|
-
*/
|
|
348
|
-
getBoundingBox() {
|
|
349
|
-
return this.boundingBox;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Move stroke by offset
|
|
354
|
-
* @param {number} dx - X offset
|
|
355
|
-
* @param {number} dy - Y offset
|
|
356
|
-
*/
|
|
357
|
-
move(dx, dy) {
|
|
358
|
-
this.points.forEach(point => {
|
|
359
|
-
point.x += dx;
|
|
360
|
-
point.y += dy;
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
this._updatePath();
|
|
364
|
-
this._updateBoundingBox();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Scale stroke by factor
|
|
369
|
-
* @param {number} scale - Scale factor
|
|
370
|
-
* @param {number} [originX=0] - Scale origin X
|
|
371
|
-
* @param {number} [originY=0] - Scale origin Y
|
|
372
|
-
*/
|
|
373
|
-
scale(scale, originX = 0, originY = 0) {
|
|
374
|
-
this.points.forEach(point => {
|
|
375
|
-
point.x = originX + (point.x - originX) * scale;
|
|
376
|
-
point.y = originY + (point.y - originY) * scale;
|
|
377
|
-
point.width *= scale;
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
this.strokeWidth *= scale;
|
|
381
|
-
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
382
|
-
|
|
383
|
-
this._updatePath();
|
|
384
|
-
this._updateBoundingBox();
|
|
385
|
-
}
|
|
1
|
+
import { BoundingBox } from '../utils/boundingBox.js';
|
|
2
|
+
import { jsvgPath } from '@teachinglab/jsvg';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a drawing stroke made up of connected points
|
|
5
|
+
*/
|
|
6
|
+
export class Stroke {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} options - Stroke configuration
|
|
9
|
+
* @param {number} options.x - Starting X coordinate
|
|
10
|
+
* @param {number} options.y - Starting Y coordinate
|
|
11
|
+
* @param {number} [options.strokeWidth=5] - Stroke width
|
|
12
|
+
* @param {string} [options.strokeColor='#000000'] - Stroke color
|
|
13
|
+
* @param {number} [options.strokeOpacity=1] - Stroke opacity
|
|
14
|
+
* @param {string} [options.tool='pencil'] - Tool that created this stroke
|
|
15
|
+
*/
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.id = options.id || this._generateId();
|
|
18
|
+
this.tool = options.tool || 'pencil';
|
|
19
|
+
|
|
20
|
+
// Stroke properties
|
|
21
|
+
this.strokeWidth = options.strokeWidth || 5;
|
|
22
|
+
this.strokeColor = options.strokeColor || '#000000';
|
|
23
|
+
this.strokeOpacity = options.strokeOpacity || 1;
|
|
24
|
+
|
|
25
|
+
// Drawing data
|
|
26
|
+
this.points = [];
|
|
27
|
+
this.isFinished = false;
|
|
28
|
+
this.isSelected = false;
|
|
29
|
+
|
|
30
|
+
// Bounding box for hit testing and selection
|
|
31
|
+
this.boundingBox = new BoundingBox();
|
|
32
|
+
|
|
33
|
+
// Create SVG element
|
|
34
|
+
this._createElement();
|
|
35
|
+
|
|
36
|
+
// Add starting point if provided
|
|
37
|
+
if (options.x !== undefined && options.y !== undefined) {
|
|
38
|
+
this.addPoint({
|
|
39
|
+
x: options.x,
|
|
40
|
+
y: options.y,
|
|
41
|
+
pressure: 0.5,
|
|
42
|
+
width: this.strokeWidth,
|
|
43
|
+
timestamp: Date.now()
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create the SVG path element
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
_createElement() {
|
|
53
|
+
this.jsvgPath = new jsvgPath();
|
|
54
|
+
this.element = this.jsvgPath.svgObject; // Get the underlying SVG element
|
|
55
|
+
this.jsvgPath.setFillColor('none');
|
|
56
|
+
this.jsvgPath.setStrokeColor(this.strokeColor);
|
|
57
|
+
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
58
|
+
this.element.setAttribute('stroke-opacity', this.strokeOpacity);
|
|
59
|
+
this.element.setAttribute('stroke-linecap', 'round');
|
|
60
|
+
this.element.setAttribute('stroke-linejoin', 'round');
|
|
61
|
+
this.element.setAttribute('data-stroke-id', this.id);
|
|
62
|
+
this.element.setAttribute('data-tool', this.tool);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add a point to the stroke
|
|
67
|
+
* @param {Object} point - Point data
|
|
68
|
+
* @param {number} point.x - X coordinate
|
|
69
|
+
* @param {number} point.y - Y coordinate
|
|
70
|
+
* @param {number} [point.pressure=0.5] - Pressure value
|
|
71
|
+
* @param {number} [point.width] - Stroke width at this point
|
|
72
|
+
* @param {number} [point.timestamp] - Timestamp
|
|
73
|
+
*/
|
|
74
|
+
addPoint(point) {
|
|
75
|
+
const normalizedPoint = {
|
|
76
|
+
x: point.x,
|
|
77
|
+
y: point.y,
|
|
78
|
+
pressure: point.pressure || 0.5,
|
|
79
|
+
width: point.width || this.strokeWidth,
|
|
80
|
+
timestamp: point.timestamp || Date.now()
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.points.push(normalizedPoint);
|
|
84
|
+
this._updatePath();
|
|
85
|
+
this._updateBoundingBox();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Update the SVG path based on current points
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
_updatePath() {
|
|
93
|
+
if (this.points.length === 0) {
|
|
94
|
+
this.jsvgPath.clearPoints();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.jsvgPath.clearPoints(); // Clear existing points before adding new ones
|
|
99
|
+
|
|
100
|
+
if (this.points.length === 1) {
|
|
101
|
+
// Single point - draw a small circle
|
|
102
|
+
const point = this.points[0];
|
|
103
|
+
this.jsvgPath.addPoint(point.x, point.y);
|
|
104
|
+
this.jsvgPath.addPoint(point.x + 0.1, point.y); // Add a second point for a tiny line
|
|
105
|
+
} else {
|
|
106
|
+
// Multiple points - create smooth path
|
|
107
|
+
this._generateSmoothPath();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.jsvgPath.updatePath();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generate smooth path using cubic Bézier curves
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_generateSmoothPath() {
|
|
118
|
+
if (this.points.length < 2) return '';
|
|
119
|
+
|
|
120
|
+
this.jsvgPath.addPoint(this.points[0].x, this.points[0].y);
|
|
121
|
+
|
|
122
|
+
if (this.points.length === 2) {
|
|
123
|
+
// Simple line for 2 points
|
|
124
|
+
this.jsvgPath.addPoint(this.points[1].x, this.points[1].y);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Use cubic Bézier curves for smoother paths
|
|
129
|
+
for (let i = 1; i < this.points.length - 1; i++) {
|
|
130
|
+
const prev = this.points[i - 1];
|
|
131
|
+
const curr = this.points[i];
|
|
132
|
+
const next = this.points[i + 1];
|
|
133
|
+
|
|
134
|
+
// Calculate control points for smooth curve
|
|
135
|
+
const cp1x = curr.x + (next.x - prev.x) * 0.25;
|
|
136
|
+
const cp1y = curr.y + (next.y - prev.y) * 0.25;
|
|
137
|
+
const cp2x = next.x - (next.x - curr.x) * 0.25;
|
|
138
|
+
const cp2y = next.y - (next.y - curr.y) * 0.25;
|
|
139
|
+
|
|
140
|
+
// jsvgPath doesn't directly support cubic bezier curves, so we'll approximate with more points
|
|
141
|
+
// For a true cubic bezier, we'd need to extend jsvgPath or use raw SVG path data.
|
|
142
|
+
// For now, we'll just add the next point.
|
|
143
|
+
this.jsvgPath.addPoint(next.x, next.y);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Update bounding box based on current points
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
_updateBoundingBox() {
|
|
152
|
+
if (this.points.length === 0) return;
|
|
153
|
+
|
|
154
|
+
let minX = Infinity, minY = Infinity;
|
|
155
|
+
let maxX = -Infinity, maxY = -Infinity;
|
|
156
|
+
|
|
157
|
+
this.points.forEach(point => {
|
|
158
|
+
const radius = point.width / 2;
|
|
159
|
+
minX = Math.min(minX, point.x - radius);
|
|
160
|
+
minY = Math.min(minY, point.y - radius);
|
|
161
|
+
maxX = Math.max(maxX, point.x + radius);
|
|
162
|
+
maxY = Math.max(maxY, point.y + radius);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this.boundingBox.set(minX, minY, maxX - minX, maxY - minY);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Finish the stroke (no more points will be added)
|
|
170
|
+
*/
|
|
171
|
+
finish() {
|
|
172
|
+
this.isFinished = true;
|
|
173
|
+
this.element.setAttribute('data-finished', 'true');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Set stroke selection state
|
|
178
|
+
* @param {boolean} selected - Whether stroke is selected
|
|
179
|
+
*/
|
|
180
|
+
setSelected(selected) {
|
|
181
|
+
this.isSelected = selected;
|
|
182
|
+
|
|
183
|
+
if (selected) {
|
|
184
|
+
this.element.setAttribute('stroke-dasharray', '5,5');
|
|
185
|
+
this.element.setAttribute('data-selected', 'true');
|
|
186
|
+
} else {
|
|
187
|
+
this.element.removeAttribute('stroke-dasharray');
|
|
188
|
+
this.element.removeAttribute('data-selected');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Update stroke configuration
|
|
194
|
+
* @param {Object} config - New configuration
|
|
195
|
+
*/
|
|
196
|
+
updateConfig(config) {
|
|
197
|
+
if (config.strokeColor !== undefined) {
|
|
198
|
+
this.strokeColor = config.strokeColor;
|
|
199
|
+
this.jsvgPath.setStrokeColor(this.strokeColor);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (config.strokeWidth !== undefined) {
|
|
203
|
+
this.strokeWidth = config.strokeWidth;
|
|
204
|
+
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (config.strokeOpacity !== undefined) {
|
|
208
|
+
this.strokeOpacity = config.strokeOpacity;
|
|
209
|
+
this.element.setAttribute('stroke-opacity', this.strokeOpacity);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if a point is near this stroke
|
|
215
|
+
* @param {number} x - X coordinate
|
|
216
|
+
* @param {number} y - Y coordinate
|
|
217
|
+
* @param {number} [tolerance=10] - Distance tolerance
|
|
218
|
+
* @returns {boolean} True if point is near stroke
|
|
219
|
+
*/
|
|
220
|
+
isNearPoint(x, y, tolerance = 10) {
|
|
221
|
+
// Quick bounding box check first
|
|
222
|
+
if (!this.boundingBox.containsPoint(x, y, tolerance)) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check distance to each line segment
|
|
227
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
228
|
+
const p1 = this.points[i - 1];
|
|
229
|
+
const p2 = this.points[i];
|
|
230
|
+
|
|
231
|
+
const distance = this._distanceToLineSegment(x, y, p1.x, p1.y, p2.x, p2.y);
|
|
232
|
+
if (distance <= tolerance) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Calculate distance from point to line segment
|
|
242
|
+
* @private
|
|
243
|
+
*/
|
|
244
|
+
_distanceToLineSegment(px, py, x1, y1, x2, y2) {
|
|
245
|
+
const dx = x2 - x1;
|
|
246
|
+
const dy = y2 - y1;
|
|
247
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
248
|
+
|
|
249
|
+
if (length === 0) {
|
|
250
|
+
// Points are the same
|
|
251
|
+
return Math.sqrt((px - x1) * (px - x1) + (py - y1) * (py - y1));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / (length * length)));
|
|
255
|
+
const projX = x1 + t * dx;
|
|
256
|
+
const projY = y1 + t * dy;
|
|
257
|
+
|
|
258
|
+
return Math.sqrt((px - projX) * (px - projX) + (py - projY) * (py - projY));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get stroke length
|
|
263
|
+
* @returns {number} Total stroke length
|
|
264
|
+
*/
|
|
265
|
+
getLength() {
|
|
266
|
+
let length = 0;
|
|
267
|
+
|
|
268
|
+
for (let i = 1; i < this.points.length; i++) {
|
|
269
|
+
const p1 = this.points[i - 1];
|
|
270
|
+
const p2 = this.points[i];
|
|
271
|
+
const dx = p2.x - p1.x;
|
|
272
|
+
const dy = p2.y - p1.y;
|
|
273
|
+
length += Math.sqrt(dx * dx + dy * dy);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return length;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get stroke data for serialization
|
|
281
|
+
* @returns {Object} Serializable stroke data
|
|
282
|
+
*/
|
|
283
|
+
toJSON() {
|
|
284
|
+
return {
|
|
285
|
+
id: this.id,
|
|
286
|
+
tool: this.tool,
|
|
287
|
+
strokeWidth: this.strokeWidth,
|
|
288
|
+
strokeColor: this.strokeColor,
|
|
289
|
+
strokeOpacity: this.strokeOpacity,
|
|
290
|
+
points: this.points,
|
|
291
|
+
isFinished: this.isFinished,
|
|
292
|
+
boundingBox: this.boundingBox.toJSON()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Create stroke from serialized data
|
|
298
|
+
* @param {Object} data - Serialized stroke data
|
|
299
|
+
* @returns {Stroke} New stroke instance
|
|
300
|
+
*/
|
|
301
|
+
static fromJSON(data) {
|
|
302
|
+
const stroke = new Stroke({
|
|
303
|
+
id: data.id,
|
|
304
|
+
tool: data.tool,
|
|
305
|
+
strokeWidth: data.strokeWidth,
|
|
306
|
+
strokeColor: data.strokeColor,
|
|
307
|
+
strokeOpacity: data.strokeOpacity
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Add all points
|
|
311
|
+
data.points.forEach(point => stroke.addPoint(point));
|
|
312
|
+
|
|
313
|
+
if (data.isFinished) {
|
|
314
|
+
stroke.finish();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return stroke;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate unique stroke ID
|
|
322
|
+
* @private
|
|
323
|
+
*/
|
|
324
|
+
_generateId() {
|
|
325
|
+
return `stroke_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Clone this stroke
|
|
330
|
+
* @returns {Stroke} New stroke instance
|
|
331
|
+
*/
|
|
332
|
+
clone() {
|
|
333
|
+
return Stroke.fromJSON(this.toJSON());
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get point count
|
|
338
|
+
* @returns {number} Number of points in stroke
|
|
339
|
+
*/
|
|
340
|
+
getPointCount() {
|
|
341
|
+
return this.points.length;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get bounding box
|
|
346
|
+
* @returns {BoundingBox} Stroke bounding box
|
|
347
|
+
*/
|
|
348
|
+
getBoundingBox() {
|
|
349
|
+
return this.boundingBox;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Move stroke by offset
|
|
354
|
+
* @param {number} dx - X offset
|
|
355
|
+
* @param {number} dy - Y offset
|
|
356
|
+
*/
|
|
357
|
+
move(dx, dy) {
|
|
358
|
+
this.points.forEach(point => {
|
|
359
|
+
point.x += dx;
|
|
360
|
+
point.y += dy;
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
this._updatePath();
|
|
364
|
+
this._updateBoundingBox();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Scale stroke by factor
|
|
369
|
+
* @param {number} scale - Scale factor
|
|
370
|
+
* @param {number} [originX=0] - Scale origin X
|
|
371
|
+
* @param {number} [originY=0] - Scale origin Y
|
|
372
|
+
*/
|
|
373
|
+
scale(scale, originX = 0, originY = 0) {
|
|
374
|
+
this.points.forEach(point => {
|
|
375
|
+
point.x = originX + (point.x - originX) * scale;
|
|
376
|
+
point.y = originY + (point.y - originY) * scale;
|
|
377
|
+
point.width *= scale;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
this.strokeWidth *= scale;
|
|
381
|
+
this.jsvgPath.setStrokeWidth(this.strokeWidth);
|
|
382
|
+
|
|
383
|
+
this._updatePath();
|
|
384
|
+
this._updateBoundingBox();
|
|
385
|
+
}
|
|
386
386
|
}
|