etassia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (727) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +140 -0
  3. data/LICENSE +25 -0
  4. data/README.adoc +436 -0
  5. data/exe/etassia +7 -0
  6. data/lib/etassia/cli.rb +187 -0
  7. data/lib/etassia/commands/batch.rb +210 -0
  8. data/lib/etassia/commands/notation_loader.rb +22 -0
  9. data/lib/etassia/commands/render.rb +98 -0
  10. data/lib/etassia/commands/types.rb +33 -0
  11. data/lib/etassia/commands/version.rb +24 -0
  12. data/lib/etassia/corpus_type_names.rb +52 -0
  13. data/lib/etassia/diagram/architecture.rb +108 -0
  14. data/lib/etassia/diagram/base.rb +61 -0
  15. data/lib/etassia/diagram/block.rb +102 -0
  16. data/lib/etassia/diagram/c4.rb +337 -0
  17. data/lib/etassia/diagram/class_diagram.rb +466 -0
  18. data/lib/etassia/diagram/class_namespace.rb +17 -0
  19. data/lib/etassia/diagram/class_note.rb +17 -0
  20. data/lib/etassia/diagram/containment.rb +107 -0
  21. data/lib/etassia/diagram/er_diagram.rb +317 -0
  22. data/lib/etassia/diagram/error.rb +38 -0
  23. data/lib/etassia/diagram/flowchart.rb +419 -0
  24. data/lib/etassia/diagram/flowchart_label_text.rb +63 -0
  25. data/lib/etassia/diagram/gantt.rb +94 -0
  26. data/lib/etassia/diagram/generic_text.rb +30 -0
  27. data/lib/etassia/diagram/git_graph.rb +57 -0
  28. data/lib/etassia/diagram/info.rb +38 -0
  29. data/lib/etassia/diagram/kanban.rb +198 -0
  30. data/lib/etassia/diagram/mindmap.rb +71 -0
  31. data/lib/etassia/diagram/packet.rb +93 -0
  32. data/lib/etassia/diagram/pie.rb +115 -0
  33. data/lib/etassia/diagram/quadrant.rb +135 -0
  34. data/lib/etassia/diagram/radar.rb +65 -0
  35. data/lib/etassia/diagram/requirement.rb +168 -0
  36. data/lib/etassia/diagram/sankey.rb +217 -0
  37. data/lib/etassia/diagram/sequence.rb +299 -0
  38. data/lib/etassia/diagram/sequence_box.rb +18 -0
  39. data/lib/etassia/diagram/sequence_frame.rb +37 -0
  40. data/lib/etassia/diagram/sequence_frame_section.rb +23 -0
  41. data/lib/etassia/diagram/sequence_text.rb +69 -0
  42. data/lib/etassia/diagram/sequence_wrap.rb +23 -0
  43. data/lib/etassia/diagram/state_diagram.rb +282 -0
  44. data/lib/etassia/diagram/timeline.rb +183 -0
  45. data/lib/etassia/diagram/treemap.rb +95 -0
  46. data/lib/etassia/diagram/user_journey.rb +153 -0
  47. data/lib/etassia/diagram/xy_chart.rb +90 -0
  48. data/lib/etassia/diagram.rb +8 -0
  49. data/lib/etassia/diagram_registry.rb +34 -0
  50. data/lib/etassia/engine.rb +334 -0
  51. data/lib/etassia/error/diagram_type_error.rb +10 -0
  52. data/lib/etassia/error/layout_error.rb +12 -0
  53. data/lib/etassia/error/notation_registration_error.rb +10 -0
  54. data/lib/etassia/error/parse_error.rb +11 -0
  55. data/lib/etassia/error/pipeline_error.rb +10 -0
  56. data/lib/etassia/error/render_error.rb +12 -0
  57. data/lib/etassia/error/score_error.rb +12 -0
  58. data/lib/etassia/error.rb +10 -0
  59. data/lib/etassia/error_report.rb +69 -0
  60. data/lib/etassia/ir/data.rb +39 -0
  61. data/lib/etassia/ir/data_value.rb +35 -0
  62. data/lib/etassia/ir/dimension.rb +10 -0
  63. data/lib/etassia/ir/edge.rb +34 -0
  64. data/lib/etassia/ir/graph.rb +28 -0
  65. data/lib/etassia/ir/integrity.rb +80 -0
  66. data/lib/etassia/ir/item.rb +46 -0
  67. data/lib/etassia/ir/model.rb +56 -0
  68. data/lib/etassia/ir/node.rb +10 -0
  69. data/lib/etassia/ir/placement.rb +47 -0
  70. data/lib/etassia/ir/prepositioned.rb +25 -0
  71. data/lib/etassia/ir/prepositioned_item.rb +26 -0
  72. data/lib/etassia/ir/property_set.rb +43 -0
  73. data/lib/etassia/ir/scalar.rb +25 -0
  74. data/lib/etassia/ir/series.rb +10 -0
  75. data/lib/etassia/ir.rb +17 -0
  76. data/lib/etassia/js_math.rb +511 -0
  77. data/lib/etassia/js_number.rb +64 -0
  78. data/lib/etassia/layout/architecture.rb +1472 -0
  79. data/lib/etassia/layout/base.rb +276 -0
  80. data/lib/etassia/layout/block.rb +738 -0
  81. data/lib/etassia/layout/c4.rb +876 -0
  82. data/lib/etassia/layout/c4_boundary_metrics.rb +35 -0
  83. data/lib/etassia/layout/c4_bounds.rb +106 -0
  84. data/lib/etassia/layout/c4_intersection.rb +77 -0
  85. data/lib/etassia/layout/c4_placement.rb +135 -0
  86. data/lib/etassia/layout/c4_shape_metrics.rb +97 -0
  87. data/lib/etassia/layout/c4_shape_outline.rb +67 -0
  88. data/lib/etassia/layout/c4_stereotype.rb +30 -0
  89. data/lib/etassia/layout/c4_text.rb +54 -0
  90. data/lib/etassia/layout/class_cell_placement.rb +148 -0
  91. data/lib/etassia/layout/class_diagram.rb +1116 -0
  92. data/lib/etassia/layout/class_diagram_namespaces.rb +26 -0
  93. data/lib/etassia/layout/class_diagram_notes.rb +26 -0
  94. data/lib/etassia/layout/class_name_advances.rb +31 -0
  95. data/lib/etassia/layout/class_namespace_box.rb +21 -0
  96. data/lib/etassia/layout/class_namespace_nodes.rb +197 -0
  97. data/lib/etassia/layout/class_namespace_placement.rb +71 -0
  98. data/lib/etassia/layout/class_note.rb +44 -0
  99. data/lib/etassia/layout/class_note_nodes.rb +84 -0
  100. data/lib/etassia/layout/elk_notices.rb +48 -0
  101. data/lib/etassia/layout/elk_placement.rb +197 -0
  102. data/lib/etassia/layout/er_diagram.rb +1002 -0
  103. data/lib/etassia/layout/er_diagram_adjacency.rb +20 -0
  104. data/lib/etassia/layout/er_diagram_alignment.rb +72 -0
  105. data/lib/etassia/layout/er_diagram_compaction.rb +85 -0
  106. data/lib/etassia/layout/er_diagram_conflicts.rb +70 -0
  107. data/lib/etassia/layout/er_diagram_coordinates.rb +104 -0
  108. data/lib/etassia/layout/er_diagram_entity_size.rb +82 -0
  109. data/lib/etassia/layout/er_diagram_layering.rb +109 -0
  110. data/lib/etassia/layout/er_diagram_ordering.rb +143 -0
  111. data/lib/etassia/layout/er_diagram_placement.rb +163 -0
  112. data/lib/etassia/layout/er_diagram_placement_node.rb +22 -0
  113. data/lib/etassia/layout/error.rb +133 -0
  114. data/lib/etassia/layout/flowchart/brandes_koepf.rb +208 -0
  115. data/lib/etassia/layout/flowchart/cluster_reach.rb +70 -0
  116. data/lib/etassia/layout/flowchart/component_packing.rb +129 -0
  117. data/lib/etassia/layout/flowchart/directed_placement.rb +428 -0
  118. data/lib/etassia/layout/flowchart/edge_chains.rb +111 -0
  119. data/lib/etassia/layout/flowchart/edge_order.rb +34 -0
  120. data/lib/etassia/layout/flowchart/edge_router.rb +533 -0
  121. data/lib/etassia/layout/flowchart/label_backdrop_geometry.rb +62 -0
  122. data/lib/etassia/layout/flowchart/layer_spacing.rb +85 -0
  123. data/lib/etassia/layout/flowchart/level_edges.rb +74 -0
  124. data/lib/etassia/layout/flowchart/level_frame.rb +47 -0
  125. data/lib/etassia/layout/flowchart/minimum_rank_spacing.rb +74 -0
  126. data/lib/etassia/layout/flowchart/row_centering.rb +229 -0
  127. data/lib/etassia/layout/flowchart.rb +1290 -0
  128. data/lib/etassia/layout/gantt.rb +814 -0
  129. data/lib/etassia/layout/gantt_duration.rb +49 -0
  130. data/lib/etassia/layout/gantt_exclusions.rb +74 -0
  131. data/lib/etassia/layout/gantt_ticks.rb +165 -0
  132. data/lib/etassia/layout/git_graph.rb +1075 -0
  133. data/lib/etassia/layout/grid.rb +165 -0
  134. data/lib/etassia/layout/info.rb +113 -0
  135. data/lib/etassia/layout/kanban.rb +452 -0
  136. data/lib/etassia/layout/kanban_card_text.rb +95 -0
  137. data/lib/etassia/layout/mindmap.rb +425 -0
  138. data/lib/etassia/layout/mindmap_cose.rb +81 -0
  139. data/lib/etassia/layout/mindmap_cose_body.rb +93 -0
  140. data/lib/etassia/layout/mindmap_cose_geometry.rb +190 -0
  141. data/lib/etassia/layout/mindmap_cose_grid.rb +96 -0
  142. data/lib/etassia/layout/mindmap_cose_link.rb +42 -0
  143. data/lib/etassia/layout/mindmap_cose_relaxation.rb +166 -0
  144. data/lib/etassia/layout/mindmap_cose_seed.rb +93 -0
  145. data/lib/etassia/layout/mindmap_cose_wedge.rb +37 -0
  146. data/lib/etassia/layout/mindmap_node_size.rb +130 -0
  147. data/lib/etassia/layout/packet.rb +285 -0
  148. data/lib/etassia/layout/pie.rb +305 -0
  149. data/lib/etassia/layout/pie_legend.rb +64 -0
  150. data/lib/etassia/layout/pie_legend_entry.rb +21 -0
  151. data/lib/etassia/layout/quadrant.rb +402 -0
  152. data/lib/etassia/layout/radar.rb +442 -0
  153. data/lib/etassia/layout/requirement.rb +765 -0
  154. data/lib/etassia/layout/sankey.rb +501 -0
  155. data/lib/etassia/layout/scene.rb +18 -0
  156. data/lib/etassia/layout/sequence/actor_gaps.rb +151 -0
  157. data/lib/etassia/layout/sequence/box_shape.rb +23 -0
  158. data/lib/etassia/layout/sequence/creations.rb +73 -0
  159. data/lib/etassia/layout/sequence/frame_divider.rb +22 -0
  160. data/lib/etassia/layout/sequence/frame_placement.rb +424 -0
  161. data/lib/etassia/layout/sequence/frame_reader.rb +75 -0
  162. data/lib/etassia/layout/sequence/frame_shape.rb +45 -0
  163. data/lib/etassia/layout/sequence/geometry.rb +24 -0
  164. data/lib/etassia/layout/sequence/message_rows.rb +165 -0
  165. data/lib/etassia/layout/sequence/note.rb +16 -0
  166. data/lib/etassia/layout/sequence/note_box.rb +114 -0
  167. data/lib/etassia/layout/sequence/note_placement.rb +223 -0
  168. data/lib/etassia/layout/sequence/text_width.rb +69 -0
  169. data/lib/etassia/layout/sequence/text_wrap.rb +93 -0
  170. data/lib/etassia/layout/sequence.rb +936 -0
  171. data/lib/etassia/layout/state_diagram.rb +1277 -0
  172. data/lib/etassia/layout/timeline.rb +165 -0
  173. data/lib/etassia/layout/timeline_card.rb +142 -0
  174. data/lib/etassia/layout/timeline_flow.rb +164 -0
  175. data/lib/etassia/layout/timeline_line.rb +33 -0
  176. data/lib/etassia/layout/timeline_rounding.rb +19 -0
  177. data/lib/etassia/layout/timeline_text.rb +105 -0
  178. data/lib/etassia/layout/treemap.rb +303 -0
  179. data/lib/etassia/layout/treemap_placement.rb +52 -0
  180. data/lib/etassia/layout/treemap_squarify.rb +108 -0
  181. data/lib/etassia/layout/user_journey.rb +300 -0
  182. data/lib/etassia/layout/user_journey_face.rb +33 -0
  183. data/lib/etassia/layout/user_journey_face_geometry.rb +80 -0
  184. data/lib/etassia/layout/user_journey_geometry.rb +336 -0
  185. data/lib/etassia/layout/user_journey_legend.rb +84 -0
  186. data/lib/etassia/layout/xy_chart.rb +620 -0
  187. data/lib/etassia/layout.rb +19 -0
  188. data/lib/etassia/lint_debt_scoreboard.rb +219 -0
  189. data/lib/etassia/markdown_text/emphasis_simulator.rb +373 -0
  190. data/lib/etassia/markdown_text.rb +393 -0
  191. data/lib/etassia/notation/builtin.rb +39 -0
  192. data/lib/etassia/notation/entry.rb +15 -0
  193. data/lib/etassia/notation/mermaid/class_diagram_config.rb +42 -0
  194. data/lib/etassia/notation/mermaid/git_graph_config.rb +46 -0
  195. data/lib/etassia/notation/mermaid/ir_adapter.rb +27 -0
  196. data/lib/etassia/notation/mermaid/ir_adapters/architecture.rb +196 -0
  197. data/lib/etassia/notation/mermaid/ir_adapters/block.rb +138 -0
  198. data/lib/etassia/notation/mermaid/ir_adapters/c4.rb +258 -0
  199. data/lib/etassia/notation/mermaid/ir_adapters/class_diagram.rb +257 -0
  200. data/lib/etassia/notation/mermaid/ir_adapters/er_diagram.rb +220 -0
  201. data/lib/etassia/notation/mermaid/ir_adapters/error.rb +32 -0
  202. data/lib/etassia/notation/mermaid/ir_adapters/flowchart.rb +208 -0
  203. data/lib/etassia/notation/mermaid/ir_adapters/gantt.rb +236 -0
  204. data/lib/etassia/notation/mermaid/ir_adapters/git_graph.rb +186 -0
  205. data/lib/etassia/notation/mermaid/ir_adapters/info.rb +34 -0
  206. data/lib/etassia/notation/mermaid/ir_adapters/kanban.rb +110 -0
  207. data/lib/etassia/notation/mermaid/ir_adapters/mindmap.rb +76 -0
  208. data/lib/etassia/notation/mermaid/ir_adapters/packet.rb +51 -0
  209. data/lib/etassia/notation/mermaid/ir_adapters/pie.rb +71 -0
  210. data/lib/etassia/notation/mermaid/ir_adapters/quadrant.rb +116 -0
  211. data/lib/etassia/notation/mermaid/ir_adapters/radar.rb +142 -0
  212. data/lib/etassia/notation/mermaid/ir_adapters/requirement.rb +237 -0
  213. data/lib/etassia/notation/mermaid/ir_adapters/sankey.rb +74 -0
  214. data/lib/etassia/notation/mermaid/ir_adapters/sequence.rb +304 -0
  215. data/lib/etassia/notation/mermaid/ir_adapters/state_diagram.rb +225 -0
  216. data/lib/etassia/notation/mermaid/ir_adapters/timeline.rb +93 -0
  217. data/lib/etassia/notation/mermaid/ir_adapters/treemap.rb +143 -0
  218. data/lib/etassia/notation/mermaid/ir_adapters/user_journey.rb +127 -0
  219. data/lib/etassia/notation/mermaid/ir_adapters/xychart.rb +131 -0
  220. data/lib/etassia/notation/mermaid.rb +399 -0
  221. data/lib/etassia/notation/parsed.rb +31 -0
  222. data/lib/etassia/notation/plantuml/activity/arrow_head.rb +45 -0
  223. data/lib/etassia/notation/plantuml/activity/arrow_style.rb +53 -0
  224. data/lib/etassia/notation/plantuml/activity/bar_columns.rb +75 -0
  225. data/lib/etassia/notation/plantuml/activity/box.rb +60 -0
  226. data/lib/etassia/notation/plantuml/activity/builder.rb +77 -0
  227. data/lib/etassia/notation/plantuml/activity/canvas.rb +117 -0
  228. data/lib/etassia/notation/plantuml/activity/column_centres.rb +151 -0
  229. data/lib/etassia/notation/plantuml/activity/columns.rb +224 -0
  230. data/lib/etassia/notation/plantuml/activity/diagram.rb +35 -0
  231. data/lib/etassia/notation/plantuml/activity/edge.rb +35 -0
  232. data/lib/etassia/notation/plantuml/activity/edge_marks.rb +68 -0
  233. data/lib/etassia/notation/plantuml/activity/exits.rb +61 -0
  234. data/lib/etassia/notation/plantuml/activity/frame_marks.rb +116 -0
  235. data/lib/etassia/notation/plantuml/activity/lane_steps.rb +82 -0
  236. data/lib/etassia/notation/plantuml/activity/lane_titles.rb +46 -0
  237. data/lib/etassia/notation/plantuml/activity/layout.rb +50 -0
  238. data/lib/etassia/notation/plantuml/activity/legacy_parser.rb +181 -0
  239. data/lib/etassia/notation/plantuml/activity/legacy_row_gaps.rb +65 -0
  240. data/lib/etassia/notation/plantuml/activity/mark.rb +23 -0
  241. data/lib/etassia/notation/plantuml/activity/marks.rb +62 -0
  242. data/lib/etassia/notation/plantuml/activity/metrics.rb +169 -0
  243. data/lib/etassia/notation/plantuml/activity/new_parser/blocks.rb +166 -0
  244. data/lib/etassia/notation/plantuml/activity/new_parser.rb +177 -0
  245. data/lib/etassia/notation/plantuml/activity/node.rb +50 -0
  246. data/lib/etassia/notation/plantuml/activity/node_marks.rb +174 -0
  247. data/lib/etassia/notation/plantuml/activity/note.rb +27 -0
  248. data/lib/etassia/notation/plantuml/activity/parser.rb +51 -0
  249. data/lib/etassia/notation/plantuml/activity/placement.rb +327 -0
  250. data/lib/etassia/notation/plantuml/activity/ranking.rb +142 -0
  251. data/lib/etassia/notation/plantuml/activity/reach.rb +58 -0
  252. data/lib/etassia/notation/plantuml/activity/renderer.rb +220 -0
  253. data/lib/etassia/notation/plantuml/activity/route.rb +22 -0
  254. data/lib/etassia/notation/plantuml/activity/router.rb +308 -0
  255. data/lib/etassia/notation/plantuml/activity/row_gaps.rb +105 -0
  256. data/lib/etassia/notation/plantuml/activity/row_spread.rb +59 -0
  257. data/lib/etassia/notation/plantuml/activity/scene.rb +20 -0
  258. data/lib/etassia/notation/plantuml/activity/text.rb +36 -0
  259. data/lib/etassia/notation/plantuml/activity.rb +50 -0
  260. data/lib/etassia/notation/plantuml/arrow.rb +135 -0
  261. data/lib/etassia/notation/plantuml/caption.rb +50 -0
  262. data/lib/etassia/notation/plantuml/caption_layout.rb +97 -0
  263. data/lib/etassia/notation/plantuml/caption_rows.rb +67 -0
  264. data/lib/etassia/notation/plantuml/class_header.rb +178 -0
  265. data/lib/etassia/notation/plantuml/class_name.rb +51 -0
  266. data/lib/etassia/notation/plantuml/component.rb +37 -0
  267. data/lib/etassia/notation/plantuml/creole.rb +278 -0
  268. data/lib/etassia/notation/plantuml/creole_icons.rb +33 -0
  269. data/lib/etassia/notation/plantuml/diagram.rb +60 -0
  270. data/lib/etassia/notation/plantuml/diagram_builder.rb +442 -0
  271. data/lib/etassia/notation/plantuml/diagram_name.rb +51 -0
  272. data/lib/etassia/notation/plantuml/directives.rb +33 -0
  273. data/lib/etassia/notation/plantuml/drawing/box.rb +28 -0
  274. data/lib/etassia/notation/plantuml/drawing/face.rb +39 -0
  275. data/lib/etassia/notation/plantuml/drawing/pen.rb +105 -0
  276. data/lib/etassia/notation/plantuml/drawing/renderer.rb +36 -0
  277. data/lib/etassia/notation/plantuml/drawing/scene.rb +28 -0
  278. data/lib/etassia/notation/plantuml/drawing.rb +19 -0
  279. data/lib/etassia/notation/plantuml/ebnf/blocks.rb +272 -0
  280. data/lib/etassia/notation/plantuml/ebnf/layout.rb +117 -0
  281. data/lib/etassia/notation/plantuml/ebnf/model.rb +22 -0
  282. data/lib/etassia/notation/plantuml/ebnf/reader.rb +131 -0
  283. data/lib/etassia/notation/plantuml/ebnf.rb +34 -0
  284. data/lib/etassia/notation/plantuml/end_texts.rb +78 -0
  285. data/lib/etassia/notation/plantuml/entity.rb +54 -0
  286. data/lib/etassia/notation/plantuml/further.rb +38 -0
  287. data/lib/etassia/notation/plantuml/gantt/layout.rb +269 -0
  288. data/lib/etassia/notation/plantuml/gantt/reader.rb +210 -0
  289. data/lib/etassia/notation/plantuml/gantt/renderer.rb +81 -0
  290. data/lib/etassia/notation/plantuml/gantt.rb +34 -0
  291. data/lib/etassia/notation/plantuml/include/blocks.rb +66 -0
  292. data/lib/etassia/notation/plantuml/include/finder.rb +61 -0
  293. data/lib/etassia/notation/plantuml/include.rb +78 -0
  294. data/lib/etassia/notation/plantuml/ir_adapter/annotations.rb +40 -0
  295. data/lib/etassia/notation/plantuml/ir_adapter/links.rb +91 -0
  296. data/lib/etassia/notation/plantuml/ir_adapter/sink.rb +49 -0
  297. data/lib/etassia/notation/plantuml/ir_adapter/structure.rb +86 -0
  298. data/lib/etassia/notation/plantuml/ir_adapter.rb +32 -0
  299. data/lib/etassia/notation/plantuml/ir_reader/annotations.rb +51 -0
  300. data/lib/etassia/notation/plantuml/ir_reader/index.rb +49 -0
  301. data/lib/etassia/notation/plantuml/ir_reader/links.rb +133 -0
  302. data/lib/etassia/notation/plantuml/ir_reader/structure.rb +83 -0
  303. data/lib/etassia/notation/plantuml/ir_reader.rb +42 -0
  304. data/lib/etassia/notation/plantuml/junction.rb +20 -0
  305. data/lib/etassia/notation/plantuml/klass.rb +41 -0
  306. data/lib/etassia/notation/plantuml/label_clearance.rb +67 -0
  307. data/lib/etassia/notation/plantuml/layout.rb +2806 -0
  308. data/lib/etassia/notation/plantuml/member.rb +37 -0
  309. data/lib/etassia/notation/plantuml/member_icon.rb +87 -0
  310. data/lib/etassia/notation/plantuml/member_icon_elements.rb +91 -0
  311. data/lib/etassia/notation/plantuml/member_pitch.rb +76 -0
  312. data/lib/etassia/notation/plantuml/member_rule.rb +48 -0
  313. data/lib/etassia/notation/plantuml/network/grid.rb +170 -0
  314. data/lib/etassia/notation/plantuml/network/layout.rb +158 -0
  315. data/lib/etassia/notation/plantuml/network/model.rb +22 -0
  316. data/lib/etassia/notation/plantuml/network/placement.rb +64 -0
  317. data/lib/etassia/notation/plantuml/network/reader.rb +126 -0
  318. data/lib/etassia/notation/plantuml/network/shapes.rb +109 -0
  319. data/lib/etassia/notation/plantuml/network.rb +37 -0
  320. data/lib/etassia/notation/plantuml/node_graph/archimate.rb +49 -0
  321. data/lib/etassia/notation/plantuml/node_graph/box_reader.rb +86 -0
  322. data/lib/etassia/notation/plantuml/node_graph/cloud.rb +73 -0
  323. data/lib/etassia/notation/plantuml/node_graph/crows_foot.rb +85 -0
  324. data/lib/etassia/notation/plantuml/node_graph/curve.rb +66 -0
  325. data/lib/etassia/notation/plantuml/node_graph/data_reader.rb +77 -0
  326. data/lib/etassia/notation/plantuml/node_graph/data_shape.rb +39 -0
  327. data/lib/etassia/notation/plantuml/node_graph/detour.rb +98 -0
  328. data/lib/etassia/notation/plantuml/node_graph/edge.rb +26 -0
  329. data/lib/etassia/notation/plantuml/node_graph/entity_metrics.rb +49 -0
  330. data/lib/etassia/notation/plantuml/node_graph/entity_reader.rb +65 -0
  331. data/lib/etassia/notation/plantuml/node_graph/entity_shape.rb +111 -0
  332. data/lib/etassia/notation/plantuml/node_graph/figures.rb +92 -0
  333. data/lib/etassia/notation/plantuml/node_graph/frame_ports.rb +73 -0
  334. data/lib/etassia/notation/plantuml/node_graph/graph.rb +40 -0
  335. data/lib/etassia/notation/plantuml/node_graph/heads.rb +89 -0
  336. data/lib/etassia/notation/plantuml/node_graph/label_fan.rb +76 -0
  337. data/lib/etassia/notation/plantuml/node_graph/label_placer.rb +310 -0
  338. data/lib/etassia/notation/plantuml/node_graph/label_waypoint.rb +47 -0
  339. data/lib/etassia/notation/plantuml/node_graph/layout.rb +293 -0
  340. data/lib/etassia/notation/plantuml/node_graph/link.rb +78 -0
  341. data/lib/etassia/notation/plantuml/node_graph/macros.rb +39 -0
  342. data/lib/etassia/notation/plantuml/node_graph/metrics.rb +113 -0
  343. data/lib/etassia/notation/plantuml/node_graph/node.rb +44 -0
  344. data/lib/etassia/notation/plantuml/node_graph/note_tail.rb +58 -0
  345. data/lib/etassia/notation/plantuml/node_graph/packer.rb +126 -0
  346. data/lib/etassia/notation/plantuml/node_graph/parser.rb +309 -0
  347. data/lib/etassia/notation/plantuml/node_graph/paths.rb +48 -0
  348. data/lib/etassia/notation/plantuml/node_graph/ranking.rb +231 -0
  349. data/lib/etassia/notation/plantuml/node_graph/reader.rb +53 -0
  350. data/lib/etassia/notation/plantuml/node_graph/references.rb +38 -0
  351. data/lib/etassia/notation/plantuml/node_graph/renderer.rb +160 -0
  352. data/lib/etassia/notation/plantuml/node_graph/scene.rb +22 -0
  353. data/lib/etassia/notation/plantuml/node_graph/shapes.rb +302 -0
  354. data/lib/etassia/notation/plantuml/node_graph/skipped_ranks.rb +61 -0
  355. data/lib/etassia/notation/plantuml/node_graph/tab.rb +68 -0
  356. data/lib/etassia/notation/plantuml/node_graph/usecase_ellipse.rb +100 -0
  357. data/lib/etassia/notation/plantuml/node_graph.rb +38 -0
  358. data/lib/etassia/notation/plantuml/note.rb +27 -0
  359. data/lib/etassia/notation/plantuml/note_layout.rb +257 -0
  360. data/lib/etassia/notation/plantuml/note_outline.rb +68 -0
  361. data/lib/etassia/notation/plantuml/objects/map_rows.rb +46 -0
  362. data/lib/etassia/notation/plantuml/objects/reader.rb +208 -0
  363. data/lib/etassia/notation/plantuml/objects.rb +39 -0
  364. data/lib/etassia/notation/plantuml/package.rb +45 -0
  365. data/lib/etassia/notation/plantuml/package_color.rb +32 -0
  366. data/lib/etassia/notation/plantuml/package_frames.rb +178 -0
  367. data/lib/etassia/notation/plantuml/parser.rb +622 -0
  368. data/lib/etassia/notation/plantuml/portion_visibility.rb +91 -0
  369. data/lib/etassia/notation/plantuml/qualified_association_layout.rb +332 -0
  370. data/lib/etassia/notation/plantuml/qualifier_box.rb +78 -0
  371. data/lib/etassia/notation/plantuml/rank_chain.rb +527 -0
  372. data/lib/etassia/notation/plantuml/rank_hierarchy.rb +347 -0
  373. data/lib/etassia/notation/plantuml/relation.rb +103 -0
  374. data/lib/etassia/notation/plantuml/relation_label_separator.rb +199 -0
  375. data/lib/etassia/notation/plantuml/relation_path_points.rb +84 -0
  376. data/lib/etassia/notation/plantuml/renderer.rb +490 -0
  377. data/lib/etassia/notation/plantuml/scene.rb +134 -0
  378. data/lib/etassia/notation/plantuml/segment_box.rb +85 -0
  379. data/lib/etassia/notation/plantuml/sequence/activation.rb +28 -0
  380. data/lib/etassia/notation/plantuml/sequence/appearance.rb +71 -0
  381. data/lib/etassia/notation/plantuml/sequence/arrow_end.rb +23 -0
  382. data/lib/etassia/notation/plantuml/sequence/arrow_marks.rb +111 -0
  383. data/lib/etassia/notation/plantuml/sequence/arrow_style.rb +42 -0
  384. data/lib/etassia/notation/plantuml/sequence/arrow_syntax.rb +122 -0
  385. data/lib/etassia/notation/plantuml/sequence/bar_tracker.rb +59 -0
  386. data/lib/etassia/notation/plantuml/sequence/box.rb +23 -0
  387. data/lib/etassia/notation/plantuml/sequence/chrome.rb +60 -0
  388. data/lib/etassia/notation/plantuml/sequence/chrome_rows.rb +184 -0
  389. data/lib/etassia/notation/plantuml/sequence/create.rb +34 -0
  390. data/lib/etassia/notation/plantuml/sequence/delay.rb +19 -0
  391. data/lib/etassia/notation/plantuml/sequence/destroy.rb +20 -0
  392. data/lib/etassia/notation/plantuml/sequence/diagram.rb +86 -0
  393. data/lib/etassia/notation/plantuml/sequence/divider.rb +19 -0
  394. data/lib/etassia/notation/plantuml/sequence/edge.rb +53 -0
  395. data/lib/etassia/notation/plantuml/sequence/embedded.rb +77 -0
  396. data/lib/etassia/notation/plantuml/sequence/fill.rb +56 -0
  397. data/lib/etassia/notation/plantuml/sequence/fragment.rb +28 -0
  398. data/lib/etassia/notation/plantuml/sequence/fragment_shape.rb +138 -0
  399. data/lib/etassia/notation/plantuml/sequence/head_style.rb +43 -0
  400. data/lib/etassia/notation/plantuml/sequence/ir_adapter/blocks.rb +73 -0
  401. data/lib/etassia/notation/plantuml/sequence/ir_adapter/fills.rb +24 -0
  402. data/lib/etassia/notation/plantuml/sequence/ir_adapter/items.rb +48 -0
  403. data/lib/etassia/notation/plantuml/sequence/ir_adapter/messages.rb +62 -0
  404. data/lib/etassia/notation/plantuml/sequence/ir_adapter/participants.rb +42 -0
  405. data/lib/etassia/notation/plantuml/sequence/ir_adapter/settings.rb +54 -0
  406. data/lib/etassia/notation/plantuml/sequence/ir_adapter.rb +38 -0
  407. data/lib/etassia/notation/plantuml/sequence/ir_reader/blocks.rb +77 -0
  408. data/lib/etassia/notation/plantuml/sequence/ir_reader/fills.rb +27 -0
  409. data/lib/etassia/notation/plantuml/sequence/ir_reader/index.rb +36 -0
  410. data/lib/etassia/notation/plantuml/sequence/ir_reader/items.rb +35 -0
  411. data/lib/etassia/notation/plantuml/sequence/ir_reader/messages.rb +70 -0
  412. data/lib/etassia/notation/plantuml/sequence/ir_reader/participants.rb +42 -0
  413. data/lib/etassia/notation/plantuml/sequence/ir_reader/settings.rb +74 -0
  414. data/lib/etassia/notation/plantuml/sequence/ir_reader.rb +38 -0
  415. data/lib/etassia/notation/plantuml/sequence/keyword_defaults.rb +28 -0
  416. data/lib/etassia/notation/plantuml/sequence/layout.rb +1567 -0
  417. data/lib/etassia/notation/plantuml/sequence/message.rb +97 -0
  418. data/lib/etassia/notation/plantuml/sequence/message_wrap.rb +77 -0
  419. data/lib/etassia/notation/plantuml/sequence/note.rb +61 -0
  420. data/lib/etassia/notation/plantuml/sequence/note_fill.rb +35 -0
  421. data/lib/etassia/notation/plantuml/sequence/note_geometry.rb +126 -0
  422. data/lib/etassia/notation/plantuml/sequence/note_shape.rb +51 -0
  423. data/lib/etassia/notation/plantuml/sequence/numbered_label.rb +57 -0
  424. data/lib/etassia/notation/plantuml/sequence/numbering.rb +49 -0
  425. data/lib/etassia/notation/plantuml/sequence/outline.rb +204 -0
  426. data/lib/etassia/notation/plantuml/sequence/page_break.rb +17 -0
  427. data/lib/etassia/notation/plantuml/sequence/paint.rb +79 -0
  428. data/lib/etassia/notation/plantuml/sequence/parallel_message.rb +19 -0
  429. data/lib/etassia/notation/plantuml/sequence/parser.rb +844 -0
  430. data/lib/etassia/notation/plantuml/sequence/participant.rb +27 -0
  431. data/lib/etassia/notation/plantuml/sequence/picture.rb +29 -0
  432. data/lib/etassia/notation/plantuml/sequence/ref.rb +24 -0
  433. data/lib/etassia/notation/plantuml/sequence/ref_shape.rb +74 -0
  434. data/lib/etassia/notation/plantuml/sequence/refusals.rb +47 -0
  435. data/lib/etassia/notation/plantuml/sequence/renderer.rb +501 -0
  436. data/lib/etassia/notation/plantuml/sequence/scene.rb +200 -0
  437. data/lib/etassia/notation/plantuml/sequence/skin.rb +76 -0
  438. data/lib/etassia/notation/plantuml/sequence/spacing.rb +28 -0
  439. data/lib/etassia/notation/plantuml/sequence/style.rb +151 -0
  440. data/lib/etassia/notation/plantuml/sequence/tab_size.rb +42 -0
  441. data/lib/etassia/notation/plantuml/sequence/walker.rb +1190 -0
  442. data/lib/etassia/notation/plantuml/sequence.rb +47 -0
  443. data/lib/etassia/notation/plantuml/source_lines.rb +33 -0
  444. data/lib/etassia/notation/plantuml/state/axis_solver.rb +272 -0
  445. data/lib/etassia/notation/plantuml/state/cluster_padding.rb +75 -0
  446. data/lib/etassia/notation/plantuml/state/curve.rb +73 -0
  447. data/lib/etassia/notation/plantuml/state/dot_axis.rb +248 -0
  448. data/lib/etassia/notation/plantuml/state/extent.rb +42 -0
  449. data/lib/etassia/notation/plantuml/state/hierarchy.rb +355 -0
  450. data/lib/etassia/notation/plantuml/state/label_clearance.rb +148 -0
  451. data/lib/etassia/notation/plantuml/state/label_reach.rb +139 -0
  452. data/lib/etassia/notation/plantuml/state/layout.rb +173 -0
  453. data/lib/etassia/notation/plantuml/state/level_edges.rb +113 -0
  454. data/lib/etassia/notation/plantuml/state/look.rb +36 -0
  455. data/lib/etassia/notation/plantuml/state/note_shape.rb +73 -0
  456. data/lib/etassia/notation/plantuml/state/plane.rb +85 -0
  457. data/lib/etassia/notation/plantuml/state/ranking.rb +181 -0
  458. data/lib/etassia/notation/plantuml/state/reader.rb +310 -0
  459. data/lib/etassia/notation/plantuml/state/renderer.rb +253 -0
  460. data/lib/etassia/notation/plantuml/state/route_detour.rb +115 -0
  461. data/lib/etassia/notation/plantuml/state/route_geometry.rb +186 -0
  462. data/lib/etassia/notation/plantuml/state/routes.rb +196 -0
  463. data/lib/etassia/notation/plantuml/state/row.rb +88 -0
  464. data/lib/etassia/notation/plantuml/state/rows.rb +50 -0
  465. data/lib/etassia/notation/plantuml/state/stretch.rb +71 -0
  466. data/lib/etassia/notation/plantuml/state.rb +62 -0
  467. data/lib/etassia/notation/plantuml/stereotype_head.rb +58 -0
  468. data/lib/etassia/notation/plantuml/style/bound.rb +34 -0
  469. data/lib/etassia/notation/plantuml/style/colour.rb +41 -0
  470. data/lib/etassia/notation/plantuml/style/colour_names.rb +89 -0
  471. data/lib/etassia/notation/plantuml/style/defaults.rb +33 -0
  472. data/lib/etassia/notation/plantuml/style/inline.rb +56 -0
  473. data/lib/etassia/notation/plantuml/style/reader.rb +242 -0
  474. data/lib/etassia/notation/plantuml/style/sheet.rb +103 -0
  475. data/lib/etassia/notation/plantuml/style.rb +32 -0
  476. data/lib/etassia/notation/plantuml/style_reader.rb +160 -0
  477. data/lib/etassia/notation/plantuml/style_sheet.rb +27 -0
  478. data/lib/etassia/notation/plantuml/styled_relation_curve.rb +71 -0
  479. data/lib/etassia/notation/plantuml/timing/arrows.rb +81 -0
  480. data/lib/etassia/notation/plantuml/timing/axis.rb +100 -0
  481. data/lib/etassia/notation/plantuml/timing/layout.rb +172 -0
  482. data/lib/etassia/notation/plantuml/timing/model.rb +34 -0
  483. data/lib/etassia/notation/plantuml/timing/reader.rb +173 -0
  484. data/lib/etassia/notation/plantuml/timing/signals.rb +328 -0
  485. data/lib/etassia/notation/plantuml/timing.rb +38 -0
  486. data/lib/etassia/notation/plantuml/tree/layout.rb +276 -0
  487. data/lib/etassia/notation/plantuml/tree/mindmap_layout.rb +157 -0
  488. data/lib/etassia/notation/plantuml/tree/reader.rb +186 -0
  489. data/lib/etassia/notation/plantuml/tree/renderer.rb +67 -0
  490. data/lib/etassia/notation/plantuml/tree/role.rb +57 -0
  491. data/lib/etassia/notation/plantuml/tree/rules.rb +156 -0
  492. data/lib/etassia/notation/plantuml/tree/wbs_layout.rb +164 -0
  493. data/lib/etassia/notation/plantuml/tree.rb +41 -0
  494. data/lib/etassia/notation/plantuml/unsupported_construct_error.rb +39 -0
  495. data/lib/etassia/notation/plantuml/unsupported_constructs.rb +103 -0
  496. data/lib/etassia/notation/plantuml/usecase.rb +36 -0
  497. data/lib/etassia/notation/plantuml.rb +132 -0
  498. data/lib/etassia/notation/plantuml_data/boxes/frames.rb +101 -0
  499. data/lib/etassia/notation/plantuml_data/boxes/graph_layout.rb +348 -0
  500. data/lib/etassia/notation/plantuml_data/boxes/heads.rb +65 -0
  501. data/lib/etassia/notation/plantuml_data/boxes/highlights.rb +70 -0
  502. data/lib/etassia/notation/plantuml_data/boxes/inline_rows.rb +99 -0
  503. data/lib/etassia/notation/plantuml_data/boxes/json_reader.rb +24 -0
  504. data/lib/etassia/notation/plantuml_data/boxes/link_text.rb +94 -0
  505. data/lib/etassia/notation/plantuml_data/boxes/marks.rb +76 -0
  506. data/lib/etassia/notation/plantuml_data/boxes/ornaments.rb +130 -0
  507. data/lib/etassia/notation/plantuml_data/boxes/renderer.rb +95 -0
  508. data/lib/etassia/notation/plantuml_data/boxes/row_alignment.rb +58 -0
  509. data/lib/etassia/notation/plantuml_data/boxes/row_bend.rb +43 -0
  510. data/lib/etassia/notation/plantuml_data/boxes/scene.rb +27 -0
  511. data/lib/etassia/notation/plantuml_data/boxes/table.rb +56 -0
  512. data/lib/etassia/notation/plantuml_data/boxes/table_box.rb +355 -0
  513. data/lib/etassia/notation/plantuml_data/boxes/text.rb +101 -0
  514. data/lib/etassia/notation/plantuml_data/boxes/tree_builder.rb +82 -0
  515. data/lib/etassia/notation/plantuml_data/boxes/tree_layout.rb +82 -0
  516. data/lib/etassia/notation/plantuml_data/boxes/yaml_reader.rb +105 -0
  517. data/lib/etassia/notation/plantuml_data/openiconic.LICENSE +6 -0
  518. data/lib/etassia/notation/plantuml_data/openiconic.json +1 -0
  519. data/lib/etassia/notation/plantuml_data.rb +84 -0
  520. data/lib/etassia/notation/plantuml_salt/block.rb +80 -0
  521. data/lib/etassia/notation/plantuml_salt/box.rb +30 -0
  522. data/lib/etassia/notation/plantuml_salt/font_metrics.rb +28 -0
  523. data/lib/etassia/notation/plantuml_salt/grid_marks.rb +66 -0
  524. data/lib/etassia/notation/plantuml_salt/group_marks.rb +77 -0
  525. data/lib/etassia/notation/plantuml_salt/layout.rb +206 -0
  526. data/lib/etassia/notation/plantuml_salt/mark.rb +36 -0
  527. data/lib/etassia/notation/plantuml_salt/markup.rb +57 -0
  528. data/lib/etassia/notation/plantuml_salt/parser.rb +239 -0
  529. data/lib/etassia/notation/plantuml_salt/renderer.rb +138 -0
  530. data/lib/etassia/notation/plantuml_salt/row.rb +19 -0
  531. data/lib/etassia/notation/plantuml_salt/run.rb +34 -0
  532. data/lib/etassia/notation/plantuml_salt/scene.rb +23 -0
  533. data/lib/etassia/notation/plantuml_salt/scroll_marks.rb +94 -0
  534. data/lib/etassia/notation/plantuml_salt/tab_marks.rb +74 -0
  535. data/lib/etassia/notation/plantuml_salt/tree_marks.rb +127 -0
  536. data/lib/etassia/notation/plantuml_salt/widget.rb +40 -0
  537. data/lib/etassia/notation/plantuml_salt/widget_marks.rb +319 -0
  538. data/lib/etassia/notation/plantuml_salt.rb +66 -0
  539. data/lib/etassia/notation/plugin_failure.rb +42 -0
  540. data/lib/etassia/notation.rb +289 -0
  541. data/lib/etassia/parser/architecture.rb +25 -0
  542. data/lib/etassia/parser/atoms/delimited_run.rb +82 -0
  543. data/lib/etassia/parser/atoms/greedy_run.rb +116 -0
  544. data/lib/etassia/parser/atoms/joined.rb +47 -0
  545. data/lib/etassia/parser/atoms/trimmed_run.rb +47 -0
  546. data/lib/etassia/parser/base.rb +258 -0
  547. data/lib/etassia/parser/block.rb +32 -0
  548. data/lib/etassia/parser/builders/architecture.rb +135 -0
  549. data/lib/etassia/parser/builders/block.rb +288 -0
  550. data/lib/etassia/parser/builders/c4.rb +275 -0
  551. data/lib/etassia/parser/builders/capture_string.rb +17 -0
  552. data/lib/etassia/parser/builders/class_diagram.rb +539 -0
  553. data/lib/etassia/parser/builders/er_diagram.rb +308 -0
  554. data/lib/etassia/parser/builders/error.rb +51 -0
  555. data/lib/etassia/parser/builders/flowchart.rb +1150 -0
  556. data/lib/etassia/parser/builders/gantt.rb +267 -0
  557. data/lib/etassia/parser/builders/git_graph.rb +301 -0
  558. data/lib/etassia/parser/builders/info.rb +57 -0
  559. data/lib/etassia/parser/builders/kanban.rb +411 -0
  560. data/lib/etassia/parser/builders/mindmap.rb +271 -0
  561. data/lib/etassia/parser/builders/packet.rb +85 -0
  562. data/lib/etassia/parser/builders/pie.rb +105 -0
  563. data/lib/etassia/parser/builders/quadrant.rb +165 -0
  564. data/lib/etassia/parser/builders/radar.rb +157 -0
  565. data/lib/etassia/parser/builders/requirement.rb +230 -0
  566. data/lib/etassia/parser/builders/sankey.rb +142 -0
  567. data/lib/etassia/parser/builders/sequence.rb +431 -0
  568. data/lib/etassia/parser/builders/sequence_blocks.rb +117 -0
  569. data/lib/etassia/parser/builders/sequence_box_title.rb +57 -0
  570. data/lib/etassia/parser/builders/state_diagram.rb +364 -0
  571. data/lib/etassia/parser/builders/timeline.rb +133 -0
  572. data/lib/etassia/parser/builders/treemap.rb +138 -0
  573. data/lib/etassia/parser/builders/xy_chart.rb +148 -0
  574. data/lib/etassia/parser/c4.rb +37 -0
  575. data/lib/etassia/parser/class_diagram.rb +58 -0
  576. data/lib/etassia/parser/class_namespaces.rb +62 -0
  577. data/lib/etassia/parser/class_notes.rb +54 -0
  578. data/lib/etassia/parser/er_diagram.rb +28 -0
  579. data/lib/etassia/parser/error.rb +30 -0
  580. data/lib/etassia/parser/flowchart.rb +87 -0
  581. data/lib/etassia/parser/gantt.rb +43 -0
  582. data/lib/etassia/parser/git_graph.rb +106 -0
  583. data/lib/etassia/parser/grammars/architecture.rb +165 -0
  584. data/lib/etassia/parser/grammars/block.rb +190 -0
  585. data/lib/etassia/parser/grammars/c4.rb +229 -0
  586. data/lib/etassia/parser/grammars/class_diagram.rb +606 -0
  587. data/lib/etassia/parser/grammars/common.rb +107 -0
  588. data/lib/etassia/parser/grammars/er_diagram.rb +232 -0
  589. data/lib/etassia/parser/grammars/error.rb +32 -0
  590. data/lib/etassia/parser/grammars/flowchart.rb +1722 -0
  591. data/lib/etassia/parser/grammars/gantt.rb +198 -0
  592. data/lib/etassia/parser/grammars/git_graph.rb +234 -0
  593. data/lib/etassia/parser/grammars/info.rb +53 -0
  594. data/lib/etassia/parser/grammars/kanban.rb +417 -0
  595. data/lib/etassia/parser/grammars/mermaid_unicode_text.rb +108 -0
  596. data/lib/etassia/parser/grammars/mindmap.rb +220 -0
  597. data/lib/etassia/parser/grammars/packet.rb +71 -0
  598. data/lib/etassia/parser/grammars/pie.rb +128 -0
  599. data/lib/etassia/parser/grammars/quadrant.rb +202 -0
  600. data/lib/etassia/parser/grammars/radar.rb +158 -0
  601. data/lib/etassia/parser/grammars/requirement.rb +258 -0
  602. data/lib/etassia/parser/grammars/sankey.rb +104 -0
  603. data/lib/etassia/parser/grammars/sequence.rb +940 -0
  604. data/lib/etassia/parser/grammars/state_diagram.rb +382 -0
  605. data/lib/etassia/parser/grammars/timeline.rb +153 -0
  606. data/lib/etassia/parser/grammars/treemap.rb +122 -0
  607. data/lib/etassia/parser/grammars/xy_chart.rb +119 -0
  608. data/lib/etassia/parser/info.rb +32 -0
  609. data/lib/etassia/parser/kanban.rb +131 -0
  610. data/lib/etassia/parser/mermaid_shapes.rb +106 -0
  611. data/lib/etassia/parser/metadata_yaml.rb +452 -0
  612. data/lib/etassia/parser/mindmap.rb +92 -0
  613. data/lib/etassia/parser/native/builders/architecture.rb +103 -0
  614. data/lib/etassia/parser/native/builders/block.rb +68 -0
  615. data/lib/etassia/parser/native/builders/c4.rb +16 -0
  616. data/lib/etassia/parser/native/builders/class_diagram.rb +63 -0
  617. data/lib/etassia/parser/native/builders/er_diagram.rb +47 -0
  618. data/lib/etassia/parser/native/builders/flowchart.rb +58 -0
  619. data/lib/etassia/parser/native/builders/gantt.rb +35 -0
  620. data/lib/etassia/parser/native/builders/gitgraph.rb +35 -0
  621. data/lib/etassia/parser/native/builders/info.rb +30 -0
  622. data/lib/etassia/parser/native/builders/kanban.rb +37 -0
  623. data/lib/etassia/parser/native/builders/mindmap.rb +26 -0
  624. data/lib/etassia/parser/native/builders/packet.rb +63 -0
  625. data/lib/etassia/parser/native/builders/pie.rb +82 -0
  626. data/lib/etassia/parser/native/builders/quadrant.rb +27 -0
  627. data/lib/etassia/parser/native/builders/radar.rb +84 -0
  628. data/lib/etassia/parser/native/builders/requirement.rb +25 -0
  629. data/lib/etassia/parser/native/builders/sankey.rb +18 -0
  630. data/lib/etassia/parser/native/builders/sequence.rb +48 -0
  631. data/lib/etassia/parser/native/builders/state_diagram.rb +61 -0
  632. data/lib/etassia/parser/native/builders/timeline.rb +46 -0
  633. data/lib/etassia/parser/native/builders/treemap.rb +60 -0
  634. data/lib/etassia/parser/native/builders/user_journey.rb +83 -0
  635. data/lib/etassia/parser/native/builders/xy_chart.rb +94 -0
  636. data/lib/etassia/parser/native/declined.rb +12 -0
  637. data/lib/etassia/parser/native/mermaid.json +1 -0
  638. data/lib/etassia/parser/native/text.rb +31 -0
  639. data/lib/etassia/parser/native.rb +99 -0
  640. data/lib/etassia/parser/packet.rb +56 -0
  641. data/lib/etassia/parser/pie.rb +32 -0
  642. data/lib/etassia/parser/quadrant.rb +77 -0
  643. data/lib/etassia/parser/radar.rb +99 -0
  644. data/lib/etassia/parser/requirement.rb +32 -0
  645. data/lib/etassia/parser/sankey.rb +47 -0
  646. data/lib/etassia/parser/sequence.rb +34 -0
  647. data/lib/etassia/parser/state_diagram.rb +29 -0
  648. data/lib/etassia/parser/timeline.rb +40 -0
  649. data/lib/etassia/parser/treemap.rb +25 -0
  650. data/lib/etassia/parser/user_journey.rb +253 -0
  651. data/lib/etassia/parser/xy_chart.rb +98 -0
  652. data/lib/etassia/parser.rb +18 -0
  653. data/lib/etassia/renderer/architecture.rb +232 -0
  654. data/lib/etassia/renderer/architecture_edge_router.rb +379 -0
  655. data/lib/etassia/renderer/base.rb +295 -0
  656. data/lib/etassia/renderer/block.rb +157 -0
  657. data/lib/etassia/renderer/c4.rb +294 -0
  658. data/lib/etassia/renderer/class_diagram.rb +671 -0
  659. data/lib/etassia/renderer/er_diagram.rb +842 -0
  660. data/lib/etassia/renderer/error.rb +131 -0
  661. data/lib/etassia/renderer/flowchart.rb +315 -0
  662. data/lib/etassia/renderer/gantt.rb +177 -0
  663. data/lib/etassia/renderer/git_graph.rb +224 -0
  664. data/lib/etassia/renderer/info.rb +59 -0
  665. data/lib/etassia/renderer/kanban.rb +157 -0
  666. data/lib/etassia/renderer/line_break_text.rb +56 -0
  667. data/lib/etassia/renderer/markdown_text.rb +106 -0
  668. data/lib/etassia/renderer/mindmap.rb +290 -0
  669. data/lib/etassia/renderer/packet.rb +87 -0
  670. data/lib/etassia/renderer/pie.rb +113 -0
  671. data/lib/etassia/renderer/quadrant.rb +189 -0
  672. data/lib/etassia/renderer/radar.rb +191 -0
  673. data/lib/etassia/renderer/requirement.rb +209 -0
  674. data/lib/etassia/renderer/sankey.rb +114 -0
  675. data/lib/etassia/renderer/sequence/frame_drawing.rb +169 -0
  676. data/lib/etassia/renderer/sequence/note_drawing.rb +69 -0
  677. data/lib/etassia/renderer/sequence.rb +490 -0
  678. data/lib/etassia/renderer/state_diagram.rb +202 -0
  679. data/lib/etassia/renderer/timeline.rb +134 -0
  680. data/lib/etassia/renderer/timeline_palette.rb +18 -0
  681. data/lib/etassia/renderer/treemap/section_total.rb +33 -0
  682. data/lib/etassia/renderer/treemap.rb +128 -0
  683. data/lib/etassia/renderer/user_journey.rb +209 -0
  684. data/lib/etassia/renderer/xy_chart.rb +180 -0
  685. data/lib/etassia/renderer.rb +22 -0
  686. data/lib/etassia/runtime.rb +125 -0
  687. data/lib/etassia/source/frontmatter.rb +501 -0
  688. data/lib/etassia/source.rb +224 -0
  689. data/lib/etassia/svg/arrowhead.rb +205 -0
  690. data/lib/etassia/svg/circle.rb +19 -0
  691. data/lib/etassia/svg/document.rb +106 -0
  692. data/lib/etassia/svg/element.rb +209 -0
  693. data/lib/etassia/svg/ellipse.rb +23 -0
  694. data/lib/etassia/svg/escaping.rb +149 -0
  695. data/lib/etassia/svg/group.rb +64 -0
  696. data/lib/etassia/svg/line.rb +21 -0
  697. data/lib/etassia/svg/numbers.rb +53 -0
  698. data/lib/etassia/svg/path.rb +67 -0
  699. data/lib/etassia/svg/path_geometry.rb +378 -0
  700. data/lib/etassia/svg/polygon.rb +25 -0
  701. data/lib/etassia/svg/polyline.rb +28 -0
  702. data/lib/etassia/svg/rect.rb +34 -0
  703. data/lib/etassia/svg/style.rb +50 -0
  704. data/lib/etassia/svg/text.rb +181 -0
  705. data/lib/etassia/svg/tspan.rb +48 -0
  706. data/lib/etassia/svg.rb +38 -0
  707. data/lib/etassia/text_measurement/arial_advances.rb +122 -0
  708. data/lib/etassia/text_measurement/dejavu_advances.rb +111 -0
  709. data/lib/etassia/text_measurement/times_advances.rb +64 -0
  710. data/lib/etassia/text_measurement/trebuchet_advances.rb +53 -0
  711. data/lib/etassia/text_measurement/trebuchet_kerning.rb +29 -0
  712. data/lib/etassia/text_measurement.rb +198 -0
  713. data/lib/etassia/theme/builtin/dark.yml +229 -0
  714. data/lib/etassia/theme/builtin/default.yml +239 -0
  715. data/lib/etassia/theme/builtin/high_contrast.yml +231 -0
  716. data/lib/etassia/theme/builtin/light.yml +229 -0
  717. data/lib/etassia/theme/color_palette.rb +97 -0
  718. data/lib/etassia/theme/completion.rb +50 -0
  719. data/lib/etassia/theme/effect_styles.rb +34 -0
  720. data/lib/etassia/theme/registry.rb +45 -0
  721. data/lib/etassia/theme/shape_styles.rb +34 -0
  722. data/lib/etassia/theme/spacing_config.rb +30 -0
  723. data/lib/etassia/theme/typography.rb +40 -0
  724. data/lib/etassia/theme.rb +73 -0
  725. data/lib/etassia/version.rb +5 -0
  726. data/lib/etassia.rb +40 -0
  727. metadata +865 -0
@@ -0,0 +1,533 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../js_math"
4
+
5
+ module Etassia
6
+ module Layout
7
+ # Works out where an edge's line runs between two boxes.
8
+ #
9
+ # This is geometry only: it takes two laid-out boxes and gives back
10
+ # the points the line passes through. Nothing here knows about SVG,
11
+ # and Flowchart turns the points into a path.
12
+ #
13
+ # The maths exists because of clusters. A node is painted OVER the
14
+ # line, so a line drawn from centre to centre looks right without
15
+ # any trimming. A cluster is painted BEHIND the line, so the stretch
16
+ # from its centre out to its border stays visible across the inside
17
+ # of the box. Every rule below is about keeping that stretch off the
18
+ # page while the line still touches the box it belongs to.
19
+ #
20
+ # One point shape throughout: `{ x: <number>, y: <number> }`, which
21
+ # is the shape ELK bend points already have. `ends_of` is the single
22
+ # place it becomes anything else.
23
+ #
24
+ # @example Route an edge
25
+ # points, label_ends = FlowchartEdgeRouter.new.route(
26
+ # source, target, bends
27
+ # )
28
+ class FlowchartEdgeRouter
29
+ # Rounded the way mermaid paints a cluster. The Scene carries the same
30
+ # radius, and an endpoint on a corner has to land on it.
31
+ CLUSTER_CORNER = 5
32
+
33
+ # Two boxes that touch add up to exactly one run between their
34
+ # centres, and the arithmetic lands a hair under it. Without the
35
+ # slack they get trimmed to the same point and the edge is drawn
36
+ # zero units long.
37
+ TOUCHING = 1e-9
38
+
39
+ # Routes are written to two decimal places after trimming. Centres
40
+ # less than one output unit apart cannot provide a useful straight run.
41
+ COINCIDENT = 0.01
42
+
43
+ # Trimmed endpoints are written to two decimals, so a coordinate can
44
+ # sit half a hundredth away from the unrounded side it reached.
45
+ ROUTE_EPSILON = 0.01
46
+
47
+ # How far a degenerate loop reaches past the borders it leaves.
48
+ # Flowchart's page sizing adds 40 past the drawn maxima, and the
49
+ # Scene framing keeps the origin at zero and adds another 40
50
+ # extents, leaving 80 units there. A reach beyond that needs the
51
+ # page to grow with it.
52
+ LOOP_REACH = 20
53
+
54
+ # The marker Layout::Flowchart puts on a subgraph.
55
+ # Layout::Grid#cluster? makes the same test: change one and
56
+ # change the others, or the layers stop agreeing about what a box
57
+ # is.
58
+ #
59
+ # @param box [Hash] a laid-out box
60
+ # @return [Boolean] true when the box is a cluster
61
+ def self.cluster?(box)
62
+ box.dig(:metadata, :cluster) == true
63
+ end
64
+
65
+ # The exact centre of a laid-out box. Flowchart aims routes, heads and
66
+ # outlines at this same point, so keep one copy: two drifted
67
+ # apart once and put routed ends half a pixel off their heads.
68
+ #
69
+ # @param box [Hash] a laid-out box
70
+ # @return [Hash] :x and :y of the centre
71
+ def self.centre(box)
72
+ { x: (box[:x] || 0) + ((box[:width] || 100) / 2.0),
73
+ y: (box[:y] || 0) + ((box[:height] || 50) / 2.0) }
74
+ end
75
+
76
+ # The two ends of a run, flattened for the path and label builders.
77
+ #
78
+ # @param points [Array<Hash>] the points of a run
79
+ # @return [Array<Numeric>] source x, source y, target x, target y
80
+ def self.ends_of(points)
81
+ first = points.first
82
+ last = points.last
83
+ [first[:x], first[:y], last[:x], last[:y]]
84
+ end
85
+
86
+ # Routes one edge.
87
+ #
88
+ # @param source [Hash] the laid-out box the edge leaves
89
+ # @param target [Hash] the laid-out box the edge reaches
90
+ # @param bends [Array<Hash>, nil] bend points the layout supplied
91
+ # @return [Array(Array<Hash>, Array<Numeric>)] the points of the
92
+ # run, and the ends of the stretch the label sits on
93
+ def route(source, target, bends = nil)
94
+ points = initial_route(source, target)
95
+ clamp_route(points, source, target, bends)
96
+ label_points = points.length > 2 ? points.slice(1, 2) : points
97
+ [points, self.class.ends_of(label_points)]
98
+ end
99
+
100
+ private
101
+
102
+ def initial_route(source, target)
103
+ points = coincident_loop(source, target) unless
104
+ exposed_cluster_overlap?(source, target)
105
+ points || trimmed_route(source, target)
106
+ end
107
+
108
+ def clamp_route(points, source, target, bends)
109
+ source_neighbour = bends&.first || points[1]
110
+ target_neighbour = bends&.last || points[-2]
111
+ points[0] = clamp_cluster_corner(points[0], source_neighbour, source)
112
+ points[-1] = clamp_cluster_corner(points[-1], target_neighbour, target)
113
+ end
114
+
115
+ def cluster?(box)
116
+ self.class.cluster?(box)
117
+ end
118
+
119
+ # Nodes and unmarked containers stay unchanged. A cluster is painted
120
+ # with integer rectangle coordinates, so its endpoint is moved from the
121
+ # layout box onto that exact rounded outline.
122
+ def clamp_cluster_corner(point, neighbour, box)
123
+ return point unless cluster?(box)
124
+
125
+ x_side, y_side = cluster_sides(point, box)
126
+ return point unless x_side || y_side
127
+
128
+ outline = painted_cluster_outline(box)
129
+ return point if outline.values_at(:rx, :ry).any?(&:zero?)
130
+
131
+ painted_cluster_contact(point, neighbour, outline, x_side, y_side)
132
+ end
133
+
134
+ def painted_cluster_contact(point, neighbour, outline, x_side, y_side)
135
+ if x_side && y_side
136
+ return rounded_corner(neighbour, outline, x_side, y_side)
137
+ end
138
+
139
+ clamp_cluster_side(point, outline, x_side, y_side)
140
+ end
141
+
142
+ def cluster_sides(point, box)
143
+ [edge_side(point[:x], [box[:x] || 0, right_of(box)]),
144
+ edge_side(point[:y], [box[:y] || 0, bottom_of(box)])]
145
+ end
146
+
147
+ def edge_side(coordinate, edges)
148
+ side = edges.each_index.min_by do |index|
149
+ (coordinate - edges[index]).abs
150
+ end
151
+ side if (coordinate - edges[side]).abs <= ROUTE_EPSILON
152
+ end
153
+
154
+ def painted_cluster_outline(box)
155
+ x, y, width, height = painted_cluster_dimensions(box)
156
+ { xs: [x, x + width], ys: [y, y + height],
157
+ rx: [CLUSTER_CORNER, width / 2.0].min,
158
+ ry: [CLUSTER_CORNER, height / 2.0].min }
159
+ end
160
+
161
+ def painted_cluster_dimensions(box)
162
+ %i[x y width height].map { |key| (box[key] || 0).to_i.to_f }
163
+ end
164
+
165
+ def clamp_cluster_side(point, outline, x_side, y_side)
166
+ if x_side
167
+ clamp_vertical_side(point, outline, x_side)
168
+ else
169
+ clamp_horizontal_side(point, outline, y_side)
170
+ end
171
+ end
172
+
173
+ def clamp_vertical_side(point, outline, side)
174
+ xs, ys, ry = outline.values_at(:xs, :ys, :ry)
175
+ point.merge(x: xs[side],
176
+ y: point[:y].clamp(ys.first + ry, ys.last - ry))
177
+ end
178
+
179
+ def clamp_horizontal_side(point, outline, side)
180
+ xs, ys, rx = outline.values_at(:xs, :ys, :rx)
181
+ point.merge(x: point[:x].clamp(xs.first + rx, xs.last - rx),
182
+ y: ys[side])
183
+ end
184
+
185
+ def rounded_corner(neighbour, outline, x_side, y_side)
186
+ corner = corner_point(outline, x_side, y_side)
187
+ centre = corner_centre(outline, x_side, y_side)
188
+ direction = { x: corner[:x] - neighbour[:x],
189
+ y: corner[:y] - neighbour[:y] }
190
+ step = ellipse_entry(
191
+ corner, centre, direction, outline[:rx], outline[:ry]
192
+ )
193
+ step ? corner_at_step(corner, direction, step) : corner
194
+ end
195
+
196
+ def corner_point(outline, x_side, y_side)
197
+ xs, ys = outline.values_at(:xs, :ys)
198
+ { x: xs[x_side], y: ys[y_side] }
199
+ end
200
+
201
+ def corner_centre(outline, x_side, y_side)
202
+ xs, ys, rx, ry = outline.values_at(:xs, :ys, :rx, :ry)
203
+ { x: x_side.zero? ? xs.first + rx : xs.last - rx,
204
+ y: y_side.zero? ? ys.first + ry : ys.last - ry }
205
+ end
206
+
207
+ def corner_at_step(corner, direction, step)
208
+ corner.merge(x: (corner[:x] + (direction[:x] * step)).round(2),
209
+ y: (corner[:y] + (direction[:y] * step)).round(2))
210
+ end
211
+
212
+ def ellipse_entry(corner, centre, direction, radius_x, radius_y)
213
+ x, y = normalized_corner(corner, centre, radius_x, radius_y)
214
+ dx, dy = ellipse_direction(direction, radius_x, radius_y)
215
+ a = (dx**2) + (dy**2)
216
+ return nil if a.zero?
217
+
218
+ b = 2 * ((x * dx) + (y * dy))
219
+ smallest_quadratic_root(a, b)
220
+ end
221
+
222
+ def normalized_corner(corner, centre, radius_x, radius_y)
223
+ [(corner[:x] - centre[:x]) / radius_x,
224
+ (corner[:y] - centre[:y]) / radius_y]
225
+ end
226
+
227
+ def ellipse_direction(direction, radius_x, radius_y)
228
+ [direction[:x] / radius_x, direction[:y] / radius_y]
229
+ end
230
+
231
+ def smallest_quadratic_root(quad_coeff, linear_coeff)
232
+ discriminant = (linear_coeff**2) - (4 * quad_coeff)
233
+ return nil if discriminant.negative?
234
+
235
+ quadratic_roots(quad_coeff, linear_coeff, discriminant)
236
+ .select { |value| value >= 0 }.min
237
+ end
238
+
239
+ def quadratic_roots(quad_coeff, linear_coeff, discriminant)
240
+ [(-linear_coeff - Math.sqrt(discriminant)) / (2 * quad_coeff),
241
+ (-linear_coeff + Math.sqrt(discriminant)) / (2 * quad_coeff)]
242
+ end
243
+
244
+ # A straight run needs a direction. Self-edges and distinct boxes
245
+ # with the same centre have none, so route from the source's top to
246
+ # the target's bottom with two bends beyond both boxes.
247
+ def coincident_loop(source, target)
248
+ source_centre = FlowchartEdgeRouter.centre(source)
249
+ target_centre = FlowchartEdgeRouter.centre(target)
250
+ return nil unless near?(source_centre, target_centre)
251
+
252
+ coincident_loop_points(source, target, source_centre, target_centre)
253
+ end
254
+
255
+ def coincident_loop_points(source, target, source_centre, target_centre)
256
+ start, finish = loop_endpoints(
257
+ source, target, source_centre, target_centre
258
+ )
259
+ out = [right_of(source), right_of(target)].max + LOOP_REACH
260
+ bend_y = finish[:y]
261
+ bend_y += LOOP_REACH if near?(start, finish)
262
+ rounded_points([start, { x: out, y: start[:y] },
263
+ { x: out, y: bend_y }, finish])
264
+ end
265
+
266
+ def loop_endpoints(source, target, source_centre, target_centre)
267
+ source_top, = vertical_outline(source, source_centre[:y])
268
+ _, target_bottom = vertical_outline(target, target_centre[:y])
269
+ [{ x: source_centre[:x], y: source_top },
270
+ { x: target_centre[:x], y: target_bottom }]
271
+ end
272
+
273
+ def near?(first, second)
274
+ JsMath.hypot(first[:x] - second[:x],
275
+ first[:y] - second[:y]) < COINCIDENT
276
+ end
277
+
278
+ def right_of(box)
279
+ (box[:x] || 0) + (box[:width] || 0)
280
+ end
281
+
282
+ def bottom_of(box)
283
+ (box[:y] || 0) + (box[:height] || 0)
284
+ end
285
+
286
+ # Circle nodes use the smaller box dimension as their radius, so
287
+ # their vertical outline may sit inside the box bounds.
288
+ def vertical_outline(box, centre_y)
289
+ y = box[:y] || 0
290
+ height = box[:height] || 0
291
+ shape = box.dig(:metadata, :shape)
292
+ return [y, y + height] unless %w[circle double_circle].include?(shape)
293
+
294
+ radius = [box[:width] || 0, height].min / 2
295
+ [centre_y - radius, centre_y + radius]
296
+ end
297
+
298
+ # The points of the ordinary run: from centre to centre, with each
299
+ # end pulled back to the border of the box it leaves when that box
300
+ # is a cluster.
301
+ def trimmed_route(source, target)
302
+ from = FlowchartEdgeRouter.centre(source)
303
+ to = FlowchartEdgeRouter.centre(target)
304
+ out = step_out(source, from, to)
305
+ back = step_out(target, to, from)
306
+ trimmed_points(source, target, from, to, [out, back])
307
+ end
308
+
309
+ def trimmed_points(source, target, from, to, steps)
310
+ if exposed_cluster_overlap?(source, target)
311
+ return exterior_route(source, target, from, to)
312
+ end
313
+ if enclosing_steps?(steps)
314
+ return enclosing_route(source, target, from, to)
315
+ end
316
+ if overlapping_steps?(steps)
317
+ return exterior_route(source, target, from, to)
318
+ end
319
+
320
+ clipped_route(source, target, from, to, steps)
321
+ end
322
+
323
+ def enclosing_steps?(steps)
324
+ out, back = steps
325
+ out >= 1.0 - TOUCHING || back >= 1.0 - TOUCHING
326
+ end
327
+
328
+ def overlapping_steps?(steps)
329
+ out, back = steps
330
+ out + back >= 1.0 - TOUCHING
331
+ end
332
+
333
+ def clipped_route(source, target, from, to, steps)
334
+ out, back = steps
335
+ route = [along(from, to, out), along(to, from, back)]
336
+ if route.first == route.last
337
+ return exterior_route(source, target, from, to)
338
+ end
339
+
340
+ route
341
+ end
342
+
343
+ def exposed_cluster_overlap?(source, target)
344
+ return false unless cluster?(source) && cluster?(target)
345
+ if contains_box?(source, target) || contains_box?(target, source)
346
+ return false
347
+ end
348
+
349
+ overlapping_x?(source, target) && overlapping_y?(source, target)
350
+ end
351
+
352
+ def overlapping_x?(source, target)
353
+ (source[:x] || 0) <= right_of(target) &&
354
+ right_of(source) >= (target[:x] || 0)
355
+ end
356
+
357
+ def overlapping_y?(source, target)
358
+ (source[:y] || 0) <= bottom_of(target) &&
359
+ bottom_of(source) >= (target[:y] || 0)
360
+ end
361
+
362
+ def contains_box?(outer, inner)
363
+ contains_x?(outer, inner) && contains_y?(outer, inner)
364
+ end
365
+
366
+ def contains_x?(outer, inner)
367
+ (outer[:x] || 0) <= (inner[:x] || 0) &&
368
+ right_of(outer) >= right_of(inner)
369
+ end
370
+
371
+ def contains_y?(outer, inner)
372
+ (outer[:y] || 0) <= (inner[:y] || 0) &&
373
+ bottom_of(outer) >= bottom_of(inner)
374
+ end
375
+
376
+ def enclosing_route(source, target, from, to)
377
+ source_back = step_out(source, from, mirror(from, to))
378
+ target_back = step_out(target, to, mirror(to, from))
379
+
380
+ [along(from, to, -source_back), along(to, from, -target_back)]
381
+ end
382
+
383
+ # The point as far the other side of `from` as `to` is this side.
384
+ def mirror(from, to)
385
+ { x: (2 * from[:x]) - to[:x], y: (2 * from[:y]) - to[:y] }
386
+ end
387
+
388
+ # Corners on the sides facing away from the other box expose both
389
+ # ends. Prefer the corridor matching the centre direction, but unequal
390
+ # projections can put that corner inside the other box, so only use a
391
+ # candidate whose actual segments stay outside both open faces.
392
+ def exterior_route(source, target, from, to)
393
+ candidates = exterior_candidates(source, target, from, to)
394
+ rounded = candidates.map { |points| rounded_points(points) }
395
+ clear = rounded.find { |route| clear_route?(route, source, target) }
396
+ clear || rounded.first
397
+ end
398
+
399
+ def exterior_candidates(source, target, from, to)
400
+ bottom = bottom_exterior_route(source, target, from, to)
401
+ right = right_exterior_route(source, target, from, to)
402
+ sideways = (to[:x] - from[:x]).abs >= (to[:y] - from[:y]).abs
403
+ sideways ? [bottom, right] : [right, bottom]
404
+ end
405
+
406
+ def rounded_points(points)
407
+ points.map do |point|
408
+ { x: point[:x].round(2), y: point[:y].round(2) }
409
+ end
410
+ end
411
+
412
+ def clear_route?(points, source, target)
413
+ points.each_cons(2).none? do |from, to|
414
+ [source, target].any? { |box| crosses_face?(box, from, to) }
415
+ end
416
+ end
417
+
418
+ def crosses_face?(box, from, to)
419
+ if from[:x] == to[:x]
420
+ crosses_vertical_face?(box, from, to)
421
+ else
422
+ crosses_horizontal_face?(box, from, to)
423
+ end
424
+ end
425
+
426
+ def crosses_vertical_face?(box, from, to)
427
+ low, high = [from[:y], to[:y]].minmax
428
+ inside_x?(box, from[:x]) && overlaps_y_face?(box, low, high)
429
+ end
430
+
431
+ def crosses_horizontal_face?(box, from, to)
432
+ low, high = [from[:x], to[:x]].minmax
433
+ inside_y?(box, from[:y]) && overlaps_x_face?(box, low, high)
434
+ end
435
+
436
+ def inside_x?(box, coordinate)
437
+ coordinate > (box[:x] || 0) && coordinate < right_of(box)
438
+ end
439
+
440
+ def inside_y?(box, coordinate)
441
+ coordinate > (box[:y] || 0) && coordinate < bottom_of(box)
442
+ end
443
+
444
+ def overlaps_x_face?(box, low, high)
445
+ low < right_of(box) && high > (box[:x] || 0)
446
+ end
447
+
448
+ def overlaps_y_face?(box, low, high)
449
+ low < bottom_of(box) && high > (box[:y] || 0)
450
+ end
451
+
452
+ def bottom_exterior_route(source, target, from, to)
453
+ rightwards = to[:x] > from[:x]
454
+ source_x = rightwards ? source[:x] || 0 : right_of(source)
455
+ target_x = rightwards ? right_of(target) : target[:x] || 0
456
+ source_y = bottom_of(source)
457
+ target_y = bottom_of(target)
458
+ outside_y = [source_y, target_y].max + LOOP_REACH
459
+
460
+ [{ x: source_x, y: source_y }, { x: source_x, y: outside_y },
461
+ { x: target_x, y: outside_y }, { x: target_x, y: target_y }]
462
+ end
463
+
464
+ def right_exterior_route(source, target, from, to)
465
+ downwards = to[:y] > from[:y]
466
+ source_x = right_of(source)
467
+ target_x = right_of(target)
468
+ source_y = downwards ? source[:y] || 0 : bottom_of(source)
469
+ target_y = downwards ? bottom_of(target) : target[:y] || 0
470
+ outside_x = [source_x, target_x].max + LOOP_REACH
471
+
472
+ [{ x: source_x, y: source_y }, { x: outside_x, y: source_y },
473
+ { x: outside_x, y: target_y }, { x: target_x, y: target_y }]
474
+ end
475
+
476
+ # How far along the ray to the other centre this box's border sits.
477
+ # Values above one mean the other centre lies inside this box. A node
478
+ # is not trimmed, so it stays where it is.
479
+ #
480
+ # Measured off the sides themselves rather than from half the
481
+ # width: the centre above is the one this flowchart has always
482
+ # used, and its integer division puts it half a unit off centre on
483
+ # an odd-width box. Halves either side of that would miss the side
484
+ # by the same half unit.
485
+ def step_out(box, from, to)
486
+ return 0.0 unless cluster?(box)
487
+
488
+ reach = border_steps(box, from, to)
489
+ reach.empty? ? 0.0 : reach.min.clamp(0.0, Float::INFINITY)
490
+ end
491
+
492
+ def border_steps(box, from, to)
493
+ [axis_step(box, from[:x], to[:x], :x, :width),
494
+ axis_step(box, from[:y], to[:y], :y, :height)].compact
495
+ end
496
+
497
+ def axis_step(box, origin, target, origin_key, size_key)
498
+ delta = target - origin
499
+ return if delta.zero?
500
+
501
+ box_side(origin, delta, box, origin_key, size_key)
502
+ end
503
+
504
+ def box_side(from, delta, box, origin_key, size_key)
505
+ side(from, delta, box[origin_key] || 0, box[size_key] || 0)
506
+ end
507
+
508
+ # The side the run is heading for, as a fraction of the whole run.
509
+ def side(from, delta, near, size)
510
+ edge = delta.positive? ? near + size : near
511
+ (edge - from) / delta.to_f
512
+ end
513
+
514
+ # An end that was not trimmed comes back exactly as it went in. A
515
+ # box measured from text has a fractional centre, and touching
516
+ # that moved every ordinary edge.
517
+ #
518
+ # A trimmed one is rounded, because the arithmetic lands on
519
+ # seventeen digits behind the point and this output goes into a
520
+ # document. Two decimals is finer than anything anyone draws.
521
+ def along(from, to, step)
522
+ return from if step.zero?
523
+
524
+ { x: along_coordinate(from, to, step, :x),
525
+ y: along_coordinate(from, to, step, :y) }
526
+ end
527
+
528
+ def along_coordinate(from, to, step, axis)
529
+ (from[axis] + ((to[axis] - from[axis]) * step)).round(2)
530
+ end
531
+ end
532
+ end
533
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etassia
4
+ module Layout
5
+ class Flowchart < Base
6
+ # Measured bounds behind an edge label's alphabetic baseline.
7
+ # Paint belongs to the renderer; these coordinates belong to the Scene.
8
+ class LabelBackdropGeometry < Lutaml::Model::Serializable
9
+ attribute :x, :float
10
+ attribute :y, :float
11
+ attribute :width, :float
12
+ attribute :height, :float
13
+
14
+ class << self
15
+ def for_label(label, font_size:)
16
+ measured = measured_label(label, font_size)
17
+ new(**bounds(measured))
18
+ end
19
+
20
+ private
21
+
22
+ # Legacy callers may omit metrics; measure without changing their
23
+ # label or selecting a typography default inside the geometry layer.
24
+ def measured_label(label, font_size)
25
+ size = measured_size(label, font_size)
26
+ label.dup.tap do |measured|
27
+ measured.font_size = font_size
28
+ measured.width = size[:width]
29
+ measured.height = size[:height]
30
+ end
31
+ end
32
+
33
+ def measured_size(label, font_size)
34
+ TextMeasurement.measure(
35
+ label.text,
36
+ font_size: font_size,
37
+ width: label.width,
38
+ height: label.height,
39
+ )
40
+ end
41
+
42
+ def bounds(label)
43
+ padding = label.font_size / 8.0
44
+ { x: label.x - (label.width / 2.0) - padding,
45
+ y: top(label) - padding,
46
+ **padded_size(label, padding) }
47
+ end
48
+
49
+ def padded_size(label, padding)
50
+ { width: label.width + (padding * 2),
51
+ height: label.height + (padding * 2) }
52
+ end
53
+
54
+ def top(label)
55
+ label.y - (label.height / 2.0) -
56
+ (Flowchart::MIDDLE_BASELINE_EMS * label.font_size)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Etassia
4
+ module Layout
5
+ class Flowchart < Base
6
+ # Opens room between the layers of a laid-out level for edge labels,
7
+ # the way dagre gives a label its own rank: a gap grows by the tallest
8
+ # label on an edge that crosses it. Works on a level laid out downward,
9
+ # so a label's room is its size along the flow, `:height` there.
10
+ class LayerSpacing
11
+ # The line box mmdc gives a label is 1.5 times its text height.
12
+ LINE_HEIGHT = 1.5
13
+
14
+ # @param flat [Hash] a level whose boxes elkrb has placed
15
+ # @param label_axis [Symbol] :height, or :width for a turned level
16
+ def self.apply(flat, label_axis: :height)
17
+ new(flat, label_axis).apply
18
+ end
19
+
20
+ def initialize(flat, label_axis)
21
+ @flat = flat
22
+ @label_axis = label_axis
23
+ end
24
+
25
+ def apply
26
+ @tops = @flat[:children].map { |box| box[:y] }.uniq.sort
27
+ @shifts = shifts
28
+ return @flat if @shifts.all?(&:zero?)
29
+
30
+ @flat[:children].each { |box| box[:y] += shift_at(box[:y]) }
31
+ @flat
32
+ end
33
+
34
+ private
35
+
36
+ # How far each layer moves down: the room of every gap above it.
37
+ def shifts
38
+ rooms = gap_rooms
39
+ total = 0.0
40
+ @tops.each_index.map do |index|
41
+ total += rooms[index - 1] if index.positive?
42
+ total
43
+ end
44
+ end
45
+
46
+ def gap_rooms
47
+ rooms = Array.new(@tops.length, 0.0)
48
+ (@flat[:edges] || []).each do |edge|
49
+ gap = gap_of(edge)
50
+ next unless gap
51
+
52
+ rooms[gap] = [rooms[gap], room_of(edge)].max
53
+ end
54
+ rooms
55
+ end
56
+
57
+ # The gap an edge's label sits in: the one after the layer halfway
58
+ # along the edge.
59
+ def gap_of(edge)
60
+ from, to = [layer_of(edge[:sources].first),
61
+ layer_of(edge[:targets].first)].sort
62
+ return if to.zero? || from == to
63
+
64
+ (from + to - 1) / 2
65
+ end
66
+
67
+ def layer_of(box_id)
68
+ box = @flat[:children].find { |child| child[:id] == box_id }
69
+ @tops.index(box[:y])
70
+ end
71
+
72
+ def room_of(edge)
73
+ size = (edge[:labels] || []).filter_map { |l| l[@label_axis] }.max
74
+ return 0.0 unless size
75
+
76
+ @label_axis == :height ? size * LINE_HEIGHT : size
77
+ end
78
+
79
+ def shift_at(top)
80
+ @shifts[@tops.index(top)]
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end