@edv4h/usketch-plugin-shape-freedraw 3.0.1 → 3.1.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.
Files changed (60) hide show
  1. package/README.md +59 -0
  2. package/dist/config.d.ts +54 -0
  3. package/dist/config.d.ts.map +1 -0
  4. package/dist/config.js +46 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/draw-tool.d.ts +12 -0
  7. package/dist/draw-tool.d.ts.map +1 -0
  8. package/dist/draw-tool.js +202 -0
  9. package/dist/draw-tool.js.map +1 -0
  10. package/dist/eraser.d.ts +10 -0
  11. package/dist/eraser.d.ts.map +1 -0
  12. package/dist/eraser.js +33 -0
  13. package/dist/eraser.js.map +1 -0
  14. package/dist/geometry/brush-outline.d.ts +19 -0
  15. package/dist/geometry/brush-outline.d.ts.map +1 -0
  16. package/dist/geometry/brush-outline.js +59 -0
  17. package/dist/geometry/brush-outline.js.map +1 -0
  18. package/dist/geometry/hit.d.ts +17 -0
  19. package/dist/geometry/hit.d.ts.map +1 -0
  20. package/dist/geometry/hit.js +101 -0
  21. package/dist/geometry/hit.js.map +1 -0
  22. package/dist/geometry/simplify.d.ts +7 -0
  23. package/dist/geometry/simplify.d.ts.map +1 -0
  24. package/dist/geometry/simplify.js +47 -0
  25. package/dist/geometry/simplify.js.map +1 -0
  26. package/dist/geometry/smooth-path.d.ts +9 -0
  27. package/dist/geometry/smooth-path.d.ts.map +1 -0
  28. package/dist/geometry/smooth-path.js +21 -0
  29. package/dist/geometry/smooth-path.js.map +1 -0
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/pen-meta.d.ts +26 -0
  35. package/dist/pen-meta.d.ts.map +1 -0
  36. package/dist/pen-meta.js +39 -0
  37. package/dist/pen-meta.js.map +1 -0
  38. package/dist/plugin.d.ts +3 -2
  39. package/dist/plugin.d.ts.map +1 -1
  40. package/dist/plugin.js +106 -255
  41. package/dist/plugin.js.map +1 -1
  42. package/dist/settings-store.d.ts +22 -0
  43. package/dist/settings-store.d.ts.map +1 -0
  44. package/dist/settings-store.js +65 -0
  45. package/dist/settings-store.js.map +1 -0
  46. package/dist/shape.d.ts +6 -0
  47. package/dist/shape.d.ts.map +1 -0
  48. package/dist/shape.js +193 -0
  49. package/dist/shape.js.map +1 -0
  50. package/dist/types.d.ts +20 -3
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/ui/cursor-overlay.d.ts +21 -0
  53. package/dist/ui/cursor-overlay.d.ts.map +1 -0
  54. package/dist/ui/cursor-overlay.js +48 -0
  55. package/dist/ui/cursor-overlay.js.map +1 -0
  56. package/dist/ui/palette.d.ts +14 -0
  57. package/dist/ui/palette.d.ts.map +1 -0
  58. package/dist/ui/palette.js +89 -0
  59. package/dist/ui/palette.js.map +1 -0
  60. package/package.json +6 -4
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @edv4h/usketch-plugin-shape-freedraw
2
+
3
+ ホワイトボード用の「描く」体験を提供するフリーハンド(ペン)プラグイン。
4
+ 4種のペン、筆圧シミュレーション、色/太さ、オブジェクト単位の消しゴム、ペン先カーソルを備える。
5
+ 描画はベクター SVG なのでズームしても鮮明で、選択/移動/リサイズ・undo・同期と整合する。
6
+
7
+ ## 使い方
8
+
9
+ ```ts
10
+ import { createFreedrawPlugin } from "@edv4h/usketch-plugin-shape-freedraw";
11
+
12
+ const plugins = [
13
+ // ...
14
+ createFreedrawPlugin({
15
+ defaultPen: "ballpoint",
16
+ brushDynamics: 60, // 筆圧の効き 10..100
17
+ cursorPreview: true,
18
+ }),
19
+ ];
20
+ ```
21
+
22
+ ツール ID は `freedraw-draw`。`store.setActiveToolId("freedraw-draw")` でアクティブにすると、
23
+ 画面下部に最小パレット(ペン種別 / 消しゴム / 色 / 太さ)が表示される。`Esc` で既定ツールへ戻る。
24
+
25
+ ## ペン(設計書 §3)
26
+
27
+ | ペン | 既定太さ | 線幅 | 不透明度 | 合成 |
28
+ | --- | --- | --- | --- | --- |
29
+ | ボールペン | 2.5 | 一定 | 1.0 | normal |
30
+ | サインペン | 6 | 一定 | 1.0 | normal |
31
+ | 筆ペン | 11 | 可変(速度→疑似筆圧) | 1.0 | normal |
32
+ | 蛍光ペン | 22 | 一定 | 0.4 | multiply |
33
+
34
+ 一定幅ペンは中点を通る二次ベジェで平滑化、筆ペンは perfect-freehand で塗りアウトラインを生成。
35
+ 蛍光ペンは要素単位合成のため、同一ストローク内の交差は二重に濃くならず別ストローク同士は濃くなる。
36
+
37
+ ## 消しゴム
38
+
39
+ `mode: "eraser"` のとき、触れた freedraw ストロークを丸ごと削除する(ピクセル消去ではない)。
40
+ 1ドラッグぶんの削除は1アクションとして undo できる。
41
+
42
+ ## データモデル
43
+
44
+ ```ts
45
+ type PenKind = "ballpoint" | "felt" | "brush" | "highlighter";
46
+ interface StrokePoint { x: number; y: number; p?: number } // p=疑似筆圧 0..1
47
+ interface FreedrawShapeData extends ShapeData {
48
+ points: StrokePoint[];
49
+ pen?: PenKind; // 省略時 ballpoint(旧データ後方互換)
50
+ }
51
+ ```
52
+
53
+ 色 = `style.stroke` / 太さ = `style.strokeWidth` / 不透明度 = `style.opacity`。
54
+
55
+ ## 外部連携イベント
56
+
57
+ `ctx.events.emit` で設定を操作できる(vim プラグインの ex コマンド等から):
58
+ `freedraw:set-pen` `{pen}` / `freedraw:set-color` `{color}` / `freedraw:set-size` `{size}` /
59
+ `freedraw:toggle-eraser`。
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ /** 太さ・消しゴム・筆圧感度の範囲(設計書 §7,§11,§12)。 */
3
+ export declare const SIZE_RANGE: {
4
+ readonly min: 1;
5
+ readonly max: 48;
6
+ readonly step: 0.5;
7
+ };
8
+ export declare const ERASER_RANGE: {
9
+ readonly min: 10;
10
+ readonly max: 100;
11
+ readonly step: 1;
12
+ };
13
+ export declare const BRUSH_DYNAMICS_RANGE: {
14
+ readonly min: 10;
15
+ readonly max: 100;
16
+ readonly step: 5;
17
+ };
18
+ /** 設計書の主要定数(§12)。 */
19
+ export declare const TUNING: {
20
+ /** 点の間引き距離(screen px)。これ未満の move は破棄。 */
21
+ readonly minSampleDist: 0.8;
22
+ /** 太さ平滑化係数(前フレームへ向けた lerp)。 */
23
+ readonly widthSmoothing: 0.4;
24
+ /** 始点の初期筆圧(書き始めの入り)。 */
25
+ readonly startPressure: 0.45;
26
+ /** 確定時の RDP 間引き許容誤差(world px、0 で無効)。 */
27
+ readonly simplifyTolerance: 0.6;
28
+ };
29
+ /** プラグイン設定(JSON 可、Zod 検証)。実行時の可変状態は settings-store が持つ。 */
30
+ export declare const FreedrawConfigSchema: z.ZodObject<{
31
+ defaultPen: z.ZodDefault<z.ZodEnum<{
32
+ ballpoint: "ballpoint";
33
+ felt: "felt";
34
+ brush: "brush";
35
+ highlighter: "highlighter";
36
+ }>>;
37
+ defaultColor: z.ZodDefault<z.ZodString>;
38
+ defaultSizes: z.ZodDefault<z.ZodObject<{
39
+ ballpoint: z.ZodNumber;
40
+ felt: z.ZodNumber;
41
+ brush: z.ZodNumber;
42
+ highlighter: z.ZodNumber;
43
+ }, z.core.$strip>>;
44
+ defaultEraserSize: z.ZodDefault<z.ZodNumber>;
45
+ brushDynamics: z.ZodDefault<z.ZodNumber>;
46
+ cursorPreview: z.ZodDefault<z.ZodBoolean>;
47
+ persistSettings: z.ZodDefault<z.ZodBoolean>;
48
+ }, z.core.$strip>;
49
+ export type FreedrawConfig = z.infer<typeof FreedrawConfigSchema>;
50
+ export type FreedrawConfigInput = z.input<typeof FreedrawConfigSchema>;
51
+ export declare function parseFreedrawConfig(input?: FreedrawConfigInput): FreedrawConfig;
52
+ /** localStorage キー。 */
53
+ export declare const STORAGE_KEY = "usketch-plugin-freedraw-v1";
54
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,uCAAuC;AACvC,eAAO,MAAM,UAAU;;;;CAA0C,CAAC;AAClE,eAAO,MAAM,YAAY;;;;CAA0C,CAAC;AACpE,eAAO,MAAM,oBAAoB;;;;CAA0C,CAAC;AAE5E,qBAAqB;AACrB,eAAO,MAAM,MAAM;IAClB,yCAAyC;;IAEzC,+BAA+B;;IAE/B,wBAAwB;;IAExB,wCAAwC;;CAE/B,CAAC;AASX,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;iBAe/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,mBAAmB,GAAG,cAAc,CAE/E;AAED,uBAAuB;AACvB,eAAO,MAAM,WAAW,+BAA+B,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { DEFAULT_PEN, DEFAULT_SIZES, PRESET_COLORS } from "./pen-meta.js";
3
+ /** 太さ・消しゴム・筆圧感度の範囲(設計書 §7,§11,§12)。 */
4
+ export const SIZE_RANGE = { min: 1, max: 48, step: 0.5 };
5
+ export const ERASER_RANGE = { min: 10, max: 100, step: 1 };
6
+ export const BRUSH_DYNAMICS_RANGE = { min: 10, max: 100, step: 5 };
7
+ /** 設計書の主要定数(§12)。 */
8
+ export const TUNING = {
9
+ /** 点の間引き距離(screen px)。これ未満の move は破棄。 */
10
+ minSampleDist: 0.8,
11
+ /** 太さ平滑化係数(前フレームへ向けた lerp)。 */
12
+ widthSmoothing: 0.4,
13
+ /** 始点の初期筆圧(書き始めの入り)。 */
14
+ startPressure: 0.45,
15
+ /** 確定時の RDP 間引き許容誤差(world px、0 で無効)。 */
16
+ simplifyTolerance: 0.6,
17
+ };
18
+ const SizesSchema = z.object({
19
+ ballpoint: z.number().positive(),
20
+ felt: z.number().positive(),
21
+ brush: z.number().positive(),
22
+ highlighter: z.number().positive(),
23
+ });
24
+ /** プラグイン設定(JSON 可、Zod 検証)。実行時の可変状態は settings-store が持つ。 */
25
+ export const FreedrawConfigSchema = z.object({
26
+ /** 既定ペン。 */
27
+ defaultPen: z.enum(["ballpoint", "felt", "brush", "highlighter"]).default(DEFAULT_PEN),
28
+ /** 既定色。 */
29
+ defaultColor: z.string().default(PRESET_COLORS[0]),
30
+ /** ペンごとの既定太さ。 */
31
+ defaultSizes: SizesSchema.default(DEFAULT_SIZES),
32
+ /** 消しゴムの既定サイズ。 */
33
+ defaultEraserSize: z.number().min(ERASER_RANGE.min).max(ERASER_RANGE.max).default(30),
34
+ /** 筆圧の効き(10〜100、既定60)。 */
35
+ brushDynamics: z.number().min(BRUSH_DYNAMICS_RANGE.min).max(BRUSH_DYNAMICS_RANGE.max).default(60),
36
+ /** ペン先カーソル表示。 */
37
+ cursorPreview: z.boolean().default(true),
38
+ /** 設定の localStorage 永続化を行うか。 */
39
+ persistSettings: z.boolean().default(true),
40
+ });
41
+ export function parseFreedrawConfig(input) {
42
+ return FreedrawConfigSchema.parse(input ?? {});
43
+ }
44
+ /** localStorage キー。 */
45
+ export const STORAGE_KEY = "usketch-plugin-freedraw-v1";
46
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE1E,uCAAuC;AACvC,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAW,CAAC;AAClE,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC;AAE5E,qBAAqB;AACrB,MAAM,CAAC,MAAM,MAAM,GAAG;IACrB,yCAAyC;IACzC,aAAa,EAAE,GAAG;IAClB,+BAA+B;IAC/B,cAAc,EAAE,GAAG;IACnB,wBAAwB;IACxB,aAAa,EAAE,IAAI;IACnB,wCAAwC;IACxC,iBAAiB,EAAE,GAAG;CACb,CAAC;AAEX,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,YAAY;IACZ,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;IACtF,WAAW;IACX,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAClD,iBAAiB;IACjB,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,aAAa,CAAC;IAChD,kBAAkB;IAClB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrF,0BAA0B;IAC1B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACjG,iBAAiB;IACjB,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACxC,gCAAgC;IAChC,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CAC1C,CAAC,CAAC;AAKH,MAAM,UAAU,mBAAmB,CAAC,KAA2B;IAC9D,OAAO,oBAAoB,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,uBAAuB;AACvB,MAAM,CAAC,MAAM,WAAW,GAAG,4BAA4B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type CanvasPointerEvent, type ToolContext } from "@edv4h/usketch-shared";
2
+ import type { FreedrawSettingsStore } from "./settings-store.js";
3
+ import type { PointerStore } from "./ui/cursor-overlay.js";
4
+ export interface DrawController {
5
+ onPointerDown(toolCtx: ToolContext, event: CanvasPointerEvent): void;
6
+ onPointerMove(toolCtx: ToolContext, event: CanvasPointerEvent): void;
7
+ onPointerUp(toolCtx: ToolContext, event: CanvasPointerEvent): void;
8
+ /** ツール無効化時のクリーンアップ。 */
9
+ reset(): void;
10
+ }
11
+ export declare function createDrawController(settings: FreedrawSettingsStore, pointer: PointerStore): DrawController;
12
+ //# sourceMappingURL=draw-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draw-tool.d.ts","sourceRoot":"","sources":["../src/draw-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,kBAAkB,EAIvB,KAAK,WAAW,EAChB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AA6C3D,MAAM,WAAW,cAAc;IAC9B,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrE,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrE,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACnE,uBAAuB;IACvB,KAAK,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,YAAY,GACnB,cAAc,CAkLhB"}
@@ -0,0 +1,202 @@
1
+ import { DEFAULT_STYLE, generateId, } from "@edv4h/usketch-shared";
2
+ import { createAddShapeCommand } from "@edv4h/usketch-store";
3
+ import { TUNING } from "./config.js";
4
+ import { createEraseStrokesCommand, findErasedStrokes } from "./eraser.js";
5
+ import { smoothPressure, speedPressure } from "./geometry/brush-outline.js";
6
+ import { simplifyPoints } from "./geometry/simplify.js";
7
+ import { penMeta } from "./pen-meta.js";
8
+ function halfWidthForPen(variable, size) {
9
+ return variable ? (size * 1.55) / 2 : size / 2;
10
+ }
11
+ function boundsOf(points, pad) {
12
+ let minX = Number.POSITIVE_INFINITY;
13
+ let minY = Number.POSITIVE_INFINITY;
14
+ let maxX = Number.NEGATIVE_INFINITY;
15
+ let maxY = Number.NEGATIVE_INFINITY;
16
+ for (const p of points) {
17
+ if (p.x < minX)
18
+ minX = p.x;
19
+ if (p.y < minY)
20
+ minY = p.y;
21
+ if (p.x > maxX)
22
+ maxX = p.x;
23
+ if (p.y > maxY)
24
+ maxY = p.y;
25
+ }
26
+ return {
27
+ x: minX - pad,
28
+ y: minY - pad,
29
+ width: maxX - minX + pad * 2,
30
+ height: maxY - minY + pad * 2,
31
+ };
32
+ }
33
+ const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
34
+ export function createDrawController(settings, pointer) {
35
+ let stroke = null;
36
+ let erase = null;
37
+ let raf = 0;
38
+ function buildStyle(color, size, alpha) {
39
+ return { ...DEFAULT_STYLE, stroke: color, strokeWidth: size, opacity: alpha };
40
+ }
41
+ function flush(toolCtx) {
42
+ if (raf)
43
+ return;
44
+ raf = requestAnimationFrame(() => {
45
+ raf = 0;
46
+ if (!stroke)
47
+ return;
48
+ const pad = halfWidthForPen(stroke.variable, stroke.size);
49
+ const b = boundsOf(stroke.points, pad);
50
+ toolCtx.store.updateShape(stroke.id, {
51
+ x: b.x,
52
+ y: b.y,
53
+ width: b.width,
54
+ height: b.height,
55
+ points: [...stroke.points],
56
+ });
57
+ });
58
+ }
59
+ function doErase(toolCtx, world) {
60
+ if (!erase)
61
+ return;
62
+ const eraserSize = settings.getSnapshot().eraserSize;
63
+ const hits = findErasedStrokes(toolCtx.store, world, eraserSize);
64
+ for (const s of hits) {
65
+ if (!erase.removed.has(s.id)) {
66
+ erase.removed.set(s.id, { ...s });
67
+ toolCtx.store.deleteShape(s.id);
68
+ }
69
+ }
70
+ }
71
+ return {
72
+ onPointerDown(toolCtx, event) {
73
+ if (event.button !== 0)
74
+ return;
75
+ pointer.set(event.worldPoint);
76
+ const s = settings.getSnapshot();
77
+ if (s.mode === "eraser") {
78
+ erase = { removed: new Map() };
79
+ doErase(toolCtx, event.worldPoint);
80
+ return;
81
+ }
82
+ const pen = s.pen;
83
+ const size = s.sizes[pen];
84
+ const m = penMeta(pen);
85
+ const id = generateId();
86
+ const first = {
87
+ x: event.worldPoint.x,
88
+ y: event.worldPoint.y,
89
+ p: m.variable ? TUNING.startPressure : undefined,
90
+ };
91
+ stroke = {
92
+ id,
93
+ pen,
94
+ size,
95
+ color: s.color,
96
+ alpha: m.alpha,
97
+ variable: m.variable,
98
+ points: [first],
99
+ lastScreenX: event.screenPoint.x,
100
+ lastScreenY: event.screenPoint.y,
101
+ lastT: now(),
102
+ prevP: TUNING.startPressure,
103
+ };
104
+ const shape = {
105
+ id,
106
+ type: "freedraw",
107
+ x: event.worldPoint.x,
108
+ y: event.worldPoint.y,
109
+ width: 0,
110
+ height: 0,
111
+ style: buildStyle(s.color, size, m.alpha),
112
+ points: [first],
113
+ pen,
114
+ };
115
+ toolCtx.store.addShape(shape);
116
+ },
117
+ onPointerMove(toolCtx, event) {
118
+ pointer.set(event.worldPoint);
119
+ const s = settings.getSnapshot();
120
+ if (s.mode === "eraser") {
121
+ if (erase)
122
+ doErase(toolCtx, event.worldPoint);
123
+ return;
124
+ }
125
+ if (!stroke)
126
+ return;
127
+ const world = event.worldPoint;
128
+ const last = stroke.points[stroke.points.length - 1];
129
+ const movedWorld = Math.hypot(world.x - last.x, world.y - last.y);
130
+ const zoom = toolCtx.store.getViewport().zoom || 1;
131
+ // 間引き(screen px 基準を world に換算)。最初の点は除く。
132
+ if (movedWorld < TUNING.minSampleDist / zoom && stroke.points.length > 1) {
133
+ stroke.lastScreenX = event.screenPoint.x;
134
+ stroke.lastScreenY = event.screenPoint.y;
135
+ stroke.lastT = now();
136
+ return;
137
+ }
138
+ let p;
139
+ if (stroke.variable) {
140
+ const t = now();
141
+ const movedScreen = Math.hypot(event.screenPoint.x - stroke.lastScreenX, event.screenPoint.y - stroke.lastScreenY);
142
+ const target = speedPressure(movedScreen, t - stroke.lastT, s.brushDynamics);
143
+ const sm = smoothPressure(stroke.prevP, target, TUNING.widthSmoothing);
144
+ stroke.prevP = sm;
145
+ p = sm;
146
+ }
147
+ stroke.points.push({ x: world.x, y: world.y, p });
148
+ stroke.lastScreenX = event.screenPoint.x;
149
+ stroke.lastScreenY = event.screenPoint.y;
150
+ stroke.lastT = now();
151
+ flush(toolCtx);
152
+ },
153
+ onPointerUp(toolCtx, _event) {
154
+ const s = settings.getSnapshot();
155
+ if (s.mode === "eraser") {
156
+ if (erase) {
157
+ const removed = [...erase.removed.values()];
158
+ erase = null;
159
+ if (removed.length > 0) {
160
+ toolCtx.commands.execute(createEraseStrokesCommand(toolCtx.store, removed));
161
+ }
162
+ }
163
+ return;
164
+ }
165
+ if (!stroke)
166
+ return;
167
+ if (raf) {
168
+ cancelAnimationFrame(raf);
169
+ raf = 0;
170
+ }
171
+ const cur = stroke;
172
+ stroke = null;
173
+ // 確定: scratch を削除 → 間引き → undo 可能な追加コマンド。
174
+ toolCtx.store.deleteShape(cur.id);
175
+ const points = simplifyPoints(cur.points, TUNING.simplifyTolerance);
176
+ const pad = halfWidthForPen(cur.variable, cur.size);
177
+ const b = boundsOf(points, pad);
178
+ const shape = {
179
+ id: cur.id,
180
+ type: "freedraw",
181
+ x: b.x,
182
+ y: b.y,
183
+ width: b.width,
184
+ height: b.height,
185
+ style: buildStyle(cur.color, cur.size, cur.alpha),
186
+ points,
187
+ pen: cur.pen,
188
+ };
189
+ toolCtx.commands.execute(createAddShapeCommand(toolCtx.store, shape));
190
+ },
191
+ reset() {
192
+ if (raf) {
193
+ cancelAnimationFrame(raf);
194
+ raf = 0;
195
+ }
196
+ stroke = null;
197
+ erase = null;
198
+ pointer.set(null);
199
+ },
200
+ };
201
+ }
202
+ //# sourceMappingURL=draw-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draw-tool.js","sourceRoot":"","sources":["../src/draw-tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { BoardStore, Command, Point, ShapeData } from "@edv4h/usketch-shared";
2
+ /**
3
+ * 複数ストロークの削除を1アクションとして undo 可能にする command。
4
+ * store にバッチ削除 API が無いため自前で用意(設計書 §8,§9)。
5
+ * ドラッグ中はプレビューのため直接 delete 済みなので、execute の delete は no-op になり得る(安全)。
6
+ */
7
+ export declare function createEraseStrokesCommand(store: BoardStore, removed: ShapeData[]): Command;
8
+ /** 消しゴム円に触れた freedraw ストロークを返す(freedraw 型のみ対象)。 */
9
+ export declare function findErasedStrokes(store: BoardStore, cursor: Point, eraserSize: number): ShapeData[];
10
+ //# sourceMappingURL=eraser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eraser.d.ts","sourceRoot":"","sources":["../src/eraser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAInF;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,OAAO,CAW1F;AAED,mDAAmD;AACnD,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,KAAK,EACb,UAAU,EAAE,MAAM,GAChB,SAAS,EAAE,CAQb"}
package/dist/eraser.js ADDED
@@ -0,0 +1,33 @@
1
+ import { eraserHits } from "./shape.js";
2
+ /**
3
+ * 複数ストロークの削除を1アクションとして undo 可能にする command。
4
+ * store にバッチ削除 API が無いため自前で用意(設計書 §8,§9)。
5
+ * ドラッグ中はプレビューのため直接 delete 済みなので、execute の delete は no-op になり得る(安全)。
6
+ */
7
+ export function createEraseStrokesCommand(store, removed) {
8
+ // id 衝突や二重適用に備えてスナップショットをコピー保持。
9
+ const snapshots = removed.map((s) => ({ ...s }));
10
+ return {
11
+ execute() {
12
+ for (const s of snapshots)
13
+ store.deleteShape(s.id);
14
+ },
15
+ undo() {
16
+ for (const s of snapshots)
17
+ store.addShape(s);
18
+ },
19
+ };
20
+ }
21
+ /** 消しゴム円に触れた freedraw ストロークを返す(freedraw 型のみ対象)。 */
22
+ export function findErasedStrokes(store, cursor, eraserSize) {
23
+ const r = eraserSize / 2 + 1;
24
+ const out = [];
25
+ for (const [, shape] of store.getShapes()) {
26
+ if (shape.type !== "freedraw")
27
+ continue;
28
+ if (eraserHits(shape, cursor, r))
29
+ out.push(shape);
30
+ }
31
+ return out;
32
+ }
33
+ //# sourceMappingURL=eraser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eraser.js","sourceRoot":"","sources":["../src/eraser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAiB,EAAE,OAAoB;IAChF,gCAAgC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,OAAO;QACN,OAAO;YACN,KAAK,MAAM,CAAC,IAAI,SAAS;gBAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,IAAI;YACH,KAAK,MAAM,CAAC,IAAI,SAAS;gBAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC9C,CAAC;KACD,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,iBAAiB,CAChC,KAAiB,EACjB,MAAa,EACb,UAAkB;IAElB,MAAM,CAAC,GAAG,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAgB,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,UAAU,CAAC,KAA0B,EAAE,MAAM,EAAE,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { StrokePoint } from "../types.js";
2
+ /**
3
+ * 速度→疑似筆圧(設計書 4.3)。デスクトップは筆圧が無いため移動速度から推定する。
4
+ * ゆっくり=太い(p→1)、速い=細い(p→0)。
5
+ * @param moved 直前点からの距離(screen px)
6
+ * @param dtMs 経過時間(ms)
7
+ * @param brushDynamics 筆圧感度 10..100(既定60)
8
+ */
9
+ export declare function speedPressure(moved: number, dtMs: number, brushDynamics: number): number;
10
+ /** 太さ平滑化(前フレームへ向けた lerp、設計書 §12)。 */
11
+ export declare function smoothPressure(prev: number, target: number, factor: number): number;
12
+ /**
13
+ * 筆ペン(可変線幅)の塗りアウトラインパス(設計書 4.4 の代替=ベクター化)。
14
+ * 点ごとの疑似筆圧 `p` から perfect-freehand で幅を可変させ、塗りパス d を得る。
15
+ * `size` を最大径の基準に、thinning 固定で wmin≈size×0.22 / wmax≈size×1.55(設計書 §12)に対応づける。
16
+ * `p` は描画時に決定済みなので simulatePressure は false(決定的に再描画できる)。
17
+ */
18
+ export declare function brushOutlineD(points: StrokePoint[], size: number): string;
19
+ //# sourceMappingURL=brush-outline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brush-outline.d.ts","sourceRoot":"","sources":["../../src/geometry/brush-outline.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAMxF;AAED,qCAAqC;AACrC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnF;AAkBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAczE"}
@@ -0,0 +1,59 @@
1
+ import { getStroke } from "perfect-freehand";
2
+ const r2 = (n) => Math.round(n * 100) / 100;
3
+ /**
4
+ * 速度→疑似筆圧(設計書 4.3)。デスクトップは筆圧が無いため移動速度から推定する。
5
+ * ゆっくり=太い(p→1)、速い=細い(p→0)。
6
+ * @param moved 直前点からの距離(screen px)
7
+ * @param dtMs 経過時間(ms)
8
+ * @param brushDynamics 筆圧感度 10..100(既定60)
9
+ */
10
+ export function speedPressure(moved, dtMs, brushDynamics) {
11
+ const dt = Math.max(1, dtMs);
12
+ const v = moved / dt; // px/ms
13
+ const k = brushDynamics / 60; // 既定1.0
14
+ const p = 1 - Math.min(1, v / (1.7 / Math.max(0.3, k)));
15
+ return Math.max(0, Math.min(1, p));
16
+ }
17
+ /** 太さ平滑化(前フレームへ向けた lerp、設計書 §12)。 */
18
+ export function smoothPressure(prev, target, factor) {
19
+ return prev + (target - prev) * factor;
20
+ }
21
+ /**
22
+ * perfect-freehand のアウトライン点列を、中点を通る二次ベジェの閉じた塗りパス d に変換。
23
+ * (perfect-freehand 公式の getSvgPathFromStroke 相当)
24
+ */
25
+ function outlineToPathD(outline) {
26
+ if (outline.length < 2)
27
+ return "";
28
+ const d = ["M", r2(outline[0][0]), r2(outline[0][1]), "Q"];
29
+ for (let i = 0; i < outline.length; i++) {
30
+ const a = outline[i];
31
+ const b = outline[(i + 1) % outline.length];
32
+ d.push(r2(a[0]), r2(a[1]), r2((a[0] + b[0]) / 2), r2((a[1] + b[1]) / 2));
33
+ }
34
+ d.push("Z");
35
+ return d.join(" ");
36
+ }
37
+ /**
38
+ * 筆ペン(可変線幅)の塗りアウトラインパス(設計書 4.4 の代替=ベクター化)。
39
+ * 点ごとの疑似筆圧 `p` から perfect-freehand で幅を可変させ、塗りパス d を得る。
40
+ * `size` を最大径の基準に、thinning 固定で wmin≈size×0.22 / wmax≈size×1.55(設計書 §12)に対応づける。
41
+ * `p` は描画時に決定済みなので simulatePressure は false(決定的に再描画できる)。
42
+ */
43
+ export function brushOutlineD(points, size) {
44
+ if (points.length === 0)
45
+ return "";
46
+ const input = points.map((pt) => [pt.x, pt.y, pt.p ?? 0.5]);
47
+ const outline = getStroke(input, {
48
+ size: size * 1.55,
49
+ thinning: 0.86,
50
+ smoothing: 0.5,
51
+ streamline: 0.5,
52
+ simulatePressure: false,
53
+ last: true,
54
+ start: { cap: true },
55
+ end: { cap: true },
56
+ });
57
+ return outlineToPathD(outline);
58
+ }
59
+ //# sourceMappingURL=brush-outline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brush-outline.js","sourceRoot":"","sources":["../../src/geometry/brush-outline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { BoundingBox, Point } from "@edv4h/usketch-shared";
2
+ import type { FreedrawShapeData } from "../types.js";
3
+ /** ストロークの最大半幅。 */
4
+ export declare function maxHalfWidth(data: FreedrawShapeData): number;
5
+ /** 点列 + 最大半幅で bbox を算出(細い線でも掴める)。 */
6
+ export declare function strokeBounds(data: FreedrawShapeData): BoundingBox;
7
+ /**
8
+ * 折れ線距離による hitTest。点が線(半幅 + tolerance 内)に乗っていれば true。
9
+ * 1点ストロークは円として判定。
10
+ */
11
+ export declare function hitStroke(data: FreedrawShapeData, point: Point, tolerance?: number): boolean;
12
+ /**
13
+ * 消しゴム判定(設計書 §8)。いずれかの点が消しゴム円(r + 半幅)に入れば true。
14
+ * @param r 消しゴム半径 = eraserSize/2 + 1
15
+ */
16
+ export declare function eraserHits(data: FreedrawShapeData, cursor: Point, r: number): boolean;
17
+ //# sourceMappingURL=hit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hit.d.ts","sourceRoot":"","sources":["../../src/geometry/hit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,KAAK,EAAE,iBAAiB,EAAwB,MAAM,aAAa,CAAC;AAc3E,kBAAkB;AAClB,wBAAgB,YAAY,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAI5D;AAED,qCAAqC;AACrC,wBAAgB,YAAY,CAAC,IAAI,EAAE,iBAAiB,GAAG,WAAW,CAsBjE;AAgBD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,SAAI,GAAG,OAAO,CAkBvF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAUrF"}