w-flowchart 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/README.md +5 -1
  2. package/dist/w-flowchart.umd.js +2 -2
  3. package/dist/w-flowchart.umd.js.map +1 -1
  4. package/docs/WFlowchart.mjs.html +169 -0
  5. package/docs/global.html +657 -0
  6. package/docs/index.html +2 -2
  7. package/package.json +52 -49
  8. package/src/WFlowchart.mjs +97 -0
  9. package/src/p1/README.md +2 -2
  10. package/src/p1/gen.mjs +54 -20
  11. package/src/p10/README.md +6 -2
  12. package/src/p10/gen.mjs +235 -15
  13. package/src/p2/README.md +1 -1
  14. package/src/p2/gen.mjs +52 -7
  15. package/src/p3/README.md +1 -1
  16. package/src/p3/gen.mjs +24 -2
  17. package/src/p4/README.md +1 -1
  18. package/src/p4/gen.mjs +36 -10
  19. package/src/p5/README.md +1 -1
  20. package/src/p5/gen.mjs +45 -18
  21. package/src/p6/README.md +1 -1
  22. package/src/p6/gen.mjs +108 -5
  23. package/src/p7/README.md +1 -1
  24. package/src/p7/gen.mjs +42 -3
  25. package/src/p8/README.md +1 -1
  26. package/src/p8/gen.mjs +41 -2
  27. package/src/p9/README.md +1 -1
  28. package/src/p9/gen.mjs +14 -7
  29. package/src/p9/page9.html +13 -5
  30. package/test/cases-diamond.mjs +221 -0
  31. package/test/cases.mjs +17 -1
  32. package/test/data//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +43 -0
  33. package/test/data//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.json +132 -0
  34. package/test/data//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.json +27 -0
  35. package/test/data//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.json +111 -0
  36. package/test/data//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.json +117 -0
  37. package/test/data//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.json +203 -0
  38. package/test/data//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.json +156 -0
  39. package/test/data//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +46 -0
  40. package/test/data//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.json +137 -0
  41. package/test/data//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.json +28 -0
  42. package/test/data//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.json +134 -0
  43. package/test/data//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.json +178 -0
  44. package/test/figures.mjs +3 -2
  45. package/test/lib.mjs +3 -1
  46. package/test/p1.test.mjs +5 -0
  47. package/test/p10.test.mjs +54 -2
  48. package/test/p2.test.mjs +5 -0
  49. package/test/p3.test.mjs +5 -0
  50. package/test/p4.test.mjs +5 -0
  51. package/test/p5.test.mjs +6 -0
  52. package/test/p6.test.mjs +5 -0
  53. package/test/p7.test.mjs +5 -0
  54. package/test/p8.test.mjs +5 -0
  55. package/test/p9.test.mjs +5 -0
  56. package/test/pics/p1/cycle.png +0 -0
  57. package/test/pics/p1/deep-nest.png +0 -0
  58. package/test/pics/p1/diamond.png +0 -0
  59. package/test/pics/p1/edges-mix.png +0 -0
  60. package/test/pics/p1/english.png +0 -0
  61. package/test/pics/p1/flat-tb.png +0 -0
  62. package/test/pics/p1/fold-tall.png +0 -0
  63. package/test/pics/p1/fold-wide.png +0 -0
  64. package/test/pics/p1/group-edge.png +0 -0
  65. package/test/pics/p1/items-node.png +0 -0
  66. package/test/pics/p1/long-label.png +0 -0
  67. package/test/pics/p1/lr-flat.png +0 -0
  68. package/test/pics/p1/nested-group.png +0 -0
  69. package/test/pics/p1/no-edges.png +0 -0
  70. package/test/pics/p1/title-overflow.png +0 -0
  71. package/test/pics/p1/valign.png +0 -0
  72. package/test/pics/p1//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  73. package/test/pics/p1//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  74. package/test/pics/p1//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  75. package/test/pics/p1//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  76. package/test/pics/p1//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  77. package/test/pics/p1//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  78. package/test/pics/p1//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  79. package/test/pics/p1//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  80. package/test/pics/p1//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  81. package/test/pics/p1//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  82. package/test/pics/p1//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  83. package/test/pics/p1//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  84. package/test/pics/p10/cycle.png +0 -0
  85. package/test/pics/p10/cycle.svg +1 -0
  86. package/test/pics/p10/deep-nest.svg +1 -0
  87. package/test/pics/p10/dia-1in-1out.png +0 -0
  88. package/test/pics/p10/dia-1in-1out.svg +1 -0
  89. package/test/pics/p10/dia-1in-2out.png +0 -0
  90. package/test/pics/p10/dia-1in-2out.svg +1 -0
  91. package/test/pics/p10/dia-1in.png +0 -0
  92. package/test/pics/p10/dia-1in.svg +1 -0
  93. package/test/pics/p10/dia-1out.png +0 -0
  94. package/test/pics/p10/dia-1out.svg +1 -0
  95. package/test/pics/p10/dia-2in-1out.png +0 -0
  96. package/test/pics/p10/dia-2in-1out.svg +1 -0
  97. package/test/pics/p10/dia-2in-2out-dd.png +0 -0
  98. package/test/pics/p10/dia-2in-2out-dd.svg +1 -0
  99. package/test/pics/p10/dia-2in-2out-ss.png +0 -0
  100. package/test/pics/p10/dia-2in-2out-ss.svg +1 -0
  101. package/test/pics/p10/dia-2in.png +0 -0
  102. package/test/pics/p10/dia-2in.svg +1 -0
  103. package/test/pics/p10/dia-2out.png +0 -0
  104. package/test/pics/p10/dia-2out.svg +1 -0
  105. package/test/pics/p10/diamond.png +0 -0
  106. package/test/pics/p10/diamond.svg +1 -0
  107. package/test/pics/p10/edges-mix.png +0 -0
  108. package/test/pics/p10/edges-mix.svg +1 -0
  109. package/test/pics/p10/english.png +0 -0
  110. package/test/pics/p10/english.svg +1 -0
  111. package/test/pics/p10/flat-tb.svg +1 -0
  112. package/test/pics/p10/fold-tall.svg +1 -0
  113. package/test/pics/p10/fold-wide.svg +1 -0
  114. package/test/pics/p10/group-edge.png +0 -0
  115. package/test/pics/p10/group-edge.svg +1 -0
  116. package/test/pics/p10/items-node.svg +1 -0
  117. package/test/pics/p10/long-label.svg +1 -0
  118. package/test/pics/p10/lr-flat.svg +1 -0
  119. package/test/pics/p10/nested-group.png +0 -0
  120. package/test/pics/p10/nested-group.svg +1 -0
  121. package/test/pics/p10/no-edges.svg +1 -0
  122. package/test/pics/p10/title-overflow.svg +1 -0
  123. package/test/pics/p10/valign.svg +1 -0
  124. package/test/pics/p10//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  125. package/test/pics/p10//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +1 -0
  126. package/test/pics/p10//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  127. package/test/pics/p10//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.svg +1 -0
  128. package/test/pics/p10//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  129. package/test/pics/p10//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.svg +1 -0
  130. package/test/pics/p10//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  131. package/test/pics/p10//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.svg +1 -0
  132. package/test/pics/p10//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  133. package/test/pics/p10//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.svg +1 -0
  134. package/test/pics/p10//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  135. package/test/pics/p10//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.svg +1 -0
  136. package/test/pics/p10//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  137. package/test/pics/p10//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.svg +1 -0
  138. package/test/pics/p10//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  139. package/test/pics/p10//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +1 -0
  140. package/test/pics/p10//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  141. package/test/pics/p10//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.svg +1 -0
  142. package/test/pics/p10//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  143. package/test/pics/p10//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.svg +1 -0
  144. package/test/pics/p10//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  145. package/test/pics/p10//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.svg +1 -0
  146. package/test/pics/p10//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  147. package/test/pics/p10//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.svg +1 -0
  148. package/test/pics/p2/cycle.png +0 -0
  149. package/test/pics/p2/deep-nest.png +0 -0
  150. package/test/pics/p2/diamond.png +0 -0
  151. package/test/pics/p2/edges-mix.png +0 -0
  152. package/test/pics/p2/english.png +0 -0
  153. package/test/pics/p2/flat-tb.png +0 -0
  154. package/test/pics/p2/fold-tall.png +0 -0
  155. package/test/pics/p2/fold-wide.png +0 -0
  156. package/test/pics/p2/group-edge.png +0 -0
  157. package/test/pics/p2/items-node.png +0 -0
  158. package/test/pics/p2/long-label.png +0 -0
  159. package/test/pics/p2/lr-flat.png +0 -0
  160. package/test/pics/p2/nested-group.png +0 -0
  161. package/test/pics/p2/no-edges.png +0 -0
  162. package/test/pics/p2/title-overflow.png +0 -0
  163. package/test/pics/p2/valign.png +0 -0
  164. package/test/pics/p2//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  165. package/test/pics/p2//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  166. package/test/pics/p2//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  167. package/test/pics/p2//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  168. package/test/pics/p2//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  169. package/test/pics/p2//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  170. package/test/pics/p2//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  171. package/test/pics/p2//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  172. package/test/pics/p2//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  173. package/test/pics/p2//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  174. package/test/pics/p2//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  175. package/test/pics/p2//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  176. package/test/pics/p3/cycle.png +0 -0
  177. package/test/pics/p3/deep-nest.png +0 -0
  178. package/test/pics/p3/diamond.png +0 -0
  179. package/test/pics/p3/edges-mix.png +0 -0
  180. package/test/pics/p3/english.png +0 -0
  181. package/test/pics/p3/flat-tb.png +0 -0
  182. package/test/pics/p3/fold-tall.png +0 -0
  183. package/test/pics/p3/fold-wide.png +0 -0
  184. package/test/pics/p3/group-edge.png +0 -0
  185. package/test/pics/p3/items-node.png +0 -0
  186. package/test/pics/p3/long-label.png +0 -0
  187. package/test/pics/p3/lr-flat.png +0 -0
  188. package/test/pics/p3/nested-group.png +0 -0
  189. package/test/pics/p3/no-edges.png +0 -0
  190. package/test/pics/p3/title-overflow.png +0 -0
  191. package/test/pics/p3/valign.png +0 -0
  192. package/test/pics/p3//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  193. package/test/pics/p3//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  194. package/test/pics/p3//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  195. package/test/pics/p3//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  196. package/test/pics/p3//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  197. package/test/pics/p3//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  198. package/test/pics/p3//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  199. package/test/pics/p3//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  200. package/test/pics/p3//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  201. package/test/pics/p3//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  202. package/test/pics/p3//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  203. package/test/pics/p3//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  204. package/test/pics/p4/cycle.png +0 -0
  205. package/test/pics/p4/deep-nest.png +0 -0
  206. package/test/pics/p4/diamond.png +0 -0
  207. package/test/pics/p4/edges-mix.png +0 -0
  208. package/test/pics/p4/english.png +0 -0
  209. package/test/pics/p4/flat-tb.png +0 -0
  210. package/test/pics/p4/fold-tall.png +0 -0
  211. package/test/pics/p4/fold-wide.png +0 -0
  212. package/test/pics/p4/group-edge.png +0 -0
  213. package/test/pics/p4/items-node.png +0 -0
  214. package/test/pics/p4/long-label.png +0 -0
  215. package/test/pics/p4/lr-flat.png +0 -0
  216. package/test/pics/p4/nested-group.png +0 -0
  217. package/test/pics/p4/no-edges.png +0 -0
  218. package/test/pics/p4/title-overflow.png +0 -0
  219. package/test/pics/p4/valign.png +0 -0
  220. package/test/pics/p4//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  221. package/test/pics/p4//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  222. package/test/pics/p4//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  223. package/test/pics/p4//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  224. package/test/pics/p4//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  225. package/test/pics/p4//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  226. package/test/pics/p4//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  227. package/test/pics/p4//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  228. package/test/pics/p4//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  229. package/test/pics/p4//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  230. package/test/pics/p4//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  231. package/test/pics/p4//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  232. package/test/pics/p5/cycle.png +0 -0
  233. package/test/pics/p5/deep-nest.png +0 -0
  234. package/test/pics/p5/diamond.png +0 -0
  235. package/test/pics/p5/edges-mix.png +0 -0
  236. package/test/pics/p5/english.png +0 -0
  237. package/test/pics/p5/flat-tb.png +0 -0
  238. package/test/pics/p5/fold-tall.png +0 -0
  239. package/test/pics/p5/fold-wide.png +0 -0
  240. package/test/pics/p5/group-edge.png +0 -0
  241. package/test/pics/p5/items-node.png +0 -0
  242. package/test/pics/p5/long-label.png +0 -0
  243. package/test/pics/p5/lr-flat.png +0 -0
  244. package/test/pics/p5/nested-group.png +0 -0
  245. package/test/pics/p5/no-edges.png +0 -0
  246. package/test/pics/p5/title-overflow.png +0 -0
  247. package/test/pics/p5/valign.png +0 -0
  248. package/test/pics/p5//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  249. package/test/pics/p5//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  250. package/test/pics/p5//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  251. package/test/pics/p5//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  252. package/test/pics/p5//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  253. package/test/pics/p5//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  254. package/test/pics/p5//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  255. package/test/pics/p5//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  256. package/test/pics/p5//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  257. package/test/pics/p5//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  258. package/test/pics/p5//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  259. package/test/pics/p5//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  260. package/test/pics/p6/cycle.png +0 -0
  261. package/test/pics/p6/deep-nest.png +0 -0
  262. package/test/pics/p6/diamond.png +0 -0
  263. package/test/pics/p6/edges-mix.png +0 -0
  264. package/test/pics/p6/english.png +0 -0
  265. package/test/pics/p6/flat-tb.png +0 -0
  266. package/test/pics/p6/fold-tall.png +0 -0
  267. package/test/pics/p6/fold-wide.png +0 -0
  268. package/test/pics/p6/group-edge.png +0 -0
  269. package/test/pics/p6/items-node.png +0 -0
  270. package/test/pics/p6/long-label.png +0 -0
  271. package/test/pics/p6/lr-flat.png +0 -0
  272. package/test/pics/p6/nested-group.png +0 -0
  273. package/test/pics/p6/no-edges.png +0 -0
  274. package/test/pics/p6/title-overflow.png +0 -0
  275. package/test/pics/p6/valign.png +0 -0
  276. package/test/pics/p6//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  277. package/test/pics/p6//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  278. package/test/pics/p6//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  279. package/test/pics/p6//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  280. package/test/pics/p6//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  281. package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.alt.png +0 -0
  282. package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.new.png +0 -0
  283. package/test/pics/p6//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  284. package/test/pics/p6//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  285. package/test/pics/p6//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  286. package/test/pics/p6//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  287. package/test/pics/p6//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  288. package/test/pics/p6//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  289. package/test/pics/p6//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  290. package/test/pics/p7/cycle.png +0 -0
  291. package/test/pics/p7/deep-nest.png +0 -0
  292. package/test/pics/p7/diamond.png +0 -0
  293. package/test/pics/p7/edges-mix.png +0 -0
  294. package/test/pics/p7/english.png +0 -0
  295. package/test/pics/p7/flat-tb.png +0 -0
  296. package/test/pics/p7/fold-tall.png +0 -0
  297. package/test/pics/p7/fold-wide.png +0 -0
  298. package/test/pics/p7/group-edge.png +0 -0
  299. package/test/pics/p7/items-node.png +0 -0
  300. package/test/pics/p7/long-label.png +0 -0
  301. package/test/pics/p7/lr-flat.png +0 -0
  302. package/test/pics/p7/nested-group.png +0 -0
  303. package/test/pics/p7/no-edges.png +0 -0
  304. package/test/pics/p7/title-overflow.png +0 -0
  305. package/test/pics/p7/valign.png +0 -0
  306. package/test/pics/p7//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  307. package/test/pics/p7//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  308. package/test/pics/p7//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  309. package/test/pics/p7//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  310. package/test/pics/p7//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  311. package/test/pics/p7//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  312. package/test/pics/p7//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  313. package/test/pics/p7//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  314. package/test/pics/p7//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  315. package/test/pics/p7//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  316. package/test/pics/p7//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  317. package/test/pics/p7//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  318. package/test/pics/p8/cycle.png +0 -0
  319. package/test/pics/p8/deep-nest.png +0 -0
  320. package/test/pics/p8/diamond.png +0 -0
  321. package/test/pics/p8/edges-mix.png +0 -0
  322. package/test/pics/p8/english.png +0 -0
  323. package/test/pics/p8/flat-tb.png +0 -0
  324. package/test/pics/p8/fold-tall.png +0 -0
  325. package/test/pics/p8/fold-wide.png +0 -0
  326. package/test/pics/p8/group-edge.png +0 -0
  327. package/test/pics/p8/items-node.png +0 -0
  328. package/test/pics/p8/long-label.png +0 -0
  329. package/test/pics/p8/lr-flat.png +0 -0
  330. package/test/pics/p8/nested-group.png +0 -0
  331. package/test/pics/p8/no-edges.png +0 -0
  332. package/test/pics/p8/title-overflow.png +0 -0
  333. package/test/pics/p8/valign.png +0 -0
  334. package/test/pics/p8//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  335. package/test/pics/p8//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  336. package/test/pics/p8//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  337. package/test/pics/p8//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  338. package/test/pics/p8//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  339. package/test/pics/p8//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  340. package/test/pics/p8//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  341. package/test/pics/p8//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  342. package/test/pics/p8//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  343. package/test/pics/p8//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  344. package/test/pics/p8//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  345. package/test/pics/p8//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  346. package/test/pics/p9/cycle.png +0 -0
  347. package/test/pics/p9/deep-nest.png +0 -0
  348. package/test/pics/p9/diamond.png +0 -0
  349. package/test/pics/p9/edges-mix.png +0 -0
  350. package/test/pics/p9/english.png +0 -0
  351. package/test/pics/p9/flat-tb.png +0 -0
  352. package/test/pics/p9/fold-tall.png +0 -0
  353. package/test/pics/p9/fold-wide.png +0 -0
  354. package/test/pics/p9/group-edge.png +0 -0
  355. package/test/pics/p9/items-node.png +0 -0
  356. package/test/pics/p9/long-label.png +0 -0
  357. package/test/pics/p9/lr-flat.png +0 -0
  358. package/test/pics/p9/nested-group.png +0 -0
  359. package/test/pics/p9/no-edges.png +0 -0
  360. package/test/pics/p9/title-overflow.png +0 -0
  361. package/test/pics/p9/valign.png +0 -0
  362. package/test/pics/p9//345/267/245/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  363. package/test/pics/p9//345/276/256/346/234/215/345/213/231/345/217/242/351/233/206/346/236/266/346/247/213/345/234/226.png +0 -0
  364. package/test/pics/p9//346/234/203/345/223/241/350/250/273/345/206/212/346/265/201/347/250/213/345/234/226.png +0 -0
  365. package/test/pics/p9//346/250/241/347/265/204/347/265/204/346/210/220/345/234/226.png +0 -0
  366. package/test/pics/p9//346/254/212/351/231/220/350/247/222/350/211/262/351/227/234/350/201/257/345/234/226.png +0 -0
  367. package/test/pics/p9//347/211/251/350/201/257/347/266/262/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  368. package/test/pics/p9//347/263/273/347/265/261/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  369. package/test/pics/p9//350/250/202/345/226/256/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  370. package/test/pics/p9//350/263/207/346/226/231/346/265/201/345/220/221/345/234/226.png +0 -0
  371. package/test/pics/p9//350/263/207/346/226/231/346/270/205/346/264/227/346/265/201/347/250/213/345/234/226.png +0 -0
  372. package/test/pics/p9//351/233/273/345/225/206/345/271/263/345/217/260/346/236/266/346/247/213/345/234/226.png +0 -0
  373. package/test/pics/p9//351/240/201/351/235/242/345/260/216/350/246/275/351/227/234/350/201/257/345/234/226.png +0 -0
  374. package/test/snapshot.mjs +67 -0
  375. package/test/wflowchart.test.mjs +57 -11
  376. package/toolg/gDistRollup.mjs +3 -0
  377. package/test/data//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.json +0 -92
  378. package/test/data//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.json +0 -112
  379. package/test/data//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.json +0 -116
  380. package/test/data//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.json +0 -251
  381. package/test/data//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.json +0 -126
  382. package/test/data//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.json +0 -133
  383. package/test/data//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.json +0 -102
  384. package/test/data//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.json +0 -117
  385. package/test/data//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.json +0 -162
  386. package/test/pics/cycle.png +0 -0
  387. package/test/pics/cycle.svg +0 -1
  388. package/test/pics/deep-nest.svg +0 -1
  389. package/test/pics/diamond.png +0 -0
  390. package/test/pics/diamond.svg +0 -1
  391. package/test/pics/edges-mix.png +0 -0
  392. package/test/pics/edges-mix.svg +0 -1
  393. package/test/pics/english.png +0 -0
  394. package/test/pics/english.svg +0 -1
  395. package/test/pics/flat-tb.svg +0 -1
  396. package/test/pics/fold-tall.svg +0 -1
  397. package/test/pics/fold-wide.svg +0 -1
  398. package/test/pics/group-edge.png +0 -0
  399. package/test/pics/group-edge.svg +0 -1
  400. package/test/pics/items-node.svg +0 -1
  401. package/test/pics/long-label.svg +0 -1
  402. package/test/pics/lr-flat.svg +0 -1
  403. package/test/pics/nested-group.png +0 -0
  404. package/test/pics/nested-group.svg +0 -1
  405. package/test/pics/no-edges.svg +0 -1
  406. package/test/pics/title-overflow.svg +0 -1
  407. package/test/pics/valign.svg +0 -1
  408. package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.png +0 -0
  409. package/test/pics//344/275/234/346/245/255/346/265/201/347/250/213/345/234/226.svg +0 -1
  410. package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.png +0 -0
  411. package/test/pics//345/211/215/347/253/257/346/236/266/346/247/213/350/210/207/350/263/207/346/226/231/346/265/201/345/234/226.svg +0 -1
  412. package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.png +0 -0
  413. package/test/pics//345/276/214/347/253/257/346/234/215/345/213/231/347/265/204/346/210/220/345/234/226.svg +0 -1
  414. package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.png +0 -0
  415. package/test/pics//346/211/271/346/254/241/344/270/212/345/202/263/350/231/225/347/220/206/346/265/201/347/250/213/345/234/226.svg +0 -1
  416. package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.png +0 -0
  417. package/test/pics//347/250/213/345/274/217/345/205/203/344/273/266/347/265/204/346/210/220/345/234/226.svg +0 -1
  418. package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.png +0 -0
  419. package/test/pics//347/250/213/345/274/217/345/210/206/345/261/244/346/236/266/346/247/213/345/234/226.svg +0 -1
  420. package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.png +0 -0
  421. package/test/pics//347/263/273/347/265/261/346/236/266/346/247/213/345/234/226.svg +0 -1
  422. package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.png +0 -0
  423. package/test/pics//347/263/273/347/265/261/351/227/234/350/201/257/345/234/226.svg +0 -1
  424. package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.png +0 -0
  425. package/test/pics//347/267/232/344/270/212/347/225/253/351/235/242/351/227/234/350/201/257/345/234/226.svg +0 -1
  426. /package/test/pics/{deep-nest.png → p10/deep-nest.png} +0 -0
  427. /package/test/pics/{flat-tb.png → p10/flat-tb.png} +0 -0
  428. /package/test/pics/{fold-tall.png → p10/fold-tall.png} +0 -0
  429. /package/test/pics/{fold-wide.png → p10/fold-wide.png} +0 -0
  430. /package/test/pics/{items-node.png → p10/items-node.png} +0 -0
  431. /package/test/pics/{long-label.png → p10/long-label.png} +0 -0
  432. /package/test/pics/{lr-flat.png → p10/lr-flat.png} +0 -0
  433. /package/test/pics/{no-edges.png → p10/no-edges.png} +0 -0
  434. /package/test/pics/{title-overflow.png → p10/title-overflow.png} +0 -0
  435. /package/test/pics/{valign.png → p10/valign.png} +0 -0
package/src/p5/gen.mjs CHANGED
@@ -5,8 +5,8 @@
5
5
  // 注意:本版 D2(0.1.33 / WASM v0.7.0) 傳入自訂 font buffer 會穩定回 "invalid JSON input"。
6
6
  // 改為不傳字型編譯,渲染為 SVG 後注入 font-family 覆寫,交由 librsvg(sharp) 以系統「Microsoft JhengHei」實際描繪。
7
7
  // D2 v0.7 內建 CJK 全形寬度量測,box 寬即使長中文亦不溢出(已實測)。
8
- import { D2 } from '@terrastruct/d2'
9
- import sharp from 'sharp'
8
+ // @terrastruct/d2 sharp 為本產線專屬重依賴, 於函式內動態載入(延遲載入):
9
+ // 模組層載入會令「僅用其他產線」的使用者也付出載入成本, d2 上游缺陷會連帶影響其他產線
10
10
  import { colorOf, isGroupCls } from '../common/palette.mjs'
11
11
 
12
12
  // ── 字型注入 ────────────────────────────────────────────────────────────────
@@ -68,27 +68,27 @@ function styLines(cls, isGroup) {
68
68
 
69
69
  // ── 取節點在 D2 中的完整路徑(用於邊的 from/to 引用) ────────────────────────
70
70
  // D2 巢狀容器中子節點須以 "父.子" 表示;遞迴向上追溯直到頂層。
71
- function qualifiedId(nodeId, parentMap) {
71
+ function qualifiedId(nodeId, parentMap, qid) {
72
72
  const parts = []
73
73
  let cur = nodeId
74
74
  while (cur) {
75
75
  parts.unshift(cur)
76
76
  cur = parentMap[cur]
77
77
  }
78
- return parts.join('.')
78
+ return parts.map(qid).join('.')
79
79
  }
80
80
 
81
81
  // ── 遞迴產生 D2 區塊:容器(群組)巢狀展開 ────────────────────────────────────
82
82
  // childrenMap: parentId → [node, ...]
83
83
  // parentMap: nodeId → parentId | undefined
84
- function renderNode(nd, childrenMap, parentMap, dataDir, indent) {
84
+ function renderNode(nd, childrenMap, parentMap, dataDir, indent, qid) {
85
85
  const pad = ' '.repeat(indent)
86
86
  const isGroup = isGroupCls(nd.cls)
87
87
  const lines = []
88
88
 
89
89
  if (isGroup) {
90
90
  // 群組容器:開 block,在內部設 direction + style,再遞迴展開子節點
91
- lines.push(`${pad}${nd.id}: "${esc(nd.label)}" {`)
91
+ lines.push(`${pad}${qid(nd.id)}: "${esc(nd.label)}" {`)
92
92
  // 若群組本身有子節點,為其設定方向(延用圖的頂層方向)
93
93
  const kids = childrenMap[nd.id] || []
94
94
  if (kids.length > 0) {
@@ -98,12 +98,12 @@ function renderNode(nd, childrenMap, parentMap, dataDir, indent) {
98
98
  lines.push(`${pad}${sl}`)
99
99
  }
100
100
  for (const kid of kids) {
101
- lines.push(renderNode(kid, childrenMap, parentMap, dataDir, indent + 2))
101
+ lines.push(renderNode(kid, childrenMap, parentMap, dataDir, indent + 2, qid))
102
102
  }
103
103
  lines.push(`${pad}}`)
104
104
  } else {
105
105
  // 一般節點(含 diamond)
106
- lines.push(`${pad}${nd.id}: "${esc(nd.label)}" {`)
106
+ lines.push(`${pad}${qid(nd.id)}: "${esc(nd.label)}" {`)
107
107
  for (const sl of styLines(nd.cls, false)) {
108
108
  lines.push(`${pad}${sl}`)
109
109
  }
@@ -116,6 +116,20 @@ function renderNode(nd, childrenMap, parentMap, dataDir, indent) {
116
116
  // ── translate:標準數據 → D2 DSL 字串 ─────────────────────────────────────────
117
117
  // 邊一律在頂層宣告,跨群組時用完整路徑(parent.child)讓 D2 正確解析。
118
118
  function translate(data) {
119
+ // D2 保留字(top/left/right/bottom/style/label/shape/near/width/height 等)不可作識別子 → id 一律加前綴消歧
120
+ // 僅影響 DSL 內部識別(顯示文字仍用 label); 不同原 id 淨化後若同名, 以序號區隔避免節點被合併
121
+ const used = new Set()
122
+ const qidMap = {}
123
+ const qid = (id) => {
124
+ if (qidMap[id]) return qidMap[id]
125
+ const base = 'n_' + String(id).replace(/[^A-Za-z0-9_]/g, '_')
126
+ let q = base, k = 2
127
+ while (used.has(q)) q = base + '_' + (k++)
128
+ used.add(q)
129
+ qidMap[id] = q
130
+ return q
131
+ }
132
+
119
133
  // 建立 parentMap(nodeId → parentId) 與 childrenMap(parentId → [node])
120
134
  const parentMap = {}
121
135
  const childrenMap = {}
@@ -137,13 +151,13 @@ function translate(data) {
137
151
 
138
152
  // 頂層節點(含群組容器)
139
153
  for (const nd of topNodes) {
140
- blocks.push(renderNode(nd, childrenMap, parentMap, data.dir, 0))
154
+ blocks.push(renderNode(nd, childrenMap, parentMap, data.dir, 0, qid))
141
155
  }
142
156
 
143
157
  // 邊:全部在頂層,跨層路徑用 qualifiedId 展開
144
158
  for (const ed of data.edges) {
145
- const src = qualifiedId(ed.from, parentMap)
146
- const dst = qualifiedId(ed.to, parentMap)
159
+ const src = qualifiedId(ed.from, parentMap, qid)
160
+ const dst = qualifiedId(ed.to, parentMap, qid)
147
161
  let s = `${src} -> ${dst}`
148
162
  if (ed.label) s += `: "${esc(ed.label)}"`
149
163
  if (ed.kind === 'dashed') s += ` { style.stroke-dash: 4 }`
@@ -157,11 +171,12 @@ function translate(data) {
157
171
  // pad=40 與 themeID=0 為通用常數,對所有 9 張圖一致;無逐圖數值。
158
172
  const baseOpt = { layout: 'dagre', pad: 40, themeID: 0 }
159
173
 
160
- // ── 單張渲染:正規化繪圖數據PNG Buffer ─────────────────────────────────────
174
+ // ── 單張渲染核心:正規化繪圖數據後處理完成之 SVG 字串 ──────────────────────
161
175
  // data 結構同 FIGURES[n].data({ dir, nodes, edges }),caller 須先做 label 衍生。
162
176
  // D2 建構時即起常駐 worker 且無公開關閉 API(worker 為 node worker_threads 實例),
163
177
  // 若掛在模組層, 光 import 本檔就會令 caller 程序無法自然結束;故逐次建立並於 finally terminate。
164
- export async function genPng(data, opt = {}) {
178
+ async function d2Svg(data) {
179
+ const { D2 } = await import('@terrastruct/d2')
165
180
  const d2inst = new D2()
166
181
  try {
167
182
  const d2src = translate(data)
@@ -169,14 +184,26 @@ export async function genPng(data, opt = {}) {
169
184
  let svg = await d2inst.render(res.diagram, res.renderOptions)
170
185
  svg = injectEdgeLabelHalo(svg)
171
186
  svg = injectFont(svg)
172
- // density 通用公式:依 SVG viewBox 寬推算,讓輸出寬約落在 1600–2200px
173
- const m = svg.match(/viewBox="0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)"/)
174
- const w = m ? parseFloat(m[1]) : 1000
175
- const density = 96 * Math.min(2.4, Math.max(1.4, 1700 / w))
176
- return await sharp(Buffer.from(svg), { density }).png().toBuffer()
187
+ return svg
177
188
  }
178
189
  finally {
179
190
  await d2inst.ready.catch(() => {})
180
191
  if (d2inst.worker && d2inst.worker.terminate) await d2inst.worker.terminate()
181
192
  }
182
193
  }
194
+
195
+ // ── 單張渲染 → PNG Buffer ────────────────────────────────────────────────────
196
+ export async function genPng(data, opt = {}) {
197
+ const { default: sharp } = await import('sharp')
198
+ const svg = await d2Svg(data)
199
+ // density 通用公式:依 SVG viewBox 寬推算,讓輸出寬約落在 1600–2200px
200
+ const m = svg.match(/viewBox="0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)"/)
201
+ const w = m ? parseFloat(m[1]) : 1000
202
+ const density = 96 * Math.min(2.4, Math.max(1.4, 1700 / w))
203
+ return await sharp(Buffer.from(svg), { density }).png().toBuffer()
204
+ }
205
+
206
+ // ── 單張渲染 → SVG 字串(D2 引擎原生輸出 + 光暈/字型後處理) ───────────────────
207
+ export async function genSvg(data, opt = {}) {
208
+ return await d2Svg(data)
209
+ }
package/src/p6/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - **渲染方式**:以 `chromium.launch()` 開啟無頭瀏覽器,`page.setContent(html)` 載入內嵌 G6 頁面,於頁面內呼叫 `window.renderFig()`,以 `G6.Graph` + `layout: { type:'antv-dagre' }` 完成排版與渲染。
7
7
  - **截圖/輸出**:呼叫 `graph.toDataURL({ mode:'overall' })` 取得整圖 DataURL(非 viewport 截圖),透過 `window.snap()` 取回後在 Node 端解 base64,組成 `Buffer` 回傳(不寫檔)。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素解析度的 PNG。
9
- - **介面**:`genPng(data, opt)` 為單張渲染函式,由 `src/WFlowchart.mjs` 統一調用;`data` 為正規化繪圖數據 `{ dir, nodes, edges }`(caller 已完成 label 衍生)。每次呼叫皆逐次 `chromium.launch()`,並在 `finally` 呼叫 `browser.close()`,不跨圖共用頁面。
9
+ - **介面**:`genPng(data, opt)` 為單張渲染函式,由 `src/WFlowchart.mjs` 統一調用;`data` 為正規化繪圖數據 `{ dir, nodes, edges }`(caller 已完成 label 衍生)。每次呼叫皆逐次 `chromium.launch()`,並在 `finally` 呼叫 `browser.close()`,不跨圖共用頁面;同結構之 `genSvg(data, opt)` 換用 `@antv/g-lite` + `@antv/g-svg` 之官方 renderer API 以 SVG renderer 重繪(序列化前將相機 transform 歸零),與 `genPng` 共用同一套 `translate`/排版/樣式邏輯,經忠實度驗證。
10
10
 
11
11
  ## 產製原理(資料驅動)
12
12
 
package/src/p6/gen.mjs CHANGED
@@ -16,6 +16,10 @@ import { pkgScript } from '../common/pkg.mjs'
16
16
 
17
17
  // G6 v5 由本機 node_modules 內聯注入(取代 CDN, 斷網環境可用)
18
18
  const G6_JS = pkgScript('@antv/g6/dist/g6.min.js')
19
+ // genSvg 專用:@antv/g-svg renderer(取代預設 canvas renderer, 產出 SVG DOM)。
20
+ // 依賴 @antv/g-lite UMD(暴露全域 window.G)須先於 g-svg UMD 載入(g-svg UMD 讀取既有 window.G 掛載 window.G.SVG)。
21
+ const G_LITE_JS = pkgScript('@antv/g-lite/dist/index.umd.min.js')
22
+ const G_SVG_JS = pkgScript('@antv/g-svg/dist/index.umd.min.js')
19
23
 
20
24
  // ===== 通用排版常數(全圖共用, 無逐圖客製) =====
21
25
  const CANVAS = 2200 // 大畫布:給足夠空間, 配合 autoFit:'view' 自動縮放, 不逐圖設 w/h
@@ -52,15 +56,22 @@ function translate(data) {
52
56
  const nodes = []
53
57
  const combos = []
54
58
  // G6 v5 不接受以 combo id 當邊端點; 建「容器 → 代表葉節點」對照, 將指向容器的邊改接其代表成員。
55
- // 通用化舊版「層級邊改走成員節點(fe1→if1)」的手法:取各容器第一個直屬葉節點為代表, 不逐圖寫死成員 id。
59
+ // 通用化舊版「層級邊改走成員節點(fe1→if1)」的手法:取容器內數據序第一個「可達」葉節點為代表;
60
+ // 容器內可能只有子容器(深巢狀), 故須遞迴下鑽, 不能只看直屬成員。
56
61
  const comboIds = new Set(data.nodes.filter(nd => isGroupCls(nd.cls)).map(nd => nd.id))
57
- const kpRepByCombo = {}
62
+ const childrenOf = {}
58
63
  for (const nd of data.nodes) {
59
- if (nd.group && comboIds.has(nd.group) && !isGroupCls(nd.cls) && !(nd.group in kpRepByCombo)) {
60
- kpRepByCombo[nd.group] = nd.id
64
+ if (nd.group) (childrenOf[nd.group] = childrenOf[nd.group] || []).push(nd)
65
+ }
66
+ const firstLeaf = (cid) => {
67
+ for (const ch of (childrenOf[cid] || [])) {
68
+ if (!isGroupCls(ch.cls)) return ch.id
69
+ const r = firstLeaf(ch.id)
70
+ if (r) return r
61
71
  }
72
+ return null
62
73
  }
63
- const resolveEnd = (id) => (comboIds.has(id) && kpRepByCombo[id]) ? kpRepByCombo[id] : id
74
+ const resolveEnd = (id) => (comboIds.has(id) && firstLeaf(id)) || id
64
75
  for (const nd of data.nodes) {
65
76
  const c = colorOf(nd.cls)
66
77
  if (isGroupCls(nd.cls)) {
@@ -184,3 +195,95 @@ export async function genPng(data, opt = {}) {
184
195
  await browser.close()
185
196
  }
186
197
  }
198
+
199
+ // ===== genSvg 專用渲染外殼 =====
200
+ // 與 genPng 完全獨立之頁面/renderer 流程(genPng 之 html/renderFig/snap 不受影響、行為不變)。
201
+ // 換用 @antv/g-svg renderer(取代預設 canvas renderer)產出 SVG DOM, 其餘 translate/layout/樣式邏輯
202
+ // (dagre 排版、緊湊間距、z-order、字型)與 genPng 完全共用, 故版面結構/顏色/文字與 PNG 版一致。
203
+ const htmlSvg = `<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
204
+ <script>${G_LITE_JS}</script>
205
+ <script>${G_SVG_JS}</script>
206
+ <script>${G6_JS}</script>
207
+ </head><body style="margin:0;background:#fff">
208
+ <div id="g6" style="background:#fff"></div>
209
+ <script>
210
+ window.renderFigSvg = async function(g, rankdir, nodesep, ranksep, canvas, font, comboZ, edgeZ){
211
+ try {
212
+ const el = document.getElementById('g6')
213
+ el.style.width = canvas + 'px'; el.style.height = canvas + 'px'
214
+ if (window.__graph) { try { window.__graph.destroy() } catch(e){} }
215
+ const graph = new G6.Graph({
216
+ container: el, width: canvas, height: canvas, autoFit:'view',
217
+ renderer: () => new window.G.SVG.Renderer(),
218
+ data: { nodes: g.nodes, edges: g.edges, combos: g.combos },
219
+ layout: { type:'antv-dagre', rankdir: rankdir, nodesep: nodesep, ranksep: ranksep, sortByCombo: (g.combos && g.combos.length>0) },
220
+ combo: { type:'rect', style:{ zIndex: comboZ, padding:[30,18,18,18], labelPlacement:'top', labelFontFamily: font } },
221
+ edge: { type:'polyline', style:{ zIndex: edgeZ, lineWidth:2.2, endArrow:true, labelFontFamily: font } },
222
+ node: { style:{ labelFontFamily: font } },
223
+ })
224
+ window.__graph = graph
225
+ await graph.render()
226
+ await new Promise(r=>setTimeout(r,300))
227
+ return { ok:true }
228
+ } catch(e){ return { ok:false, err:String(e&&(e.stack||e.message)||e).slice(0,400) } }
229
+ }
230
+ // 取 main layer 之 SVG DOM(SVG renderer 下唯一承載節點/邊/combo 之 layer, background/label/transient 三層無內容),
231
+ // 以 canvas.getBounds() 對齊 toDataURL({mode:'overall'}) 之緊裁切邊界改寫 viewBox/width/height, 補白底矩形。
232
+ window.snapSvg = async function(){
233
+ try {
234
+ await new Promise(r=>setTimeout(r,200))
235
+ const canvas = window.__graph.getCanvas()
236
+ const bounds = canvas.getBounds()
237
+ const minX = bounds.min[0], minY = bounds.min[1]
238
+ const w = bounds.max[0] - minX, h = bounds.max[1] - minY
239
+ const mainEl = canvas.getLayer('main').getContextService().getDomElement()
240
+ const svg = mainEl.cloneNode(true)
241
+ // canvas.getBounds() 回傳世界座標(排版原始座標, 未經 camera 縮放平移); 但複製之 DOM 內容掛在
242
+ // #g-svg-camera 群組下, 帶有互動視圖之 camera transform(autoFit:'view' 縮放平移)。
243
+ // 兩者座標系不同, 混用會裁切錯位, 故先歸零 camera transform, 使內容回到與 bounds 相同之世界座標。
244
+ const cameraEl = svg.querySelector('#g-svg-camera')
245
+ if (cameraEl) cameraEl.removeAttribute('transform')
246
+ svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
247
+ svg.setAttribute('viewBox', minX + ' ' + minY + ' ' + w + ' ' + h)
248
+ svg.setAttribute('width', String(Math.ceil(w)))
249
+ svg.setAttribute('height', String(Math.ceil(h)))
250
+ svg.removeAttribute('tabindex')
251
+ svg.style.background = ''
252
+ svg.style.pointerEvents = ''
253
+ svg.style.gridArea = ''
254
+ svg.style.outline = ''
255
+ const bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
256
+ bgRect.setAttribute('x', String(minX)); bgRect.setAttribute('y', String(minY))
257
+ bgRect.setAttribute('width', String(w)); bgRect.setAttribute('height', String(h))
258
+ bgRect.setAttribute('fill', '#fff')
259
+ svg.insertBefore(bgRect, svg.firstChild)
260
+ const out = svg.outerHTML.replace(/&nbsp;/g, '&#160;')
261
+ return { ok:true, svg: out }
262
+ } catch(e){ return { ok:false, err:String(e&&(e.stack||e.message)||e).slice(0,400) } }
263
+ }
264
+ window.__readySvg=true
265
+ </script></body></html>`
266
+
267
+ // 單張渲染:同 genPng 之輸入(單份正規化繪圖數據), 回傳 SVG 字串(緊裁切範圍對齊 genPng 之 toDataURL overall)。
268
+ // 獨立頁面/renderer 流程(見上), genPng 之 canvas 渲染行為不受影響。
269
+ export async function genSvg(data, opt = {}) {
270
+ const browser = await chromium.launch()
271
+ try {
272
+ const page = await browser.newPage()
273
+ const errs = []
274
+ page.on('pageerror', e => errs.push(e.message))
275
+ page.on('console', m => { if (m.type() === 'error') errs.push('c:' + m.text()) })
276
+ await page.setContent(htmlSvg, { waitUntil: 'load' })
277
+ await page.waitForFunction(() => window.__readySvg, { timeout: 60000 }).catch(() => {})
278
+ await page.evaluate(() => document.fonts && document.fonts.ready)
279
+ const g = translate(data)
280
+ const r = await page.evaluate(([gg, rd, ns, rs, cv, ft, cz, ez]) => window.renderFigSvg(gg, rd, ns, rs, cv, ft, cz, ez),
281
+ [g, data.dir, NODESEP, RANKSEP, CANVAS, FONT, COMBO_Z, EDGE_Z])
282
+ if (!r.ok) throw new Error('renderFigSvg failed :: ' + String(r.err).split('\n')[0])
283
+ const res = await page.evaluate(() => window.snapSvg())
284
+ if (!res.ok) throw new Error('snapSvg failed :: ' + res.err)
285
+ return res.svg
286
+ } finally {
287
+ await browser.close()
288
+ }
289
+ }
package/src/p7/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  - **渲染方式**:排版完成後以 `P.fitToContent({ padding: 28, allowNewOrigin: 'any', useModelGeometry: true })` 讓 JointJS Paper 自適應內容邊界,截圖目標為 `#paper svg`(`page.$('#paper svg').screenshot()`),直接輸出 PNG。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })` 以 2 倍像素密度開頁,截出的 PNG 實際像素為邏輯尺寸的兩倍,保有高清品質。
9
9
  - **字型**:頁內使用 `Microsoft JhengHei`(微軟正黑體)作為主字型,canvas 量測與 JointJS 渲染均使用同一 `FONT` 字串,確保量測結果與渲染結果一致。
10
- - **調用方式**:本模組僅匯出 `genPng(data, opt) → Promise<Buffer>`,輸入為正規化繪圖數據 `{ dir, nodes, edges }`,回傳 PNG 之 Node Buffer;由 `src/WFlowchart.mjs` 統一匯入並依 `p7` 鍵值調用,本身不寫檔、不涉及批次流程。
10
+ - **調用方式**:本模組匯出 `genPng(data, opt) → Promise<Buffer>` `genSvg(data, opt) → Promise<string>`,輸入皆為正規化繪圖數據 `{ dir, nodes, edges }`;`genPng` 回傳 PNG 之 Node Buffer,`genSvg` 取 `#paper svg` 之 DOM 序列化結果並修正為 standalone SVG(補 `xmlns`、`width`/`height` 由 `100%` 改為 `fitToContent` 後之實際像素值、移除依賴容器定位之 `position:absolute;inset:0`);由 `src/WFlowchart.mjs` 統一匯入並依 `p7` 鍵值調用,本身不寫檔、不涉及批次流程。
11
11
 
12
12
  ## 產製原理(資料驅動)
13
13
 
package/src/p7/gen.mjs CHANGED
@@ -360,9 +360,9 @@ window.renderFig = renderFig
360
360
  window.__ready = true
361
361
  </script></body></html>`
362
362
 
363
- // genPng(data, opt) 單張渲染:輸入正規化繪圖數據({dir,nodes,edges}, caller 已先做 label 衍生),
364
- // 重用上方 translate/HTML/渲染管線, 回傳 PNG Node Buffer(caller 自行決定寫檔或其他用途)
365
- export async function genPng(data, opt = {}) {
363
+ // 渲染單張至就緒頁面(#paper svg 已完成排版渲染), 回傳 {browser,page,res}(res fitToContent 後之 w/h);
364
+ // caller 負責 close browser。渲染失敗(res.ok=false)或例外時, close browser 再 rethrow
365
+ async function renderFigPage(data) {
366
366
  const browser = await chromium.launch()
367
367
  try {
368
368
  const page = await browser.newPage({ deviceScaleFactor: 2 })
@@ -378,9 +378,48 @@ export async function genPng(data, opt = {}) {
378
378
  const { els, rankDir } = translate(data)
379
379
  const res = await page.evaluate(([e, rd, s]) => window.renderFig(e, rd, s), [els, rankDir, SEP])
380
380
  if (!res.ok) throw new Error(res.err + (res.stack ? '\n' + res.stack : ''))
381
+ return { browser, page, res }
382
+ }
383
+ catch (err) {
384
+ await browser.close()
385
+ throw err
386
+ }
387
+ }
388
+
389
+ // genPng(data, opt) — 單張渲染:輸入正規化繪圖數據({dir,nodes,edges}, caller 已先做 label 衍生),
390
+ // 重用上方 translate/HTML/渲染管線, 回傳 PNG 之 Node Buffer(caller 自行決定寫檔或其他用途)。
391
+ export async function genPng(data, opt = {}) {
392
+ const { browser, page } = await renderFigPage(data)
393
+ try {
381
394
  const el = await page.$('#paper svg')
382
395
  return await el.screenshot()
383
396
  } finally {
384
397
  await browser.close()
385
398
  }
386
399
  }
400
+
401
+ // genSvg(data, opt) — 單張渲染 → standalone SVG 字串:
402
+ // 取 #paper svg 之 outerHTML, 修正使其脫離 #paper 容器後仍忠實:
403
+ // 1. 補 xmlns(JointJS 只寫 xmlns:xlink, 缺 xmlns 非合法 standalone SVG)
404
+ // 2. width/height 由 JointJS 寫死的 "100%" 改為 fitToContent 後之實際像素值(res.w/res.h)
405
+ // (100% 依賴 #paper 容器之 inline width/height, 脫離容器後無父層可依附)
406
+ // 3. 移除 position:absolute;inset:0(同上, 依賴容器定位, standalone 時會撐滿最近定位祖先/viewport)
407
+ // svg 內已含 JointJS 自身注入之 <style>(non-scaling-stroke), outerHTML 序列化時隨之保留, 無需另行處理。
408
+ // &nbsp; 正規化為 &#160; 使其為合法 XML。
409
+ export async function genSvg(data, opt = {}) {
410
+ const { browser, page, res } = await renderFigPage(data)
411
+ try {
412
+ let outer = await page.$eval('#paper svg', el => el.outerHTML)
413
+ outer = outer.replace(/^<svg([^>]*)>/, (m0, attrs) => {
414
+ let a = attrs
415
+ if (!/\bxmlns=/.test(a)) a = ' xmlns="http://www.w3.org/2000/svg"' + a
416
+ a = a.replace(/\swidth="100%"/, ` width="${res.w}"`)
417
+ a = a.replace(/\sheight="100%"/, ` height="${res.h}"`)
418
+ a = a.replace(/position:\s*absolute;\s*inset:\s*[^;]*;\s*/, '')
419
+ return `<svg${a}>`
420
+ })
421
+ return outer.replace(/&nbsp;/g, '&#160;').trim()
422
+ } finally {
423
+ await browser.close()
424
+ }
425
+ }
package/src/p8/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  - **載入方式**:Node 端以 Playwright 建立 Chromium 頁面,將完整 HTML 字串(含內聯 CSS 與兩段內聯 `<script>`)透過 `page.setContent()` 注入;繪圖邏輯全部在頁內 `<script>` 執行,Node 端僅負責驅動與截圖。
7
7
  - **渲染與截圖**:頁內 `window.renderFig(fig)` 依序計算尺寸 → dagre 排版 → `lf.render()` 渲染完後以 `setTimeout(..., 200)` 等待 DOM 穩定,回傳 Promise;Node 端再呼叫 `page.evaluate(() => document.fonts.ready)` 確保字型載入完畢,最後以 `page.locator('#app').screenshot()` 截取 `#app` 元素為 PNG Buffer 回傳(不寫檔)。
8
8
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })`,輸出為 2× 實體像素密度(HiDPI),寬高由計算所得邏輯像素乘以 2 輸出。
9
- - **調用方式**:本模組僅匯出 `genPng(data, opt) → Promise<Buffer>`,輸入為正規化繪圖數據 `{ dir, nodes, edges }`,回傳 PNG Node Buffer;由 `src/WFlowchart.mjs` 統一匯入並依 `p8` 鍵值調用,本身不寫檔、不涉及批次流程。
9
+ - **調用方式**:本模組匯出 `genPng(data, opt) → Promise<Buffer>` `genSvg(data, opt) → Promise<string>`,輸入皆為正規化繪圖數據 `{ dir, nodes, edges }`;`genSvg` 序列化圖面 svg(`lf-canvas-overlay`),並將 LogicFlow CSS 內嵌進 SVG(文字自動換行之 `foreignObject` 標籤樣式依賴此 CSS);由 `src/WFlowchart.mjs` 統一匯入並依 `p8` 鍵值調用,本身不寫檔、不涉及批次流程。
10
10
 
11
11
  ## 產製原理(資料驅動)
12
12
 
package/src/p8/gen.mjs CHANGED
@@ -394,8 +394,9 @@ window.renderFig = function(fig){ return new Promise(function(resolve){
394
394
  window.__ready = true
395
395
  </script></body></html>`
396
396
 
397
- // 對外: 單張正規化數據(同 FIGURES[n].data 結構)PNG Buffer。重用上方 translate/HTML/渲染管線, 自行開關 browser, 不寫檔。
398
- export async function genPng(data, opt = {}) {
397
+ // 內部 helper: browser灌入 html 等待 LF 就緒 → renderFig(data) 畫出圖面, 回傳 { browser, page }(呼叫端負責 close)。
398
+ // 失敗時(含 renderFig 回報 !ok)於此處關閉 browser rethrow, 呼叫端不需重複收拾。
399
+ async function renderToReadyPage(data) {
399
400
  const browser = await chromium.launch()
400
401
  try {
401
402
  const page = await browser.newPage({ deviceScaleFactor: 2 })
@@ -409,8 +410,46 @@ export async function genPng(data, opt = {}) {
409
410
  const res = await page.evaluate((fig) => window.renderFig(fig), fig)
410
411
  if (!res.ok) throw new Error(res.err || 'renderFig failed')
411
412
  await page.evaluate(() => document.fonts && document.fonts.ready)
413
+ return { browser, page, res }
414
+ } catch (e) {
415
+ await browser.close()
416
+ throw e
417
+ }
418
+ }
419
+
420
+ // 對外: 單張正規化數據(同 FIGURES[n].data 結構) → PNG Buffer。重用上方 translate/HTML/渲染管線, 自行開關 browser, 不寫檔。
421
+ export async function genPng(data, opt = {}) {
422
+ const { browser, page } = await renderToReadyPage(data)
423
+ try {
412
424
  return await page.locator('#app').screenshot()
413
425
  } finally {
414
426
  await browser.close()
415
427
  }
416
428
  }
429
+
430
+ // 對外: 單張正規化數據 → 忠實且 standalone 的 SVG 字串。
431
+ // #app 內實際有 3 個 svg(canvas-overlay 圖面本體 / modification-overlay 選取輔助層(無內容)/ lf-grid 背景格線(display:none)),
432
+ // 真正含 .lf-node/.lf-edge 圖元的是 svg.lf-canvas-overlay, 序列化取此元素即可(transform 為單位矩陣, 座標已是絕對像素)。
433
+ // 節點/邊樣式多為行內屬性(fill/stroke/font-family...皆已內聯), 但文字自動換行採 foreignObject 內嵌 HTML div,
434
+ // 其置中(flex)/字重/寬度等版面由 LOGICFLOW_CSS 之 .lf-node-text-auto-wrap(-content) class 決定, 故仍需嵌入該 CSS 才能忠實呈現。
435
+ export async function genSvg(data, opt = {}) {
436
+ const { browser, page, res } = await renderToReadyPage(data)
437
+ try {
438
+ let svg = await page.evaluate(() => document.querySelector('svg.lf-canvas-overlay').outerHTML)
439
+ const openTagMatch = svg.match(/^<svg[^>]*>/)
440
+ if (!openTagMatch) throw new Error('genSvg: 找不到圖面 svg 根元素')
441
+ let openTag = openTagMatch[0]
442
+ .replace('width="100%"', `width="${res.w}"`)
443
+ .replace('height="100%"', `height="${res.h}"`)
444
+ openTag = openTag.replace(/>$/, ` viewBox="0 0 ${res.w} ${res.h}">`)
445
+ svg = openTag + svg.slice(openTagMatch[0].length)
446
+ // 嵌入 CSS(LOGICFLOW_CSS + 頁內字型規則) + 白底(比照 #app 背景), 使脫離頁面仍忠實呈現
447
+ const inject = `<style>${LOGICFLOW_CSS}\n* { font-family: ${FONT}; }</style>`
448
+ + `<rect x="0" y="0" width="${res.w}" height="${res.h}" fill="#fff"></rect>`
449
+ svg = svg.replace('<g transform', inject + '<g transform')
450
+ svg = svg.replace(/&nbsp;/g, '&#160;')
451
+ return svg
452
+ } finally {
453
+ await browser.close()
454
+ }
455
+ }
package/src/p9/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  - **截圖目標**:對 `#shot` 元素截圖(`page.locator('#shot').screenshot()`);`#shot` 為 `display:inline-block` 的 div,內含 `#stage`(React Flow 掛載點),外加 `padding:24px` 白邊,截出的 PNG 即為完整含留白之圖片。
9
9
  - **解析度**:`browser.newPage({ deviceScaleFactor: 2 })` 開啟 2x 縮放,所有量測與佈局座標仍以 CSS px 計算,截出的 PNG 實際像素為畫布尺寸之 2 倍,確保中文字型清晰。
10
10
  - **字型**:`Microsoft JhengHei, sans-serif`,中文主字型一致貫穿節點、標籤、邊標籤。
11
- - **呼叫入口**:`export async function genPng(data, opt = {})` 為唯一對外介面,回傳 `Promise<Buffer>`(PNG);由 `src/WFlowchart.mjs` 之 `GENS.p9` 統一調用(`mode: 'p9'`)。`data` 為正規化繪圖數據 `{ dir, nodes, edges }`;`opt` 保留擴充,目前原樣接收但未使用。
11
+ - **呼叫入口**:`export async function genPng(data, opt = {})` 為唯一對外介面,回傳 `Promise<Buffer>`(PNG);由 `src/WFlowchart.mjs` 之 `GENS.p9` 統一調用(`mode: 'p9'`)。`data` 為正規化繪圖數據 `{ dir, nodes, edges }`;`opt` 保留擴充,目前原樣接收但未使用。**不支援 SVG 輸出**(本產線節點為 React Flow 之 HTML div,非純 SVG,無法序列化為 standalone SVG)。
12
12
 
13
13
  ## 產製原理(資料驅動)
14
14
 
package/src/p9/gen.mjs CHANGED
@@ -13,12 +13,19 @@ import { pkgScript, pkgText } from '../common/pkg.mjs'
13
13
  const __dir = dirname(fileURLToPath(import.meta.url))
14
14
  // React / ReactDOM / @xyflow/react(UMD)/ @dagrejs/dagre 由本機 node_modules 內聯注入(取代 esm.sh, 斷網環境可用)
15
15
  // replace 用函式形式回傳, 避免庫碼內 $ 序列被當作替換樣板
16
- const html = fs.readFileSync(`${__dir}/page9.html`, 'utf8')
17
- .replace('/*__XYFLOW_CSS__*/', () => pkgText('@xyflow/react/dist/style.css'))
18
- .replace('/*__REACT_JS__*/', () => pkgScript('react/umd/react.production.min.js'))
19
- .replace('/*__REACT_DOM_JS__*/', () => pkgScript('react-dom/umd/react-dom.production.min.js'))
20
- .replace('/*__XYFLOW_JS__*/', () => pkgScript('@xyflow/react/dist/umd/index.js'))
21
- .replace('/*__DAGRE_JS__*/', () => pkgScript('@dagrejs/dagre/dist/dagre.min.js'))
16
+ // 首次呼叫才讀檔組頁(延遲載入): 模組層即讀會令「僅用其他產線」的載入方也付出讀檔成本
17
+ let _html = null
18
+ function getHtml() {
19
+ if (_html === null) {
20
+ _html = fs.readFileSync(`${__dir}/page9.html`, 'utf8')
21
+ .replace('/*__XYFLOW_CSS__*/', () => pkgText('@xyflow/react/dist/style.css'))
22
+ .replace('/*__REACT_JS__*/', () => pkgScript('react/umd/react.production.min.js'))
23
+ .replace('/*__REACT_DOM_JS__*/', () => pkgScript('react-dom/umd/react-dom.production.min.js'))
24
+ .replace('/*__XYFLOW_JS__*/', () => pkgScript('@xyflow/react/dist/umd/index.js'))
25
+ .replace('/*__DAGRE_JS__*/', () => pkgScript('@dagrejs/dagre/dist/dagre.min.js'))
26
+ }
27
+ return _html
28
+ }
22
29
 
23
30
  // 正規化數據 → page9.html renderFig spec
24
31
  // 標籤「不預折」, 原樣傳入, 由 page9.html 量測 div(max-width + overflow-wrap)自然折行:
@@ -47,7 +54,7 @@ export async function genPng(data, opt = {}) {
47
54
  const errs = []
48
55
  page.on('pageerror', e => errs.push(e.message))
49
56
  page.on('console', m => { if (m.type() === 'error') errs.push('c:' + m.text().slice(0, 200)) })
50
- await page.setContent(html, { waitUntil: 'load' })
57
+ await page.setContent(getHtml(), { waitUntil: 'load' })
51
58
  await page.waitForFunction(() => document.title === 'READY' || document.title === 'FAIL', { timeout: 60000 }).catch(() => {})
52
59
  const boot = await page.evaluate(() => ({ t: document.title, err: window.__err }))
53
60
  if (boot.t !== 'READY') throw new Error('BOOT FAIL :: ' + boot.err + ' | ' + errs.slice(0, 6).join(' | '))
package/src/p9/page9.html CHANGED
@@ -208,13 +208,21 @@ try {
208
208
  if (n.parent && grpIds.has(n.parent)) g.setParent(n.id, n.parent)
209
209
  }
210
210
  // dagre compound 不能對「容器節點」直接連邊(ranking 階段會崩),
211
- // 故 ranking 用「代表葉節點」替代容器端點(取該容器第一個葉子後代);
212
- // 實際繪線仍用容器框(後段 pickHandles 以絕對 box 計算)。此為通用處理, 非逐圖。
213
- const repLeaf = {}
211
+ // 故 ranking 用「代表葉節點」替代容器端點(取該容器數據序第一個可達葉子後代);
212
+ // 容器內可能只有子容器(深巢狀), 故須遞迴下鑽。實際繪線仍用容器框(後段 pickHandles 以絕對 box 計算)。此為通用處理, 非逐圖。
213
+ const childByParent = {}
214
214
  for (const n of rawNodes) {
215
- if (n.parent && grpIds.has(n.parent) && !grpIds.has(n.id) && repLeaf[n.parent] === undefined) repLeaf[n.parent] = n.id
215
+ if (n.parent) (childByParent[n.parent] = childByParent[n.parent] || []).push(n)
216
216
  }
217
- const rankId = id => grpIds.has(id) ? (repLeaf[id] || id) : id
217
+ const firstLeaf = (gid) => {
218
+ for (const ch of (childByParent[gid] || [])) {
219
+ if (!grpIds.has(ch.id)) return ch.id
220
+ const r = firstLeaf(ch.id)
221
+ if (r) return r
222
+ }
223
+ return null
224
+ }
225
+ const rankId = id => grpIds.has(id) ? (firstLeaf(id) || id) : id
218
226
  const seenRank = new Set()
219
227
  for (const e of rawEdges) {
220
228
  const s = rankId(e.s), t = rankId(e.t)