@tsdraw/core 0.6.2 → 0.7.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/dist/index.cjs +273 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +80 -2
- package/dist/index.d.ts +80 -2
- package/dist/index.js +270 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -298,11 +298,16 @@ var CanvasRenderer = class {
|
|
|
298
298
|
}
|
|
299
299
|
ctx.restore();
|
|
300
300
|
}
|
|
301
|
+
// Paints a single stroke
|
|
301
302
|
paintStroke(ctx, shape) {
|
|
302
303
|
const width = (STROKE_WIDTHS[shape.props.size] ?? 3.5) * shape.props.scale;
|
|
303
304
|
const samples = flattenSegments(shape);
|
|
304
305
|
if (samples.length === 0) return;
|
|
305
306
|
const color = resolveThemeColor(shape.props.color, this.theme);
|
|
307
|
+
const fillStyle = shape.props.fill ?? "none";
|
|
308
|
+
if (shape.props.isClosed && fillStyle !== "none") {
|
|
309
|
+
this.paintClosedShapeFill(ctx, samples, color, fillStyle);
|
|
310
|
+
}
|
|
306
311
|
if (shape.props.dash !== "draw") {
|
|
307
312
|
this.paintDashedStroke(ctx, samples, width, color, shape.props.dash);
|
|
308
313
|
return;
|
|
@@ -349,6 +354,30 @@ var CanvasRenderer = class {
|
|
|
349
354
|
ctx.stroke();
|
|
350
355
|
ctx.restore();
|
|
351
356
|
}
|
|
357
|
+
// Closed shapes are shapes where their start and end point are the same
|
|
358
|
+
paintClosedShapeFill(ctx, samples, color, fillStyle) {
|
|
359
|
+
if (samples.length < 3) return;
|
|
360
|
+
ctx.save();
|
|
361
|
+
ctx.beginPath();
|
|
362
|
+
ctx.moveTo(samples[0].x, samples[0].y);
|
|
363
|
+
for (let i = 1; i < samples.length; i++) {
|
|
364
|
+
const sample = samples[i];
|
|
365
|
+
ctx.lineTo(sample.x, sample.y);
|
|
366
|
+
}
|
|
367
|
+
ctx.closePath();
|
|
368
|
+
if (fillStyle === "solid") {
|
|
369
|
+
ctx.fillStyle = color;
|
|
370
|
+
ctx.globalAlpha = 0.55;
|
|
371
|
+
} else if (fillStyle === "none") {
|
|
372
|
+
ctx.fillStyle = this.theme === "dark" ? "#0f0f0f" : "#fafafa";
|
|
373
|
+
ctx.globalAlpha = 1;
|
|
374
|
+
} else {
|
|
375
|
+
ctx.fillStyle = color;
|
|
376
|
+
ctx.globalAlpha = 0.28;
|
|
377
|
+
}
|
|
378
|
+
ctx.fill();
|
|
379
|
+
ctx.restore();
|
|
380
|
+
}
|
|
352
381
|
};
|
|
353
382
|
var PRESSURE_FLOOR = 0.025;
|
|
354
383
|
var STYLUS_CURVE = (t) => t * 0.65 + Math.sin(t * Math.PI / 2) * 0.35;
|
|
@@ -362,7 +391,7 @@ function remap(value, inRange, outRange, clamp = false) {
|
|
|
362
391
|
return outLo + (outHi - outLo) * clamped;
|
|
363
392
|
}
|
|
364
393
|
function strokeConfig(shape, width) {
|
|
365
|
-
const done = shape.props.isComplete;
|
|
394
|
+
const done = shape.props.isComplete || shape.props.isClosed === true;
|
|
366
395
|
if (shape.props.isPen) {
|
|
367
396
|
return {
|
|
368
397
|
size: 1 + width * 1.2,
|
|
@@ -1035,6 +1064,236 @@ var PenDrawingState = class extends StateNode {
|
|
|
1035
1064
|
}
|
|
1036
1065
|
};
|
|
1037
1066
|
|
|
1067
|
+
// src/tools/square/states/SquareIdleState.ts
|
|
1068
|
+
var SquareIdleState = class extends StateNode {
|
|
1069
|
+
static id = "square_idle";
|
|
1070
|
+
onPointerDown(info) {
|
|
1071
|
+
this.ctx.transition("square_drawing", info);
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
// src/tools/geometric/states/GeometricDrawingState.ts
|
|
1076
|
+
var GeometricDrawingState = class extends StateNode {
|
|
1077
|
+
currentShapeId = null;
|
|
1078
|
+
startedAt = { point: { x: 0, y: 0, z: 0.5 } };
|
|
1079
|
+
onEnter(info) {
|
|
1080
|
+
this.startedAt = info ?? { point: { x: 0, y: 0, z: 0.5 } };
|
|
1081
|
+
const originPoint = this.editor.input.getOriginPagePoint();
|
|
1082
|
+
const drawStyle = this.editor.getCurrentDrawStyle();
|
|
1083
|
+
const nextShapeId = this.editor.createShapeId();
|
|
1084
|
+
const config = this.getConfig();
|
|
1085
|
+
this.editor.createShape({
|
|
1086
|
+
id: nextShapeId,
|
|
1087
|
+
type: "draw",
|
|
1088
|
+
x: originPoint.x,
|
|
1089
|
+
y: originPoint.y,
|
|
1090
|
+
props: {
|
|
1091
|
+
color: drawStyle.color,
|
|
1092
|
+
dash: drawStyle.dash,
|
|
1093
|
+
fill: drawStyle.fill,
|
|
1094
|
+
size: drawStyle.size,
|
|
1095
|
+
scale: 1,
|
|
1096
|
+
isPen: false,
|
|
1097
|
+
isComplete: false,
|
|
1098
|
+
isClosed: true,
|
|
1099
|
+
segments: config.buildSegments(1, 1)
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
this.currentShapeId = nextShapeId;
|
|
1103
|
+
}
|
|
1104
|
+
onPointerMove() {
|
|
1105
|
+
const activeShape = this.getActiveShape();
|
|
1106
|
+
if (!activeShape) return;
|
|
1107
|
+
const config = this.getConfig();
|
|
1108
|
+
const originPoint = this.editor.input.getOriginPagePoint();
|
|
1109
|
+
const cursorPoint = this.editor.input.getCurrentPagePoint();
|
|
1110
|
+
const shapeBounds = this.editor.input.getShiftKey() ? config.buildConstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildUnconstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y);
|
|
1111
|
+
this.editor.store.updateShape(activeShape.id, {
|
|
1112
|
+
x: shapeBounds.x,
|
|
1113
|
+
y: shapeBounds.y,
|
|
1114
|
+
props: {
|
|
1115
|
+
...activeShape.props,
|
|
1116
|
+
segments: config.buildSegments(shapeBounds.width, shapeBounds.height),
|
|
1117
|
+
isClosed: true
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
onPointerUp() {
|
|
1122
|
+
this.completeShape();
|
|
1123
|
+
}
|
|
1124
|
+
onCancel() {
|
|
1125
|
+
this.removeCurrentShape();
|
|
1126
|
+
this.ctx.transition(this.getConfig().idleStateId, this.startedAt);
|
|
1127
|
+
}
|
|
1128
|
+
onInterrupt() {
|
|
1129
|
+
this.completeShape();
|
|
1130
|
+
}
|
|
1131
|
+
onKeyDown() {
|
|
1132
|
+
this.onPointerMove();
|
|
1133
|
+
}
|
|
1134
|
+
onKeyUp() {
|
|
1135
|
+
this.onPointerMove();
|
|
1136
|
+
}
|
|
1137
|
+
completeShape() {
|
|
1138
|
+
const activeShape = this.getActiveShape();
|
|
1139
|
+
const config = this.getConfig();
|
|
1140
|
+
if (!activeShape) {
|
|
1141
|
+
this.ctx.transition(config.idleStateId, this.startedAt);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const originPoint = this.editor.input.getOriginPagePoint();
|
|
1145
|
+
const cursorPoint = this.editor.input.getCurrentPagePoint();
|
|
1146
|
+
const finalizedBounds = this.editor.input.getIsDragging() ? this.editor.input.getShiftKey() ? config.buildConstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildUnconstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildDefaultBounds(originPoint.x, originPoint.y);
|
|
1147
|
+
this.editor.store.updateShape(activeShape.id, {
|
|
1148
|
+
x: finalizedBounds.x,
|
|
1149
|
+
y: finalizedBounds.y,
|
|
1150
|
+
props: {
|
|
1151
|
+
...activeShape.props,
|
|
1152
|
+
fill: this.editor.getCurrentDrawStyle().fill,
|
|
1153
|
+
isComplete: true,
|
|
1154
|
+
isClosed: true,
|
|
1155
|
+
segments: config.buildSegments(finalizedBounds.width, finalizedBounds.height)
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
this.currentShapeId = null;
|
|
1159
|
+
this.ctx.transition(config.idleStateId);
|
|
1160
|
+
}
|
|
1161
|
+
removeCurrentShape() {
|
|
1162
|
+
if (!this.currentShapeId) return;
|
|
1163
|
+
this.editor.store.deleteShapes([this.currentShapeId]);
|
|
1164
|
+
this.currentShapeId = null;
|
|
1165
|
+
}
|
|
1166
|
+
getActiveShape() {
|
|
1167
|
+
if (!this.currentShapeId) return null;
|
|
1168
|
+
const shape = this.editor.getShape(this.currentShapeId);
|
|
1169
|
+
if (!shape || shape.type !== "draw") return null;
|
|
1170
|
+
return shape;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// src/tools/geometric/geometricShapeHelpers.ts
|
|
1175
|
+
var MIN_SIDE_LENGTH = 1;
|
|
1176
|
+
var DEFAULT_RECTANGLE_WIDTH = 180;
|
|
1177
|
+
var DEFAULT_RECTANGLE_HEIGHT = 120;
|
|
1178
|
+
var DEFAULT_ELLIPSE_WIDTH = 180;
|
|
1179
|
+
var DEFAULT_ELLIPSE_HEIGHT = 120;
|
|
1180
|
+
function toSizedBounds(anchorX, anchorY, cursorX, cursorY, forceEqualSides) {
|
|
1181
|
+
const rawDeltaX = cursorX - anchorX;
|
|
1182
|
+
const rawDeltaY = cursorY - anchorY;
|
|
1183
|
+
const sideLength = Math.max(Math.abs(rawDeltaX), Math.abs(rawDeltaY), MIN_SIDE_LENGTH);
|
|
1184
|
+
if (forceEqualSides) {
|
|
1185
|
+
const nextDeltaX = rawDeltaX < 0 ? -sideLength : sideLength;
|
|
1186
|
+
const nextDeltaY = rawDeltaY < 0 ? -sideLength : sideLength;
|
|
1187
|
+
return normalizeBounds(anchorX, anchorY, anchorX + nextDeltaX, anchorY + nextDeltaY);
|
|
1188
|
+
}
|
|
1189
|
+
return normalizeBounds(anchorX, anchorY, cursorX, cursorY);
|
|
1190
|
+
}
|
|
1191
|
+
function normalizeBounds(startX, startY, endX, endY) {
|
|
1192
|
+
const x = Math.min(startX, endX);
|
|
1193
|
+
const y = Math.min(startY, endY);
|
|
1194
|
+
const width = Math.max(Math.abs(endX - startX), MIN_SIDE_LENGTH);
|
|
1195
|
+
const height = Math.max(Math.abs(endY - startY), MIN_SIDE_LENGTH);
|
|
1196
|
+
return { x, y, width, height };
|
|
1197
|
+
}
|
|
1198
|
+
function buildSquareBounds(anchorX, anchorY, cursorX, cursorY) {
|
|
1199
|
+
return toSizedBounds(anchorX, anchorY, cursorX, cursorY, true);
|
|
1200
|
+
}
|
|
1201
|
+
function buildRectangleBounds(anchorX, anchorY, cursorX, cursorY) {
|
|
1202
|
+
return toSizedBounds(anchorX, anchorY, cursorX, cursorY, false);
|
|
1203
|
+
}
|
|
1204
|
+
function buildDefaultCenteredRectangleBounds(centerX, centerY) {
|
|
1205
|
+
const halfWidth = DEFAULT_RECTANGLE_WIDTH / 2;
|
|
1206
|
+
const halfHeight = DEFAULT_RECTANGLE_HEIGHT / 2;
|
|
1207
|
+
return {
|
|
1208
|
+
x: centerX - halfWidth,
|
|
1209
|
+
y: centerY - halfHeight,
|
|
1210
|
+
width: DEFAULT_RECTANGLE_WIDTH,
|
|
1211
|
+
height: DEFAULT_RECTANGLE_HEIGHT
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function buildRectangleSegments(width, height) {
|
|
1215
|
+
const topLeft = { x: 0, y: 0, z: 0.5 };
|
|
1216
|
+
const topRight = { x: width, y: 0, z: 0.5 };
|
|
1217
|
+
const bottomRight = { x: width, y: height, z: 0.5 };
|
|
1218
|
+
const bottomLeft = { x: 0, y: height, z: 0.5 };
|
|
1219
|
+
return [
|
|
1220
|
+
{ type: "straight", path: encodePoints([topLeft, topRight]) },
|
|
1221
|
+
{ type: "straight", path: encodePoints([topRight, bottomRight]) },
|
|
1222
|
+
{ type: "straight", path: encodePoints([bottomRight, bottomLeft]) },
|
|
1223
|
+
{ type: "straight", path: encodePoints([bottomLeft, topLeft]) }
|
|
1224
|
+
];
|
|
1225
|
+
}
|
|
1226
|
+
function buildCircleBounds(anchorX, anchorY, cursorX, cursorY) {
|
|
1227
|
+
return toSizedBounds(anchorX, anchorY, cursorX, cursorY, true);
|
|
1228
|
+
}
|
|
1229
|
+
function buildEllipseBounds(anchorX, anchorY, cursorX, cursorY) {
|
|
1230
|
+
return toSizedBounds(anchorX, anchorY, cursorX, cursorY, false);
|
|
1231
|
+
}
|
|
1232
|
+
function buildDefaultCenteredEllipseBounds(centerX, centerY) {
|
|
1233
|
+
const halfWidth = DEFAULT_ELLIPSE_WIDTH / 2;
|
|
1234
|
+
const halfHeight = DEFAULT_ELLIPSE_HEIGHT / 2;
|
|
1235
|
+
return {
|
|
1236
|
+
x: centerX - halfWidth,
|
|
1237
|
+
y: centerY - halfHeight,
|
|
1238
|
+
width: DEFAULT_ELLIPSE_WIDTH,
|
|
1239
|
+
height: DEFAULT_ELLIPSE_HEIGHT
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function buildEllipseSegments(width, height) {
|
|
1243
|
+
const centerX = width / 2;
|
|
1244
|
+
const centerY = height / 2;
|
|
1245
|
+
const radiusX = width / 2;
|
|
1246
|
+
const radiusY = height / 2;
|
|
1247
|
+
const sampleCount = 64;
|
|
1248
|
+
const sampledPoints = [];
|
|
1249
|
+
for (let sampleIndex = 0; sampleIndex <= sampleCount; sampleIndex += 1) {
|
|
1250
|
+
const progress = sampleIndex / sampleCount;
|
|
1251
|
+
const angle = progress * Math.PI * 2;
|
|
1252
|
+
sampledPoints.push({
|
|
1253
|
+
x: centerX + Math.cos(angle) * radiusX,
|
|
1254
|
+
y: centerY + Math.sin(angle) * radiusY,
|
|
1255
|
+
z: 0.5
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
return [{ type: "free", path: encodePoints(sampledPoints) }];
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/tools/square/states/SquareDrawingState.ts
|
|
1262
|
+
var SquareDrawingState = class extends GeometricDrawingState {
|
|
1263
|
+
static id = "square_drawing";
|
|
1264
|
+
getConfig() {
|
|
1265
|
+
return {
|
|
1266
|
+
idleStateId: "square_idle",
|
|
1267
|
+
buildConstrainedBounds: buildSquareBounds,
|
|
1268
|
+
buildUnconstrainedBounds: buildRectangleBounds,
|
|
1269
|
+
buildDefaultBounds: buildDefaultCenteredRectangleBounds,
|
|
1270
|
+
buildSegments: buildRectangleSegments
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
// src/tools/circle/states/CircleIdleState.ts
|
|
1276
|
+
var CircleIdleState = class extends StateNode {
|
|
1277
|
+
static id = "circle_idle";
|
|
1278
|
+
onPointerDown(info) {
|
|
1279
|
+
this.ctx.transition("circle_drawing", info);
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
// src/tools/circle/states/CircleDrawingState.ts
|
|
1284
|
+
var CircleDrawingState = class extends GeometricDrawingState {
|
|
1285
|
+
static id = "circle_drawing";
|
|
1286
|
+
getConfig() {
|
|
1287
|
+
return {
|
|
1288
|
+
idleStateId: "circle_idle",
|
|
1289
|
+
buildConstrainedBounds: buildCircleBounds,
|
|
1290
|
+
buildUnconstrainedBounds: buildEllipseBounds,
|
|
1291
|
+
buildDefaultBounds: buildDefaultCenteredEllipseBounds,
|
|
1292
|
+
buildSegments: buildEllipseSegments
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1038
1297
|
// src/tools/eraser/states/EraserIdleState.ts
|
|
1039
1298
|
var EraserIdleState = class extends StateNode {
|
|
1040
1299
|
static id = "eraser_idle";
|
|
@@ -1348,6 +1607,7 @@ var Editor = class {
|
|
|
1348
1607
|
drawStyle = {
|
|
1349
1608
|
color: "black",
|
|
1350
1609
|
dash: "draw",
|
|
1610
|
+
fill: "none",
|
|
1351
1611
|
size: "m"
|
|
1352
1612
|
};
|
|
1353
1613
|
toolStateContext;
|
|
@@ -1407,6 +1667,8 @@ var Editor = class {
|
|
|
1407
1667
|
getDefaultToolDefinitions() {
|
|
1408
1668
|
return [
|
|
1409
1669
|
{ id: "pen", initialStateId: PenIdleState.id, stateConstructors: [PenIdleState, PenDrawingState] },
|
|
1670
|
+
{ id: "square", initialStateId: SquareIdleState.id, stateConstructors: [SquareIdleState, SquareDrawingState] },
|
|
1671
|
+
{ id: "circle", initialStateId: CircleIdleState.id, stateConstructors: [CircleIdleState, CircleDrawingState] },
|
|
1410
1672
|
{ id: "eraser", initialStateId: EraserIdleState.id, stateConstructors: [EraserIdleState, EraserPointingState, EraserErasingState] },
|
|
1411
1673
|
{ id: "select", initialStateId: SelectIdleState.id, stateConstructors: [SelectIdleState] },
|
|
1412
1674
|
{ id: "hand", initialStateId: HandIdleState.id, stateConstructors: [HandIdleState, HandDraggingState] }
|
|
@@ -1511,7 +1773,12 @@ var Editor = class {
|
|
|
1511
1773
|
}
|
|
1512
1774
|
loadSessionStateSnapshot(snapshot) {
|
|
1513
1775
|
this.setViewport(snapshot.viewport);
|
|
1514
|
-
this.setCurrentDrawStyle(
|
|
1776
|
+
this.setCurrentDrawStyle({
|
|
1777
|
+
color: snapshot.drawStyle.color,
|
|
1778
|
+
dash: snapshot.drawStyle.dash,
|
|
1779
|
+
fill: snapshot.drawStyle.fill ?? "none",
|
|
1780
|
+
size: snapshot.drawStyle.size
|
|
1781
|
+
});
|
|
1515
1782
|
if (this.tools.hasTool(snapshot.currentToolId)) {
|
|
1516
1783
|
this.setCurrentTool(snapshot.currentToolId);
|
|
1517
1784
|
}
|
|
@@ -1893,6 +2160,8 @@ function applyResize(editor, handle, startBounds, startShapes, pointer, lockAspe
|
|
|
1893
2160
|
}
|
|
1894
2161
|
|
|
1895
2162
|
exports.CanvasRenderer = CanvasRenderer;
|
|
2163
|
+
exports.CircleDrawingState = CircleDrawingState;
|
|
2164
|
+
exports.CircleIdleState = CircleIdleState;
|
|
1896
2165
|
exports.DEFAULT_COLORS = DEFAULT_COLORS;
|
|
1897
2166
|
exports.DRAG_DISTANCE_SQUARED = DRAG_DISTANCE_SQUARED;
|
|
1898
2167
|
exports.DocumentStore = DocumentStore;
|
|
@@ -1909,6 +2178,8 @@ exports.PenDrawingState = PenDrawingState;
|
|
|
1909
2178
|
exports.PenIdleState = PenIdleState;
|
|
1910
2179
|
exports.STROKE_WIDTHS = STROKE_WIDTHS;
|
|
1911
2180
|
exports.SelectIdleState = SelectIdleState;
|
|
2181
|
+
exports.SquareDrawingState = SquareDrawingState;
|
|
2182
|
+
exports.SquareIdleState = SquareIdleState;
|
|
1912
2183
|
exports.StateNode = StateNode;
|
|
1913
2184
|
exports.ToolManager = ToolManager;
|
|
1914
2185
|
exports.applyMove = applyMove;
|