@llc3233149/core 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (457) hide show
  1. package/dist/canvaskit.d.ts +9 -0
  2. package/dist/canvaskit.d.ts.map +1 -0
  3. package/dist/canvaskit.js +55 -0
  4. package/dist/canvaskit.js.map +1 -0
  5. package/dist/clipboard.d.ts +32 -0
  6. package/dist/clipboard.d.ts.map +1 -0
  7. package/dist/clipboard.js +345 -0
  8. package/dist/clipboard.js.map +1 -0
  9. package/dist/color.d.ts +28 -0
  10. package/dist/color.d.ts.map +1 -0
  11. package/dist/color.js +67 -0
  12. package/dist/color.js.map +1 -0
  13. package/dist/constants.d.ts +159 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +224 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/copy.d.ts +20 -0
  18. package/dist/copy.d.ts.map +1 -0
  19. package/dist/copy.js +64 -0
  20. package/dist/copy.js.map +1 -0
  21. package/dist/fig-export-worker.d.ts +2 -0
  22. package/dist/fig-export-worker.d.ts.map +1 -0
  23. package/dist/fig-export-worker.js +17 -0
  24. package/dist/fig-export-worker.js.map +1 -0
  25. package/dist/fig-export.d.ts +13 -0
  26. package/dist/fig-export.d.ts.map +1 -0
  27. package/dist/fig-export.js +243 -0
  28. package/dist/fig-export.js.map +1 -0
  29. package/dist/figma-api.d.ts +268 -0
  30. package/dist/figma-api.d.ts.map +1 -0
  31. package/dist/figma-api.js +1074 -0
  32. package/dist/figma-api.js.map +1 -0
  33. package/dist/figma-rest-import.d.ts +122 -0
  34. package/dist/figma-rest-import.d.ts.map +1 -0
  35. package/dist/figma-rest-import.js +319 -0
  36. package/dist/figma-rest-import.js.map +1 -0
  37. package/dist/fonts.d.ts +24 -0
  38. package/dist/fonts.d.ts.map +1 -0
  39. package/dist/fonts.js +356 -0
  40. package/dist/fonts.js.map +1 -0
  41. package/dist/geometry.d.ts +14 -0
  42. package/dist/geometry.d.ts.map +1 -0
  43. package/dist/geometry.js +47 -0
  44. package/dist/geometry.js.map +1 -0
  45. package/dist/index.d.ts +38 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +32 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/kiwi/codec.d.ts +292 -0
  50. package/dist/kiwi/codec.d.ts.map +1 -0
  51. package/dist/kiwi/codec.js +340 -0
  52. package/dist/kiwi/codec.js.map +1 -0
  53. package/dist/kiwi/fig-file.d.ts +4 -0
  54. package/dist/kiwi/fig-file.d.ts.map +1 -0
  55. package/dist/kiwi/fig-file.js +236 -0
  56. package/dist/kiwi/fig-file.js.map +1 -0
  57. package/dist/kiwi/fig-import.d.ts +4 -0
  58. package/dist/kiwi/fig-import.d.ts.map +1 -0
  59. package/dist/kiwi/fig-import.js +248 -0
  60. package/dist/kiwi/fig-import.js.map +1 -0
  61. package/dist/kiwi/fig-parse-worker.d.ts +7 -0
  62. package/dist/kiwi/fig-parse-worker.d.ts.map +1 -0
  63. package/dist/kiwi/fig-parse-worker.js +184 -0
  64. package/dist/kiwi/fig-parse-worker.js.map +1 -0
  65. package/dist/kiwi/instance-overrides.d.ts +66 -0
  66. package/dist/kiwi/instance-overrides.d.ts.map +1 -0
  67. package/dist/kiwi/instance-overrides.js +644 -0
  68. package/dist/kiwi/instance-overrides.js.map +1 -0
  69. package/dist/kiwi/kiwi-convert.d.ts +19 -0
  70. package/dist/kiwi/kiwi-convert.d.ts.map +1 -0
  71. package/dist/kiwi/kiwi-convert.js +678 -0
  72. package/dist/kiwi/kiwi-convert.js.map +1 -0
  73. package/dist/kiwi/kiwi-schema/bb.d.ts +25 -0
  74. package/dist/kiwi/kiwi-schema/bb.d.ts.map +1 -0
  75. package/dist/kiwi/kiwi-schema/bb.js +225 -0
  76. package/dist/kiwi/kiwi-schema/bb.js.map +1 -0
  77. package/dist/kiwi/kiwi-schema/binary.d.ts +5 -0
  78. package/dist/kiwi/kiwi-schema/binary.d.ts.map +1 -0
  79. package/dist/kiwi/kiwi-schema/binary.js +86 -0
  80. package/dist/kiwi/kiwi-schema/binary.js.map +1 -0
  81. package/dist/kiwi/kiwi-schema/index.d.ts +6 -0
  82. package/dist/kiwi/kiwi-schema/index.d.ts.map +1 -0
  83. package/dist/kiwi/kiwi-schema/index.js +5 -0
  84. package/dist/kiwi/kiwi-schema/index.js.map +1 -0
  85. package/dist/kiwi/kiwi-schema/js.d.ts +4 -0
  86. package/dist/kiwi/kiwi-schema/js.d.ts.map +1 -0
  87. package/dist/kiwi/kiwi-schema/js.js +282 -0
  88. package/dist/kiwi/kiwi-schema/js.js.map +1 -0
  89. package/dist/kiwi/kiwi-schema/parser.d.ts +5 -0
  90. package/dist/kiwi/kiwi-schema/parser.d.ts.map +1 -0
  91. package/dist/kiwi/kiwi-schema/parser.js +228 -0
  92. package/dist/kiwi/kiwi-schema/parser.js.map +1 -0
  93. package/dist/kiwi/kiwi-schema/schema.d.ts +22 -0
  94. package/dist/kiwi/kiwi-schema/schema.d.ts.map +1 -0
  95. package/dist/kiwi/kiwi-schema/schema.js +2 -0
  96. package/dist/kiwi/kiwi-schema/schema.js.map +1 -0
  97. package/dist/kiwi/kiwi-schema/util.d.ts +3 -0
  98. package/dist/kiwi/kiwi-schema/util.d.ts.map +1 -0
  99. package/dist/kiwi/kiwi-schema/util.js +10 -0
  100. package/dist/kiwi/kiwi-schema/util.js.map +1 -0
  101. package/dist/kiwi/protocol.d.ts +172 -0
  102. package/dist/kiwi/protocol.d.ts.map +1 -0
  103. package/dist/kiwi/protocol.js +214 -0
  104. package/dist/kiwi/protocol.js.map +1 -0
  105. package/dist/kiwi/schema.d.ts +3 -0
  106. package/dist/kiwi/schema.d.ts.map +1 -0
  107. package/dist/kiwi/schema.js +2285 -0
  108. package/dist/kiwi/schema.js.map +1 -0
  109. package/dist/kiwi-serialize.d.ts +17 -0
  110. package/dist/kiwi-serialize.d.ts.map +1 -0
  111. package/dist/kiwi-serialize.js +419 -0
  112. package/dist/kiwi-serialize.js.map +1 -0
  113. package/dist/layout.d.ts +9 -0
  114. package/dist/layout.d.ts.map +1 -0
  115. package/dist/layout.js +533 -0
  116. package/dist/layout.js.map +1 -0
  117. package/dist/profiler/draw-call-counter.d.ts +9 -0
  118. package/dist/profiler/draw-call-counter.d.ts.map +1 -0
  119. package/dist/profiler/draw-call-counter.js +37 -0
  120. package/dist/profiler/draw-call-counter.js.map +1 -0
  121. package/dist/profiler/frame-capture.d.ts +34 -0
  122. package/dist/profiler/frame-capture.d.ts.map +1 -0
  123. package/dist/profiler/frame-capture.js +90 -0
  124. package/dist/profiler/frame-capture.js.map +1 -0
  125. package/dist/profiler/frame-stats.d.ts +33 -0
  126. package/dist/profiler/frame-stats.d.ts.map +1 -0
  127. package/dist/profiler/frame-stats.js +103 -0
  128. package/dist/profiler/frame-stats.js.map +1 -0
  129. package/dist/profiler/gpu-timer.d.ts +15 -0
  130. package/dist/profiler/gpu-timer.d.ts.map +1 -0
  131. package/dist/profiler/gpu-timer.js +77 -0
  132. package/dist/profiler/gpu-timer.js.map +1 -0
  133. package/dist/profiler/hud-renderer.d.ts +22 -0
  134. package/dist/profiler/hud-renderer.d.ts.map +1 -0
  135. package/dist/profiler/hud-renderer.js +200 -0
  136. package/dist/profiler/hud-renderer.js.map +1 -0
  137. package/dist/profiler/index.d.ts +9 -0
  138. package/dist/profiler/index.d.ts.map +1 -0
  139. package/dist/profiler/index.js +8 -0
  140. package/dist/profiler/index.js.map +1 -0
  141. package/dist/profiler/phase-timer.d.ts +9 -0
  142. package/dist/profiler/phase-timer.d.ts.map +1 -0
  143. package/dist/profiler/phase-timer.js +51 -0
  144. package/dist/profiler/phase-timer.js.map +1 -0
  145. package/dist/profiler/render-profiler.d.ts +41 -0
  146. package/dist/profiler/render-profiler.d.ts.map +1 -0
  147. package/dist/profiler/render-profiler.js +142 -0
  148. package/dist/profiler/render-profiler.js.map +1 -0
  149. package/dist/render/components.d.ts +25 -0
  150. package/dist/render/components.d.ts.map +1 -0
  151. package/dist/render/components.js +55 -0
  152. package/dist/render/components.js.map +1 -0
  153. package/dist/render/emoji-icons.d.ts +5 -0
  154. package/dist/render/emoji-icons.d.ts.map +1 -0
  155. package/dist/render/emoji-icons.js +55 -0
  156. package/dist/render/emoji-icons.js.map +1 -0
  157. package/dist/render/export-code.d.ts +9 -0
  158. package/dist/render/export-code.d.ts.map +1 -0
  159. package/dist/render/export-code.js +335 -0
  160. package/dist/render/export-code.js.map +1 -0
  161. package/dist/render/export-jsx.d.ts +5 -0
  162. package/dist/render/export-jsx.d.ts.map +1 -0
  163. package/dist/render/export-jsx.js +621 -0
  164. package/dist/render/export-jsx.js.map +1 -0
  165. package/dist/render/index.d.ts +8 -0
  166. package/dist/render/index.d.ts.map +1 -0
  167. package/dist/render/index.js +8 -0
  168. package/dist/render/index.js.map +1 -0
  169. package/dist/render/jsx-runtime.d.ts +31 -0
  170. package/dist/render/jsx-runtime.d.ts.map +1 -0
  171. package/dist/render/jsx-runtime.js +13 -0
  172. package/dist/render/jsx-runtime.js.map +1 -0
  173. package/dist/render/mini-react.d.ts +14 -0
  174. package/dist/render/mini-react.d.ts.map +1 -0
  175. package/dist/render/mini-react.js +16 -0
  176. package/dist/render/mini-react.js.map +1 -0
  177. package/dist/render/render-jsx.d.ts +24 -0
  178. package/dist/render/render-jsx.d.ts.map +1 -0
  179. package/dist/render/render-jsx.js +51 -0
  180. package/dist/render/render-jsx.js.map +1 -0
  181. package/dist/render/renderer.d.ts +16 -0
  182. package/dist/render/renderer.d.ts.map +1 -0
  183. package/dist/render/renderer.js +437 -0
  184. package/dist/render/renderer.js.map +1 -0
  185. package/dist/render/tailwind.d.ts +20 -0
  186. package/dist/render/tailwind.d.ts.map +1 -0
  187. package/dist/render/tailwind.js +115 -0
  188. package/dist/render/tailwind.js.map +1 -0
  189. package/dist/render/tree.d.ts +72 -0
  190. package/dist/render/tree.d.ts.map +1 -0
  191. package/dist/render/tree.js +63 -0
  192. package/dist/render/tree.js.map +1 -0
  193. package/dist/render-image.d.ts +19 -0
  194. package/dist/render-image.d.ts.map +1 -0
  195. package/dist/render-image.js +84 -0
  196. package/dist/render-image.js.map +1 -0
  197. package/dist/renderer/effects.d.ts +9 -0
  198. package/dist/renderer/effects.d.ts.map +1 -0
  199. package/dist/renderer/effects.js +44 -0
  200. package/dist/renderer/effects.js.map +1 -0
  201. package/dist/renderer/fills.d.ts +9 -0
  202. package/dist/renderer/fills.d.ts.map +1 -0
  203. package/dist/renderer/fills.js +189 -0
  204. package/dist/renderer/fills.js.map +1 -0
  205. package/dist/renderer/index.d.ts +2 -0
  206. package/dist/renderer/index.d.ts.map +1 -0
  207. package/dist/renderer/index.js +2 -0
  208. package/dist/renderer/index.js.map +1 -0
  209. package/dist/renderer/label-cache.d.ts +43 -0
  210. package/dist/renderer/label-cache.d.ts.map +1 -0
  211. package/dist/renderer/label-cache.js +85 -0
  212. package/dist/renderer/label-cache.js.map +1 -0
  213. package/dist/renderer/labels.d.ts +6 -0
  214. package/dist/renderer/labels.d.ts.map +1 -0
  215. package/dist/renderer/labels.js +126 -0
  216. package/dist/renderer/labels.js.map +1 -0
  217. package/dist/renderer/lru-cache.d.ts +20 -0
  218. package/dist/renderer/lru-cache.d.ts.map +1 -0
  219. package/dist/renderer/lru-cache.js +65 -0
  220. package/dist/renderer/lru-cache.js.map +1 -0
  221. package/dist/renderer/overlays.d.ts +25 -0
  222. package/dist/renderer/overlays.d.ts.map +1 -0
  223. package/dist/renderer/overlays.js +783 -0
  224. package/dist/renderer/overlays.js.map +1 -0
  225. package/dist/renderer/renderer.d.ts +229 -0
  226. package/dist/renderer/renderer.d.ts.map +1 -0
  227. package/dist/renderer/renderer.js +909 -0
  228. package/dist/renderer/renderer.js.map +1 -0
  229. package/dist/renderer/rulers.d.ts +8 -0
  230. package/dist/renderer/rulers.d.ts.map +1 -0
  231. package/dist/renderer/rulers.js +147 -0
  232. package/dist/renderer/rulers.js.map +1 -0
  233. package/dist/renderer/scene.d.ts +11 -0
  234. package/dist/renderer/scene.d.ts.map +1 -0
  235. package/dist/renderer/scene.js +392 -0
  236. package/dist/renderer/scene.js.map +1 -0
  237. package/dist/renderer/shapes.d.ts +13 -0
  238. package/dist/renderer/shapes.d.ts.map +1 -0
  239. package/dist/renderer/shapes.js +160 -0
  240. package/dist/renderer/shapes.js.map +1 -0
  241. package/dist/renderer/strokes.d.ts +9 -0
  242. package/dist/renderer/strokes.d.ts.map +1 -0
  243. package/dist/renderer/strokes.js +180 -0
  244. package/dist/renderer/strokes.js.map +1 -0
  245. package/dist/rpc/commands.d.ts +202 -0
  246. package/dist/rpc/commands.d.ts.map +1 -0
  247. package/dist/rpc/commands.js +429 -0
  248. package/dist/rpc/commands.js.map +1 -0
  249. package/dist/rpc/index.d.ts +3 -0
  250. package/dist/rpc/index.d.ts.map +1 -0
  251. package/dist/rpc/index.js +2 -0
  252. package/dist/rpc/index.js.map +1 -0
  253. package/dist/scene-graph.d.ts +318 -0
  254. package/dist/scene-graph.d.ts.map +1 -0
  255. package/dist/scene-graph.js +803 -0
  256. package/dist/scene-graph.js.map +1 -0
  257. package/dist/snap.d.ts +16 -0
  258. package/dist/snap.d.ts.map +1 -0
  259. package/dist/snap.js +97 -0
  260. package/dist/snap.js.map +1 -0
  261. package/dist/style-runs.d.ts +20 -0
  262. package/dist/style-runs.d.ts.map +1 -0
  263. package/dist/style-runs.js +168 -0
  264. package/dist/style-runs.js.map +1 -0
  265. package/dist/svg-export/defs.d.ts +19 -0
  266. package/dist/svg-export/defs.d.ts.map +1 -0
  267. package/dist/svg-export/defs.js +191 -0
  268. package/dist/svg-export/defs.js.map +1 -0
  269. package/dist/svg-export/index.d.ts +8 -0
  270. package/dist/svg-export/index.d.ts.map +1 -0
  271. package/dist/svg-export/index.js +318 -0
  272. package/dist/svg-export/index.js.map +1 -0
  273. package/dist/svg-export/paths.d.ts +9 -0
  274. package/dist/svg-export/paths.d.ts.map +1 -0
  275. package/dist/svg-export/paths.js +189 -0
  276. package/dist/svg-export/paths.js.map +1 -0
  277. package/dist/svg-node.d.ts +8 -0
  278. package/dist/svg-node.d.ts.map +1 -0
  279. package/dist/svg-node.js +48 -0
  280. package/dist/svg-node.js.map +1 -0
  281. package/dist/text-editor.d.ts +57 -0
  282. package/dist/text-editor.d.ts.map +1 -0
  283. package/dist/text-editor.js +337 -0
  284. package/dist/text-editor.js.map +1 -0
  285. package/dist/tools/ai-adapter.d.ts +22 -0
  286. package/dist/tools/ai-adapter.d.ts.map +1 -0
  287. package/dist/tools/ai-adapter.js +121 -0
  288. package/dist/tools/ai-adapter.js.map +1 -0
  289. package/dist/tools/analyze.d.ts +22 -0
  290. package/dist/tools/analyze.d.ts.map +1 -0
  291. package/dist/tools/analyze.js +677 -0
  292. package/dist/tools/analyze.js.map +1 -0
  293. package/dist/tools/create.d.ts +8 -0
  294. package/dist/tools/create.d.ts.map +1 -0
  295. package/dist/tools/create.js +204 -0
  296. package/dist/tools/create.js.map +1 -0
  297. package/dist/tools/describe.d.ts +9 -0
  298. package/dist/tools/describe.d.ts.map +1 -0
  299. package/dist/tools/describe.js +295 -0
  300. package/dist/tools/describe.js.map +1 -0
  301. package/dist/tools/export-code.d.ts +2 -0
  302. package/dist/tools/export-code.d.ts.map +1 -0
  303. package/dist/tools/export-code.js +34 -0
  304. package/dist/tools/export-code.js.map +1 -0
  305. package/dist/tools/export-tailwind-config.d.ts +27 -0
  306. package/dist/tools/export-tailwind-config.d.ts.map +1 -0
  307. package/dist/tools/export-tailwind-config.js +215 -0
  308. package/dist/tools/export-tailwind-config.js.map +1 -0
  309. package/dist/tools/icons.d.ts +5 -0
  310. package/dist/tools/icons.d.ts.map +1 -0
  311. package/dist/tools/icons.js +192 -0
  312. package/dist/tools/icons.js.map +1 -0
  313. package/dist/tools/index.d.ts +7 -0
  314. package/dist/tools/index.d.ts.map +1 -0
  315. package/dist/tools/index.js +6 -0
  316. package/dist/tools/index.js.map +1 -0
  317. package/dist/tools/modify.d.ts +22 -0
  318. package/dist/tools/modify.d.ts.map +1 -0
  319. package/dist/tools/modify.js +653 -0
  320. package/dist/tools/modify.js.map +1 -0
  321. package/dist/tools/patterns.d.ts +2 -0
  322. package/dist/tools/patterns.d.ts.map +1 -0
  323. package/dist/tools/patterns.js +173 -0
  324. package/dist/tools/patterns.js.map +1 -0
  325. package/dist/tools/read.d.ts +17 -0
  326. package/dist/tools/read.d.ts.map +1 -0
  327. package/dist/tools/read.js +379 -0
  328. package/dist/tools/read.js.map +1 -0
  329. package/dist/tools/registry.d.ts +3 -0
  330. package/dist/tools/registry.d.ts.map +1 -0
  331. package/dist/tools/registry.js +124 -0
  332. package/dist/tools/registry.js.map +1 -0
  333. package/dist/tools/schema.d.ts +46 -0
  334. package/dist/tools/schema.d.ts.map +1 -0
  335. package/dist/tools/schema.js +17 -0
  336. package/dist/tools/schema.js.map +1 -0
  337. package/dist/tools/structure.d.ts +18 -0
  338. package/dist/tools/structure.d.ts.map +1 -0
  339. package/dist/tools/structure.js +345 -0
  340. package/dist/tools/structure.js.map +1 -0
  341. package/dist/tools/variables.d.ts +12 -0
  342. package/dist/tools/variables.d.ts.map +1 -0
  343. package/dist/tools/variables.js +190 -0
  344. package/dist/tools/variables.js.map +1 -0
  345. package/dist/tools/vector.d.ts +15 -0
  346. package/dist/tools/vector.d.ts.map +1 -0
  347. package/dist/tools/vector.js +312 -0
  348. package/dist/tools/vector.js.map +1 -0
  349. package/dist/types.d.ts +29 -0
  350. package/dist/types.d.ts.map +1 -0
  351. package/dist/types.js +2 -0
  352. package/dist/types.js.map +1 -0
  353. package/dist/undo.d.ts +33 -0
  354. package/dist/undo.d.ts.map +1 -0
  355. package/dist/undo.js +98 -0
  356. package/dist/undo.js.map +1 -0
  357. package/dist/vector.d.ts +14 -0
  358. package/dist/vector.d.ts.map +1 -0
  359. package/dist/vector.js +334 -0
  360. package/dist/vector.js.map +1 -0
  361. package/dist/xpath.d.ts +8 -0
  362. package/dist/xpath.d.ts.map +1 -0
  363. package/dist/xpath.js +199 -0
  364. package/dist/xpath.js.map +1 -0
  365. package/package.json +54 -0
  366. package/src/canvaskit.ts +66 -0
  367. package/src/clipboard.ts +444 -0
  368. package/src/color.ts +83 -0
  369. package/src/constants.ts +277 -0
  370. package/src/copy.ts +75 -0
  371. package/src/fig-export-worker.ts +28 -0
  372. package/src/fig-export.ts +321 -0
  373. package/src/figma-api.ts +1297 -0
  374. package/src/figma-rest-import.ts +443 -0
  375. package/src/fonts.ts +369 -0
  376. package/src/geometry.ts +73 -0
  377. package/src/global.d.ts +20 -0
  378. package/src/index.ts +247 -0
  379. package/src/kiwi/codec.ts +614 -0
  380. package/src/kiwi/fig-file.ts +248 -0
  381. package/src/kiwi/fig-import.ts +300 -0
  382. package/src/kiwi/fig-parse-worker.ts +213 -0
  383. package/src/kiwi/instance-overrides.ts +682 -0
  384. package/src/kiwi/kiwi-convert.ts +771 -0
  385. package/src/kiwi/kiwi-schema/bb.ts +244 -0
  386. package/src/kiwi/kiwi-schema/binary.ts +102 -0
  387. package/src/kiwi/kiwi-schema/index.ts +5 -0
  388. package/src/kiwi/kiwi-schema/js.ts +339 -0
  389. package/src/kiwi/kiwi-schema/parser.ts +287 -0
  390. package/src/kiwi/kiwi-schema/schema.ts +24 -0
  391. package/src/kiwi/kiwi-schema/util.ts +10 -0
  392. package/src/kiwi/protocol.ts +237 -0
  393. package/src/kiwi/schema.ts +2286 -0
  394. package/src/kiwi-serialize.ts +467 -0
  395. package/src/layout.ts +608 -0
  396. package/src/profiler/draw-call-counter.ts +43 -0
  397. package/src/profiler/frame-capture.ts +139 -0
  398. package/src/profiler/frame-stats.ts +122 -0
  399. package/src/profiler/gpu-timer.ts +93 -0
  400. package/src/profiler/hud-renderer.ts +242 -0
  401. package/src/profiler/index.ts +8 -0
  402. package/src/profiler/phase-timer.ts +62 -0
  403. package/src/profiler/render-profiler.ts +163 -0
  404. package/src/render/components.ts +69 -0
  405. package/src/render/emoji-icons.ts +60 -0
  406. package/src/render/export-code.ts +366 -0
  407. package/src/render/export-jsx.ts +633 -0
  408. package/src/render/index.ts +38 -0
  409. package/src/render/jsx-runtime.ts +36 -0
  410. package/src/render/mini-react.ts +30 -0
  411. package/src/render/render-jsx.ts +74 -0
  412. package/src/render/renderer.ts +508 -0
  413. package/src/render/tailwind.ts +114 -0
  414. package/src/render/tree.ts +137 -0
  415. package/src/render-image.ts +139 -0
  416. package/src/renderer/effects.ts +64 -0
  417. package/src/renderer/fills.ts +248 -0
  418. package/src/renderer/index.ts +1 -0
  419. package/src/renderer/label-cache.ts +114 -0
  420. package/src/renderer/labels.ts +168 -0
  421. package/src/renderer/lru-cache.ts +71 -0
  422. package/src/renderer/overlays.ts +1089 -0
  423. package/src/renderer/renderer.ts +1226 -0
  424. package/src/renderer/rulers.ts +230 -0
  425. package/src/renderer/scene.ts +554 -0
  426. package/src/renderer/shapes.ts +193 -0
  427. package/src/renderer/strokes.ts +221 -0
  428. package/src/rpc/commands.ts +667 -0
  429. package/src/rpc/index.ts +25 -0
  430. package/src/scene-graph.ts +1254 -0
  431. package/src/snap.ts +122 -0
  432. package/src/style-runs.ts +234 -0
  433. package/src/svg-export/defs.ts +255 -0
  434. package/src/svg-export/index.ts +427 -0
  435. package/src/svg-export/paths.ts +212 -0
  436. package/src/svg-node.ts +62 -0
  437. package/src/text-editor.ts +349 -0
  438. package/src/tools/ai-adapter.ts +144 -0
  439. package/src/tools/analyze.ts +786 -0
  440. package/src/tools/create.ts +206 -0
  441. package/src/tools/describe.ts +280 -0
  442. package/src/tools/export-code.ts +41 -0
  443. package/src/tools/export-tailwind-config.ts +266 -0
  444. package/src/tools/icons.ts +213 -0
  445. package/src/tools/index.ts +6 -0
  446. package/src/tools/modify.ts +651 -0
  447. package/src/tools/patterns.ts +189 -0
  448. package/src/tools/read.ts +401 -0
  449. package/src/tools/registry.ts +155 -0
  450. package/src/tools/schema.ts +65 -0
  451. package/src/tools/structure.ts +348 -0
  452. package/src/tools/variables.ts +193 -0
  453. package/src/tools/vector.ts +311 -0
  454. package/src/types.ts +32 -0
  455. package/src/undo.ts +126 -0
  456. package/src/vector.ts +413 -0
  457. package/src/xpath.ts +259 -0
@@ -0,0 +1,1089 @@
1
+ import {
2
+ ROTATION_HANDLE_OFFSET,
3
+ ROTATION_HANDLE_RADIUS,
4
+ HANDLE_HALF_SIZE,
5
+ LABEL_OFFSET_Y,
6
+ MEASUREMENT_COLOR,
7
+ MEASUREMENT_LINE_WIDTH,
8
+ MEASUREMENT_PILL_HEIGHT,
9
+ MEASUREMENT_PILL_PADDING_X,
10
+ MEASUREMENT_PILL_RADIUS,
11
+ MEASUREMENT_FONT_SIZE,
12
+ MEASUREMENT_DASH,
13
+ MEASUREMENT_ARROW_SIZE,
14
+ SIZE_PILL_PADDING_X,
15
+ SIZE_PILL_PADDING_Y,
16
+ SIZE_PILL_HEIGHT,
17
+ SIZE_PILL_RADIUS,
18
+ SIZE_PILL_TEXT_OFFSET_Y,
19
+ MARQUEE_FILL_ALPHA,
20
+ SELECTION_DASH_ALPHA,
21
+ LAYOUT_INDICATOR_STROKE,
22
+ PEN_HANDLE_RADIUS,
23
+ PEN_VERTEX_RADIUS,
24
+ PEN_CLOSE_RADIUS_BOOST,
25
+ TEXT_SELECTION_COLOR,
26
+ TEXT_CARET_COLOR,
27
+ TEXT_CARET_WIDTH,
28
+ FLASH_COLOR,
29
+ FLASH_ATTACK_MS,
30
+ FLASH_HOLD_MS,
31
+ FLASH_RELEASE_MS,
32
+ FLASH_STROKE_WIDTH,
33
+ FLASH_PADDING,
34
+ FLASH_OVERSHOOT,
35
+ FLASH_RADIUS
36
+ } from '../constants'
37
+ import type { SceneNode, SceneGraph } from '../scene-graph'
38
+ import type { SnapGuide } from '../snap'
39
+ import type { TextEditor } from '../text-editor'
40
+ import type { Rect, Vector } from '../types'
41
+ import type { Canvas, Paint } from 'canvaskit-wasm'
42
+ import type { SkiaRenderer, RenderOverlays } from './renderer'
43
+
44
+ export function drawHoverHighlight(
45
+ r: SkiaRenderer,
46
+ canvas: Canvas,
47
+ graph: SceneGraph,
48
+ hoveredNodeId?: string | null
49
+ ): void {
50
+ if (!hoveredNodeId) return
51
+ const node = graph.getNode(hoveredNodeId)
52
+ if (!node) return
53
+
54
+ const abs = graph.getAbsolutePosition(node.id)
55
+ const sx = abs.x * r.zoom + r.panX
56
+ const sy = abs.y * r.zoom + r.panY
57
+
58
+ r.auxStroke.setStrokeWidth(1 / r.zoom)
59
+ r.auxStroke.setColor(
60
+ r.isComponentType(node.type) ? r.compColor() : r.selColor()
61
+ )
62
+ r.auxStroke.setPathEffect(null)
63
+
64
+ canvas.save()
65
+ canvas.translate(sx, sy)
66
+ if (node.rotation !== 0) {
67
+ const cx = (node.width / 2) * r.zoom
68
+ const cy = (node.height / 2) * r.zoom
69
+ canvas.rotate(node.rotation, cx, cy)
70
+ }
71
+ canvas.scale(r.zoom, r.zoom)
72
+ r.strokeNodeShape(canvas, node, r.auxStroke)
73
+ canvas.restore()
74
+ }
75
+
76
+ export function drawSelection(
77
+ r: SkiaRenderer,
78
+ canvas: Canvas,
79
+ graph: SceneGraph,
80
+ selectedIds: Set<string>,
81
+ overlays: RenderOverlays
82
+ ): void {
83
+ if (selectedIds.size === 0) return
84
+
85
+ r.drawParentFrameOutlines(canvas, graph, selectedIds)
86
+
87
+ if (selectedIds.size === 1) {
88
+ const id = [...selectedIds][0]
89
+ if (overlays.editingTextId === id) return
90
+ const node = graph.getNode(id)
91
+ if (!node) return
92
+
93
+ const useComponentColor = r.isComponentType(node.type)
94
+ r.selectionPaint.setColor(useComponentColor ? r.compColor() : r.selColor())
95
+ r.selectionPaint.setStrokeWidth(1)
96
+
97
+ const rotation =
98
+ overlays.rotationPreview?.nodeId === id ? overlays.rotationPreview.angle : node.rotation
99
+ r.drawNodeSelection(canvas, node, rotation, graph)
100
+ r.drawSelectionLabels(canvas, graph, selectedIds)
101
+
102
+ r.selectionPaint.setColor(r.selColor())
103
+ return
104
+ }
105
+
106
+ for (const id of selectedIds) {
107
+ const node = graph.getNode(id)
108
+ if (!node) continue
109
+
110
+ const useComponentColor = r.isComponentType(node.type)
111
+ r.selectionPaint.setColor(useComponentColor ? r.compColor() : r.selColor())
112
+ r.selectionPaint.setStrokeWidth(1)
113
+
114
+ const rotation =
115
+ overlays.rotationPreview?.nodeId === id ? overlays.rotationPreview.angle : node.rotation
116
+ r.drawNodeOutline(canvas, node, rotation, graph)
117
+ }
118
+
119
+ r.selectionPaint.setColor(r.selColor())
120
+
121
+ const nodes = [...selectedIds]
122
+ .map((id) => graph.getNode(id))
123
+ .filter((n): n is SceneNode => n !== undefined)
124
+ r.drawGroupBounds(canvas, nodes, graph)
125
+
126
+ r.drawSelectionLabels(canvas, graph, selectedIds)
127
+ }
128
+
129
+ export function drawNodeSelection(
130
+ r: SkiaRenderer,
131
+ canvas: Canvas,
132
+ node: SceneNode,
133
+ rotation: number,
134
+ graph: SceneGraph
135
+ ): void {
136
+ const abs = graph.getAbsolutePosition(node.id)
137
+ const cx = (abs.x + node.width / 2) * r.zoom + r.panX
138
+ const cy = (abs.y + node.height / 2) * r.zoom + r.panY
139
+ const hw = (node.width / 2) * r.zoom
140
+ const hh = (node.height / 2) * r.zoom
141
+
142
+ canvas.save()
143
+ if (rotation !== 0) {
144
+ canvas.rotate(rotation, cx, cy)
145
+ }
146
+
147
+ const x1 = cx - hw
148
+ const y1 = cy - hh
149
+ const x2 = cx + hw
150
+ const y2 = cy + hh
151
+
152
+ canvas.drawRect(r.ck.LTRBRect(x1, y1, x2, y2), r.selectionPaint)
153
+
154
+ r.drawHandle(canvas, x1, y1)
155
+ r.drawHandle(canvas, x2, y1)
156
+ r.drawHandle(canvas, x1, y2)
157
+ r.drawHandle(canvas, x2, y2)
158
+
159
+ const mx = (x1 + x2) / 2
160
+ const my = (y1 + y2) / 2
161
+ r.drawHandle(canvas, mx, y1)
162
+ r.drawHandle(canvas, mx, y2)
163
+ r.drawHandle(canvas, x1, my)
164
+ r.drawHandle(canvas, x2, my)
165
+
166
+ const rotHandleY = y1 - ROTATION_HANDLE_OFFSET - ROTATION_HANDLE_RADIUS
167
+ r.auxStroke.setStrokeWidth(1)
168
+ r.auxStroke.setColor(r.selColor())
169
+ r.auxStroke.setPathEffect(null)
170
+ canvas.drawLine(mx, y1, mx, rotHandleY, r.auxStroke)
171
+
172
+ r.auxFill.setColor(r.ck.WHITE)
173
+ canvas.drawCircle(mx, rotHandleY, ROTATION_HANDLE_RADIUS, r.auxFill)
174
+ canvas.drawCircle(mx, rotHandleY, ROTATION_HANDLE_RADIUS, r.auxStroke)
175
+
176
+ canvas.restore()
177
+ }
178
+
179
+ export function drawSelectionLabels(
180
+ r: SkiaRenderer,
181
+ canvas: Canvas,
182
+ graph: SceneGraph,
183
+ selectedIds: Set<string>
184
+ ): void {
185
+ if (!r.labelFont || !r.sizeFont) return
186
+
187
+ let minX = Infinity
188
+ let minY = Infinity
189
+ let maxX = -Infinity
190
+ let maxY = -Infinity
191
+ const nodes: SceneNode[] = []
192
+
193
+ for (const id of selectedIds) {
194
+ const node = graph.getNode(id)
195
+ if (!node) continue
196
+ nodes.push(node)
197
+ const abs = graph.getAbsolutePosition(id)
198
+ minX = Math.min(minX, abs.x)
199
+ minY = Math.min(minY, abs.y)
200
+ maxX = Math.max(maxX, abs.x + node.width)
201
+ maxY = Math.max(maxY, abs.y + node.height)
202
+ }
203
+
204
+ if (nodes.length === 0) return
205
+
206
+ const sx1 = minX * r.zoom + r.panX
207
+ const sy1 = minY * r.zoom + r.panY
208
+ const sx2 = maxX * r.zoom + r.panX
209
+ const sy2 = maxY * r.zoom + r.panY
210
+ const smx = (sx1 + sx2) / 2
211
+
212
+ if (nodes.length === 1) {
213
+ const node = nodes[0]
214
+ const parentNode = node.parentId ? graph.getNode(node.parentId) : null
215
+ const isTopLevel =
216
+ !parentNode || parentNode.type === 'CANVAS' || parentNode.type === 'SECTION'
217
+ if (node.type === 'FRAME' && isTopLevel) {
218
+ r.auxFill.setColor(r.selColor())
219
+ canvas.drawText(node.name, sx1, sy1 - LABEL_OFFSET_Y, r.auxFill, r.labelFont)
220
+ }
221
+ }
222
+
223
+ const w = Math.round(maxX - minX)
224
+ const h = Math.round(maxY - minY)
225
+ const sizeText = `${w} × ${h}`
226
+ const glyphIds = r.sizeFont.getGlyphIDs(sizeText)
227
+ const widths = r.sizeFont.getGlyphWidths(glyphIds)
228
+ let textWidth = 0
229
+ for (const w of widths) textWidth += w
230
+ const pillW = textWidth + SIZE_PILL_PADDING_X * 2
231
+ const pillH = SIZE_PILL_HEIGHT
232
+ const pillX = smx - pillW / 2
233
+ const pillY = sy2 + SIZE_PILL_PADDING_Y
234
+
235
+ const allComponents = nodes.length > 0 && nodes.every((n) => r.isComponentType(n.type))
236
+ const pillColor = allComponents ? r.compColor() : r.selColor()
237
+
238
+ r.auxFill.setColor(pillColor)
239
+ const rrect = r.ck.RRectXY(
240
+ r.ck.LTRBRect(pillX, pillY, pillX + pillW, pillY + pillH),
241
+ SIZE_PILL_RADIUS,
242
+ SIZE_PILL_RADIUS
243
+ )
244
+ canvas.drawRRect(rrect, r.auxFill)
245
+
246
+ r.auxFill.setColor(r.ck.WHITE)
247
+ canvas.drawText(
248
+ sizeText,
249
+ pillX + SIZE_PILL_PADDING_X,
250
+ pillY + SIZE_PILL_TEXT_OFFSET_Y,
251
+ r.auxFill,
252
+ r.sizeFont
253
+ )
254
+ }
255
+
256
+ export function drawParentFrameOutlines(
257
+ r: SkiaRenderer,
258
+ canvas: Canvas,
259
+ graph: SceneGraph,
260
+ selectedIds: Set<string>
261
+ ): void {
262
+ const drawn = new Set<string>()
263
+ for (const id of selectedIds) {
264
+ const node = graph.getNode(id)
265
+ if (!node?.parentId) continue
266
+ const nodeParent = graph.getNode(node.parentId)
267
+ if (!nodeParent || nodeParent.type === 'CANVAS') continue
268
+ if (drawn.has(node.parentId) || selectedIds.has(node.parentId)) continue
269
+
270
+ const parent = nodeParent
271
+
272
+ const grandparent = parent.parentId ? graph.getNode(parent.parentId) : null
273
+ if (!grandparent || grandparent.type === 'CANVAS') continue
274
+
275
+ drawn.add(node.parentId)
276
+
277
+ const abs = graph.getAbsolutePosition(parent.id)
278
+ const x = abs.x * r.zoom + r.panX
279
+ const y = abs.y * r.zoom + r.panY
280
+ const w = parent.width * r.zoom
281
+ const h = parent.height * r.zoom
282
+
283
+ canvas.save()
284
+ if (parent.rotation !== 0) {
285
+ canvas.rotate(parent.rotation, x + w / 2, y + h / 2)
286
+ }
287
+ canvas.drawRect(r.ck.LTRBRect(x, y, x + w, y + h), r.parentOutlinePaint)
288
+ canvas.restore()
289
+ }
290
+ }
291
+
292
+ export function drawNodeOutline(
293
+ r: SkiaRenderer,
294
+ canvas: Canvas,
295
+ node: SceneNode,
296
+ rotation: number,
297
+ graph: SceneGraph
298
+ ): void {
299
+ const abs = graph.getAbsolutePosition(node.id)
300
+ const cx = (abs.x + node.width / 2) * r.zoom + r.panX
301
+ const cy = (abs.y + node.height / 2) * r.zoom + r.panY
302
+ const hw = (node.width / 2) * r.zoom
303
+ const hh = (node.height / 2) * r.zoom
304
+
305
+ canvas.save()
306
+ if (rotation !== 0) {
307
+ canvas.rotate(rotation, cx, cy)
308
+ }
309
+
310
+ canvas.drawRect(r.ck.LTRBRect(cx - hw, cy - hh, cx + hw, cy + hh), r.selectionPaint)
311
+ canvas.restore()
312
+ }
313
+
314
+ export function drawGroupBounds(
315
+ r: SkiaRenderer,
316
+ canvas: Canvas,
317
+ nodes: SceneNode[],
318
+ graph: SceneGraph
319
+ ): void {
320
+ let minX = Infinity
321
+ let minY = Infinity
322
+ let maxX = -Infinity
323
+ let maxY = -Infinity
324
+
325
+ for (const n of nodes) {
326
+ const abs = graph.getAbsolutePosition(n.id)
327
+ if (n.rotation !== 0) {
328
+ const corners = r.getRotatedCorners(n, abs)
329
+ for (const c of corners) {
330
+ minX = Math.min(minX, c.x)
331
+ minY = Math.min(minY, c.y)
332
+ maxX = Math.max(maxX, c.x)
333
+ maxY = Math.max(maxY, c.y)
334
+ }
335
+ } else {
336
+ const x1 = abs.x * r.zoom + r.panX
337
+ const y1 = abs.y * r.zoom + r.panY
338
+ const x2 = (abs.x + n.width) * r.zoom + r.panX
339
+ const y2 = (abs.y + n.height) * r.zoom + r.panY
340
+ minX = Math.min(minX, x1)
341
+ minY = Math.min(minY, y1)
342
+ maxX = Math.max(maxX, x2)
343
+ maxY = Math.max(maxY, y2)
344
+ }
345
+ }
346
+
347
+ r.auxStroke.setStrokeWidth(1)
348
+ r.auxStroke.setColor(r.selColor(SELECTION_DASH_ALPHA))
349
+ r.auxStroke.setPathEffect(null)
350
+
351
+ canvas.drawRect(r.ck.LTRBRect(minX, minY, maxX, maxY), r.auxStroke)
352
+
353
+ r.drawHandle(canvas, minX, minY)
354
+ r.drawHandle(canvas, maxX, minY)
355
+ r.drawHandle(canvas, minX, maxY)
356
+ r.drawHandle(canvas, maxX, maxY)
357
+ const gmx = (minX + maxX) / 2
358
+ const gmy = (minY + maxY) / 2
359
+ r.drawHandle(canvas, gmx, minY)
360
+ r.drawHandle(canvas, gmx, maxY)
361
+ r.drawHandle(canvas, minX, gmy)
362
+ r.drawHandle(canvas, maxX, gmy)
363
+ }
364
+
365
+ export function getRotatedCorners(
366
+ r: SkiaRenderer,
367
+ n: SceneNode,
368
+ abs: Vector
369
+ ): Vector[] {
370
+ const cx = (abs.x + n.width / 2) * r.zoom + r.panX
371
+ const cy = (abs.y + n.height / 2) * r.zoom + r.panY
372
+ const hw = (n.width / 2) * r.zoom
373
+ const hh = (n.height / 2) * r.zoom
374
+ const rad = (n.rotation * Math.PI) / 180
375
+ const cos = Math.cos(rad)
376
+ const sin = Math.sin(rad)
377
+
378
+ return [
379
+ { x: cx + -hw * cos - -hh * sin, y: cy + -hw * sin + -hh * cos },
380
+ { x: cx + hw * cos - -hh * sin, y: cy + hw * sin + -hh * cos },
381
+ { x: cx + hw * cos - hh * sin, y: cy + hw * sin + hh * cos },
382
+ { x: cx + -hw * cos - hh * sin, y: cy + -hw * sin + hh * cos }
383
+ ]
384
+ }
385
+
386
+ export function drawHandle(r: SkiaRenderer, canvas: Canvas, x: number, y: number): void {
387
+ r.auxFill.setColor(r.ck.WHITE)
388
+ const rect = r.ck.LTRBRect(
389
+ x - HANDLE_HALF_SIZE,
390
+ y - HANDLE_HALF_SIZE,
391
+ x + HANDLE_HALF_SIZE,
392
+ y + HANDLE_HALF_SIZE
393
+ )
394
+ canvas.drawRect(rect, r.auxFill)
395
+ canvas.drawRect(rect, r.selectionPaint)
396
+ }
397
+
398
+ export function drawSnapGuides(
399
+ r: SkiaRenderer,
400
+ canvas: Canvas,
401
+ guides?: SnapGuide[]
402
+ ): void {
403
+ if (!guides || guides.length === 0) return
404
+
405
+ for (const guide of guides) {
406
+ if (guide.axis === 'x') {
407
+ const x = guide.position * r.zoom + r.panX
408
+ const y1 = guide.from * r.zoom + r.panY
409
+ const y2 = guide.to * r.zoom + r.panY
410
+ canvas.drawLine(x, y1, x, y2, r.snapPaint)
411
+ } else {
412
+ const y = guide.position * r.zoom + r.panY
413
+ const x1 = guide.from * r.zoom + r.panX
414
+ const x2 = guide.to * r.zoom + r.panX
415
+ canvas.drawLine(x1, y, x2, y, r.snapPaint)
416
+ }
417
+ }
418
+ }
419
+
420
+ export function drawMarquee(r: SkiaRenderer, canvas: Canvas, marquee?: Rect | null): void {
421
+ if (!marquee || marquee.width <= 0 || marquee.height <= 0) return
422
+
423
+ const x1 = marquee.x * r.zoom + r.panX
424
+ const y1 = marquee.y * r.zoom + r.panY
425
+ const x2 = (marquee.x + marquee.width) * r.zoom + r.panX
426
+ const y2 = (marquee.y + marquee.height) * r.zoom + r.panY
427
+ const rect = r.ck.LTRBRect(x1, y1, x2, y2)
428
+
429
+ r.auxFill.setColor(r.selColor(MARQUEE_FILL_ALPHA))
430
+ canvas.drawRect(rect, r.auxFill)
431
+ canvas.drawRect(rect, r.selectionPaint)
432
+ }
433
+
434
+ export function drawFlashes(r: SkiaRenderer, canvas: Canvas, graph: SceneGraph): void {
435
+ if (r._flashes.length === 0) return
436
+
437
+ const now = performance.now()
438
+ const ck = r.ck
439
+ const totalMs = FLASH_ATTACK_MS + FLASH_HOLD_MS + FLASH_RELEASE_MS
440
+
441
+ if (!r._flashPaint) {
442
+ r._flashPaint = new ck.Paint()
443
+ r._flashPaint.setStyle(ck.PaintStyle.Stroke)
444
+ r._flashPaint.setAntiAlias(true)
445
+ }
446
+
447
+ const paint = r._flashPaint
448
+ const zoom = r.zoom
449
+
450
+ for (let i = r._flashes.length - 1; i >= 0; i--) {
451
+ const flash = r._flashes[i]
452
+ const elapsed = now - flash.startTime
453
+ if (elapsed > totalMs) {
454
+ r._flashes.splice(i, 1)
455
+ continue
456
+ }
457
+
458
+ const node = graph.getNode(flash.nodeId)
459
+ if (!node) {
460
+ r._flashes.splice(i, 1)
461
+ continue
462
+ }
463
+
464
+ const abs = graph.getAbsolutePosition(flash.nodeId)
465
+ const cx = (abs.x + node.width / 2) * zoom + r.panX
466
+ const cy = (abs.y + node.height / 2) * zoom + r.panY
467
+ const hw = (node.width / 2) * zoom
468
+ const hh = (node.height / 2) * zoom
469
+
470
+ let opacity: number
471
+ let extraPad: number
472
+
473
+ if (elapsed < FLASH_ATTACK_MS) {
474
+ const t = elapsed / FLASH_ATTACK_MS
475
+ const ease = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
476
+ opacity = ease
477
+ extraPad = (1 - ease) * FLASH_OVERSHOOT
478
+ } else if (elapsed < FLASH_ATTACK_MS + FLASH_HOLD_MS) {
479
+ opacity = 1
480
+ extraPad = 0
481
+ } else {
482
+ const t = (elapsed - FLASH_ATTACK_MS - FLASH_HOLD_MS) / FLASH_RELEASE_MS
483
+ opacity = 1 - t * t
484
+ extraPad = 0
485
+ }
486
+
487
+ const pad = FLASH_PADDING + extraPad
488
+ const rad = FLASH_RADIUS
489
+
490
+ paint.setColor(ck.Color4f(FLASH_COLOR.r, FLASH_COLOR.g, FLASH_COLOR.b, opacity))
491
+ paint.setStrokeWidth(FLASH_STROKE_WIDTH)
492
+
493
+ canvas.save()
494
+ if (node.rotation !== 0) canvas.rotate(node.rotation, cx, cy)
495
+
496
+ const rect = ck.RRectXY(
497
+ ck.LTRBRect(cx - hw - pad, cy - hh - pad, cx + hw + pad, cy + hh + pad),
498
+ rad,
499
+ rad
500
+ )
501
+ canvas.drawRRect(rect, paint)
502
+ canvas.restore()
503
+ }
504
+ }
505
+
506
+ export function drawLayoutInsertIndicator(
507
+ r: SkiaRenderer,
508
+ canvas: Canvas,
509
+ indicator?: RenderOverlays['layoutInsertIndicator']
510
+ ): void {
511
+ if (!indicator) return
512
+
513
+ r.auxStroke.setStrokeWidth(LAYOUT_INDICATOR_STROKE)
514
+ r.auxStroke.setColor(r.selColor())
515
+ r.auxStroke.setPathEffect(null)
516
+
517
+ if (indicator.direction === 'HORIZONTAL') {
518
+ const y = indicator.y * r.zoom + r.panY
519
+ const x1 = indicator.x * r.zoom + r.panX
520
+ const x2 = (indicator.x + indicator.length) * r.zoom + r.panX
521
+ canvas.drawLine(x1, y, x2, y, r.auxStroke)
522
+ } else {
523
+ const x = indicator.x * r.zoom + r.panX
524
+ const y1 = indicator.y * r.zoom + r.panY
525
+ const y2 = (indicator.y + indicator.length) * r.zoom + r.panY
526
+ canvas.drawLine(x, y1, x, y2, r.auxStroke)
527
+ }
528
+ }
529
+
530
+ export function drawTextEditOverlay(
531
+ r: SkiaRenderer,
532
+ canvas: Canvas,
533
+ node: SceneNode,
534
+ editor: TextEditor
535
+ ): void {
536
+ r.auxStroke.setStrokeWidth(1 / r.zoom)
537
+ r.auxStroke.setColor(r.selColor())
538
+ r.auxStroke.setPathEffect(null)
539
+ canvas.drawRect(r.ck.LTRBRect(0, 0, node.width, node.height), r.auxStroke)
540
+
541
+ const selRects = editor.getSelectionRects()
542
+ if (selRects.length > 0) {
543
+ r.auxFill.setColor(
544
+ r.ck.Color4f(
545
+ TEXT_SELECTION_COLOR.r,
546
+ TEXT_SELECTION_COLOR.g,
547
+ TEXT_SELECTION_COLOR.b,
548
+ TEXT_SELECTION_COLOR.a
549
+ )
550
+ )
551
+ for (const sel of selRects) {
552
+ canvas.drawRect(
553
+ r.ck.LTRBRect(sel.x, sel.y, sel.x + sel.width, sel.y + sel.height),
554
+ r.auxFill
555
+ )
556
+ }
557
+ }
558
+
559
+ if (editor.caretVisible && !editor.hasSelection()) {
560
+ const caret = editor.getCaretRect()
561
+ if (caret) {
562
+ r.auxFill.setColor(
563
+ r.ck.Color4f(
564
+ TEXT_CARET_COLOR.r,
565
+ TEXT_CARET_COLOR.g,
566
+ TEXT_CARET_COLOR.b,
567
+ TEXT_CARET_COLOR.a
568
+ )
569
+ )
570
+ const w = TEXT_CARET_WIDTH / r.zoom
571
+ canvas.drawRect(
572
+ r.ck.LTRBRect(caret.x - w / 2, caret.y0, caret.x + w / 2, caret.y1),
573
+ r.auxFill
574
+ )
575
+ }
576
+ }
577
+ }
578
+
579
+ type ToScreenFn = (x: number, y: number) => Vector
580
+
581
+ function buildPenPath(
582
+ r: SkiaRenderer,
583
+ canvas: Canvas,
584
+ penState: NonNullable<RenderOverlays['penState']>,
585
+ toScreen: ToScreenFn
586
+ ): void {
587
+ const { vertices, segments, dragTangent, cursorX, cursorY } = penState
588
+
589
+ const path = new r.ck.Path()
590
+ for (const seg of segments) {
591
+ const s = toScreen(vertices[seg.start].x, vertices[seg.start].y)
592
+ const e = toScreen(vertices[seg.end].x, vertices[seg.end].y)
593
+ path.moveTo(s.x, s.y)
594
+
595
+ const isLine =
596
+ seg.tangentStart.x === 0 &&
597
+ seg.tangentStart.y === 0 &&
598
+ seg.tangentEnd.x === 0 &&
599
+ seg.tangentEnd.y === 0
600
+ if (isLine) {
601
+ path.lineTo(e.x, e.y)
602
+ } else {
603
+ const cp1 = toScreen(
604
+ vertices[seg.start].x + seg.tangentStart.x,
605
+ vertices[seg.start].y + seg.tangentStart.y
606
+ )
607
+ const cp2 = toScreen(
608
+ vertices[seg.end].x + seg.tangentEnd.x,
609
+ vertices[seg.end].y + seg.tangentEnd.y
610
+ )
611
+ path.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y)
612
+ }
613
+ }
614
+
615
+ if (vertices.length > 0 && cursorX != null && cursorY != null) {
616
+ const last = toScreen(vertices[vertices.length - 1].x, vertices[vertices.length - 1].y)
617
+ const cursor = toScreen(cursorX, cursorY)
618
+ path.moveTo(last.x, last.y)
619
+ if (dragTangent) {
620
+ const cp1 = toScreen(
621
+ vertices[vertices.length - 1].x + dragTangent.x,
622
+ vertices[vertices.length - 1].y + dragTangent.y
623
+ )
624
+ path.cubicTo(cp1.x, cp1.y, cursor.x, cursor.y, cursor.x, cursor.y)
625
+ } else {
626
+ path.lineTo(cursor.x, cursor.y)
627
+ }
628
+ }
629
+
630
+ canvas.drawPath(path, r.penPathPaint)
631
+ path.delete()
632
+ }
633
+
634
+ function drawPenHandlePoint(
635
+ canvas: Canvas,
636
+ x: number,
637
+ y: number,
638
+ vertexFill: Paint,
639
+ handlePaint: Paint
640
+ ): void {
641
+ canvas.drawCircle(x, y, PEN_HANDLE_RADIUS, vertexFill)
642
+ canvas.drawCircle(x, y, PEN_HANDLE_RADIUS, handlePaint)
643
+ }
644
+
645
+ function drawPenTangentHandles(
646
+ canvas: Canvas,
647
+ penState: NonNullable<RenderOverlays['penState']>,
648
+ toScreen: ToScreenFn,
649
+ handlePaint: Paint,
650
+ vertexFill: Paint
651
+ ): void {
652
+ const { vertices, segments, dragTangent } = penState
653
+
654
+ for (const seg of segments) {
655
+ const ts = seg.tangentStart
656
+ const te = seg.tangentEnd
657
+ if (ts.x !== 0 || ts.y !== 0) {
658
+ const s = toScreen(vertices[seg.start].x, vertices[seg.start].y)
659
+ const cp = toScreen(vertices[seg.start].x + ts.x, vertices[seg.start].y + ts.y)
660
+ canvas.drawLine(s.x, s.y, cp.x, cp.y, handlePaint)
661
+ drawPenHandlePoint(canvas, cp.x, cp.y, vertexFill, handlePaint)
662
+ }
663
+ if (te.x !== 0 || te.y !== 0) {
664
+ const e = toScreen(vertices[seg.end].x, vertices[seg.end].y)
665
+ const cp = toScreen(vertices[seg.end].x + te.x, vertices[seg.end].y + te.y)
666
+ canvas.drawLine(e.x, e.y, cp.x, cp.y, handlePaint)
667
+ drawPenHandlePoint(canvas, cp.x, cp.y, vertexFill, handlePaint)
668
+ }
669
+ }
670
+
671
+ if (dragTangent && vertices.length > 0) {
672
+ const last = vertices[vertices.length - 1]
673
+ const cp1 = toScreen(last.x + dragTangent.x, last.y + dragTangent.y)
674
+ const cp2 = toScreen(last.x - dragTangent.x, last.y - dragTangent.y)
675
+ canvas.drawLine(cp2.x, cp2.y, cp1.x, cp1.y, handlePaint)
676
+ drawPenHandlePoint(canvas, cp1.x, cp1.y, vertexFill, handlePaint)
677
+ drawPenHandlePoint(canvas, cp2.x, cp2.y, vertexFill, handlePaint)
678
+ }
679
+ }
680
+
681
+ export function drawPenOverlay(
682
+ r: SkiaRenderer,
683
+ canvas: Canvas,
684
+ penState: RenderOverlays['penState']
685
+ ): void {
686
+ if (!penState || penState.vertices.length === 0) return
687
+
688
+ const { vertices } = penState
689
+ const vertexFill = r.penVertexFill
690
+ const vertexStroke = r.penVertexStroke
691
+
692
+ const toScreen: ToScreenFn = (x, y) => ({
693
+ x: x * r.zoom + r.panX,
694
+ y: y * r.zoom + r.panY
695
+ })
696
+
697
+ buildPenPath(r, canvas, penState, toScreen)
698
+ drawPenTangentHandles(canvas, penState, toScreen, r.penHandlePaint, vertexFill)
699
+
700
+ for (let i = 0; i < vertices.length; i++) {
701
+ const v = toScreen(vertices[i].x, vertices[i].y)
702
+ const radius =
703
+ i === 0 && penState.closingToFirst
704
+ ? PEN_VERTEX_RADIUS + PEN_CLOSE_RADIUS_BOOST
705
+ : PEN_VERTEX_RADIUS
706
+ canvas.drawCircle(v.x, v.y, radius, vertexFill)
707
+ canvas.drawCircle(v.x, v.y, radius, vertexStroke)
708
+ }
709
+ }
710
+
711
+ export function drawRemoteCursors(
712
+ r: SkiaRenderer,
713
+ canvas: Canvas,
714
+ graph: SceneGraph,
715
+ cursors?: RenderOverlays['remoteCursors']
716
+ ): void {
717
+ if (!cursors || cursors.length === 0) return
718
+
719
+ const CURSOR_SIZE = 9
720
+ const LABEL_PADDING_X = 4
721
+ const LABEL_PADDING_Y = 2
722
+ const LABEL_FONT_SIZE = 10
723
+ const LABEL_OFFSET_X = 12
724
+ const LABEL_OFFSET_Y = 20
725
+
726
+ for (const cursor of cursors) {
727
+ const screenX = cursor.x * r.zoom + r.panX
728
+ const screenY = cursor.y * r.zoom + r.panY
729
+ const { r: cr, g, b } = cursor.color
730
+
731
+ if (cursor.selection?.length) {
732
+ r.auxStroke.setColor(r.ck.Color4f(cr, g, b, 0.6))
733
+ r.auxStroke.setStrokeWidth(1.5)
734
+ r.auxStroke.setPathEffect(null)
735
+ for (const nodeId of cursor.selection) {
736
+ const node = graph.getNode(nodeId)
737
+ if (!node) continue
738
+ const abs = graph.getAbsolutePosition(nodeId)
739
+ const sx = abs.x * r.zoom + r.panX
740
+ const sy = abs.y * r.zoom + r.panY
741
+ const sw = node.width * r.zoom
742
+ const sh = node.height * r.zoom
743
+ canvas.drawRect(r.ck.XYWHRect(sx, sy, sw, sh), r.auxStroke)
744
+ }
745
+ }
746
+
747
+ const S = CURSOR_SIZE
748
+ const path = new r.ck.Path()
749
+ path.moveTo(screenX, screenY)
750
+ path.lineTo(screenX, screenY + S * 1.35)
751
+ path.lineTo(screenX + S * 0.38, screenY + S * 1.0)
752
+ path.lineTo(screenX + S * 0.72, screenY + S * 1.5)
753
+ path.lineTo(screenX + S * 0.92, screenY + S * 1.38)
754
+ path.lineTo(screenX + S * 0.58, screenY + S * 0.88)
755
+ path.lineTo(screenX + S * 1.0, screenY + S * 0.82)
756
+ path.close()
757
+
758
+ r.auxStroke.setColor(r.ck.Color4f(1, 1, 1, 1))
759
+ r.auxStroke.setStrokeWidth(2)
760
+ r.auxStroke.setPathEffect(null)
761
+ canvas.drawPath(path, r.auxStroke)
762
+
763
+ r.auxFill.setColor(r.ck.Color4f(cr, g, b, 1))
764
+ canvas.drawPath(path, r.auxFill)
765
+ path.delete()
766
+
767
+ if (cursor.name) {
768
+ const font = r.labelFont
769
+ if (font) {
770
+ font.setSize(LABEL_FONT_SIZE)
771
+ const labelX = screenX + LABEL_OFFSET_X
772
+ const labelY = screenY + LABEL_OFFSET_Y
773
+ const glyphIds = font.getGlyphIDs(cursor.name)
774
+ const widths = font.getGlyphWidths(glyphIds)
775
+ let textWidth = 0
776
+ for (const w of widths) textWidth += w
777
+
778
+ r.auxFill.setColor(r.ck.Color4f(cr, g, b, 1))
779
+ const bgRect = r.ck.RRectXY(
780
+ r.ck.XYWHRect(
781
+ labelX - LABEL_PADDING_X,
782
+ labelY - LABEL_FONT_SIZE - LABEL_PADDING_Y + 2,
783
+ textWidth + LABEL_PADDING_X * 2,
784
+ LABEL_FONT_SIZE + LABEL_PADDING_Y * 2
785
+ ),
786
+ 4,
787
+ 4
788
+ )
789
+ canvas.drawRRect(bgRect, r.auxFill)
790
+
791
+ r.auxFill.setColor(r.ck.Color4f(1, 1, 1, 1))
792
+ canvas.drawText(cursor.name, labelX, labelY, r.auxFill, font)
793
+ }
794
+ }
795
+ }
796
+ }
797
+
798
+ // ── Measurement / Redline Overlay ──
799
+
800
+ interface MeasurementBounds {
801
+ x1: number
802
+ y1: number
803
+ x2: number
804
+ y2: number
805
+ }
806
+
807
+ function nodeBounds(graph: SceneGraph, id: string, node: SceneNode): MeasurementBounds {
808
+ const abs = graph.getAbsolutePosition(id)
809
+ return {
810
+ x1: abs.x,
811
+ y1: abs.y,
812
+ x2: abs.x + node.width,
813
+ y2: abs.y + node.height,
814
+ }
815
+ }
816
+
817
+ function drawMeasurementLine(
818
+ _r: SkiaRenderer,
819
+ canvas: Canvas,
820
+ x1: number,
821
+ y1: number,
822
+ x2: number,
823
+ y2: number,
824
+ paint: Paint
825
+ ): void {
826
+ canvas.drawLine(x1, y1, x2, y2, paint)
827
+ // Draw small perpendicular end caps
828
+ const isHorizontal = Math.abs(y2 - y1) < 0.5
829
+ const cap = MEASUREMENT_ARROW_SIZE
830
+ if (isHorizontal) {
831
+ canvas.drawLine(x1, y1 - cap, x1, y1 + cap, paint)
832
+ canvas.drawLine(x2, y2 - cap, x2, y2 + cap, paint)
833
+ } else {
834
+ canvas.drawLine(x1 - cap, y1, x1 + cap, y1, paint)
835
+ canvas.drawLine(x2 - cap, y2, x2 + cap, y2, paint)
836
+ }
837
+ }
838
+
839
+ function drawMeasurementPill(
840
+ r: SkiaRenderer,
841
+ canvas: Canvas,
842
+ cx: number,
843
+ cy: number,
844
+ text: string
845
+ ): void {
846
+ const font = r.sizeFont
847
+ if (!font) return
848
+
849
+ font.setSize(MEASUREMENT_FONT_SIZE)
850
+ const glyphIds = font.getGlyphIDs(text)
851
+ const widths = font.getGlyphWidths(glyphIds)
852
+ let textWidth = 0
853
+ for (const w of widths) textWidth += w
854
+
855
+ const pw = textWidth + MEASUREMENT_PILL_PADDING_X * 2
856
+ const ph = MEASUREMENT_PILL_HEIGHT
857
+ const px = cx - pw / 2
858
+ const py = cy - ph / 2
859
+
860
+ // Background pill
861
+ r.auxFill.setColor(
862
+ r.ck.Color4f(MEASUREMENT_COLOR.r, MEASUREMENT_COLOR.g, MEASUREMENT_COLOR.b, 1)
863
+ )
864
+ const bgRect = r.ck.RRectXY(
865
+ r.ck.XYWHRect(px, py, pw, ph),
866
+ MEASUREMENT_PILL_RADIUS,
867
+ MEASUREMENT_PILL_RADIUS
868
+ )
869
+ canvas.drawRRect(bgRect, r.auxFill)
870
+
871
+ // White text
872
+ r.auxFill.setColor(r.ck.Color4f(1, 1, 1, 1))
873
+ canvas.drawText(text, cx - textWidth / 2, cy + MEASUREMENT_FONT_SIZE * 0.35, r.auxFill, font)
874
+ }
875
+
876
+ export function drawMeasurements(
877
+ r: SkiaRenderer,
878
+ canvas: Canvas,
879
+ graph: SceneGraph,
880
+ selectedIds: Set<string>,
881
+ hoveredNodeId?: string | null
882
+ ): void {
883
+ if (!hoveredNodeId || selectedIds.size !== 1) return
884
+ const selId = [...selectedIds][0]
885
+ if (selId === hoveredNodeId) return
886
+ // Don't measure if hovered is selected
887
+ if (selectedIds.has(hoveredNodeId)) return
888
+
889
+ const selNode = graph.getNode(selId)
890
+ const hovNode = graph.getNode(hoveredNodeId)
891
+ if (!selNode || !hovNode) return
892
+
893
+ const a = nodeBounds(graph, selId, selNode)
894
+ const b = nodeBounds(graph, hoveredNodeId, hovNode)
895
+
896
+ // Set up measurement paint
897
+ const mPaint = r.auxStroke
898
+ mPaint.setColor(
899
+ r.ck.Color4f(MEASUREMENT_COLOR.r, MEASUREMENT_COLOR.g, MEASUREMENT_COLOR.b, 1)
900
+ )
901
+ mPaint.setStrokeWidth(MEASUREMENT_LINE_WIDTH)
902
+ mPaint.setPathEffect(
903
+ r.ck.PathEffect.MakeDash(MEASUREMENT_DASH, 0)
904
+ )
905
+
906
+ // Convert to screen coords
907
+ const sa = {
908
+ x1: a.x1 * r.zoom + r.panX,
909
+ y1: a.y1 * r.zoom + r.panY,
910
+ x2: a.x2 * r.zoom + r.panX,
911
+ y2: a.y2 * r.zoom + r.panY,
912
+ }
913
+ const sb = {
914
+ x1: b.x1 * r.zoom + r.panX,
915
+ y1: b.y1 * r.zoom + r.panY,
916
+ x2: b.x2 * r.zoom + r.panX,
917
+ y2: b.y2 * r.zoom + r.panY,
918
+ }
919
+
920
+ // Compute horizontal gap (left/right)
921
+ const hGaps: Array<{ dist: number; fromX: number; toX: number; label: string }> = []
922
+
923
+ // A is left of B
924
+ if (a.x2 <= b.x1) {
925
+ hGaps.push({ dist: b.x1 - a.x2, fromX: sa.x2, toX: sb.x1, label: `${Math.round(b.x1 - a.x2)}` })
926
+ }
927
+ // A is right of B
928
+ if (a.x1 >= b.x2) {
929
+ hGaps.push({ dist: a.x1 - b.x2, fromX: sb.x2, toX: sa.x1, label: `${Math.round(a.x1 - b.x2)}` })
930
+ }
931
+ // A contains B or overlaps — show edge distances
932
+ if (a.x1 < b.x1 && a.x2 > b.x2) {
933
+ const leftDist = b.x1 - a.x1
934
+ const rightDist = a.x2 - b.x2
935
+ if (leftDist > 0) hGaps.push({ dist: leftDist, fromX: sa.x1, toX: sb.x1, label: `${Math.round(leftDist)}` })
936
+ if (rightDist > 0) hGaps.push({ dist: rightDist, fromX: sb.x2, toX: sa.x2, label: `${Math.round(rightDist)}` })
937
+ }
938
+ // B contains A
939
+ if (b.x1 < a.x1 && b.x2 > a.x2) {
940
+ const leftDist = a.x1 - b.x1
941
+ const rightDist = b.x2 - a.x2
942
+ if (leftDist > 0) hGaps.push({ dist: leftDist, fromX: sb.x1, toX: sa.x1, label: `${Math.round(leftDist)}` })
943
+ if (rightDist > 0) hGaps.push({ dist: rightDist, fromX: sa.x2, toX: sb.x2, label: `${Math.round(rightDist)}` })
944
+ }
945
+
946
+ // Compute vertical gap (top/bottom)
947
+ const vGaps: Array<{ dist: number; fromY: number; toY: number; label: string }> = []
948
+
949
+ // A is above B
950
+ if (a.y2 <= b.y1) {
951
+ vGaps.push({ dist: b.y1 - a.y2, fromY: sa.y2, toY: sb.y1, label: `${Math.round(b.y1 - a.y2)}` })
952
+ }
953
+ // A is below B
954
+ if (a.y1 >= b.y2) {
955
+ vGaps.push({ dist: a.y1 - b.y2, fromY: sb.y2, toY: sa.y1, label: `${Math.round(a.y1 - b.y2)}` })
956
+ }
957
+ // A contains B vertically
958
+ if (a.y1 < b.y1 && a.y2 > b.y2) {
959
+ const topDist = b.y1 - a.y1
960
+ const bottomDist = a.y2 - b.y2
961
+ if (topDist > 0) vGaps.push({ dist: topDist, fromY: sa.y1, toY: sb.y1, label: `${Math.round(topDist)}` })
962
+ if (bottomDist > 0) vGaps.push({ dist: bottomDist, fromY: sb.y2, toY: sa.y2, label: `${Math.round(bottomDist)}` })
963
+ }
964
+ // B contains A vertically
965
+ if (b.y1 < a.y1 && b.y2 > a.y2) {
966
+ const topDist = a.y1 - b.y1
967
+ const bottomDist = b.y2 - a.y2
968
+ if (topDist > 0) vGaps.push({ dist: topDist, fromY: sb.y1, toY: sa.y1, label: `${Math.round(topDist)}` })
969
+ if (bottomDist > 0) vGaps.push({ dist: bottomDist, fromY: sa.y2, toY: sb.y2, label: `${Math.round(bottomDist)}` })
970
+ }
971
+
972
+ // Draw horizontal measurements
973
+ for (const gap of hGaps) {
974
+ if (gap.dist < 1) continue
975
+ // Y midpoint: use overlap region or average of centers
976
+ const overlapTop = Math.max(sa.y1, sb.y1)
977
+ const overlapBot = Math.min(sa.y2, sb.y2)
978
+ const lineY = overlapTop < overlapBot
979
+ ? (overlapTop + overlapBot) / 2
980
+ : ((sa.y1 + sa.y2) / 2 + (sb.y1 + sb.y2) / 2) / 2
981
+
982
+ drawMeasurementLine(r, canvas, gap.fromX, lineY, gap.toX, lineY, mPaint)
983
+ drawMeasurementPill(r, canvas, (gap.fromX + gap.toX) / 2, lineY - MEASUREMENT_PILL_HEIGHT, gap.label)
984
+ }
985
+
986
+ // Draw vertical measurements
987
+ for (const gap of vGaps) {
988
+ if (gap.dist < 1) continue
989
+ // X midpoint: use overlap region or average of centers
990
+ const overlapLeft = Math.max(sa.x1, sb.x1)
991
+ const overlapRight = Math.min(sa.x2, sb.x2)
992
+ const lineX = overlapLeft < overlapRight
993
+ ? (overlapLeft + overlapRight) / 2
994
+ : ((sa.x1 + sa.x2) / 2 + (sb.x1 + sb.x2) / 2) / 2
995
+
996
+ drawMeasurementLine(r, canvas, lineX, gap.fromY, lineX, gap.toY, mPaint)
997
+ drawMeasurementPill(r, canvas, lineX + MEASUREMENT_PILL_HEIGHT + 4, (gap.fromY + gap.toY) / 2, gap.label)
998
+ }
999
+
1000
+ // Also draw a dashed outline around the hovered node
1001
+ mPaint.setStrokeWidth(1)
1002
+ canvas.drawRect(r.ck.LTRBRect(sb.x1, sb.y1, sb.x2, sb.y2), mPaint)
1003
+
1004
+ // Reset path effect
1005
+ mPaint.setPathEffect(null)
1006
+ }
1007
+
1008
+ // ── Padding visualization ────────────────────────────────────
1009
+
1010
+ const PADDING_COLOR = { r: 0.3, g: 0.8, b: 0.4, a: 0.15 }
1011
+ const GAP_COLOR = { r: 0.9, g: 0.5, b: 0.2, a: 0.15 }
1012
+
1013
+ export function drawPaddingOverlay(
1014
+ r: SkiaRenderer,
1015
+ canvas: Canvas,
1016
+ graph: SceneGraph,
1017
+ selectedIds: Set<string>
1018
+ ): void {
1019
+ if (selectedIds.size !== 1) return
1020
+ const nodeId = [...selectedIds][0]
1021
+ const node = graph.getNode(nodeId)
1022
+ if (!node) return
1023
+
1024
+ const { paddingTop: pt, paddingRight: pr, paddingBottom: pb, paddingLeft: pl } = node
1025
+ if (pt === 0 && pr === 0 && pb === 0 && pl === 0) return
1026
+
1027
+ const bounds = nodeBounds(graph, nodeId, node)
1028
+ const sx1 = bounds.x1 * r.zoom + r.panX
1029
+ const sy1 = bounds.y1 * r.zoom + r.panY
1030
+ const sx2 = bounds.x2 * r.zoom + r.panX
1031
+ const sy2 = bounds.y2 * r.zoom + r.panY
1032
+
1033
+ const fill = r.auxFill
1034
+ fill.setColor(r.ck.Color4f(PADDING_COLOR.r, PADDING_COLOR.g, PADDING_COLOR.b, PADDING_COLOR.a))
1035
+
1036
+ const ptS = pt * r.zoom
1037
+ const prS = pr * r.zoom
1038
+ const pbS = pb * r.zoom
1039
+ const plS = pl * r.zoom
1040
+
1041
+ // Top padding
1042
+ if (pt > 0) canvas.drawRect(r.ck.LTRBRect(sx1, sy1, sx2, sy1 + ptS), fill)
1043
+ // Bottom padding
1044
+ if (pb > 0) canvas.drawRect(r.ck.LTRBRect(sx1, sy2 - pbS, sx2, sy2), fill)
1045
+ // Left padding (between top and bottom)
1046
+ if (pl > 0) canvas.drawRect(r.ck.LTRBRect(sx1, sy1 + ptS, sx1 + plS, sy2 - pbS), fill)
1047
+ // Right padding (between top and bottom)
1048
+ if (pr > 0) canvas.drawRect(r.ck.LTRBRect(sx2 - prS, sy1 + ptS, sx2, sy2 - pbS), fill)
1049
+
1050
+ // Draw padding labels
1051
+ if (pt > 0) drawMeasurementPill(r, canvas, (sx1 + sx2) / 2, sy1 + ptS / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(pt)}`)
1052
+ if (pb > 0) drawMeasurementPill(r, canvas, (sx1 + sx2) / 2, sy2 - pbS / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(pb)}`)
1053
+ if (pl > 0) drawMeasurementPill(r, canvas, sx1 + plS / 2, (sy1 + ptS + sy2 - pbS) / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(pl)}`)
1054
+ if (pr > 0) drawMeasurementPill(r, canvas, sx2 - prS / 2, (sy1 + ptS + sy2 - pbS) / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(pr)}`)
1055
+
1056
+ // Gap visualization for auto-layout
1057
+ if (node.layoutMode !== 'NONE' && node.itemSpacing > 0) {
1058
+ const children = graph.getChildren(nodeId).filter((c) => c.visible)
1059
+ if (children.length < 2) return
1060
+
1061
+ fill.setColor(r.ck.Color4f(GAP_COLOR.r, GAP_COLOR.g, GAP_COLOR.b, GAP_COLOR.a))
1062
+ const isHorizontal = node.layoutMode === 'HORIZONTAL'
1063
+
1064
+ for (let i = 0; i < children.length - 1; i++) {
1065
+ const cb = nodeBounds(graph, children[i].id, children[i])
1066
+ const nb = nodeBounds(graph, children[i + 1].id, children[i + 1])
1067
+
1068
+ if (isHorizontal) {
1069
+ const gx1 = cb.x2 * r.zoom + r.panX
1070
+ const gx2 = nb.x1 * r.zoom + r.panX
1071
+ const gy1 = Math.max(cb.y1, nb.y1) * r.zoom + r.panY
1072
+ const gy2 = Math.min(cb.y2, nb.y2) * r.zoom + r.panY
1073
+ if (gx2 > gx1 && gy2 > gy1) {
1074
+ canvas.drawRect(r.ck.LTRBRect(gx1, gy1, gx2, gy2), fill)
1075
+ drawMeasurementPill(r, canvas, (gx1 + gx2) / 2, (gy1 + gy2) / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(node.itemSpacing)}`)
1076
+ }
1077
+ } else {
1078
+ const gy1 = cb.y2 * r.zoom + r.panY
1079
+ const gy2 = nb.y1 * r.zoom + r.panY
1080
+ const gx1 = Math.max(cb.x1, nb.x1) * r.zoom + r.panX
1081
+ const gx2 = Math.min(cb.x2, nb.x2) * r.zoom + r.panX
1082
+ if (gy2 > gy1 && gx2 > gx1) {
1083
+ canvas.drawRect(r.ck.LTRBRect(gx1, gy1, gx2, gy2), fill)
1084
+ drawMeasurementPill(r, canvas, (gx1 + gx2) / 2, (gy1 + gy2) / 2 - MEASUREMENT_PILL_HEIGHT / 2, `${Math.round(node.itemSpacing)}`)
1085
+ }
1086
+ }
1087
+ }
1088
+ }
1089
+ }