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,378 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+ require_relative "../js_math"
5
+
6
+ module Etassia
7
+ module Svg
8
+ # Where a path starts and ends, and which way it is travelling there.
9
+ # Only the two ends matter (for drawing an arrowhead), so this walks
10
+ # commands for the current point and never builds the curves.
11
+ #
12
+ # An arc's chord is not its tangent, so it yields NO heading rather
13
+ # than guessing wrong -- don't add arc tangents without the centre
14
+ # parameterisation.
15
+ #
16
+ # An unrecognised byte is skipped and following numbers are read as
17
+ # more arguments for the command in force -- deliberate leniency,
18
+ # reachable only by a caller assigning a hand-built `d` string
19
+ # directly (Path.from_xml no longer exists as a way in).
20
+ class PathGeometry
21
+ # One anchor: a point on the path and the unit direction of travel
22
+ # through it.
23
+ Anchor = Struct.new(:x, :y, :dx, :dy)
24
+
25
+ # Command letter to the number of numbers it consumes. Repeated
26
+ # argument groups are the SVG rule, so `L 1 2 3 4` is two segments.
27
+ ARITY = {
28
+ "m" => 2, "l" => 2, "h" => 1, "v" => 1, "c" => 6,
29
+ "s" => 4, "q" => 4, "t" => 2, "a" => 7, "z" => 0
30
+ }.freeze
31
+ private_constant :ARITY
32
+
33
+ TOKEN = /([MmLlHhVvCcSsQqTtAaZz])|
34
+ ([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/x
35
+ private_constant :TOKEN
36
+
37
+ # Arc flags are single digits even when no separator follows, so they
38
+ # cannot use the generic number scan at their two argument positions.
39
+ ARC_FLAG_TOKEN = /([MmLlHhVvCcSsQqTtAaZz])|([01])/
40
+ private_constant :ARC_FLAG_TOKEN
41
+
42
+ # A move followed by more coordinates draws lines: only the first
43
+ # group of `M 1 2 3 4` is a move. Every other command simply repeats.
44
+ AFTER_FIRST = { "M" => "L", "m" => "l" }.freeze
45
+ REFERENCE_BUILDERS = {
46
+ "l" => :line_references,
47
+ "h" => :horizontal_references,
48
+ "v" => :vertical_references,
49
+ "c" => :cubic_references,
50
+ "s" => :smooth_cubic_references,
51
+ "q" => :quadratic_references,
52
+ "t" => :smooth_quadratic_references,
53
+ }.freeze
54
+ private_constant :AFTER_FIRST, :REFERENCE_BUILDERS
55
+
56
+ # @param data [String, nil] the `d` attribute
57
+ def initialize(data)
58
+ @point = [0.0, 0.0]
59
+ @subpath_start = @point
60
+ @origin = nil
61
+ @terminus = nil
62
+ @origin_direction = nil
63
+ @recent_direction = nil
64
+ @quadratic_control = nil
65
+ @cubic_control = nil
66
+ walk(data.to_s)
67
+ end
68
+
69
+ # @return [Anchor, nil] the first point, pointing along the first segment
70
+ def origin
71
+ # `false` means a command that anchors nothing came first, so the
72
+ # segment after it must not be promoted to the start of the path.
73
+ return nil unless @origin
74
+
75
+ start, reference = @origin
76
+ heading(start, start, reference) ||
77
+ (@origin_direction && heading(start, *@origin_direction))
78
+ end
79
+
80
+ # @return [Anchor, nil] the last point, pointing along the last segment
81
+ def terminus
82
+ return nil unless @terminus
83
+
84
+ reference, finish = @terminus
85
+ heading(finish, reference, finish) ||
86
+ (@recent_direction && heading(finish, *@recent_direction))
87
+ end
88
+
89
+ private
90
+
91
+ def walk(data)
92
+ each_command(data) { |letter, args| apply(letter, args) }
93
+ end
94
+
95
+ # Yields one command letter and one full argument group at a time,
96
+ # repeating the letter while arguments remain. A bare `M` with four
97
+ # numbers draws a line after the move, which is the SVG rule and the
98
+ # reason this cannot just split on letters.
99
+ def each_command(data, &)
100
+ letter = nil
101
+ numbers = []
102
+ scanner = StringScanner.new(data)
103
+ while scan_token(scanner, letter, numbers)
104
+ letter, numbers = consume_token(scanner, letter, numbers, &)
105
+ end
106
+ flush(letter, numbers, &)
107
+ end
108
+
109
+ # An invalid byte sequence makes the scan regex itself raise rather
110
+ # than simply fail to match. Stop as a genuine non-match would instead
111
+ # of letting a foreign document's garbage encoding escape raw.
112
+ def scan_token(scanner, letter, numbers)
113
+ token = arc_flag_position?(letter, numbers) ? ARC_FLAG_TOKEN : TOKEN
114
+ scanner.scan_until(token)
115
+ rescue ArgumentError
116
+ nil
117
+ end
118
+
119
+ def consume_token(scanner, letter, numbers, &)
120
+ command = scanner[1]
121
+ return append_number(scanner[2], letter, numbers) unless command
122
+
123
+ flush(letter, numbers, &)
124
+ [command, []]
125
+ end
126
+
127
+ def append_number(number, letter, numbers)
128
+ numbers << number.to_f
129
+ [letter, numbers]
130
+ end
131
+
132
+ def arc_flag_position?(letter, numbers)
133
+ return false unless letter&.downcase == "a"
134
+
135
+ (numbers.length % ARITY.fetch("a")).between?(3, 4)
136
+ end
137
+
138
+ # A group with too few numbers is dropped, and the walk carries on with
139
+ # the next command. An arrowhead placed off a half-read command would
140
+ # be worse than none. A conformant renderer is stricter still — it
141
+ # abandons the whole path from the first error — but Etassia never emits
142
+ # malformed data, and reading the rest costs nothing.
143
+ def flush(letter, numbers)
144
+ return if letter.nil?
145
+
146
+ size = ARITY.fetch(letter.downcase)
147
+ return yield(letter, []) if size.zero?
148
+
149
+ numbers.each_slice(size).with_index do |group, index|
150
+ break if group.size < size
151
+
152
+ yield(index.zero? ? letter : AFTER_FIRST.fetch(letter, letter), group)
153
+ end
154
+ end
155
+
156
+ def apply(letter, args)
157
+ lower = letter.downcase
158
+ args = relative(letter, lower, args)
159
+ clear_unrelated_control(lower)
160
+ return move(args) if lower == "m"
161
+ return close if lower == "z"
162
+ return arc(args) if lower == "a"
163
+
164
+ segment(*references(lower, args))
165
+ end
166
+
167
+ # Smooth commands may only reflect a control from the same curve
168
+ # family immediately before them; retaining either control through a
169
+ # different command would bend a later shorthand curve unexpectedly.
170
+ def clear_unrelated_control(lower)
171
+ case lower
172
+ when "q", "t" then @cubic_control = nil
173
+ when "c", "s" then @quadratic_control = nil
174
+ else
175
+ @quadratic_control = nil
176
+ @cubic_control = nil
177
+ end
178
+ end
179
+
180
+ # SVG gives an arc two degenerate forms, and they are not the same
181
+ # thing. An arc whose endpoints coincide is omitted entirely, so it must
182
+ # not disturb the anchors either: the segment before it is still the end
183
+ # of the path and the segment after it is still the start. Only the
184
+ # zero-radius form becomes a straight line. Handling one and not the
185
+ # other silently dropped the arrowhead off
186
+ # `M 0 0 L 10 0 A 5 5 0 0 1 10 0`.
187
+ #
188
+ # Exact equality, because a relative endpoint of `0 0` is computed as
189
+ # `@point + 0.0` and lands on the current point exactly.
190
+ def arc(args)
191
+ end_point = [args[5], args[6]]
192
+ return if end_point == @point
193
+ return segment(*straight(end_point)) if args[0].zero? || args[1].zero?
194
+
195
+ traverse(end_point)
196
+ end
197
+
198
+ # Moves the current point along a segment that offers no usable
199
+ # tangent. The point has to advance or every command after it lands in
200
+ # the wrong place, but neither end of an arc may anchor an arrowhead.
201
+ #
202
+ # A break in continuity before a pending origin gains a usable direction
203
+ # closes the question rather than letting a later segment supply that
204
+ # direction. An arc always breaks it because the path starts on that
205
+ # unanchorable segment. A later move breaks it only after a zero-length
206
+ # segment has recorded an origin; moves alone leave the question open for
207
+ # the first real segment.
208
+ def traverse(end_point)
209
+ @origin = false if @origin_direction.nil?
210
+ @terminus = nil
211
+ @recent_direction = nil
212
+ @point = end_point
213
+ end
214
+
215
+ # The segment a command draws, as [outgoing reference, incoming
216
+ # reference, end point]. A straight segment is its own tangent, so both
217
+ # references are the far end of it; a curve takes the control point
218
+ # nearest each end.
219
+ #
220
+ # Smooth curves reflect the preceding control in absolute space so
221
+ # relative and absolute commands follow the same geometry. An arc gives
222
+ # no references at all — see the note on the class.
223
+ def references(lower, args)
224
+ send(REFERENCE_BUILDERS.fetch(lower), args)
225
+ end
226
+
227
+ def line_references(args)
228
+ straight(args.values_at(0, 1))
229
+ end
230
+
231
+ def horizontal_references(args)
232
+ straight([args[0], @point[1]])
233
+ end
234
+
235
+ def vertical_references(args)
236
+ straight([@point[0], args[0]])
237
+ end
238
+
239
+ def cubic_references(args)
240
+ @cubic_control = args.values_at(2, 3)
241
+ [args.values_at(0, 1), @cubic_control, args.values_at(4, 5)]
242
+ end
243
+
244
+ def smooth_cubic_references(args)
245
+ outgoing = reflection(@cubic_control)
246
+ @cubic_control = args.values_at(0, 1)
247
+ [outgoing, @cubic_control, args.values_at(2, 3)]
248
+ end
249
+
250
+ def quadratic_references(args)
251
+ @quadratic_control = args.values_at(0, 1)
252
+ [@quadratic_control, @quadratic_control, args.values_at(2, 3)]
253
+ end
254
+
255
+ # ARITY and TOKEN admit no other reference-bearing command here.
256
+ def smooth_quadratic_references(args)
257
+ @quadratic_control = reflection(@quadratic_control)
258
+ [@quadratic_control, @quadratic_control, args.values_at(0, 1)]
259
+ end
260
+
261
+ # Reflecting a control point through the current point. Written as
262
+ # `point + (point - source)` rather than `2 * point - source`, which is
263
+ # the same number but overflows on the way: at `M 1e308 0 S …` the
264
+ # source IS the current point, so the reflection is that point, and
265
+ # doubling it reached Infinity and pointed the head backwards.
266
+ def reflection(control)
267
+ source = control || @point
268
+ @point.zip(source).map { |point, original| point + (point - original) }
269
+ end
270
+
271
+ def straight(destination)
272
+ [destination, @point, destination]
273
+ end
274
+
275
+ # Relative commands offset from the current point. On an arc only the
276
+ # final coordinate pair is a point; the radii and flags are not.
277
+ def relative(letter, lower, args)
278
+ return args if letter == letter.upcase
279
+
280
+ case lower
281
+ when "h" then offset_coordinate(args, 0)
282
+ when "v" then offset_coordinate(args, 1)
283
+ when "a" then relative_arc(args)
284
+ else relative_points(args)
285
+ end
286
+ end
287
+
288
+ def offset_coordinate(args, axis)
289
+ [args[0] + @point[axis]]
290
+ end
291
+
292
+ def relative_arc(args)
293
+ args[0, 5] + relative_points(args.drop(5))
294
+ end
295
+
296
+ def relative_points(args)
297
+ args.each_slice(2).flat_map do |x, y|
298
+ [x + @point[0], y + @point[1]]
299
+ end
300
+ end
301
+
302
+ def move(args)
303
+ @origin = false if @origin && @origin_direction.nil?
304
+ @point = [args[0], args[1]]
305
+ @subpath_start = @point
306
+ @terminus = nil
307
+ @recent_direction = nil
308
+ end
309
+
310
+ def close
311
+ segment(@subpath_start, @point, @subpath_start)
312
+ end
313
+
314
+ def segment(out_reference, in_reference, end_point)
315
+ @origin = [@point, out_reference] if @origin.nil?
316
+ remember_directions(@point, out_reference, in_reference, end_point)
317
+ @terminus = [in_reference, end_point]
318
+ @point = end_point
319
+ end
320
+
321
+ # Degenerate endpoint derivatives still have a direction when another
322
+ # edge of the same control polygon, or an earlier segment, establishes
323
+ # one. Keeping the pair instead of an anchor lets the true endpoint be
324
+ # supplied only when the caller asks for it. For a straight segment the
325
+ # middle pair is reversed, but the first and third pairs are the same,
326
+ # so the reversed one can never be `directions.first` or
327
+ # `directions.last`.
328
+ def remember_directions(*points)
329
+ directions = points.each_cons(2).select do |from, to|
330
+ heading(to, from, to)
331
+ end
332
+ return if directions.empty?
333
+
334
+ @origin_direction ||= directions.first
335
+ @recent_direction = directions.last
336
+ end
337
+
338
+ # Where the anchor sits is not where its direction comes from: the last
339
+ # point heads AWAY from the control point behind it, while the first
340
+ # heads TOWARDS the one in front. Both are the direction of travel, so
341
+ # both are read from the same pair, and the point is passed separately
342
+ # rather than assumed to be one end of it.
343
+ #
344
+ # @param at [Array] the point the anchor sits on
345
+ # @param from [Array] the tail of the direction of travel
346
+ # @param to [Array] the head of the direction of travel
347
+ # @return [Anchor, nil] nil when the two coincide, which leaves no
348
+ # direction to point an arrowhead along
349
+ def heading(at, from, to)
350
+ dx, dy = direction(from, to)
351
+ length = JsMath.hypot(dx, dy)
352
+ return nil unless usable_heading?(at, length)
353
+
354
+ Anchor.new(at[0], at[1], dx / length, dy / length)
355
+ end
356
+
357
+ def direction(from, to)
358
+ delta = to.zip(from).map { |finish, start| finish - start }
359
+ return delta if delta.all?(&:finite?)
360
+
361
+ # Two finite coordinates can still overflow the distance between them:
362
+ # `M -1e308 0 L 1e308 0` is 2e308 apart. A direction does not change
363
+ # with scale, so halving both ends brings the difference back into
364
+ # range and answers the same question. Only reached on overflow, so
365
+ # nothing else loses precision to it.
366
+ to.zip(from).map { |finish, start| (finish / 2) - (start / 2) }
367
+ end
368
+
369
+ def usable_heading?(at, length)
370
+ # Not merely non-zero: path data carrying an out-of-range exponent
371
+ # can put Infinity in the anchor or in a coordinate delta. The guard
372
+ # keeps Infinity/Infinity out of the heading and a non-finite anchor
373
+ # out of the polygon's points.
374
+ at.all?(&:finite?) && length.finite? && length.positive?
375
+ end
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "element"
5
+
6
+ module Etassia
7
+ module Svg
8
+ # SVG Polygon element <polygon>
9
+ #
10
+ # Represents a closed shape defined by a series of points.
11
+ class Polygon < Element
12
+ attribute :points, :string
13
+
14
+ writes_attributes :points
15
+
16
+ # Helper to build points string from coordinates array
17
+ #
18
+ # @param coords [Array<Array>] Array of [x, y] coordinates
19
+ # @return [String] Points string for polygon
20
+ def self.build_points(coords)
21
+ coords.map { |x, y| "#{x},#{y}" }.join(" ")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "element"
5
+
6
+ module Etassia
7
+ module Svg
8
+ # SVG Polyline element <polyline>
9
+ #
10
+ # Represents an open shape defined by a series of connected points.
11
+ class Polyline < Element
12
+ attribute :points, :string
13
+
14
+ # Emitted nothing before this: a Polyline rendered as
15
+ # `<polyline stroke="blue"/>` with no points, and
16
+ # renderer/xy_chart.rb:337 draws line series with it.
17
+ writes_attributes :points
18
+
19
+ # Helper to build points string from coordinates array
20
+ #
21
+ # @param coords [Array<Array>] Array of [x, y] coordinates
22
+ # @return [String] Points string for polyline
23
+ def self.build_points(coords)
24
+ coords.map { |x, y| "#{x},#{y}" }.join(" ")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "element"
5
+
6
+ module Etassia
7
+ module Svg
8
+ # SVG Rectangle element <rect>
9
+ #
10
+ # Represents a rectangle shape with position, dimensions, and optional
11
+ # rounded corners.
12
+ class Rect < Element
13
+ attribute :x, :float
14
+ attribute :y, :float
15
+ attribute :width, :float
16
+ attribute :height, :float
17
+ attribute :rx, :float
18
+ attribute :ry, :float
19
+ attribute :stroke_dasharray, :string
20
+ attribute :fill_opacity, :string
21
+
22
+ # fill-opacity is NOT listed here. Element already emits it for every
23
+ # element, and listing it again produced
24
+ # `fill-opacity="0.5" fill-opacity="0.5"` — the last 5 malformed cases
25
+ # in the corpus.
26
+ #
27
+ # The attribute declaration above stays so `fill_opacity=` is still a
28
+ # settable Ruby accessor -- from_xml no longer works on Rect at all
29
+ # (Lutaml::Model::TypeOnlyMappingError, no `root` declared). It still
30
+ # re-emits through Element exactly once, whichever way it was set.
31
+ writes_attributes :x, :y, :width, :height, :rx, :ry, :stroke_dasharray
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Etassia
6
+ module Svg
7
+ # Represents CSS styling for SVG elements
8
+ #
9
+ # Encapsulates style properties that can be applied to SVG elements
10
+ # as inline styles or style attributes.
11
+ class Style < Lutaml::Model::Serializable
12
+ attribute :fill, :string
13
+ attribute :stroke, :string
14
+ attribute :stroke_width, :float
15
+ attribute :stroke_dasharray, :string
16
+ attribute :opacity, :float
17
+ attribute :fill_opacity, :float
18
+ attribute :stroke_opacity, :float
19
+ attribute :font_family, :string
20
+ attribute :font_size, :float
21
+ attribute :font_weight, :string
22
+ attribute :text_anchor, :string
23
+
24
+ CSS_PROPERTIES = {
25
+ fill: "fill",
26
+ stroke: "stroke",
27
+ stroke_width: "stroke-width",
28
+ stroke_dasharray: "stroke-dasharray",
29
+ opacity: "opacity",
30
+ fill_opacity: "fill-opacity",
31
+ stroke_opacity: "stroke-opacity",
32
+ font_family: "font-family",
33
+ font_size: "font-size",
34
+ font_weight: "font-weight",
35
+ text_anchor: "text-anchor",
36
+ }.freeze
37
+ private_constant :CSS_PROPERTIES
38
+
39
+ # Convert style to CSS string for inline style attribute
40
+ #
41
+ # @return [String] CSS style string
42
+ def to_css
43
+ CSS_PROPERTIES.filter_map do |attribute, property|
44
+ value = public_send(attribute)
45
+ "#{property}:#{value}" if value
46
+ end.join(";")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "element"
5
+ require_relative "numbers"
6
+ require_relative "tspan"
7
+
8
+ module Etassia
9
+ module Svg
10
+ # SVG Text element <text>
11
+ #
12
+ # Displays text content at specified coordinates with optional styling
13
+ # and anchoring. Used for labels, annotations, and textual content.
14
+ class Text < Element
15
+ attribute :x, :float
16
+ attribute :y, :float
17
+ attribute :dx, :float
18
+ attribute :dy, :float
19
+ attribute :text_anchor, :string
20
+ attribute :font_family, :string
21
+ attribute :font_size, :string
22
+ attribute :font_weight, :string
23
+ attribute :font_style, :string
24
+ attribute :text_decoration, :string
25
+ # `textLength` and `lengthAdjust`: the width the text must fill, so a
26
+ # viewer's font cannot move what follows it.
27
+ attribute :text_length, :float
28
+ attribute :length_adjust, :string
29
+ attribute :dominant_baseline, :string
30
+ attribute :content, :string, collection: true
31
+ attribute :tspans, Tspan, collection: true
32
+ # "preserve" keeps the spaces at the edges of tspan runs.
33
+ attribute :xml_space, :string
34
+
35
+ # How far below `y` the baseline sits, in ems -- stands in for
36
+ # `dominant-baseline` (rejected under :metanorma). Conventional
37
+ # approximations, NOT font metrics: `central` is deliberately
38
+ # collapsed onto `middle` since Etassia has none. An unnamed value
39
+ # stays on the alphabetic baseline (deliberate for `mathematical`).
40
+ #
41
+ # Needs a unitless font size in user units; a relative size from
42
+ # a foreign document (`2em`) has no parent to resolve against and
43
+ # reads as the bare number.
44
+ BASELINE_SHIFTS = {
45
+ "middle" => 0.35,
46
+ "central" => 0.35,
47
+ "hanging" => 0.8,
48
+ "text-before-edge" => 0.8,
49
+ "text-after-edge" => -0.2,
50
+ "ideographic" => -0.2,
51
+ }.freeze
52
+
53
+ # The CSS initial font-size, used when a Text carries a baseline
54
+ # request but no size of its own. Every renderer sets one; this keeps
55
+ # a hand-built element from shifting by an arbitrary amount.
56
+ DEFAULT_FONT_SIZE = 16.0
57
+
58
+ # One line of a multi-line label, in ems (the former `dy="1.2em"`).
59
+ LINE_HEIGHT_EMS = 1.2
60
+ private_constant :BASELINE_SHIFTS, :DEFAULT_FONT_SIZE, :LINE_HEIGHT_EMS
61
+
62
+ # `dx` and `dy` are folded into x/y rather than emitted, never written
63
+ # out directly.
64
+ #
65
+ # Written out rather than declared with .writes_attributes, because
66
+ # `x` and `y` are emitted from computed readers — see #offset_x and
67
+ # #baseline_y.
68
+ ATTRIBUTE_PAIRS = [
69
+ ["x", :offset_x],
70
+ ["y", :baseline_y],
71
+ ["text-anchor", :text_anchor],
72
+ ["font-family", :font_family],
73
+ ["font-size", :font_size],
74
+ ["font-weight", :font_weight],
75
+ ["font-style", :font_style],
76
+ ["text-decoration", :text_decoration],
77
+ ["textLength", :text_length],
78
+ ["lengthAdjust", :length_adjust],
79
+ ["xml:space", :xml_space],
80
+ ].map(&:freeze).freeze
81
+ private_constant :ATTRIBUTE_PAIRS
82
+
83
+ protected
84
+
85
+ # A text element carries content, so it is not the self-closing tag
86
+ # the base class writes.
87
+ #
88
+ # `content` is declared `collection: true`. Renderers assign a plain
89
+ # String, so join rather than interpolate — Array(content).join is a
90
+ # no-op for a plain string and correct if content is ever multi-valued.
91
+ # Content may hold newlines, so this is the one entry in #xml_lines
92
+ # that is not a single line. It is why Group indents entries rather
93
+ # than lines.
94
+ #
95
+ # Every renderer call site sets exactly one of `content`/`tspans`.
96
+ #
97
+ # @return [String] XML string
98
+ def element_markup
99
+ "<text#{build_attributes}>#{body}</text>"
100
+ end
101
+
102
+ def element_attributes
103
+ attribute_pairs(ATTRIBUTE_PAIRS)
104
+ end
105
+
106
+ # SVG dx/dy are per-glyph offset lists. Their :float declarations make
107
+ # lutaml keep only the leading number before this method sees the value.
108
+ def offset_x
109
+ offset = Numbers.read(dx)
110
+ return x if offset.nil?
111
+
112
+ computed_x = (Numbers.read(x) || 0.0) + offset
113
+ return x unless computed_x.finite?
114
+
115
+ Numbers.write(computed_x)
116
+ end
117
+
118
+ # `y` with the baseline request folded in.
119
+ #
120
+ # Returns the reader untouched when there is no shift, so a Text that
121
+ # never asked for one serialises exactly as it did before. It is also
122
+ # the only usable fallback when the computed coordinate overflows.
123
+ #
124
+ # @return [Object, nil] the y attribute value
125
+ def baseline_y
126
+ shift = baseline_shift
127
+ offset = Numbers.read(dy)
128
+ return y if shift.zero? && offset.nil?
129
+
130
+ computed_y = (Numbers.read(y) || 0.0) + (offset || 0.0) + shift
131
+ return y unless computed_y.finite?
132
+
133
+ Numbers.write(computed_y)
134
+ end
135
+
136
+ private
137
+
138
+ # Every renderer call site sets exactly one of `content`/`tspans`.
139
+ #
140
+ # @return [String]
141
+ def body
142
+ place_lines
143
+ escaped_content = Escaping.escape_text(Array(content).join)
144
+ escaped_content + Array(tspans).map(&:to_xml).join
145
+ end
146
+
147
+ # Gives each run that starts a new line an absolute `y`: this text's
148
+ # baseline plus the lines accumulated so far. Idempotent, so repeated
149
+ # serialisation writes the same values.
150
+ def place_lines
151
+ lines = 0
152
+ Array(tspans).each do |run|
153
+ next unless run.line_shift
154
+
155
+ lines += run.line_shift
156
+ run.y = line_y(lines)
157
+ end
158
+ end
159
+
160
+ def line_y(lines)
161
+ size = Numbers.read(font_size) || DEFAULT_FONT_SIZE
162
+ base = Numbers.read(baseline_y) || 0.0
163
+ placed = base + (lines * LINE_HEIGHT_EMS * size)
164
+ placed.finite? ? placed.round(4) : nil
165
+ end
166
+
167
+ # May be non-finite when the font size is: #baseline_y is the one place
168
+ # that can fall back, so the check lives there rather than here too.
169
+ #
170
+ # @return [Float] the baseline offset in user units
171
+ def baseline_shift
172
+ return 0.0 if Escaping.blank?(dominant_baseline)
173
+
174
+ ems = BASELINE_SHIFTS.fetch(dominant_baseline.to_s.strip.downcase, 0.0)
175
+ return 0.0 if ems.zero?
176
+
177
+ ems * (Numbers.read(font_size) || DEFAULT_FONT_SIZE)
178
+ end
179
+ end
180
+ end
181
+ end