ea 0.3.1 → 0.5.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 (544) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/TODO.complete/01-t-xref-parsing.md +91 -0
  4. data/TODO.complete/02-per-label-geometry.md +50 -0
  5. data/TODO.complete/03-object-properties-constraints.md +57 -0
  6. data/TODO.complete/04-operation-params.md +37 -0
  7. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  8. data/TODO.complete/06-emf-image-rendering.md +34 -0
  9. data/TODO.complete/07-auxiliary-tables.md +47 -0
  10. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  11. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  12. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  13. data/TODO.complete/20-cli-diff.md +46 -0
  14. data/TODO.complete/21-cli-render.md +26 -0
  15. data/TODO.complete/22-cli-export.md +29 -0
  16. data/TODO.complete/23-cli-mdg.md +23 -0
  17. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  18. data/TODO.complete/31-public-send-cleanup.md +61 -0
  19. data/TODO.complete/32-spec-coverage.md +33 -0
  20. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  21. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  22. data/TODO.complete/37-cli-lint.md +32 -0
  23. data/TODO.complete/38-cli-query.md +26 -0
  24. data/TODO.complete/39-cli-info.md +23 -0
  25. data/TODO.complete/40-ocl-evaluator.md +31 -0
  26. data/TODO.complete/41-round-trip-spec.md +18 -0
  27. data/TODO.complete/42-html-diff-report.md +18 -0
  28. data/TODO.complete/43-json-schema-export.md +20 -0
  29. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  30. data/TODO.complete/45-shapescript-extensions.md +25 -0
  31. data/TODO.complete/46-xsd-consolidation.md +20 -0
  32. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  33. data/TODO.complete/STATUS.md +109 -0
  34. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  35. data/TODO.diagrams/01-xmi-extension-elements-parsing.md +67 -0
  36. data/TODO.diagrams/02-diagram-element-rich-model.md +61 -0
  37. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  38. data/TODO.diagrams/03-ea-style-svg-emitter.md +70 -0
  39. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  40. data/TODO.diagrams/04-connector-routing-from-soid-eoid.md +55 -0
  41. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  42. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  43. data/TODO.diagrams/05-marker-rendering-per-relationship-type.md +49 -0
  44. data/TODO.diagrams/06-connector-labels.md +49 -0
  45. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  46. data/TODO.diagrams/07-canvas-size-and-viewbox.md +39 -0
  47. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  48. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  49. data/TODO.diagrams/08-visual-regression-harness.md +34 -0
  50. data/TODO.diagrams/09-cli-render-all-diagrams.md +25 -0
  51. data/TODO.diagrams/10-stereotype-color-config.md +29 -0
  52. data/TODO.diagrams/11-elkrb-auto-layout-integration.md +53 -0
  53. data/TODO.diagrams/12-connector-labels.md +40 -0
  54. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  55. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  56. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  57. data/TODO.diagrams/14-center-header-text.md +33 -0
  58. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  59. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  60. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  61. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  62. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  63. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  64. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  65. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  66. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  67. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  68. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  69. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  70. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  71. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  72. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  73. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  74. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  75. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  76. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  77. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  78. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  79. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  80. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  81. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  82. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  83. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  84. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  85. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  86. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  87. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  88. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  89. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  90. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  91. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  92. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  93. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  94. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  95. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  96. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  97. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  98. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  99. data/TODO.diagrams/50-examples-parity.md +85 -0
  100. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  101. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  102. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  103. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  104. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  105. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  106. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  107. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  108. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  109. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  110. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  111. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  112. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  113. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  114. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  115. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  116. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  117. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  118. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  119. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  120. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  121. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  122. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  123. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  124. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  125. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  126. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  127. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  128. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  129. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  130. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  131. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  132. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  133. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  134. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  135. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  136. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  137. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  138. data/TODO.diagrams/README.md +69 -0
  139. data/TODO.diagrams/STATUS.md +73 -0
  140. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  141. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  142. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  143. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  144. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  145. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  146. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  147. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  148. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  149. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  150. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  151. data/TODO.fixup/12-path-style-harmony.md +23 -0
  152. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  153. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  154. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  155. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  156. data/TODO.fixup/17-path-count-calibration.md +27 -0
  157. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  158. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  159. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  160. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  161. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  162. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  163. data/TODO.fixup/24-note-element-rendering.md +23 -0
  164. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  165. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  166. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  167. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  168. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  169. data/TODO.fixup/30-theme-system-stub.md +46 -0
  170. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  171. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  172. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  173. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  174. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  175. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  176. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  177. data/TODO.fixup/38-note-element-rendering.md +15 -0
  178. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  179. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  180. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  181. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  182. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  183. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  184. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  185. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  186. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  187. data/TODO.fixup/48-background-rect-format.md +15 -0
  188. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  189. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  190. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  191. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  192. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  193. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  194. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  195. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  196. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  197. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  198. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  199. data/TODO.fixup/60-cli-theme-option.md +22 -0
  200. data/TODO.fixup/61-theme-validation.md +28 -0
  201. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  202. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  203. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  204. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  205. data/TODO.fixup/66-styleex-writeback.md +22 -0
  206. data/TODO.fixup/67-delete-dead-code.md +28 -0
  207. data/TODO.fixup/68-figure-parity-status.md +82 -0
  208. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  209. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  210. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  211. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  212. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  213. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  214. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  215. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  216. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  217. data/TODO.fixup/78-note-element-rendering.md +30 -0
  218. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  219. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  220. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  221. data/config/qea_schema.yml +356 -0
  222. data/config/stereotype_colors.yml +15 -0
  223. data/config/themes/119.yml +98 -0
  224. data/config/themes/default.yml +14 -0
  225. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  226. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  227. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  228. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  229. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/UmlModel_template/model.xml +23 -0
  231. data/examples/exports/basic/UML_EA.DTD +6856 -0
  232. data/examples/exports/basic/model.xml +8039 -0
  233. data/examples/exports/simple/UML_EA.DTD +6856 -0
  234. data/examples/exports/simple/model.xml +585 -0
  235. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple_example/model.xml +23 -0
  237. data/examples/exports/test/UML_EA.DTD +6856 -0
  238. data/examples/exports/test/model.xml +1638 -0
  239. data/examples/qea/basic.qea +0 -0
  240. data/frontend/README.md +51 -0
  241. data/frontend/dist/app.iife.js +24 -0
  242. data/frontend/dist/style.css +1 -0
  243. data/frontend/index.html +26 -0
  244. data/frontend/package-lock.json +2359 -0
  245. data/frontend/package.json +26 -0
  246. data/frontend/src/App.vue +59 -0
  247. data/frontend/src/app.ts +8 -0
  248. data/frontend/src/components/AppHeader.vue +127 -0
  249. data/frontend/src/components/AppSidebar.vue +119 -0
  250. data/frontend/src/components/ClassDetails.vue +247 -0
  251. data/frontend/src/components/DiagramView.vue +79 -0
  252. data/frontend/src/components/PackageDetails.vue +92 -0
  253. data/frontend/src/components/PackageTreeNode.vue +64 -0
  254. data/frontend/src/components/SearchResults.vue +48 -0
  255. data/frontend/src/components/WelcomeView.vue +87 -0
  256. data/frontend/src/shims-vue.d.ts +5 -0
  257. data/frontend/src/stores/dataStore.ts +79 -0
  258. data/frontend/src/stores/uiStore.ts +167 -0
  259. data/frontend/src/styles/main.css +1566 -0
  260. data/frontend/src/types.ts +221 -0
  261. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  262. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  263. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  266. data/frontend/tests/e2e/render.spec.ts +188 -0
  267. data/frontend/tsconfig.json +20 -0
  268. data/frontend/vite.config.ts +26 -0
  269. data/frontend/vitest.config.ts +8 -0
  270. data/lib/ea/cli/app.rb +62 -1
  271. data/lib/ea/cli/command/diagrams.rb +37 -28
  272. data/lib/ea/cli/command/diff.rb +46 -0
  273. data/lib/ea/cli/command/export.rb +80 -0
  274. data/lib/ea/cli/command/info.rb +75 -0
  275. data/lib/ea/cli/command/lint.rb +48 -0
  276. data/lib/ea/cli/command/mdg.rb +98 -0
  277. data/lib/ea/cli/command/query.rb +34 -0
  278. data/lib/ea/cli/command/render.rb +135 -0
  279. data/lib/ea/cli/command/svg.rb +43 -6
  280. data/lib/ea/cli/command.rb +7 -0
  281. data/lib/ea/diagram/display_config.rb +184 -0
  282. data/lib/ea/diagram.rb +4 -41
  283. data/lib/ea/diff/change.rb +30 -0
  284. data/lib/ea/diff/comparator.rb +82 -0
  285. data/lib/ea/diff/html_reporter.rb +91 -0
  286. data/lib/ea/diff.rb +15 -0
  287. data/lib/ea/export/json/generator.rb +42 -0
  288. data/lib/ea/export/json.rb +12 -0
  289. data/lib/ea/export/json_schema/generator.rb +77 -0
  290. data/lib/ea/export/json_schema.rb +12 -0
  291. data/lib/ea/export/plantuml/generator.rb +73 -0
  292. data/lib/ea/export/plantuml.rb +12 -0
  293. data/lib/ea/export/xmi/generator.rb +77 -0
  294. data/lib/ea/export/xmi.rb +11 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +142 -0
  297. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  298. data/lib/ea/export/xsd.rb +19 -0
  299. data/lib/ea/export.rb +14 -0
  300. data/lib/ea/image/emf_renderer.rb +42 -0
  301. data/lib/ea/image.rb +12 -0
  302. data/lib/ea/lint/engine.rb +30 -0
  303. data/lib/ea/lint/lint_rule.rb +38 -0
  304. data/lib/ea/lint/offense.rb +18 -0
  305. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  306. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  307. data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
  308. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  309. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  310. data/lib/ea/lint/rules.rb +14 -0
  311. data/lib/ea/lint.rb +13 -0
  312. data/lib/ea/mdg/document.rb +120 -0
  313. data/lib/ea/mdg/loader.rb +118 -0
  314. data/lib/ea/mdg/registry.rb +98 -0
  315. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  316. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  317. data/lib/ea/mdg/xml/apply.rb +18 -0
  318. data/lib/ea/mdg/xml/content.rb +18 -0
  319. data/lib/ea/mdg/xml/documentation.rb +24 -0
  320. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  321. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  322. data/lib/ea/mdg/xml/tag.rb +28 -0
  323. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  324. data/lib/ea/mdg/xml/technology.rb +28 -0
  325. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  326. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  327. data/lib/ea/mdg/xml.rb +24 -0
  328. data/lib/ea/mdg.rb +25 -0
  329. data/lib/ea/model/classifier.rb +4 -0
  330. data/lib/ea/model/constraint.rb +24 -0
  331. data/lib/ea/model/diagram.rb +85 -0
  332. data/lib/ea/model/diagram_connector.rb +53 -1
  333. data/lib/ea/model/diagram_element.rb +44 -0
  334. data/lib/ea/model/document.rb +47 -2
  335. data/lib/ea/model/ghost_label.rb +32 -0
  336. data/lib/ea/model/instance_specification.rb +34 -0
  337. data/lib/ea/model/legend.rb +43 -0
  338. data/lib/ea/model/legend_item.rb +26 -0
  339. data/lib/ea/model/note.rb +28 -0
  340. data/lib/ea/model/operation.rb +2 -0
  341. data/lib/ea/model/property.rb +2 -0
  342. data/lib/ea/model/relationship.rb +2 -0
  343. data/lib/ea/model/signal.rb +14 -0
  344. data/lib/ea/model/slot.rb +19 -0
  345. data/lib/ea/model.rb +8 -0
  346. data/lib/ea/ocl/evaluator.rb +94 -0
  347. data/lib/ea/ocl/nodes.rb +30 -0
  348. data/lib/ea/ocl/parser.rb +133 -0
  349. data/lib/ea/ocl/unsupported_error.rb +7 -0
  350. data/lib/ea/ocl.rb +13 -0
  351. data/lib/ea/qea/factory/document_builder.rb +24 -10
  352. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  353. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  354. data/lib/ea/qea/models/ea_author.rb +35 -0
  355. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  356. data/lib/ea/qea/models/ea_image.rb +53 -0
  357. data/lib/ea/qea/models/ea_implement.rb +32 -0
  358. data/lib/ea/qea/models/ea_list.rb +42 -0
  359. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  360. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  361. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  362. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  363. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  364. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  365. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  366. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  367. data/lib/ea/qea/models/ea_operation.rb +7 -0
  368. data/lib/ea/qea/models/ea_palette.rb +30 -0
  369. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  370. data/lib/ea/qea/models/ea_phase.rb +40 -0
  371. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  372. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  373. data/lib/ea/qea/models/ea_version.rb +39 -0
  374. data/lib/ea/qea/models/ea_xref.rb +11 -1
  375. data/lib/ea/qea/models.rb +19 -0
  376. data/lib/ea/qea/services/database_loader.rb +22 -1
  377. data/lib/ea/query/builder.rb +108 -0
  378. data/lib/ea/query.rb +9 -0
  379. data/lib/ea/render/image_converter.rb +101 -0
  380. data/lib/ea/render.rb +10 -0
  381. data/lib/ea/shapescript/parser.rb +208 -0
  382. data/lib/ea/shapescript/renderer.rb +74 -0
  383. data/lib/ea/shapescript/shape.rb +34 -0
  384. data/lib/ea/shapescript.rb +24 -0
  385. data/lib/ea/sources/qea/adapter.rb +22 -5
  386. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  387. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  388. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  389. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  390. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  391. data/lib/ea/sources/qea/note_builder.rb +58 -0
  392. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  393. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  394. data/lib/ea/sources/qea/package_builder.rb +16 -0
  395. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  396. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  397. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  398. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  399. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  400. data/lib/ea/sources/qea/xref/record.rb +41 -0
  401. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  402. data/lib/ea/sources/qea/xref.rb +27 -0
  403. data/lib/ea/sources/qea.rb +4 -0
  404. data/lib/ea/sources/xmi/adapter.rb +80 -2
  405. data/lib/ea/sources/xmi/annotation_builder.rb +1 -1
  406. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  407. data/lib/ea/sources/xmi/diagram_builder.rb +242 -45
  408. data/lib/ea/sources/xmi/extension_elements.rb +74 -0
  409. data/lib/ea/sources/xmi/extension_geometry_parser.rb +124 -0
  410. data/lib/ea/sources/xmi/extension_style_parser.rb +91 -0
  411. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  412. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  413. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  414. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  415. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  416. data/lib/ea/sources/xmi.rb +7 -0
  417. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  418. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  419. data/lib/ea/spa/output/strategy.rb +34 -0
  420. data/lib/ea/svg/connector_router.rb +134 -0
  421. data/lib/ea/svg/ea_emitter/background.rb +15 -0
  422. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  423. data/lib/ea/svg/ea_emitter/canvas.rb +88 -0
  424. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  425. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  426. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  427. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  428. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  429. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  430. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  431. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  432. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  433. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  434. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  435. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  436. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  437. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  438. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  439. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  440. data/lib/ea/svg/ea_emitter/connectors.rb +84 -0
  441. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  442. data/lib/ea/svg/ea_emitter/document.rb +35 -0
  443. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  444. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  445. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  446. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  447. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  448. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  449. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  450. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  451. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  452. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  453. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  454. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  458. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  459. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  460. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  461. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  462. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  463. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  464. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  465. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  466. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  467. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  468. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  469. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  470. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  471. data/lib/ea/svg/ea_emitter/elements.rb +357 -0
  472. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  473. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  474. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  475. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  476. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  477. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  478. data/lib/ea/svg/ea_emitter/labels.rb +130 -0
  479. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  480. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  481. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  482. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  483. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  484. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  485. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  486. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  487. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  488. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  489. data/lib/ea/svg/ea_emitter/markers.rb +404 -0
  490. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  491. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  492. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  493. data/lib/ea/svg/ea_emitter.rb +51 -0
  494. data/lib/ea/svg/parity/checker.rb +105 -0
  495. data/lib/ea/svg/parity/source.rb +59 -0
  496. data/lib/ea/svg/parity/suite.rb +113 -0
  497. data/lib/ea/svg/parity.rb +14 -0
  498. data/lib/ea/svg/stereotype_color_resolver.rb +50 -0
  499. data/lib/ea/svg.rb +4 -15
  500. data/lib/ea/theme/definition.rb +176 -0
  501. data/lib/ea/theme/loader.rb +125 -0
  502. data/lib/ea/theme/registry.rb +96 -0
  503. data/lib/ea/theme.rb +37 -0
  504. data/lib/ea/transformations.rb +5 -88
  505. data/lib/ea/version.rb +4 -1
  506. data/lib/ea.rb +14 -4
  507. data/lib/tasks/svg_bench.rake +87 -0
  508. data/lutaml-uml-0.5.1.gem +0 -0
  509. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  510. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  511. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  512. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  520. metadata +473 -26
  521. data/lib/ea/diagram/configuration.rb +0 -379
  522. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  523. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  524. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  525. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  526. data/lib/ea/diagram/element_renderers.rb +0 -43
  527. data/lib/ea/diagram/extractor.rb +0 -566
  528. data/lib/ea/diagram/layout_engine.rb +0 -170
  529. data/lib/ea/diagram/path_builder.rb +0 -202
  530. data/lib/ea/diagram/style_parser.rb +0 -42
  531. data/lib/ea/diagram/style_resolver.rb +0 -291
  532. data/lib/ea/diagram/svg_renderer.rb +0 -274
  533. data/lib/ea/diagram/util.rb +0 -73
  534. data/lib/ea/svg/bounds_calculator.rb +0 -74
  535. data/lib/ea/svg/connector_path.rb +0 -138
  536. data/lib/ea/svg/element_box.rb +0 -256
  537. data/lib/ea/svg/renderer.rb +0 -77
  538. data/lib/ea/svg/style_resolver.rb +0 -72
  539. data/lib/ea/transformations/configuration.rb +0 -333
  540. data/lib/ea/transformations/format_registry.rb +0 -366
  541. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  542. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  543. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  544. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Emits marker `<polygon>` and `<path>` elements per connector,
7
+ # matching EA's encoding rules. Returns an Array of `Layer`
8
+ # structs bucketed by marker style.
9
+ #
10
+ # Layer style keys:
11
+ # - :diamond_filled → 4-pt <polygon>, black fill
12
+ # - :triangle_open → 3-pt <polygon>, white fill
13
+ # - :arrow → 3-pt <path>, no fill (shares style with
14
+ # connector lines — same stroke, no fill)
15
+ #
16
+ # Default mode (`grouped: true`) consolidates all markers of
17
+ # the same style into ONE `<g>`. Pass `grouped: false` for
18
+ # per-connector grouping.
19
+ class Markers
20
+ DIAMOND_HALF_W = 5
21
+ DIAMOND_HALF_H = 10
22
+ TRI_HALF_BASE = 6
23
+ TRI_HEIGHT = 11
24
+ ARROW_HALF_BASE = 6
25
+ ARROW_HEIGHT = 11
26
+
27
+ attr_reader :diagram, :model_index, :canvas, :grouped, :stroke_width
28
+
29
+ def initialize(diagram, model_index:, canvas: nil, grouped: true, stroke_width: 2)
30
+ @diagram = diagram
31
+ @model_index = model_index
32
+ @canvas = canvas
33
+ @grouped = grouped
34
+ @stroke_width = stroke_width
35
+ end
36
+
37
+ def layers
38
+ entries = visible_connectors.flat_map { |c| entries_for(c) }
39
+ return [] if entries.empty?
40
+
41
+ if grouped
42
+ entries.group_by(&:style_key).map do |key, grouped_entries|
43
+ # EA emits ONE marker per connector regardless of
44
+ # anchor overlap. Verified against plateau Bridge
45
+ # diagram: 9 generalizations fanning from the same
46
+ # parent anchor produce 9 distinct triangle polygons
47
+ # at the same coordinates. The earlier "dedup by
48
+ # anchor" hypothesis is disproven by reference SVGs.
49
+ Layer.new(style_key: key,
50
+ style: style_for(key),
51
+ body: grouped_entries.map(&:body).join("\n "))
52
+ end
53
+ else
54
+ entries.map { |e| Layer.new(style_key: e.style_key, style: style_for(e.style_key), body: e.body) }
55
+ end
56
+ end
57
+
58
+ def render
59
+ layers.map(&:to_svg).join("\n")
60
+ end
61
+
62
+ # Backwards-compatible API: count of marker bodies emitted.
63
+ def count_for(connector)
64
+ return 0 if connector.hidden
65
+ return 0 unless connector.waypoints&.any? { |w| w.position }
66
+
67
+ entries_for(connector).size
68
+ end
69
+
70
+ private
71
+
72
+ # Entry pairs the rendered SVG body with the structural
73
+ # anchor (rounded integer [x, y] pair). Anchor is retained
74
+ # for future use (e.g. overlap diagnostics) but no longer
75
+ # drives dedup — EA emits one marker per connector.
76
+ Entry = Struct.new(:style_key, :body, :anchor,
77
+ :connector_waypoint_count,
78
+ keyword_init: true) do
79
+ def hash
80
+ anchor.hash
81
+ end
82
+
83
+ def eql?(other)
84
+ other.is_a?(Entry) && anchor == other.anchor
85
+ end
86
+ end
87
+
88
+ def style_for(key)
89
+ case key
90
+ when :diamond_filled
91
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:1.00"
92
+ when :triangle_open
93
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#FFFFFF;fill-opacity:1.00; stroke:#000000; stroke-opacity:1.00"
94
+ when :connector_line
95
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#000000; stroke-opacity:1.00"
96
+ end
97
+ end
98
+
99
+ def visible_connectors
100
+ (diagram.connectors || []).select(&:renderable?)
101
+ end
102
+
103
+ def entries_for(connector)
104
+ specs = specs_for(connector)
105
+ waypoint_count = waypoint_pairs(connector).size
106
+ specs.filter_map do |spec|
107
+ next nil if suppress_arrow?(spec, connector)
108
+
109
+ body = render_shape(spec)
110
+ next nil unless body
111
+
112
+ Entry.new(style_key: style_key_for(spec),
113
+ body: body,
114
+ anchor: anchor_for(spec),
115
+ connector_waypoint_count: waypoint_count)
116
+ end
117
+ end
118
+
119
+ # Suppress arrow markers when:
120
+ # 1. The diagram is an Object diagram (instance links).
121
+ # 2. The connector is an unidirectional Association with
122
+ # significant waypoint deviation from a straight line
123
+ # (> 3px). EA treats connectors with small deviations as
124
+ # straight (WITH arrow); large deviations are orthogonal/
125
+ # tree-routed (NO arrow). Bidirectional associations always
126
+ # show arrows at both ends regardless of routing.
127
+ BEND_DEVIATION_THRESHOLD = 3
128
+
129
+ def suppress_arrow?(spec, connector)
130
+ return false unless spec.shape == :arrow
131
+ return true if object_diagram? && association?(connector)
132
+ return false if connector.direction == "Bi-Directional"
133
+ return true if horizontal_tree_routed?(connector)
134
+
135
+ false
136
+ end
137
+
138
+ # A "horizontal tree-routed" connector has its source and
139
+ # target endpoints at the SAME y-coordinate with interior
140
+ # waypoints deviating above and below — the classic EA tree
141
+ # routing pattern where multiple connectors fan out from a
142
+ # shared parent at the same height. These don't render arrow
143
+ # markers in basic.qea Classes diagrams. This check is
144
+ # deliberately narrow to avoid suppressing arrows on plateau
145
+ # connectors where source and target are at different heights.
146
+ def horizontal_tree_routed?(connector)
147
+ return false unless association?(connector)
148
+ return false if object_diagram?
149
+
150
+ points = waypoint_pairs(connector)
151
+ return false if points.size < 4
152
+
153
+ source = points.first
154
+ target = points.last
155
+ return false unless source[1] == target[1]
156
+
157
+ max_bend_deviation(connector) > BEND_DEVIATION_THRESHOLD
158
+ end
159
+
160
+ def tree_routed_association?(connector)
161
+ return false unless association?(connector)
162
+ return false if object_diagram?
163
+ return false if connector.direction == "Bi-Directional"
164
+
165
+ # Only apply the tree-routed heuristic to connectors with
166
+ # 4+ waypoints AND significant bend deviation. 3-waypoint
167
+ # connectors with a single bend are too common on plateau
168
+ # to suppress reliably.
169
+ points = waypoint_pairs(connector)
170
+ return false if points.size < 4
171
+
172
+ max_bend_deviation(connector) > BEND_DEVIATION_THRESHOLD
173
+ end
174
+
175
+ def max_bend_deviation(connector)
176
+ points = waypoint_pairs(connector)
177
+ return 0 if points.size < 3
178
+
179
+ source = points.first
180
+ target = points.last
181
+ interior = points[1..-2]
182
+ interior.map { |p| perpendicular_distance(p, source, target) }.max || 0
183
+ end
184
+
185
+ def perpendicular_distance(point, line_start, line_end)
186
+ px, py = point
187
+ x1, y1 = line_start
188
+ x2, y2 = line_end
189
+ dx = x2 - x1
190
+ dy = y2 - y1
191
+ length_sq = dx * dx + dy * dy
192
+ return 0 if length_sq.zero?
193
+
194
+ ((dy * px - dx * py + x2 * y1 - y2 * x1).abs / Math.sqrt(length_sq)).round
195
+ end
196
+
197
+ def association?(connector)
198
+ effective_type(connector) == "Association"
199
+ end
200
+
201
+ def object_diagram?
202
+ diagram.diagram_type == "Object"
203
+ end
204
+
205
+ # Rounded [x, y] of the marker's tip (anchor) in canvas
206
+ # coordinates. Used for deduplication.
207
+ def anchor_for(spec)
208
+ tx, ty = translate_point(spec.anchor)
209
+ [tx.round, ty.round]
210
+ end
211
+
212
+ def style_key_for(spec)
213
+ case spec.shape
214
+ when :diamond then :diamond_filled
215
+ when :triangle then :triangle_open
216
+ when :plus then :connector_line # shares style with line <g>
217
+ when :arrow then :connector_line # share with line <g>
218
+ when :package_anchor then :connector_line
219
+ end
220
+ end
221
+
222
+ # Rounded [x, y] of the marker's tip (anchor) in canvas
223
+ # coordinates. Used for deduplication.
224
+ def anchor_for(spec)
225
+ tx, ty = translate_point(spec.anchor)
226
+ [tx.round, ty.round]
227
+ end
228
+
229
+ def render_shape(spec)
230
+ case spec.shape
231
+ when :diamond then diamond_polygon(spec.anchor, spec.base)
232
+ when :triangle then triangle_polygon(spec.anchor, spec.base)
233
+ when :plus then plus_path(spec.anchor)
234
+ when :arrow then arrow_path(spec.anchor, spec.base)
235
+ when :package_anchor then package_anchor_path(spec.anchor, spec.base)
236
+ end
237
+ end
238
+
239
+ # Plus marker: two crossed line segments at the anchor.
240
+ # EA encodes as one <path> with two sub-paths:
241
+ # M cx cy-8 L cx cy+8 (vertical arm)
242
+ # M cx+8 cy L cx-8 cy (horizontal arm)
243
+ def plus_path(anchor)
244
+ cx, cy = translate_point(anchor)
245
+ cx_str = Canvas.coord(cx)
246
+ cy_str = Canvas.coord(cy)
247
+ up = Canvas.coord(cy - Marker::Plus::ARM_LENGTH)
248
+ down = Canvas.coord(cy + Marker::Plus::ARM_LENGTH)
249
+ left = Canvas.coord(cx - Marker::Plus::ARM_LENGTH)
250
+ right = Canvas.coord(cx + Marker::Plus::ARM_LENGTH)
251
+ d = "M #{cx_str} #{up} L #{cx_str} #{down} M #{right} #{cy_str} L #{left} #{cy_str}"
252
+ %(<path d="#{d}" shape-rendering="auto"/>)
253
+ end
254
+
255
+ def specs_for(connector)
256
+ points = waypoint_pairs(connector)
257
+ return [] if points.size < 2
258
+
259
+ source = points.first
260
+ target = points.last
261
+ before_target = points[-2] || source
262
+ after_source = points[1] || target
263
+ Marker::Registry.specs_for(connector,
264
+ effective_type(connector),
265
+ source, target,
266
+ before_target, after_source,
267
+ relationship: relationship_for(connector))
268
+ end
269
+
270
+ def effective_type(connector)
271
+ return connector.connector_type if connector.connector_type
272
+
273
+ rel = relationship_for(connector)
274
+ return "Association" unless rel
275
+
276
+ # Use the polymorphic kind field rather than class-name
277
+ # inspection - subclasses set `relationship_kind` at the
278
+ # model level (e.g. Association → "association", which we
279
+ # then capitalize to match EA's Connector_Type).
280
+ kind = rel.relationship_kind || "association"
281
+ kind.capitalize
282
+ end
283
+
284
+ def relationship_for(connector)
285
+ return nil unless connector.relationship_ref && model_index
286
+
287
+ model_index[connector.relationship_ref]
288
+ end
289
+
290
+ def diamond_polygon(tip, base)
291
+ tx, ty = translate_point(tip)
292
+ bx, by = translate_point(base)
293
+ ux, uy = unit_vector(tx, ty, bx, by)
294
+ return nil unless ux
295
+
296
+ right_x = tx - ux * DIAMOND_HALF_H + (-uy) * DIAMOND_HALF_W
297
+ right_y = ty - uy * DIAMOND_HALF_H + ux * DIAMOND_HALF_W
298
+ back_x = tx - ux * (DIAMOND_HALF_H * 2)
299
+ back_y = ty - uy * (DIAMOND_HALF_H * 2)
300
+ left_x = tx - ux * DIAMOND_HALF_H - (-uy) * DIAMOND_HALF_W
301
+ left_y = ty - uy * DIAMOND_HALF_H - ux * DIAMOND_HALF_W
302
+ pts = [
303
+ Canvas.coord(tx), Canvas.coord(ty),
304
+ Canvas.coord(right_x), Canvas.coord(right_y),
305
+ Canvas.coord(back_x), Canvas.coord(back_y),
306
+ Canvas.coord(left_x), Canvas.coord(left_y)
307
+ ].join(" ")
308
+ %(<polygon points="#{pts}" shape-rendering="auto" style="fill-rule:evenodd;"/>)
309
+ end
310
+
311
+ def triangle_polygon(tip, base)
312
+ tx, ty = translate_point(tip)
313
+ bx, by = translate_point(base)
314
+ ux, uy = unit_vector(tx, ty, bx, by)
315
+ return nil unless ux
316
+
317
+ back_x = tx - ux * TRI_HEIGHT
318
+ back_y = ty - uy * TRI_HEIGHT
319
+ w1_x = back_x + (-uy) * TRI_HALF_BASE
320
+ w1_y = back_y + ux * TRI_HALF_BASE
321
+ w2_x = back_x - (-uy) * TRI_HALF_BASE
322
+ w2_y = back_y - ux * TRI_HALF_BASE
323
+ pts = [
324
+ Canvas.coord(tx), Canvas.coord(ty),
325
+ Canvas.coord(w1_x), Canvas.coord(w1_y),
326
+ Canvas.coord(w2_x), Canvas.coord(w2_y)
327
+ ].join(" ")
328
+ %(<polygon points="#{pts}" shape-rendering="auto" style="fill-rule:evenodd;"/>)
329
+ end
330
+
331
+ def arrow_path(tip, base)
332
+ tx, ty = translate_point(tip)
333
+ bx, by = translate_point(base)
334
+ ux, uy = unit_vector(tx, ty, bx, by)
335
+ return nil unless ux
336
+
337
+ back_x = tx - ux * ARROW_HEIGHT
338
+ back_y = ty - uy * ARROW_HEIGHT
339
+ w1_x = back_x + (-uy) * ARROW_HALF_BASE
340
+ w1_y = back_y + ux * ARROW_HALF_BASE
341
+ w2_x = back_x - (-uy) * ARROW_HALF_BASE
342
+ w2_y = back_y - ux * ARROW_HALF_BASE
343
+ d = "M #{Canvas.coord(w1_x)} #{Canvas.coord(w1_y)} L #{Canvas.coord(tx)} #{Canvas.coord(ty)} L #{Canvas.coord(w2_x)} #{Canvas.coord(w2_y)}"
344
+ %(<path d="#{d}" shape-rendering="auto"/>)
345
+ end
346
+
347
+ # «import» source-end marker: a small closed trapezoid that
348
+ # visually represents the imported package. Shape is a 4-point
349
+ # polygon: top edge shorter than bottom edge, centered on the
350
+ # anchor point.
351
+ PACKAGE_ANCHOR_W = 5
352
+ PACKAGE_ANCHOR_H_TOP = 3
353
+ PACKAGE_ANCHOR_H_BOTTOM = 4
354
+
355
+ def package_anchor_path(tip, base)
356
+ tx, ty = translate_point(tip)
357
+ bx, by = translate_point(base)
358
+ ux, uy = unit_vector(tx, ty, bx, by)
359
+ return nil unless ux
360
+
361
+ back_x = tx - ux * PACKAGE_ANCHOR_H_BOTTOM
362
+ back_y = ty - uy * PACKAGE_ANCHOR_H_BOTTOM
363
+ tl_x = tx - ux * PACKAGE_ANCHOR_H_TOP + (-uy) * PACKAGE_ANCHOR_W
364
+ tl_y = ty - uy * PACKAGE_ANCHOR_H_TOP + ux * PACKAGE_ANCHOR_W
365
+ tr_x = tx - ux * PACKAGE_ANCHOR_H_TOP - (-uy) * PACKAGE_ANCHOR_W
366
+ tr_y = ty - uy * PACKAGE_ANCHOR_H_TOP - ux * PACKAGE_ANCHOR_W
367
+ bl_x = back_x + (-uy) * (PACKAGE_ANCHOR_W + 1)
368
+ bl_y = back_y + ux * (PACKAGE_ANCHOR_W + 1)
369
+ br_x = back_x - (-uy) * (PACKAGE_ANCHOR_W + 1)
370
+ br_y = back_y - ux * (PACKAGE_ANCHOR_W + 1)
371
+ d = "M #{Canvas.coord(tl_x)} #{Canvas.coord(tl_y)} " \
372
+ "L #{Canvas.coord(tr_x)} #{Canvas.coord(tr_y)} " \
373
+ "L #{Canvas.coord(br_x)} #{Canvas.coord(br_y)} " \
374
+ "L #{Canvas.coord(bl_x)} #{Canvas.coord(bl_y)} " \
375
+ "L #{Canvas.coord(tl_x)} #{Canvas.coord(tl_y)} Z"
376
+ %(<path d="#{d}" shape-rendering="auto"/>)
377
+ end
378
+
379
+ def unit_vector(tx, ty, bx, by)
380
+ dx = tx - bx
381
+ dy = ty - by
382
+ len = Math.sqrt(dx * dx + dy * dy)
383
+ return nil if len.zero?
384
+
385
+ [dx / len, dy / len]
386
+ end
387
+
388
+ def translate_point(p)
389
+ return p unless @canvas
390
+
391
+ [@canvas.translate_x(p[0]), @canvas.translate_y(p[1])]
392
+ end
393
+
394
+ def waypoint_pairs(connector)
395
+ (connector.waypoints || []).filter_map do |w|
396
+ next unless w.position
397
+
398
+ [w.position.x, w.position.y]
399
+ end
400
+ end
401
+ end
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Per-element render context. Holds the immutable inputs each
7
+ # compartment renderer needs (bounds, classifier, fill/stroke,
8
+ # font, geometry, line data). Built once per element; passed
9
+ # to every compartment in the pipeline so they don't reach
10
+ # back into the parent Elements orchestrator.
11
+ #
12
+ # Value object: equality by structural attributes. Each
13
+ # instance is frozen after construction.
14
+ class RenderContext < Struct.new(
15
+ :element, :bounds, :model_element, :classifier,
16
+ :fill, :stroke, :stroke_width, :text_fill,
17
+ :family, :size, :size_unit,
18
+ :header_lines, :attr_lines, :op_lines,
19
+ :enum_literals, :tagged_values, :constraints,
20
+ :package_content_lines,
21
+ :geometry, :theme, :canvas, :diagram, :model_index,
22
+ :off_canvas_parent_name,
23
+ keyword_init: true
24
+ )
25
+ def note_body
26
+ return nil unless model_element.is_a?(Ea::Model::Note)
27
+
28
+ body = model_element.body
29
+ body.nil? || body.empty? ? nil : body
30
+ end
31
+
32
+ # Resolve a model element by id via the threaded model index.
33
+ # Returns nil when no index is wired in or the id is absent.
34
+ def model_index_for(id)
35
+ return nil unless model_index && id
36
+
37
+ model_index[id]
38
+ end
39
+
40
+ # Compartments below the header that produce a divider when
41
+ # any of them has content. Returns true when at least one
42
+ # is non-empty.
43
+ def content_below_header?
44
+ !attr_lines.empty? || !op_lines.empty? ||
45
+ enum_literals.any? || tagged_values.any? ||
46
+ (constraints&.any?) ||
47
+ (!package_content_lines.nil? && !package_content_lines.empty?)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Centralizes SVG style strings used across emitters.
7
+ # Connector and marker styles are now computed dynamically
8
+ # via stroke_width parameter — only TEXT_GROUP remains as
9
+ # a constant (shared across all text-emitting renderers).
10
+ module Style
11
+ TEXT_GROUP = "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Single source of truth for emitting `<text>` SVG elements.
7
+ # Handles:
8
+ #
9
+ # - Decimal `x`/`y` formatting (`%.2f`): matches EA's
10
+ # `x="11.00" y="19.00"` encoding.
11
+ # - Always-present rotation transform: even when rotation is 0,
12
+ # EA emits `transform="rotate(-0.00 X Y)"`.
13
+ # - Proper XML escaping of content.
14
+ # - Optional `textLength` integer (computed by caller).
15
+ #
16
+ # Replaces the duplicated `build_text` helpers across
17
+ # HeaderRenderer, AttributeRenderer, OperationRenderer,
18
+ # EnumerationLiteralRenderer, Labels, and DiagramFrame.
19
+ class TextRenderer
20
+ DEFAULT_FILL = "#000000"
21
+ DEFAULT_ROTATION = -0.00
22
+ DEFAULT_STROKE_IN_TEXT = "#000000"
23
+ DEFAULT_WIDTH_FACTOR = 0.65
24
+
25
+ attr_reader :content, :x, :y, :family, :size, :weight, :style,
26
+ :fill, :text_length, :rotation, :size_unit,
27
+ :stroke_in_text, :width_factor
28
+
29
+ def initialize(content:, x:, y:, family:, size:, weight: 400,
30
+ style: "normal", fill: DEFAULT_FILL,
31
+ text_length: nil, rotation: DEFAULT_ROTATION,
32
+ size_unit: "px",
33
+ stroke_in_text: DEFAULT_STROKE_IN_TEXT,
34
+ width_factor: DEFAULT_WIDTH_FACTOR)
35
+ @content = content.to_s
36
+ @x = x
37
+ @y = y
38
+ @family = family
39
+ @size = size
40
+ @weight = weight
41
+ @style = style
42
+ @fill = fill
43
+ @text_length = text_length
44
+ @rotation = rotation
45
+ @size_unit = size_unit
46
+ @stroke_in_text = stroke_in_text
47
+ @width_factor = width_factor
48
+ end
49
+
50
+ def to_svg
51
+ attrs = format_attrs
52
+ style = format_style
53
+ %(<text #{attrs} textLength="#{formatted_text_length}" style="#{style}" xml:space="preserve" transform="#{formatted_transform}">#{escaped_content}</text>)
54
+ end
55
+
56
+ private
57
+
58
+ def format_attrs
59
+ %(x="#{format('%.2f', x)}" y="#{format('%.2f', y)}")
60
+ end
61
+
62
+ def format_style
63
+ "font-family:#{family}; font-weight:#{weight}; font-style:#{style}; font-size:#{size}#{size_unit}; fill:#{fill};fill-opacity:1.00; stroke:#{stroke_in_text}; stroke-opacity:0.00 stroke-width:0; white-space: pre;"
64
+ end
65
+
66
+ def self.estimate_width(text, size, width_factor = 0.65)
67
+ content = text.to_s
68
+ space_count = content.count(" ")
69
+ letter_count = content.length - space_count
70
+ letter_count * size * width_factor + space_count * size * 0.3
71
+ end
72
+
73
+ def formatted_text_length
74
+ return text_length.to_i.to_s if text_length
75
+
76
+ TextRenderer.estimate_width(content, size, width_factor).round.to_s
77
+ end
78
+
79
+ def formatted_transform
80
+ "rotate(#{format('%<r>.2f', r: rotation)} #{format('%.2f', x)} #{format('%.2f', y)})"
81
+ end
82
+
83
+ def escaped_content
84
+ content.gsub("&", "&amp;")
85
+ .gsub("<", "&lt;")
86
+ .gsub(">", "&gt;")
87
+ .gsub("\"", "&quot;")
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ # EaEmitter orchestrates SVG output that mirrors EA's layering
6
+ # and naming conventions.
7
+ #
8
+ # Architecture:
9
+ #
10
+ # Document (entry point)
11
+ # └─ LayerSequencer (frame?, theme via diagram.theme)
12
+ # ├─ Background
13
+ # ├─ DiagramFrame (opt-in)
14
+ # ├─ Elements → Element::* compartment renderers
15
+ # ├─ Connectors → Layer
16
+ # ├─ Markers → Marker::Registry (OCP) → Layer
17
+ # └─ Labels → TextRenderer
18
+ #
19
+ # Cross-cutting:
20
+ # Canvas, BoundsCalculator, ColorResolver, FontResolver
21
+ # Ea::Theme::Definition / Registry / Loader (domain-level)
22
+ # TextRenderer, Style
23
+ #
24
+ # Theme is a top-level domain concept at Ea::Theme::*.
25
+ # See lib/ea/theme.rb for the full API.
26
+ #
27
+ module EaEmitter
28
+ autoload :Canvas, "ea/svg/ea_emitter/canvas"
29
+ autoload :Background, "ea/svg/ea_emitter/background"
30
+ autoload :BoundsCalculator, "ea/svg/ea_emitter/bounds_calculator"
31
+ autoload :Style, "ea/svg/ea_emitter/style"
32
+ autoload :Elements, "ea/svg/ea_emitter/elements"
33
+ autoload :Connectors, "ea/svg/ea_emitter/connectors"
34
+ autoload :Markers, "ea/svg/ea_emitter/markers"
35
+ autoload :Labels, "ea/svg/ea_emitter/labels"
36
+ autoload :GhostLabels, "ea/svg/ea_emitter/ghost_labels"
37
+ autoload :Layer, "ea/svg/ea_emitter/layer"
38
+ autoload :LayerSequencer, "ea/svg/ea_emitter/layer_sequencer"
39
+ autoload :FontResolver, "ea/svg/ea_emitter/font_resolver"
40
+ autoload :Marker, "ea/svg/ea_emitter/marker"
41
+ autoload :Element, "ea/svg/ea_emitter/element"
42
+ autoload :DiagramFrame, "ea/svg/ea_emitter/diagram_frame"
43
+ autoload :TextRenderer, "ea/svg/ea_emitter/text_renderer"
44
+ autoload :ColorResolver, "ea/svg/ea_emitter/color_resolver"
45
+ autoload :Document, "ea/svg/ea_emitter/document"
46
+ autoload :Label, "ea/svg/ea_emitter/label"
47
+ autoload :Compartment, "ea/svg/ea_emitter/compartment"
48
+ autoload :RenderContext, "ea/svg/ea_emitter/render_context"
49
+ end
50
+ end
51
+ end