@shibayama/pdgkit 0.1.0 → 0.1.1
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 +45 -27
- package/dist/core.cjs +43 -14
- package/dist/core.js +43 -14
- package/dist/index.cjs +44 -15
- package/dist/index.d.cts +20 -78
- package/dist/index.d.ts +20 -78
- package/dist/index.js +44 -15
- package/dist/pdgkit-mcp.cjs +45 -16
- package/dist/pdgkit-mcp.js +45 -16
- package/dist/pdgkit.cjs +44 -15
- package/dist/pdgkit.global.js +6 -6
- package/dist/pdgkit.js +44 -15
- package/docs/ai-authoring-guide.md +233 -283
- package/docs/spec.md +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,48 +154,60 @@ pdgkit の主な使い方は、AI に日本語で頼んで図を作らせるこ
|
|
|
154
154
|
|
|
155
155
|
#### ステップ 2: あとは日本語で頼むだけ
|
|
156
156
|
|
|
157
|
-
「準備完了」と返ってきたら、作りたい図を言葉で伝えるだけです。図種も出力形式も、指定しなければ AI
|
|
157
|
+
「準備完了」と返ってきたら、作りたい図を言葉で伝えるだけです。図種も出力形式も、指定しなければ AI が内容から選びます。下はそのまま使える依頼文の例です(コードボックス 1 つが 1 つの依頼。コピーして送ってください)。
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
**構成・装置(ブロック図になりやすい)** — 何が何を含み、何がどこへ流れるかを書く:
|
|
160
160
|
|
|
161
161
|
```text
|
|
162
|
-
|
|
163
|
-
センサ端末の構成図を描いて。検出部(温度センサと加速度センサ)、処理部(取得部と判定部)、通信部を備え、検出部→処理部→通信部の順にデータが流れ、通信部からゲートウェイへ無線送信する。日英併記で
|
|
164
|
-
制御部・駆動部・センサ部・対象装置から成る制御ループの図。指令で駆動し、センサが状態を検出して制御部へフィードバック
|
|
162
|
+
制御装置の構成図を描いて。制御装置は CPU、メモリ、I/O インターフェースを内蔵し、I/O インターフェースが外部機器へ信号を送る。
|
|
165
163
|
```
|
|
166
164
|
|
|
167
|
-
|
|
165
|
+
```text
|
|
166
|
+
センサ端末の構成図を描いて。検出部(温度センサと加速度センサ)、処理部(取得部と判定部)、通信部を備え、検出部から処理部、処理部から通信部の順にデータが流れ、通信部からゲートウェイへ無線送信する。日英併記で。
|
|
167
|
+
```
|
|
168
168
|
|
|
169
169
|
```text
|
|
170
|
-
|
|
171
|
-
ログイン処理のフローを描いて。入力→認証成功?で分岐、失敗ならリトライ、成功ならホームへ
|
|
170
|
+
制御ループの図を描いて。制御システムは制御部・駆動部・センサ部・対象装置から成り、制御部が指令で駆動部を動かし、駆動部が対象装置を駆動し、センサ部が状態を検出して制御部へフィードバックする。
|
|
172
171
|
```
|
|
173
172
|
|
|
174
|
-
|
|
173
|
+
**処理・方法(フローチャートになりやすい)** — 手順と分岐・ループを書く:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
検査方法のフローチャートを描いて。画像取得→前処理→特徴抽出→欠陥あり? と進み、欠陥ありなら警告出力、無しなら正常記録、いずれも終了へ。
|
|
177
|
+
```
|
|
175
178
|
|
|
176
179
|
```text
|
|
177
|
-
|
|
178
|
-
クライアントとサーバの、認証からリソース取得までのシーケンス図
|
|
180
|
+
ログイン処理のフローチャートを描いて。入力→認証成功? で分岐し、成功ならホーム画面へ、失敗なら入力へ戻って再試行する。
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
**状態・やりとり:**
|
|
182
184
|
|
|
183
185
|
```text
|
|
184
|
-
|
|
185
|
-
この方法クレームをフローチャートにして: 画像を取得するステップと、特徴量を抽出するステップと、欠陥の有無を判定するステップと、…を含む検査方法。
|
|
186
|
+
装置の状態遷移図を描いて。待機・動作中・エラーの3状態があり、起動で動作中、停止で待機、異常でエラー、リセットで待機に戻る。
|
|
186
187
|
```
|
|
187
188
|
|
|
188
|
-
|
|
189
|
+
```text
|
|
190
|
+
クライアントとサーバの、認証からリソース取得までのシーケンス図を描いて。認証要求→トークン応答→リソース要求→リソース応答の順。
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**クレーム文からの変換** — クレーム本文をそのまま貼って図種を指定する:
|
|
189
194
|
|
|
190
195
|
```text
|
|
191
|
-
|
|
192
|
-
編集できる PPTX にして
|
|
193
|
-
PowerPoint のスライドにして
|
|
194
|
-
PNG で出して
|
|
195
|
-
日英併記で出して
|
|
196
|
-
符号表(符号の説明)も Markdown で出して
|
|
196
|
+
次の装置クレームを構成図にして。「撮像部と、前記撮像部が取得した画像を処理する画像処理部と、処理結果を表示する表示部と、を備える撮像装置。」
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
```text
|
|
200
|
+
次の方法クレームをフローチャートにして。「画像を取得するステップと、前記画像から特徴量を抽出するステップと、欠陥の有無を判定するステップと、欠陥が有る場合に警告を出力するステップと、を含む検査方法。」
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**出力形式を指定したいとき** — 続けて一言頼むだけです(指定しなければ SVG):
|
|
204
|
+
|
|
205
|
+
- 出願用の PDF にする → 「さっきの図を PDF にして」
|
|
206
|
+
- 編集できる PowerPoint にする → 「編集できる PPTX にして」
|
|
207
|
+
- 画像(PNG)にする → 「PNG で出して」
|
|
208
|
+
- 日英併記にする → 「日英併記で出して」
|
|
209
|
+
- 符号表(符号の説明)も出す → 「符号表も Markdown で出して」
|
|
210
|
+
|
|
199
211
|
> うまくいかないときは、AI に「`npx @shibayama/pdgkit guide` をもう一度読んで、`npx @shibayama/pdgkit validate` のエラーが無くなるまで直して」と伝えてください。PNG / PDF / PPTX は環境により失敗することがありますが、SVG は確実に出ます。
|
|
200
212
|
|
|
201
213
|
### ライブラリとして(Node / TypeScript)
|
|
@@ -500,12 +512,18 @@ command = "pdgkit-mcp"
|
|
|
500
512
|
|
|
501
513
|
#### 3. 使う
|
|
502
514
|
|
|
503
|
-
|
|
515
|
+
あとは普通に日本語で頼むだけです(1 つずつ送れます)。例:
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
制御装置の構成図を描いて。制御装置は CPU、メモリ、I/O インターフェースを内蔵し、I/O インターフェースが外部機器へ信号を送る。
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
```text
|
|
522
|
+
さっきの図を PNG で見せて。
|
|
523
|
+
```
|
|
504
524
|
|
|
505
525
|
```text
|
|
506
|
-
|
|
507
|
-
さっきの図を PNG で見せて
|
|
508
|
-
編集できる PPTX にして
|
|
526
|
+
編集できる PPTX にして。
|
|
509
527
|
```
|
|
510
528
|
|
|
511
529
|
Claude は内容に応じて次のツールを使い分けます(あなたが意識する必要はありません)。
|
|
@@ -655,7 +673,7 @@ src/core 純粋・依存ゼロ(PatentDSL から移植、唯一の真実源)
|
|
|
655
673
|
|
|
656
674
|
## 本家との忠実性
|
|
657
675
|
|
|
658
|
-
- [src/core](src/core) は PatentDSL
|
|
676
|
+
- [src/core](src/core) は PatentDSL のソースを基に移植している。本家からの変更は次の 2 点のみ: (1) コンテンツボックス計算で再利用するための `estimateTextWidth` の `export` 追加、(2) フローチャート・状態遷移図で前のランクへ戻る辺(ループ・リトライ)を側方のレーンへ回し、前進線との重なりを避けるレイアウト改良。
|
|
659
677
|
- 本家のテスト(構文解析・レイアウト・レイアウト回帰・ラベル配置)を移植し、すべて合格している。
|
|
660
678
|
- SVG の切り出し余白(3 単位)・表示寸法(長辺 1600px 以上)は本家の定数と一致する。
|
|
661
679
|
|
|
@@ -664,7 +682,7 @@ src/core 純粋・依存ゼロ(PatentDSL から移植、唯一の真実源)
|
|
|
664
682
|
```bash
|
|
665
683
|
npm install
|
|
666
684
|
npm run typecheck
|
|
667
|
-
npm test # vitest(コア移植 + Node 層、計
|
|
685
|
+
npm test # vitest(コア移植 + Node 層、計 137 テスト)
|
|
668
686
|
npm run cli -- render --sample block -o /tmp/x.svg
|
|
669
687
|
npm run mcp # MCP サーバを起動
|
|
670
688
|
npm run build # tsup で dist/(ESM + CJS + 型定義)を生成
|
package/dist/core.cjs
CHANGED
|
@@ -228,6 +228,8 @@ var THICK_ARROW_TERMINAL_CLEARANCE = 5.4;
|
|
|
228
228
|
var VERTICAL_PORT_RATIO = 0.25;
|
|
229
229
|
var PORT_STUB = 6;
|
|
230
230
|
var MAX_ROUTE_LANES = 18;
|
|
231
|
+
var LOOP_LANE_GAP = 10;
|
|
232
|
+
var LOOP_LANE_STEP = 7;
|
|
231
233
|
var EPS = 1e-3;
|
|
232
234
|
function layout(doc) {
|
|
233
235
|
switch (doc.kind) {
|
|
@@ -407,7 +409,6 @@ function layoutFlow(doc) {
|
|
|
407
409
|
const positions = /* @__PURE__ */ new Map();
|
|
408
410
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
409
411
|
let y = MARGIN;
|
|
410
|
-
let maxX = 0;
|
|
411
412
|
for (const r of sortedRanks) {
|
|
412
413
|
const lane = byRank.get(r);
|
|
413
414
|
const widths = lane.map((id) => shapeOf(id) === "diamond" ? NODE_W * 1.2 : NODE_W);
|
|
@@ -419,7 +420,6 @@ function layoutFlow(doc) {
|
|
|
419
420
|
positions.set(lane[i], { x, y, w: widths[i], h: NODE_H });
|
|
420
421
|
x += widths[i] + H_GAP;
|
|
421
422
|
}
|
|
422
|
-
if (x > maxX) maxX = x;
|
|
423
423
|
y += NODE_H + V_GAP;
|
|
424
424
|
}
|
|
425
425
|
for (const id of ids) {
|
|
@@ -439,7 +439,8 @@ function layoutFlow(doc) {
|
|
|
439
439
|
});
|
|
440
440
|
}
|
|
441
441
|
const edges = makeEdges(doc.edges, positions);
|
|
442
|
-
|
|
442
|
+
const { width, height } = flowExtent(placed, edges);
|
|
443
|
+
return { nodes: placed, edges, width, height, kind: "flow" };
|
|
443
444
|
}
|
|
444
445
|
function layoutState(doc) {
|
|
445
446
|
const { byRank } = computeRanks(doc);
|
|
@@ -451,7 +452,6 @@ function layoutState(doc) {
|
|
|
451
452
|
const positions = /* @__PURE__ */ new Map();
|
|
452
453
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
453
454
|
let y = MARGIN;
|
|
454
|
-
let maxX = 0;
|
|
455
455
|
for (const r of sortedRanks) {
|
|
456
456
|
const lane = byRank.get(r);
|
|
457
457
|
const widths = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_W);
|
|
@@ -463,7 +463,6 @@ function layoutState(doc) {
|
|
|
463
463
|
positions.set(lane[i], { x, y: y + (NODE_H - heights[i]) / 2, w: widths[i], h: heights[i] });
|
|
464
464
|
x += widths[i] + H_GAP;
|
|
465
465
|
}
|
|
466
|
-
if (x > maxX) maxX = x;
|
|
467
466
|
y += NODE_H + V_GAP;
|
|
468
467
|
}
|
|
469
468
|
for (const id of doc.nodes.keys()) {
|
|
@@ -483,7 +482,8 @@ function layoutState(doc) {
|
|
|
483
482
|
});
|
|
484
483
|
}
|
|
485
484
|
const edges = makeEdges(doc.edges, positions);
|
|
486
|
-
|
|
485
|
+
const { width, height } = flowExtent(placed, edges);
|
|
486
|
+
return { nodes: placed, edges, width, height, kind: "state" };
|
|
487
487
|
}
|
|
488
488
|
function layoutSeq(doc) {
|
|
489
489
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -602,21 +602,50 @@ function computeRanks(doc) {
|
|
|
602
602
|
return { byRank };
|
|
603
603
|
}
|
|
604
604
|
function makeEdges(srcEdges, positions) {
|
|
605
|
-
|
|
605
|
+
const boxes = [...positions.values()];
|
|
606
|
+
const rightLimit = boxes.length ? Math.max(...boxes.map((b) => b.x + b.w)) : MARGIN;
|
|
607
|
+
const feedback = srcEdges.map((e, index) => ({ index, a: positions.get(e.from), b: positions.get(e.to) })).filter((r) => !!r.a && !!r.b && r.b.y + r.b.h <= r.a.y + EPS).sort((p, q) => feedbackSpan(p.a, p.b) - feedbackSpan(q.a, q.b));
|
|
608
|
+
const laneOf = /* @__PURE__ */ new Map();
|
|
609
|
+
feedback.forEach((r, nest) => laneOf.set(r.index, nest));
|
|
610
|
+
return srcEdges.map((e, index) => {
|
|
606
611
|
const a = positions.get(e.from);
|
|
607
612
|
const b = positions.get(e.to);
|
|
608
613
|
if (!a || !b) {
|
|
609
614
|
return { from: e.from, to: e.to, points: [], label: e.label, op: e.op };
|
|
610
615
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
points: orthogonalRoute(a, b),
|
|
615
|
-
label: e.label,
|
|
616
|
-
op: e.op
|
|
617
|
-
};
|
|
616
|
+
const nest = laneOf.get(index);
|
|
617
|
+
const points = nest === void 0 ? orthogonalRoute(a, b) : feedbackRoute(a, b, rightLimit + LOOP_LANE_GAP + nest * LOOP_LANE_STEP);
|
|
618
|
+
return { from: e.from, to: e.to, points, label: e.label, op: e.op };
|
|
618
619
|
});
|
|
619
620
|
}
|
|
621
|
+
function feedbackSpan(a, b) {
|
|
622
|
+
return a.y + a.h / 2 - (b.y + b.h / 2);
|
|
623
|
+
}
|
|
624
|
+
function feedbackRoute(a, b, laneX) {
|
|
625
|
+
const ay = a.y + a.h / 2;
|
|
626
|
+
const by = b.y + b.h / 2;
|
|
627
|
+
return [
|
|
628
|
+
[a.x + a.w, ay],
|
|
629
|
+
[laneX, ay],
|
|
630
|
+
[laneX, by],
|
|
631
|
+
[b.x + b.w, by]
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
function flowExtent(placed, edges) {
|
|
635
|
+
let maxX = MARGIN;
|
|
636
|
+
let maxY = MARGIN;
|
|
637
|
+
for (const n of placed) {
|
|
638
|
+
if (n.x + n.w > maxX) maxX = n.x + n.w;
|
|
639
|
+
if (n.y + n.h > maxY) maxY = n.y + n.h;
|
|
640
|
+
}
|
|
641
|
+
for (const e of edges) {
|
|
642
|
+
for (const [x, y] of e.points) {
|
|
643
|
+
if (x > maxX) maxX = x;
|
|
644
|
+
if (y > maxY) maxY = y;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return { width: maxX + MARGIN, height: maxY + MARGIN };
|
|
648
|
+
}
|
|
620
649
|
function hasLinearChildFlow(children, edges, childMap) {
|
|
621
650
|
const childSet = new Set(children);
|
|
622
651
|
const pairs = /* @__PURE__ */ new Set();
|
package/dist/core.js
CHANGED
|
@@ -189,6 +189,8 @@ var THICK_ARROW_TERMINAL_CLEARANCE = 5.4;
|
|
|
189
189
|
var VERTICAL_PORT_RATIO = 0.25;
|
|
190
190
|
var PORT_STUB = 6;
|
|
191
191
|
var MAX_ROUTE_LANES = 18;
|
|
192
|
+
var LOOP_LANE_GAP = 10;
|
|
193
|
+
var LOOP_LANE_STEP = 7;
|
|
192
194
|
var EPS = 1e-3;
|
|
193
195
|
function layout(doc) {
|
|
194
196
|
switch (doc.kind) {
|
|
@@ -368,7 +370,6 @@ function layoutFlow(doc) {
|
|
|
368
370
|
const positions = /* @__PURE__ */ new Map();
|
|
369
371
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
370
372
|
let y = MARGIN;
|
|
371
|
-
let maxX = 0;
|
|
372
373
|
for (const r of sortedRanks) {
|
|
373
374
|
const lane = byRank.get(r);
|
|
374
375
|
const widths = lane.map((id) => shapeOf(id) === "diamond" ? NODE_W * 1.2 : NODE_W);
|
|
@@ -380,7 +381,6 @@ function layoutFlow(doc) {
|
|
|
380
381
|
positions.set(lane[i], { x, y, w: widths[i], h: NODE_H });
|
|
381
382
|
x += widths[i] + H_GAP;
|
|
382
383
|
}
|
|
383
|
-
if (x > maxX) maxX = x;
|
|
384
384
|
y += NODE_H + V_GAP;
|
|
385
385
|
}
|
|
386
386
|
for (const id of ids) {
|
|
@@ -400,7 +400,8 @@ function layoutFlow(doc) {
|
|
|
400
400
|
});
|
|
401
401
|
}
|
|
402
402
|
const edges = makeEdges(doc.edges, positions);
|
|
403
|
-
|
|
403
|
+
const { width, height } = flowExtent(placed, edges);
|
|
404
|
+
return { nodes: placed, edges, width, height, kind: "flow" };
|
|
404
405
|
}
|
|
405
406
|
function layoutState(doc) {
|
|
406
407
|
const { byRank } = computeRanks(doc);
|
|
@@ -412,7 +413,6 @@ function layoutState(doc) {
|
|
|
412
413
|
const positions = /* @__PURE__ */ new Map();
|
|
413
414
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
414
415
|
let y = MARGIN;
|
|
415
|
-
let maxX = 0;
|
|
416
416
|
for (const r of sortedRanks) {
|
|
417
417
|
const lane = byRank.get(r);
|
|
418
418
|
const widths = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_W);
|
|
@@ -424,7 +424,6 @@ function layoutState(doc) {
|
|
|
424
424
|
positions.set(lane[i], { x, y: y + (NODE_H - heights[i]) / 2, w: widths[i], h: heights[i] });
|
|
425
425
|
x += widths[i] + H_GAP;
|
|
426
426
|
}
|
|
427
|
-
if (x > maxX) maxX = x;
|
|
428
427
|
y += NODE_H + V_GAP;
|
|
429
428
|
}
|
|
430
429
|
for (const id of doc.nodes.keys()) {
|
|
@@ -444,7 +443,8 @@ function layoutState(doc) {
|
|
|
444
443
|
});
|
|
445
444
|
}
|
|
446
445
|
const edges = makeEdges(doc.edges, positions);
|
|
447
|
-
|
|
446
|
+
const { width, height } = flowExtent(placed, edges);
|
|
447
|
+
return { nodes: placed, edges, width, height, kind: "state" };
|
|
448
448
|
}
|
|
449
449
|
function layoutSeq(doc) {
|
|
450
450
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -563,21 +563,50 @@ function computeRanks(doc) {
|
|
|
563
563
|
return { byRank };
|
|
564
564
|
}
|
|
565
565
|
function makeEdges(srcEdges, positions) {
|
|
566
|
-
|
|
566
|
+
const boxes = [...positions.values()];
|
|
567
|
+
const rightLimit = boxes.length ? Math.max(...boxes.map((b) => b.x + b.w)) : MARGIN;
|
|
568
|
+
const feedback = srcEdges.map((e, index) => ({ index, a: positions.get(e.from), b: positions.get(e.to) })).filter((r) => !!r.a && !!r.b && r.b.y + r.b.h <= r.a.y + EPS).sort((p, q) => feedbackSpan(p.a, p.b) - feedbackSpan(q.a, q.b));
|
|
569
|
+
const laneOf = /* @__PURE__ */ new Map();
|
|
570
|
+
feedback.forEach((r, nest) => laneOf.set(r.index, nest));
|
|
571
|
+
return srcEdges.map((e, index) => {
|
|
567
572
|
const a = positions.get(e.from);
|
|
568
573
|
const b = positions.get(e.to);
|
|
569
574
|
if (!a || !b) {
|
|
570
575
|
return { from: e.from, to: e.to, points: [], label: e.label, op: e.op };
|
|
571
576
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
points: orthogonalRoute(a, b),
|
|
576
|
-
label: e.label,
|
|
577
|
-
op: e.op
|
|
578
|
-
};
|
|
577
|
+
const nest = laneOf.get(index);
|
|
578
|
+
const points = nest === void 0 ? orthogonalRoute(a, b) : feedbackRoute(a, b, rightLimit + LOOP_LANE_GAP + nest * LOOP_LANE_STEP);
|
|
579
|
+
return { from: e.from, to: e.to, points, label: e.label, op: e.op };
|
|
579
580
|
});
|
|
580
581
|
}
|
|
582
|
+
function feedbackSpan(a, b) {
|
|
583
|
+
return a.y + a.h / 2 - (b.y + b.h / 2);
|
|
584
|
+
}
|
|
585
|
+
function feedbackRoute(a, b, laneX) {
|
|
586
|
+
const ay = a.y + a.h / 2;
|
|
587
|
+
const by = b.y + b.h / 2;
|
|
588
|
+
return [
|
|
589
|
+
[a.x + a.w, ay],
|
|
590
|
+
[laneX, ay],
|
|
591
|
+
[laneX, by],
|
|
592
|
+
[b.x + b.w, by]
|
|
593
|
+
];
|
|
594
|
+
}
|
|
595
|
+
function flowExtent(placed, edges) {
|
|
596
|
+
let maxX = MARGIN;
|
|
597
|
+
let maxY = MARGIN;
|
|
598
|
+
for (const n of placed) {
|
|
599
|
+
if (n.x + n.w > maxX) maxX = n.x + n.w;
|
|
600
|
+
if (n.y + n.h > maxY) maxY = n.y + n.h;
|
|
601
|
+
}
|
|
602
|
+
for (const e of edges) {
|
|
603
|
+
for (const [x, y] of e.points) {
|
|
604
|
+
if (x > maxX) maxX = x;
|
|
605
|
+
if (y > maxY) maxY = y;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return { width: maxX + MARGIN, height: maxY + MARGIN };
|
|
609
|
+
}
|
|
581
610
|
function hasLinearChildFlow(children, edges, childMap) {
|
|
582
611
|
const childSet = new Set(children);
|
|
583
612
|
const pairs = /* @__PURE__ */ new Set();
|
package/dist/index.cjs
CHANGED
|
@@ -251,6 +251,8 @@ var THICK_ARROW_TERMINAL_CLEARANCE = 5.4;
|
|
|
251
251
|
var VERTICAL_PORT_RATIO = 0.25;
|
|
252
252
|
var PORT_STUB = 6;
|
|
253
253
|
var MAX_ROUTE_LANES = 18;
|
|
254
|
+
var LOOP_LANE_GAP = 10;
|
|
255
|
+
var LOOP_LANE_STEP = 7;
|
|
254
256
|
var EPS = 1e-3;
|
|
255
257
|
function layout(doc) {
|
|
256
258
|
switch (doc.kind) {
|
|
@@ -430,7 +432,6 @@ function layoutFlow(doc) {
|
|
|
430
432
|
const positions = /* @__PURE__ */ new Map();
|
|
431
433
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
432
434
|
let y = MARGIN;
|
|
433
|
-
let maxX = 0;
|
|
434
435
|
for (const r of sortedRanks) {
|
|
435
436
|
const lane = byRank.get(r);
|
|
436
437
|
const widths = lane.map((id) => shapeOf(id) === "diamond" ? NODE_W * 1.2 : NODE_W);
|
|
@@ -442,7 +443,6 @@ function layoutFlow(doc) {
|
|
|
442
443
|
positions.set(lane[i], { x, y, w: widths[i], h: NODE_H });
|
|
443
444
|
x += widths[i] + H_GAP;
|
|
444
445
|
}
|
|
445
|
-
if (x > maxX) maxX = x;
|
|
446
446
|
y += NODE_H + V_GAP;
|
|
447
447
|
}
|
|
448
448
|
for (const id of ids) {
|
|
@@ -462,7 +462,8 @@ function layoutFlow(doc) {
|
|
|
462
462
|
});
|
|
463
463
|
}
|
|
464
464
|
const edges = makeEdges(doc.edges, positions);
|
|
465
|
-
|
|
465
|
+
const { width, height } = flowExtent(placed, edges);
|
|
466
|
+
return { nodes: placed, edges, width, height, kind: "flow" };
|
|
466
467
|
}
|
|
467
468
|
function layoutState(doc) {
|
|
468
469
|
const { byRank } = computeRanks(doc);
|
|
@@ -474,7 +475,6 @@ function layoutState(doc) {
|
|
|
474
475
|
const positions = /* @__PURE__ */ new Map();
|
|
475
476
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
476
477
|
let y = MARGIN;
|
|
477
|
-
let maxX = 0;
|
|
478
478
|
for (const r of sortedRanks) {
|
|
479
479
|
const lane = byRank.get(r);
|
|
480
480
|
const widths = lane.map((id) => shapeOf(id) === "circle" ? 6 : NODE_W);
|
|
@@ -486,7 +486,6 @@ function layoutState(doc) {
|
|
|
486
486
|
positions.set(lane[i], { x, y: y + (NODE_H - heights[i]) / 2, w: widths[i], h: heights[i] });
|
|
487
487
|
x += widths[i] + H_GAP;
|
|
488
488
|
}
|
|
489
|
-
if (x > maxX) maxX = x;
|
|
490
489
|
y += NODE_H + V_GAP;
|
|
491
490
|
}
|
|
492
491
|
for (const id of doc.nodes.keys()) {
|
|
@@ -506,7 +505,8 @@ function layoutState(doc) {
|
|
|
506
505
|
});
|
|
507
506
|
}
|
|
508
507
|
const edges = makeEdges(doc.edges, positions);
|
|
509
|
-
|
|
508
|
+
const { width, height } = flowExtent(placed, edges);
|
|
509
|
+
return { nodes: placed, edges, width, height, kind: "state" };
|
|
510
510
|
}
|
|
511
511
|
function layoutSeq(doc) {
|
|
512
512
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -625,21 +625,50 @@ function computeRanks(doc) {
|
|
|
625
625
|
return { byRank };
|
|
626
626
|
}
|
|
627
627
|
function makeEdges(srcEdges, positions) {
|
|
628
|
-
|
|
628
|
+
const boxes = [...positions.values()];
|
|
629
|
+
const rightLimit = boxes.length ? Math.max(...boxes.map((b) => b.x + b.w)) : MARGIN;
|
|
630
|
+
const feedback = srcEdges.map((e, index) => ({ index, a: positions.get(e.from), b: positions.get(e.to) })).filter((r) => !!r.a && !!r.b && r.b.y + r.b.h <= r.a.y + EPS).sort((p, q) => feedbackSpan(p.a, p.b) - feedbackSpan(q.a, q.b));
|
|
631
|
+
const laneOf = /* @__PURE__ */ new Map();
|
|
632
|
+
feedback.forEach((r, nest) => laneOf.set(r.index, nest));
|
|
633
|
+
return srcEdges.map((e, index) => {
|
|
629
634
|
const a = positions.get(e.from);
|
|
630
635
|
const b = positions.get(e.to);
|
|
631
636
|
if (!a || !b) {
|
|
632
637
|
return { from: e.from, to: e.to, points: [], label: e.label, op: e.op };
|
|
633
638
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
points: orthogonalRoute(a, b),
|
|
638
|
-
label: e.label,
|
|
639
|
-
op: e.op
|
|
640
|
-
};
|
|
639
|
+
const nest = laneOf.get(index);
|
|
640
|
+
const points = nest === void 0 ? orthogonalRoute(a, b) : feedbackRoute(a, b, rightLimit + LOOP_LANE_GAP + nest * LOOP_LANE_STEP);
|
|
641
|
+
return { from: e.from, to: e.to, points, label: e.label, op: e.op };
|
|
641
642
|
});
|
|
642
643
|
}
|
|
644
|
+
function feedbackSpan(a, b) {
|
|
645
|
+
return a.y + a.h / 2 - (b.y + b.h / 2);
|
|
646
|
+
}
|
|
647
|
+
function feedbackRoute(a, b, laneX) {
|
|
648
|
+
const ay = a.y + a.h / 2;
|
|
649
|
+
const by = b.y + b.h / 2;
|
|
650
|
+
return [
|
|
651
|
+
[a.x + a.w, ay],
|
|
652
|
+
[laneX, ay],
|
|
653
|
+
[laneX, by],
|
|
654
|
+
[b.x + b.w, by]
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
function flowExtent(placed, edges) {
|
|
658
|
+
let maxX = MARGIN;
|
|
659
|
+
let maxY = MARGIN;
|
|
660
|
+
for (const n of placed) {
|
|
661
|
+
if (n.x + n.w > maxX) maxX = n.x + n.w;
|
|
662
|
+
if (n.y + n.h > maxY) maxY = n.y + n.h;
|
|
663
|
+
}
|
|
664
|
+
for (const e of edges) {
|
|
665
|
+
for (const [x, y] of e.points) {
|
|
666
|
+
if (x > maxX) maxX = x;
|
|
667
|
+
if (y > maxY) maxY = y;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return { width: maxX + MARGIN, height: maxY + MARGIN };
|
|
671
|
+
}
|
|
643
672
|
function hasLinearChildFlow(children, edges, childMap) {
|
|
644
673
|
const childSet = new Set(children);
|
|
645
674
|
const pairs = /* @__PURE__ */ new Set();
|
|
@@ -3500,7 +3529,7 @@ async function renderToPptx(source, opts = {}) {
|
|
|
3500
3529
|
}
|
|
3501
3530
|
|
|
3502
3531
|
// src/node/index.ts
|
|
3503
|
-
var VERSION = "0.1.
|
|
3532
|
+
var VERSION = "0.1.1";
|
|
3504
3533
|
function toSvgString(source, lang = "ja") {
|
|
3505
3534
|
return renderToSvg(source, { lang }).svg;
|
|
3506
3535
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { Lang, DiagramKind, Diagnostic } from './core.cjs';
|
|
2
2
|
export { Bilingual, Box, Containment, Doc, Edge, EdgeOp, LabelPlacement, LaidOut, LaidOutEdge, LaidOutNode, Node, OP_TABLE, PATTERN_LABEL, PATTERN_SOURCE, PatternId, RenderOptions, SAMPLES, SAMPLE_ORDER, SampleId, Shape, chooseLabelPlacement, estimateTextWidth, layout, parse, refsToCsv, refsToMarkdown, render, splitBilingual, stripComment } from './core.cjs';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
* renderer needs (createElementNS, setAttribute, appendChild, textContent) and
|
|
7
|
-
* serializes the tree to XML. `withShimDocument()` scopes the shim around a render
|
|
8
|
-
* call, so rendering also works in a real browser without altering the page's document.
|
|
9
|
-
*/
|
|
10
|
-
/** A serializable SVG node. The shape `render()` actually produces and consumes. */
|
|
4
|
+
/** Minimal, zero-dependency SVG DOM shim: the element operations render() needs, plus XML serialization. */
|
|
5
|
+
/** A serializable SVG node. */
|
|
11
6
|
interface SvgNode {
|
|
12
7
|
readonly nodeType: 'element';
|
|
13
8
|
tagName: string;
|
|
14
9
|
namespaceURI: string;
|
|
15
|
-
/**
|
|
10
|
+
/** Attributes, in insertion order. */
|
|
16
11
|
attrs: Map<string, string>;
|
|
17
12
|
children: SvgNode[];
|
|
18
|
-
/** Text content, or null when the
|
|
13
|
+
/** Text content, or null when the node has element children. */
|
|
19
14
|
text: string | null;
|
|
20
15
|
setAttribute(name: string, value: string): void;
|
|
21
16
|
getAttribute(name: string): string | null;
|
|
@@ -24,24 +19,12 @@ interface SvgNode {
|
|
|
24
19
|
set textContent(value: string);
|
|
25
20
|
get textContent(): string;
|
|
26
21
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Install the shim as `globalThis.document`, idempotently, but only when no DOM
|
|
29
|
-
* with `createElementNS` is already present. In a real browser this is a no-op,
|
|
30
|
-
* so the same code path serves both environments.
|
|
31
|
-
*/
|
|
22
|
+
/** Install the shim as `globalThis.document`, unless a real DOM is already present. Idempotent. */
|
|
32
23
|
declare function installDomShim(): void;
|
|
33
|
-
/**
|
|
34
|
-
* Serialize a shim element tree to an XML string. Produces self-closing tags for
|
|
35
|
-
* empty elements and correctly escapes attribute values and text content.
|
|
36
|
-
*/
|
|
24
|
+
/** Serialize a shim element tree to an XML string (self-closing empty tags, escaped values). */
|
|
37
25
|
declare function serializeSvg(node: SvgNode): string;
|
|
38
26
|
|
|
39
|
-
/**
|
|
40
|
-
* Compute the tight content bounding box of a rendered SVG tree analytically
|
|
41
|
-
* (without `getBBox`), by walking the element tree and measuring each primitive;
|
|
42
|
-
* text width reuses {@link estimateTextWidth}. Used to crop exports to the drawn
|
|
43
|
-
* extent. Also provides raster and display dimension helpers.
|
|
44
|
-
*/
|
|
27
|
+
/** Tight content bounding box of a rendered SVG tree, computed analytically (no `getBBox`), reusing {@link estimateTextWidth} for text. Also provides raster/display dimension helpers. */
|
|
45
28
|
|
|
46
29
|
interface ViewBox {
|
|
47
30
|
minX: number;
|
|
@@ -49,26 +32,16 @@ interface ViewBox {
|
|
|
49
32
|
width: number;
|
|
50
33
|
height: number;
|
|
51
34
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Compute the tight content bounding box of a rendered SVG tree, padded by `bleed`
|
|
54
|
-
* (default 3 units).
|
|
55
|
-
*/
|
|
35
|
+
/** Tight content bounding box of a rendered SVG tree, padded by `bleed` (default 3 units). */
|
|
56
36
|
declare function computeContentBox(root: SvgNode, bleed?: number): ViewBox;
|
|
57
|
-
/**
|
|
58
|
-
* Display dimensions for a viewBox so the long side is at least `targetSide` px
|
|
59
|
-
* (browser default: 1600). Mirrors `svgDisplayDimensionsForViewBox()`.
|
|
60
|
-
*/
|
|
37
|
+
/** Display dimensions for a viewBox so the long side is at least `targetSide` px (default 1600). */
|
|
61
38
|
declare function svgDisplayDimensions(viewBox: Pick<ViewBox, 'width' | 'height'>, targetSide?: number): {
|
|
62
39
|
width: number;
|
|
63
40
|
height: number;
|
|
64
41
|
scale: number;
|
|
65
42
|
};
|
|
66
43
|
|
|
67
|
-
/**
|
|
68
|
-
* Headless SVG rendering: `.pdg` source -> standalone SVG string.
|
|
69
|
-
* Crops to the real drawn extent (bleed 3) and sizes the document so the long side
|
|
70
|
-
* is at least `targetSide` px (default 1600).
|
|
71
|
-
*/
|
|
44
|
+
/** Headless SVG rendering: `.pdg` source → standalone SVG string. */
|
|
72
45
|
|
|
73
46
|
interface RenderToSvgOptions {
|
|
74
47
|
/** Display language: 'ja' (default), 'en', or 'both' (bilingual two-line labels). */
|
|
@@ -100,16 +73,10 @@ interface RenderToSvgResult {
|
|
|
100
73
|
declare function renderToSvg(source: string, opts?: RenderToSvgOptions): RenderToSvgResult;
|
|
101
74
|
|
|
102
75
|
/**
|
|
103
|
-
* Validation for AI authoring
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* in a comment) is checked against the kind inferred from structure; a mismatch
|
|
108
|
-
* is reported as an error. Guards against producing the wrong diagram type.
|
|
109
|
-
* - Lints: clearer hints for chained connections (`A -> B -> C`) and operators
|
|
110
|
-
* without surrounding spaces (`11-12`).
|
|
111
|
-
*
|
|
112
|
-
* Run on AI-generated `.pdg` and feed the diagnostics back until there are no errors.
|
|
76
|
+
* Validation for AI authoring: runs the core parser, checks the optional
|
|
77
|
+
* `#! kind: block|flow|state|seq` directive against the kind inferred from
|
|
78
|
+
* structure (mismatch = error), and adds lints for chained connections and
|
|
79
|
+
* unspaced operators. Feed the diagnostics back to the author until no errors remain.
|
|
113
80
|
*/
|
|
114
81
|
|
|
115
82
|
interface ValidateResult {
|
|
@@ -135,11 +102,7 @@ interface ValidateResult {
|
|
|
135
102
|
*/
|
|
136
103
|
declare function validate(source: string): ValidateResult;
|
|
137
104
|
|
|
138
|
-
/**
|
|
139
|
-
* Raster output (PNG / JPEG) via `@resvg/resvg-js`, with the bundled IPAex Gothic
|
|
140
|
-
* font embedded and a white background. Default scale is 8x the content box (capped
|
|
141
|
-
* to 24000 px/side and 1e8 px total). Native dependencies are imported lazily.
|
|
142
|
-
*/
|
|
105
|
+
/** Raster output (PNG / JPEG) via `@resvg/resvg-js` with the bundled IPAex Gothic font and a white background. Native dependencies are imported lazily. */
|
|
143
106
|
|
|
144
107
|
interface RasterOptions {
|
|
145
108
|
lang?: Lang;
|
|
@@ -153,12 +116,7 @@ declare function renderToPng(source: string, opts?: RasterOptions): Promise<Uint
|
|
|
153
116
|
/** Render `.pdg` source to a JPEG (8× by default, white background). */
|
|
154
117
|
declare function renderToJpeg(source: string, opts?: RasterOptions): Promise<Uint8Array>;
|
|
155
118
|
|
|
156
|
-
/**
|
|
157
|
-
* PDF output (A4, IPAex font embedded). Vector by default (jsPDF + svg2pdf.js under
|
|
158
|
-
* jsdom, with analytic getBBox / getComputedTextLength shims), falling back to a
|
|
159
|
-
* high-resolution raster PDF if the vector path fails. The figure is centered with
|
|
160
|
-
* a 10 mm margin and the page orientation follows its aspect ratio.
|
|
161
|
-
*/
|
|
119
|
+
/** PDF output (A4, IPAex font embedded). Vector by default (jsPDF + svg2pdf.js under jsdom, with analytic getBBox/getComputedTextLength shims), falling back to a high-resolution raster PDF if the vector path fails. */
|
|
162
120
|
|
|
163
121
|
interface PdfOptions {
|
|
164
122
|
lang?: Lang;
|
|
@@ -172,14 +130,7 @@ interface PdfOptions {
|
|
|
172
130
|
/** Render `.pdg` source to a PDF (A4, IPAex font, vector preferred). */
|
|
173
131
|
declare function renderToPdf(source: string, opts?: PdfOptions): Promise<Uint8Array>;
|
|
174
132
|
|
|
175
|
-
/**
|
|
176
|
-
* PPTX output: `.pdg` → PowerPoint presentation (one 16:9 slide).
|
|
177
|
-
*
|
|
178
|
-
* - image mode (default): the figure is rasterized (resvg) and placed as a
|
|
179
|
-
* centered picture — highest visual fidelity.
|
|
180
|
-
* - editable mode (`editable: true`): every SVG primitive is converted to an
|
|
181
|
-
* editable PowerPoint shape / connector / text box, for later tweaking.
|
|
182
|
-
*/
|
|
133
|
+
/** PPTX output: `.pdg` → PowerPoint (one 16:9 slide). Image mode (default) places a rasterized picture; editable mode converts each SVG primitive to an editable PowerPoint shape, connector, or text box. */
|
|
183
134
|
|
|
184
135
|
interface PptxOptions {
|
|
185
136
|
lang?: Lang;
|
|
@@ -193,22 +144,13 @@ interface PptxOptions {
|
|
|
193
144
|
/** Render `.pdg` source to a PPTX (image by default, or editable shapes). */
|
|
194
145
|
declare function renderToPptx(source: string, opts?: PptxOptions): Promise<Uint8Array>;
|
|
195
146
|
|
|
196
|
-
/**
|
|
197
|
-
* pdgkit Node API — the browser-free rendering surface a host tool calls.
|
|
198
|
-
*
|
|
199
|
-
* Implemented: SVG, PNG, JPEG, PDF, PPTX (image + editable), validation, and the
|
|
200
|
-
* reference-sign table exporters. All run with no browser.
|
|
201
|
-
*/
|
|
147
|
+
/** pdgkit Node API — the browser-free rendering surface a host tool calls. */
|
|
202
148
|
|
|
203
149
|
/** The pdgkit package version. Keep in sync with package.json. */
|
|
204
|
-
declare const VERSION = "0.1.
|
|
150
|
+
declare const VERSION = "0.1.1";
|
|
205
151
|
/** Convenience: render to SVG and return just the string. */
|
|
206
152
|
declare function toSvgString(source: string, lang?: Lang): string;
|
|
207
|
-
/**
|
|
208
|
-
* Read the bundled AI authoring guide (docs/ai-authoring-guide.md) as a string.
|
|
209
|
-
* Inject this into an LLM's system prompt so it can write valid `.pdg`, instead of
|
|
210
|
-
* pasting the guide by hand.
|
|
211
|
-
*/
|
|
153
|
+
/** Read the bundled AI authoring guide (docs/ai-authoring-guide.md) as a string, for injecting into an LLM's system prompt. */
|
|
212
154
|
declare function loadAuthoringGuide(): string;
|
|
213
155
|
|
|
214
156
|
export { Diagnostic, DiagramKind, Lang, type PdfOptions, type PptxOptions, type RasterOptions, type RenderToSvgOptions, type RenderToSvgResult, type SvgNode, VERSION, type ValidateResult, type ViewBox, computeContentBox, installDomShim, loadAuthoringGuide, renderToJpeg, renderToPdf, renderToPng, renderToPptx, renderToSvg, serializeSvg, svgDisplayDimensions, toSvgString, validate };
|