@fieldnotes/core 0.28.0 → 0.30.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/README.md +595 -583
- package/dist/index.cjs +179 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +179 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -27,6 +27,8 @@ interface StrokeElement extends BaseElement {
|
|
|
27
27
|
color: string;
|
|
28
28
|
width: number;
|
|
29
29
|
opacity: number;
|
|
30
|
+
/** Optional canvas blend mode (e.g. highlighter uses 'multiply'). */
|
|
31
|
+
blendMode?: 'multiply';
|
|
30
32
|
}
|
|
31
33
|
interface NoteElement extends BaseElement {
|
|
32
34
|
type: 'note';
|
|
@@ -72,7 +74,7 @@ interface TextElement extends BaseElement {
|
|
|
72
74
|
color: string;
|
|
73
75
|
textAlign: 'left' | 'center' | 'right';
|
|
74
76
|
}
|
|
75
|
-
type ShapeKind = 'rectangle' | 'ellipse';
|
|
77
|
+
type ShapeKind = 'rectangle' | 'ellipse' | 'line';
|
|
76
78
|
interface ShapeElement extends BaseElement {
|
|
77
79
|
type: 'shape';
|
|
78
80
|
shape: ShapeKind;
|
|
@@ -80,6 +82,8 @@ interface ShapeElement extends BaseElement {
|
|
|
80
82
|
strokeColor: string;
|
|
81
83
|
strokeWidth: number;
|
|
82
84
|
fillColor: string;
|
|
85
|
+
/** Line-only: which bbox diagonal the segment runs along. Absent/false = main diagonal. */
|
|
86
|
+
flip?: boolean;
|
|
83
87
|
}
|
|
84
88
|
type HexOrientation = 'pointy' | 'flat';
|
|
85
89
|
interface GridElement extends BaseElement {
|
|
@@ -562,6 +566,7 @@ interface StrokeInput extends BaseDefaults {
|
|
|
562
566
|
color?: string;
|
|
563
567
|
width?: number;
|
|
564
568
|
opacity?: number;
|
|
569
|
+
blendMode?: 'multiply';
|
|
565
570
|
}
|
|
566
571
|
interface NoteInput extends BaseDefaults {
|
|
567
572
|
position: Point;
|
|
@@ -612,6 +617,7 @@ interface ShapeInput extends BaseDefaults {
|
|
|
612
617
|
strokeColor?: string;
|
|
613
618
|
strokeWidth?: number;
|
|
614
619
|
fillColor?: string;
|
|
620
|
+
flip?: boolean;
|
|
615
621
|
}
|
|
616
622
|
declare function createShape(input: ShapeInput): ShapeElement;
|
|
617
623
|
interface GridInput extends BaseDefaults {
|
|
@@ -669,14 +675,17 @@ declare class HandTool implements Tool {
|
|
|
669
675
|
}
|
|
670
676
|
|
|
671
677
|
interface PencilToolOptions {
|
|
678
|
+
name?: string;
|
|
672
679
|
color?: string;
|
|
673
680
|
width?: number;
|
|
674
681
|
smoothing?: number;
|
|
675
682
|
minPointDistance?: number;
|
|
676
683
|
progressiveSimplifyThreshold?: number;
|
|
684
|
+
opacity?: number;
|
|
685
|
+
blendMode?: 'multiply';
|
|
677
686
|
}
|
|
678
687
|
declare class PencilTool implements Tool {
|
|
679
|
-
readonly name
|
|
688
|
+
readonly name: string;
|
|
680
689
|
private drawing;
|
|
681
690
|
private points;
|
|
682
691
|
private color;
|
|
@@ -685,6 +694,8 @@ declare class PencilTool implements Tool {
|
|
|
685
694
|
private minPointDistance;
|
|
686
695
|
private progressiveThreshold;
|
|
687
696
|
private nextSimplifyAt;
|
|
697
|
+
private opacity;
|
|
698
|
+
private blendMode;
|
|
688
699
|
private optionListeners;
|
|
689
700
|
constructor(options?: PencilToolOptions);
|
|
690
701
|
onActivate(ctx: ToolContext): void;
|
|
@@ -701,14 +712,17 @@ declare class PencilTool implements Tool {
|
|
|
701
712
|
|
|
702
713
|
interface EraserToolOptions {
|
|
703
714
|
radius?: number;
|
|
715
|
+
mode?: 'partial' | 'stroke';
|
|
704
716
|
}
|
|
705
717
|
declare class EraserTool implements Tool {
|
|
706
718
|
readonly name = "eraser";
|
|
707
719
|
private erasing;
|
|
708
|
-
private
|
|
709
|
-
private
|
|
720
|
+
private radius;
|
|
721
|
+
private cursor;
|
|
722
|
+
private mode;
|
|
710
723
|
constructor(options?: EraserToolOptions);
|
|
711
724
|
getOptions(): EraserToolOptions;
|
|
725
|
+
setOptions(options: EraserToolOptions): void;
|
|
712
726
|
onActivate(ctx: ToolContext): void;
|
|
713
727
|
onDeactivate(ctx: ToolContext): void;
|
|
714
728
|
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
@@ -957,6 +971,6 @@ declare class TemplateTool implements Tool {
|
|
|
957
971
|
private notifyOptionsChange;
|
|
958
972
|
}
|
|
959
973
|
|
|
960
|
-
declare const VERSION = "0.
|
|
974
|
+
declare const VERSION = "0.30.0";
|
|
961
975
|
|
|
962
976
|
export { type ActiveFormats, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ interface StrokeElement extends BaseElement {
|
|
|
27
27
|
color: string;
|
|
28
28
|
width: number;
|
|
29
29
|
opacity: number;
|
|
30
|
+
/** Optional canvas blend mode (e.g. highlighter uses 'multiply'). */
|
|
31
|
+
blendMode?: 'multiply';
|
|
30
32
|
}
|
|
31
33
|
interface NoteElement extends BaseElement {
|
|
32
34
|
type: 'note';
|
|
@@ -72,7 +74,7 @@ interface TextElement extends BaseElement {
|
|
|
72
74
|
color: string;
|
|
73
75
|
textAlign: 'left' | 'center' | 'right';
|
|
74
76
|
}
|
|
75
|
-
type ShapeKind = 'rectangle' | 'ellipse';
|
|
77
|
+
type ShapeKind = 'rectangle' | 'ellipse' | 'line';
|
|
76
78
|
interface ShapeElement extends BaseElement {
|
|
77
79
|
type: 'shape';
|
|
78
80
|
shape: ShapeKind;
|
|
@@ -80,6 +82,8 @@ interface ShapeElement extends BaseElement {
|
|
|
80
82
|
strokeColor: string;
|
|
81
83
|
strokeWidth: number;
|
|
82
84
|
fillColor: string;
|
|
85
|
+
/** Line-only: which bbox diagonal the segment runs along. Absent/false = main diagonal. */
|
|
86
|
+
flip?: boolean;
|
|
83
87
|
}
|
|
84
88
|
type HexOrientation = 'pointy' | 'flat';
|
|
85
89
|
interface GridElement extends BaseElement {
|
|
@@ -562,6 +566,7 @@ interface StrokeInput extends BaseDefaults {
|
|
|
562
566
|
color?: string;
|
|
563
567
|
width?: number;
|
|
564
568
|
opacity?: number;
|
|
569
|
+
blendMode?: 'multiply';
|
|
565
570
|
}
|
|
566
571
|
interface NoteInput extends BaseDefaults {
|
|
567
572
|
position: Point;
|
|
@@ -612,6 +617,7 @@ interface ShapeInput extends BaseDefaults {
|
|
|
612
617
|
strokeColor?: string;
|
|
613
618
|
strokeWidth?: number;
|
|
614
619
|
fillColor?: string;
|
|
620
|
+
flip?: boolean;
|
|
615
621
|
}
|
|
616
622
|
declare function createShape(input: ShapeInput): ShapeElement;
|
|
617
623
|
interface GridInput extends BaseDefaults {
|
|
@@ -669,14 +675,17 @@ declare class HandTool implements Tool {
|
|
|
669
675
|
}
|
|
670
676
|
|
|
671
677
|
interface PencilToolOptions {
|
|
678
|
+
name?: string;
|
|
672
679
|
color?: string;
|
|
673
680
|
width?: number;
|
|
674
681
|
smoothing?: number;
|
|
675
682
|
minPointDistance?: number;
|
|
676
683
|
progressiveSimplifyThreshold?: number;
|
|
684
|
+
opacity?: number;
|
|
685
|
+
blendMode?: 'multiply';
|
|
677
686
|
}
|
|
678
687
|
declare class PencilTool implements Tool {
|
|
679
|
-
readonly name
|
|
688
|
+
readonly name: string;
|
|
680
689
|
private drawing;
|
|
681
690
|
private points;
|
|
682
691
|
private color;
|
|
@@ -685,6 +694,8 @@ declare class PencilTool implements Tool {
|
|
|
685
694
|
private minPointDistance;
|
|
686
695
|
private progressiveThreshold;
|
|
687
696
|
private nextSimplifyAt;
|
|
697
|
+
private opacity;
|
|
698
|
+
private blendMode;
|
|
688
699
|
private optionListeners;
|
|
689
700
|
constructor(options?: PencilToolOptions);
|
|
690
701
|
onActivate(ctx: ToolContext): void;
|
|
@@ -701,14 +712,17 @@ declare class PencilTool implements Tool {
|
|
|
701
712
|
|
|
702
713
|
interface EraserToolOptions {
|
|
703
714
|
radius?: number;
|
|
715
|
+
mode?: 'partial' | 'stroke';
|
|
704
716
|
}
|
|
705
717
|
declare class EraserTool implements Tool {
|
|
706
718
|
readonly name = "eraser";
|
|
707
719
|
private erasing;
|
|
708
|
-
private
|
|
709
|
-
private
|
|
720
|
+
private radius;
|
|
721
|
+
private cursor;
|
|
722
|
+
private mode;
|
|
710
723
|
constructor(options?: EraserToolOptions);
|
|
711
724
|
getOptions(): EraserToolOptions;
|
|
725
|
+
setOptions(options: EraserToolOptions): void;
|
|
712
726
|
onActivate(ctx: ToolContext): void;
|
|
713
727
|
onDeactivate(ctx: ToolContext): void;
|
|
714
728
|
onPointerDown(state: PointerState, ctx: ToolContext): void;
|
|
@@ -957,6 +971,6 @@ declare class TemplateTool implements Tool {
|
|
|
957
971
|
private notifyOptionsChange;
|
|
958
972
|
}
|
|
959
973
|
|
|
960
|
-
declare const VERSION = "0.
|
|
974
|
+
declare const VERSION = "0.30.0";
|
|
961
975
|
|
|
962
976
|
export { type ActiveFormats, type ArrowElement, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraChangeInfo, type CameraOptions, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportImageOptions, type FontSizePreset, type GridElement, type GridInfo, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HtmlElement, type ImageElement, ImageTool, type ImageToolOptions, type Layer, LayerManager, MeasureTool, type MeasureToolOptions, type Measurement, type NoteElement, NoteTool, type NoteToolOptions, PencilTool, type PencilToolOptions, type Point, type PointerState, type RenderStatsSnapshot, SelectTool, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, boundsIntersect, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, exportImage, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInSquare, getHexDistance, isNearBezier, setFontSize, smartSnap, snapPoint, snapToHexCenter, styleToPatch, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
|
package/dist/index.js
CHANGED
|
@@ -2195,6 +2195,19 @@ function getArrowRenderGeometry(arrow) {
|
|
|
2195
2195
|
return geometry;
|
|
2196
2196
|
}
|
|
2197
2197
|
|
|
2198
|
+
// src/elements/shape-geometry.ts
|
|
2199
|
+
function lineEndpoints(shape) {
|
|
2200
|
+
const { x, y } = shape.position;
|
|
2201
|
+
const { w, h } = shape.size;
|
|
2202
|
+
return shape.flip ? [
|
|
2203
|
+
{ x, y: y + h },
|
|
2204
|
+
{ x: x + w, y }
|
|
2205
|
+
] : [
|
|
2206
|
+
{ x, y },
|
|
2207
|
+
{ x: x + w, y: y + h }
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2198
2211
|
// src/elements/arrow-binding.ts
|
|
2199
2212
|
var BINDABLE_TYPES = /* @__PURE__ */ new Set(["note", "text", "image", "html", "shape"]);
|
|
2200
2213
|
function isBindable(element) {
|
|
@@ -2719,6 +2732,7 @@ var ElementRenderer = class {
|
|
|
2719
2732
|
renderStroke(ctx, stroke) {
|
|
2720
2733
|
if (stroke.points.length < 2) return;
|
|
2721
2734
|
ctx.save();
|
|
2735
|
+
if (stroke.blendMode) ctx.globalCompositeOperation = stroke.blendMode;
|
|
2722
2736
|
ctx.translate(stroke.position.x, stroke.position.y);
|
|
2723
2737
|
ctx.strokeStyle = stroke.color;
|
|
2724
2738
|
ctx.lineCap = "round";
|
|
@@ -2841,7 +2855,7 @@ var ElementRenderer = class {
|
|
|
2841
2855
|
}
|
|
2842
2856
|
renderShape(ctx, shape) {
|
|
2843
2857
|
ctx.save();
|
|
2844
|
-
if (shape.fillColor !== "none") {
|
|
2858
|
+
if (shape.fillColor !== "none" && shape.shape !== "line") {
|
|
2845
2859
|
ctx.fillStyle = shape.fillColor;
|
|
2846
2860
|
this.fillShapePath(ctx, shape);
|
|
2847
2861
|
}
|
|
@@ -2880,6 +2894,15 @@ var ElementRenderer = class {
|
|
|
2880
2894
|
ctx.stroke();
|
|
2881
2895
|
break;
|
|
2882
2896
|
}
|
|
2897
|
+
case "line": {
|
|
2898
|
+
const [a, b] = lineEndpoints(shape);
|
|
2899
|
+
ctx.lineCap = "round";
|
|
2900
|
+
ctx.beginPath();
|
|
2901
|
+
ctx.moveTo(a.x, a.y);
|
|
2902
|
+
ctx.lineTo(b.x, b.y);
|
|
2903
|
+
ctx.stroke();
|
|
2904
|
+
break;
|
|
2905
|
+
}
|
|
2883
2906
|
}
|
|
2884
2907
|
}
|
|
2885
2908
|
renderGrid(ctx, grid) {
|
|
@@ -3153,7 +3176,7 @@ var ElementRenderer = class {
|
|
|
3153
3176
|
// src/elements/element-factory.ts
|
|
3154
3177
|
var DEFAULT_NOTE_FONT_SIZE = 18;
|
|
3155
3178
|
function createStroke(input) {
|
|
3156
|
-
|
|
3179
|
+
const result = {
|
|
3157
3180
|
id: createId("stroke"),
|
|
3158
3181
|
type: "stroke",
|
|
3159
3182
|
position: input.position ?? { x: 0, y: 0 },
|
|
@@ -3165,6 +3188,8 @@ function createStroke(input) {
|
|
|
3165
3188
|
width: input.width ?? 2,
|
|
3166
3189
|
opacity: input.opacity ?? 1
|
|
3167
3190
|
};
|
|
3191
|
+
if (input.blendMode) result.blendMode = input.blendMode;
|
|
3192
|
+
return result;
|
|
3168
3193
|
}
|
|
3169
3194
|
function createNote(input) {
|
|
3170
3195
|
return {
|
|
@@ -3229,7 +3254,7 @@ function createHtmlElement(input) {
|
|
|
3229
3254
|
return el;
|
|
3230
3255
|
}
|
|
3231
3256
|
function createShape(input) {
|
|
3232
|
-
|
|
3257
|
+
const result = {
|
|
3233
3258
|
id: createId("shape"),
|
|
3234
3259
|
type: "shape",
|
|
3235
3260
|
position: input.position,
|
|
@@ -3242,6 +3267,8 @@ function createShape(input) {
|
|
|
3242
3267
|
strokeWidth: input.strokeWidth ?? 2,
|
|
3243
3268
|
fillColor: input.fillColor ?? "none"
|
|
3244
3269
|
};
|
|
3270
|
+
if (input.flip) result.flip = input.flip;
|
|
3271
|
+
return result;
|
|
3245
3272
|
}
|
|
3246
3273
|
function createGrid(input) {
|
|
3247
3274
|
return {
|
|
@@ -6072,7 +6099,7 @@ var DEFAULT_MIN_POINT_DISTANCE = 3;
|
|
|
6072
6099
|
var DEFAULT_PROGRESSIVE_THRESHOLD = 200;
|
|
6073
6100
|
var PROGRESSIVE_HOT_ZONE = 30;
|
|
6074
6101
|
var PencilTool = class {
|
|
6075
|
-
name
|
|
6102
|
+
name;
|
|
6076
6103
|
drawing = false;
|
|
6077
6104
|
points = [];
|
|
6078
6105
|
color;
|
|
@@ -6081,14 +6108,19 @@ var PencilTool = class {
|
|
|
6081
6108
|
minPointDistance;
|
|
6082
6109
|
progressiveThreshold;
|
|
6083
6110
|
nextSimplifyAt;
|
|
6111
|
+
opacity;
|
|
6112
|
+
blendMode;
|
|
6084
6113
|
optionListeners = /* @__PURE__ */ new Set();
|
|
6085
6114
|
constructor(options = {}) {
|
|
6115
|
+
this.name = options.name ?? "pencil";
|
|
6086
6116
|
this.color = options.color ?? "#000000";
|
|
6087
6117
|
this.width = options.width ?? 2;
|
|
6088
6118
|
this.smoothing = options.smoothing ?? DEFAULT_SMOOTHING;
|
|
6089
6119
|
this.minPointDistance = options.minPointDistance ?? DEFAULT_MIN_POINT_DISTANCE;
|
|
6090
6120
|
this.progressiveThreshold = options.progressiveSimplifyThreshold ?? DEFAULT_PROGRESSIVE_THRESHOLD;
|
|
6091
6121
|
this.nextSimplifyAt = this.progressiveThreshold;
|
|
6122
|
+
this.opacity = options.opacity ?? 1;
|
|
6123
|
+
this.blendMode = options.blendMode;
|
|
6092
6124
|
}
|
|
6093
6125
|
onActivate(ctx) {
|
|
6094
6126
|
ctx.setCursor?.("crosshair");
|
|
@@ -6102,7 +6134,9 @@ var PencilTool = class {
|
|
|
6102
6134
|
width: this.width,
|
|
6103
6135
|
smoothing: this.smoothing,
|
|
6104
6136
|
minPointDistance: this.minPointDistance,
|
|
6105
|
-
progressiveSimplifyThreshold: this.progressiveThreshold
|
|
6137
|
+
progressiveSimplifyThreshold: this.progressiveThreshold,
|
|
6138
|
+
opacity: this.opacity,
|
|
6139
|
+
blendMode: this.blendMode
|
|
6106
6140
|
};
|
|
6107
6141
|
}
|
|
6108
6142
|
onOptionsChange(listener) {
|
|
@@ -6116,6 +6150,8 @@ var PencilTool = class {
|
|
|
6116
6150
|
if (options.minPointDistance !== void 0) this.minPointDistance = options.minPointDistance;
|
|
6117
6151
|
if (options.progressiveSimplifyThreshold !== void 0)
|
|
6118
6152
|
this.progressiveThreshold = options.progressiveSimplifyThreshold;
|
|
6153
|
+
if (options.opacity !== void 0) this.opacity = options.opacity;
|
|
6154
|
+
if (options.blendMode !== void 0) this.blendMode = options.blendMode;
|
|
6119
6155
|
this.notifyOptionsChange();
|
|
6120
6156
|
}
|
|
6121
6157
|
onPointerDown(state, ctx) {
|
|
@@ -6157,7 +6193,9 @@ var PencilTool = class {
|
|
|
6157
6193
|
points: simplified,
|
|
6158
6194
|
color: this.color,
|
|
6159
6195
|
width: this.width,
|
|
6160
|
-
layerId: ctx.activeLayerId ?? ""
|
|
6196
|
+
layerId: ctx.activeLayerId ?? "",
|
|
6197
|
+
opacity: this.opacity,
|
|
6198
|
+
blendMode: this.blendMode
|
|
6161
6199
|
});
|
|
6162
6200
|
ctx.store.add(stroke);
|
|
6163
6201
|
computeStrokeSegments(stroke);
|
|
@@ -6173,7 +6211,8 @@ var PencilTool = class {
|
|
|
6173
6211
|
ctx.strokeStyle = this.color;
|
|
6174
6212
|
ctx.lineCap = "round";
|
|
6175
6213
|
ctx.lineJoin = "round";
|
|
6176
|
-
ctx.globalAlpha = 0.8;
|
|
6214
|
+
ctx.globalAlpha = this.blendMode ? this.opacity : 0.8;
|
|
6215
|
+
if (this.blendMode) ctx.globalCompositeOperation = this.blendMode;
|
|
6177
6216
|
const segments = smoothToSegments(this.points);
|
|
6178
6217
|
for (const seg of segments) {
|
|
6179
6218
|
const w = (pressureToWidth(seg.start.pressure, this.width) + pressureToWidth(seg.end.pressure, this.width)) / 2;
|
|
@@ -6210,6 +6249,79 @@ function hitTestStroke(stroke, point, radius) {
|
|
|
6210
6249
|
return false;
|
|
6211
6250
|
}
|
|
6212
6251
|
|
|
6252
|
+
// src/elements/stroke-erase.ts
|
|
6253
|
+
function lerp(a, b, t) {
|
|
6254
|
+
return {
|
|
6255
|
+
x: a.x + (b.x - a.x) * t,
|
|
6256
|
+
y: a.y + (b.y - a.y) * t,
|
|
6257
|
+
pressure: a.pressure + (b.pressure - a.pressure) * t
|
|
6258
|
+
};
|
|
6259
|
+
}
|
|
6260
|
+
function erasePoints(points, eraser, radius) {
|
|
6261
|
+
const r2 = radius * radius;
|
|
6262
|
+
if (points.length < 2) {
|
|
6263
|
+
const p = points[0];
|
|
6264
|
+
if (p && (p.x - eraser.x) ** 2 + (p.y - eraser.y) ** 2 <= r2) return [];
|
|
6265
|
+
return null;
|
|
6266
|
+
}
|
|
6267
|
+
const runs = [];
|
|
6268
|
+
let current = [];
|
|
6269
|
+
let erased = false;
|
|
6270
|
+
const flush = () => {
|
|
6271
|
+
if (current.length >= 2) runs.push(current);
|
|
6272
|
+
current = [];
|
|
6273
|
+
};
|
|
6274
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
6275
|
+
const a = points[i];
|
|
6276
|
+
const b = points[i + 1];
|
|
6277
|
+
if (!a || !b) continue;
|
|
6278
|
+
const dx = b.x - a.x;
|
|
6279
|
+
const dy = b.y - a.y;
|
|
6280
|
+
const fx = a.x - eraser.x;
|
|
6281
|
+
const fy = a.y - eraser.y;
|
|
6282
|
+
const A = dx * dx + dy * dy;
|
|
6283
|
+
const B = 2 * (fx * dx + fy * dy);
|
|
6284
|
+
const C = fx * fx + fy * fy - r2;
|
|
6285
|
+
let tLo = 1;
|
|
6286
|
+
let tHi = 0;
|
|
6287
|
+
if (A === 0) {
|
|
6288
|
+
if (C <= 0) {
|
|
6289
|
+
tLo = 0;
|
|
6290
|
+
tHi = 1;
|
|
6291
|
+
}
|
|
6292
|
+
} else {
|
|
6293
|
+
const disc = B * B - 4 * A * C;
|
|
6294
|
+
if (disc >= 0) {
|
|
6295
|
+
const sq = Math.sqrt(disc);
|
|
6296
|
+
const lo = Math.max(0, (-B - sq) / (2 * A));
|
|
6297
|
+
const hi = Math.min(1, (-B + sq) / (2 * A));
|
|
6298
|
+
if (lo < hi) {
|
|
6299
|
+
tLo = lo;
|
|
6300
|
+
tHi = hi;
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
if (tLo > tHi) {
|
|
6305
|
+
if (current.length === 0) current.push(a);
|
|
6306
|
+
current.push(b);
|
|
6307
|
+
continue;
|
|
6308
|
+
}
|
|
6309
|
+
erased = true;
|
|
6310
|
+
if (tLo > 0) {
|
|
6311
|
+
if (current.length === 0) current.push(a);
|
|
6312
|
+
current.push(lerp(a, b, tLo));
|
|
6313
|
+
flush();
|
|
6314
|
+
} else {
|
|
6315
|
+
flush();
|
|
6316
|
+
}
|
|
6317
|
+
if (tHi < 1) {
|
|
6318
|
+
current = [lerp(a, b, tHi), b];
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
flush();
|
|
6322
|
+
return erased ? runs : null;
|
|
6323
|
+
}
|
|
6324
|
+
|
|
6213
6325
|
// src/tools/eraser-tool.ts
|
|
6214
6326
|
var DEFAULT_RADIUS = 20;
|
|
6215
6327
|
function makeEraserCursor(radius) {
|
|
@@ -6222,12 +6334,21 @@ var EraserTool = class {
|
|
|
6222
6334
|
erasing = false;
|
|
6223
6335
|
radius;
|
|
6224
6336
|
cursor;
|
|
6337
|
+
mode;
|
|
6225
6338
|
constructor(options = {}) {
|
|
6226
6339
|
this.radius = options.radius ?? DEFAULT_RADIUS;
|
|
6227
6340
|
this.cursor = makeEraserCursor(this.radius);
|
|
6341
|
+
this.mode = options.mode ?? "partial";
|
|
6228
6342
|
}
|
|
6229
6343
|
getOptions() {
|
|
6230
|
-
return { radius: this.radius };
|
|
6344
|
+
return { radius: this.radius, mode: this.mode };
|
|
6345
|
+
}
|
|
6346
|
+
setOptions(options) {
|
|
6347
|
+
if (options.mode !== void 0) this.mode = options.mode;
|
|
6348
|
+
if (options.radius !== void 0) {
|
|
6349
|
+
this.radius = options.radius;
|
|
6350
|
+
this.cursor = makeEraserCursor(this.radius);
|
|
6351
|
+
}
|
|
6231
6352
|
}
|
|
6232
6353
|
onActivate(ctx) {
|
|
6233
6354
|
ctx.setCursor?.(this.cursor);
|
|
@@ -6260,10 +6381,30 @@ var EraserTool = class {
|
|
|
6260
6381
|
if (el.type !== "stroke") continue;
|
|
6261
6382
|
if (ctx.isLayerVisible && !ctx.isLayerVisible(el.layerId)) continue;
|
|
6262
6383
|
if (ctx.isLayerLocked && ctx.isLayerLocked(el.layerId)) continue;
|
|
6263
|
-
if (this.strokeIntersects(el, world))
|
|
6384
|
+
if (!this.strokeIntersects(el, world)) continue;
|
|
6385
|
+
if (this.mode === "stroke") {
|
|
6264
6386
|
ctx.store.remove(el.id);
|
|
6265
6387
|
erased = true;
|
|
6388
|
+
continue;
|
|
6266
6389
|
}
|
|
6390
|
+
const localEraser = { x: world.x - el.position.x, y: world.y - el.position.y };
|
|
6391
|
+
const runs = erasePoints(el.points, localEraser, this.radius);
|
|
6392
|
+
if (runs === null) continue;
|
|
6393
|
+
ctx.store.remove(el.id);
|
|
6394
|
+
for (const run of runs) {
|
|
6395
|
+
ctx.store.add(
|
|
6396
|
+
createStroke({
|
|
6397
|
+
points: run,
|
|
6398
|
+
color: el.color,
|
|
6399
|
+
width: el.width,
|
|
6400
|
+
opacity: el.opacity,
|
|
6401
|
+
layerId: el.layerId,
|
|
6402
|
+
zIndex: el.zIndex,
|
|
6403
|
+
position: el.position
|
|
6404
|
+
})
|
|
6405
|
+
);
|
|
6406
|
+
}
|
|
6407
|
+
erased = true;
|
|
6267
6408
|
}
|
|
6268
6409
|
if (erased) ctx.requestRender();
|
|
6269
6410
|
}
|
|
@@ -6961,6 +7102,11 @@ var SelectTool = class {
|
|
|
6961
7102
|
}
|
|
6962
7103
|
isInsideBounds(point, el) {
|
|
6963
7104
|
if (el.type === "grid") return false;
|
|
7105
|
+
if (el.type === "shape" && el.shape === "line") {
|
|
7106
|
+
const [a, b] = lineEndpoints(el);
|
|
7107
|
+
const threshold = Math.max(el.strokeWidth / 2, 6);
|
|
7108
|
+
return distSqToSegment(point, a, b) <= threshold * threshold;
|
|
7109
|
+
}
|
|
6964
7110
|
if ("size" in el) {
|
|
6965
7111
|
const s = el.size;
|
|
6966
7112
|
return point.x >= el.position.x && point.x <= el.position.x + s.w && point.y >= el.position.y && point.y <= el.position.y + s.h;
|
|
@@ -7274,6 +7420,15 @@ var ImageTool = class {
|
|
|
7274
7420
|
};
|
|
7275
7421
|
|
|
7276
7422
|
// src/tools/shape-tool.ts
|
|
7423
|
+
function snapTo45(start, end) {
|
|
7424
|
+
const dx = end.x - start.x;
|
|
7425
|
+
const dy = end.y - start.y;
|
|
7426
|
+
const len = Math.hypot(dx, dy);
|
|
7427
|
+
if (len === 0) return { ...end };
|
|
7428
|
+
const step = Math.PI / 4;
|
|
7429
|
+
const angle = Math.round(Math.atan2(dy, dx) / step) * step;
|
|
7430
|
+
return { x: start.x + Math.cos(angle) * len, y: start.y + Math.sin(angle) * len };
|
|
7431
|
+
}
|
|
7277
7432
|
var ShapeTool = class {
|
|
7278
7433
|
name = "shape";
|
|
7279
7434
|
drawing = false;
|
|
@@ -7331,13 +7486,17 @@ var ShapeTool = class {
|
|
|
7331
7486
|
onPointerMove(state, ctx) {
|
|
7332
7487
|
if (!this.drawing) return;
|
|
7333
7488
|
this.end = this.snap(ctx.camera.screenToWorld({ x: state.x, y: state.y }), ctx);
|
|
7489
|
+
if (this.shape === "line" && this.shiftHeld) {
|
|
7490
|
+
this.end = snapTo45(this.start, this.end);
|
|
7491
|
+
}
|
|
7334
7492
|
ctx.requestRender();
|
|
7335
7493
|
}
|
|
7336
7494
|
onPointerUp(_state, ctx) {
|
|
7337
7495
|
if (!this.drawing) return;
|
|
7338
7496
|
this.drawing = false;
|
|
7339
7497
|
const { position, size } = this.computeRect();
|
|
7340
|
-
|
|
7498
|
+
const isLine = this.shape === "line";
|
|
7499
|
+
if (isLine ? size.w === 0 && size.h === 0 : size.w === 0 || size.h === 0) return;
|
|
7341
7500
|
const shape = createShape({
|
|
7342
7501
|
position,
|
|
7343
7502
|
size,
|
|
@@ -7345,6 +7504,7 @@ var ShapeTool = class {
|
|
|
7345
7504
|
strokeColor: this.strokeColor,
|
|
7346
7505
|
strokeWidth: this.strokeWidth,
|
|
7347
7506
|
fillColor: this.fillColor,
|
|
7507
|
+
...isLine ? { flip: this.end.x > this.start.x !== this.end.y > this.start.y } : {},
|
|
7348
7508
|
layerId: ctx.activeLayerId ?? ""
|
|
7349
7509
|
});
|
|
7350
7510
|
ctx.store.add(shape);
|
|
@@ -7378,6 +7538,13 @@ var ShapeTool = class {
|
|
|
7378
7538
|
ctx.stroke();
|
|
7379
7539
|
break;
|
|
7380
7540
|
}
|
|
7541
|
+
case "line":
|
|
7542
|
+
ctx.lineCap = "round";
|
|
7543
|
+
ctx.beginPath();
|
|
7544
|
+
ctx.moveTo(this.start.x, this.start.y);
|
|
7545
|
+
ctx.lineTo(this.end.x, this.end.y);
|
|
7546
|
+
ctx.stroke();
|
|
7547
|
+
break;
|
|
7381
7548
|
}
|
|
7382
7549
|
ctx.restore();
|
|
7383
7550
|
}
|
|
@@ -7386,7 +7553,7 @@ var ShapeTool = class {
|
|
|
7386
7553
|
let y = Math.min(this.start.y, this.end.y);
|
|
7387
7554
|
let w = Math.abs(this.end.x - this.start.x);
|
|
7388
7555
|
let h = Math.abs(this.end.y - this.start.y);
|
|
7389
|
-
if (this.shiftHeld) {
|
|
7556
|
+
if (this.shiftHeld && this.shape !== "line") {
|
|
7390
7557
|
const side = Math.max(w, h);
|
|
7391
7558
|
w = side;
|
|
7392
7559
|
h = side;
|
|
@@ -7802,7 +7969,7 @@ var TemplateTool = class {
|
|
|
7802
7969
|
};
|
|
7803
7970
|
|
|
7804
7971
|
// src/index.ts
|
|
7805
|
-
var VERSION = "0.
|
|
7972
|
+
var VERSION = "0.30.0";
|
|
7806
7973
|
export {
|
|
7807
7974
|
ArrowTool,
|
|
7808
7975
|
AutoSave,
|