ea 0.4.0 → 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 (529) 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/02-polygon-over-rendering.md +29 -0
  36. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  37. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  38. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  39. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  40. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  41. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  42. data/TODO.diagrams/12-connector-labels.md +40 -0
  43. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  44. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  45. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  46. data/TODO.diagrams/14-center-header-text.md +33 -0
  47. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  48. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  49. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  50. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  51. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  52. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  53. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  54. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  55. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  56. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  57. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  58. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  59. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  60. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  61. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  62. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  63. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  64. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  65. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  66. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  67. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  68. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  69. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  70. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  71. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  72. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  73. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  74. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  75. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  76. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  77. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  78. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  79. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  80. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  81. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  82. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  83. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  84. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  85. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  86. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  87. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  88. data/TODO.diagrams/50-examples-parity.md +85 -0
  89. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  90. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  91. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  92. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  93. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  94. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  95. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  96. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  97. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  98. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  99. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  100. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  101. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  102. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  103. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  104. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  105. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  106. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  107. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  108. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  109. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  110. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  111. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  112. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  113. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  114. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  115. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  116. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  117. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  118. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  119. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  120. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  121. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  122. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  123. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  124. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  125. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  126. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  127. data/TODO.diagrams/README.md +69 -0
  128. data/TODO.diagrams/STATUS.md +73 -0
  129. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  130. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  131. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  132. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  133. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  134. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  135. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  136. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  137. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  138. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  139. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  140. data/TODO.fixup/12-path-style-harmony.md +23 -0
  141. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  142. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  143. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  144. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  145. data/TODO.fixup/17-path-count-calibration.md +27 -0
  146. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  147. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  148. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  149. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  150. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  151. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  152. data/TODO.fixup/24-note-element-rendering.md +23 -0
  153. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  154. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  155. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  156. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  157. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  158. data/TODO.fixup/30-theme-system-stub.md +46 -0
  159. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  160. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  161. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  162. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  163. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  164. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  165. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  166. data/TODO.fixup/38-note-element-rendering.md +15 -0
  167. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  168. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  169. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  170. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  171. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  172. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  173. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  174. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  175. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  176. data/TODO.fixup/48-background-rect-format.md +15 -0
  177. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  178. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  179. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  180. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  181. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  182. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  183. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  184. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  185. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  186. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  187. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  188. data/TODO.fixup/60-cli-theme-option.md +22 -0
  189. data/TODO.fixup/61-theme-validation.md +28 -0
  190. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  191. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  192. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  193. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  194. data/TODO.fixup/66-styleex-writeback.md +22 -0
  195. data/TODO.fixup/67-delete-dead-code.md +28 -0
  196. data/TODO.fixup/68-figure-parity-status.md +82 -0
  197. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  198. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  199. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  200. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  201. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  202. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  203. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  204. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  205. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  206. data/TODO.fixup/78-note-element-rendering.md +30 -0
  207. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  208. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  209. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  210. data/config/qea_schema.yml +356 -0
  211. data/config/themes/119.yml +98 -0
  212. data/config/themes/default.yml +14 -0
  213. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  214. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  215. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  216. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  217. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  218. data/examples/exports/UmlModel_template/model.xml +23 -0
  219. data/examples/exports/basic/UML_EA.DTD +6856 -0
  220. data/examples/exports/basic/model.xml +8039 -0
  221. data/examples/exports/simple/UML_EA.DTD +6856 -0
  222. data/examples/exports/simple/model.xml +585 -0
  223. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  224. data/examples/exports/simple_example/model.xml +23 -0
  225. data/examples/exports/test/UML_EA.DTD +6856 -0
  226. data/examples/exports/test/model.xml +1638 -0
  227. data/examples/qea/basic.qea +0 -0
  228. data/frontend/README.md +51 -0
  229. data/frontend/dist/app.iife.js +24 -0
  230. data/frontend/dist/style.css +1 -0
  231. data/frontend/index.html +26 -0
  232. data/frontend/package-lock.json +2359 -0
  233. data/frontend/package.json +26 -0
  234. data/frontend/src/App.vue +59 -0
  235. data/frontend/src/app.ts +8 -0
  236. data/frontend/src/components/AppHeader.vue +127 -0
  237. data/frontend/src/components/AppSidebar.vue +119 -0
  238. data/frontend/src/components/ClassDetails.vue +247 -0
  239. data/frontend/src/components/DiagramView.vue +79 -0
  240. data/frontend/src/components/PackageDetails.vue +92 -0
  241. data/frontend/src/components/PackageTreeNode.vue +64 -0
  242. data/frontend/src/components/SearchResults.vue +48 -0
  243. data/frontend/src/components/WelcomeView.vue +87 -0
  244. data/frontend/src/shims-vue.d.ts +5 -0
  245. data/frontend/src/stores/dataStore.ts +79 -0
  246. data/frontend/src/stores/uiStore.ts +167 -0
  247. data/frontend/src/styles/main.css +1566 -0
  248. data/frontend/src/types.ts +221 -0
  249. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  250. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  251. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  252. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  253. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  254. data/frontend/tests/e2e/render.spec.ts +188 -0
  255. data/frontend/tsconfig.json +20 -0
  256. data/frontend/vite.config.ts +26 -0
  257. data/frontend/vitest.config.ts +8 -0
  258. data/lib/ea/cli/app.rb +57 -2
  259. data/lib/ea/cli/command/diagrams.rb +37 -28
  260. data/lib/ea/cli/command/diff.rb +46 -0
  261. data/lib/ea/cli/command/export.rb +80 -0
  262. data/lib/ea/cli/command/info.rb +75 -0
  263. data/lib/ea/cli/command/lint.rb +48 -0
  264. data/lib/ea/cli/command/mdg.rb +98 -0
  265. data/lib/ea/cli/command/query.rb +34 -0
  266. data/lib/ea/cli/command/render.rb +135 -0
  267. data/lib/ea/cli/command/svg.rb +8 -15
  268. data/lib/ea/cli/command.rb +7 -0
  269. data/lib/ea/diagram/display_config.rb +184 -0
  270. data/lib/ea/diagram.rb +4 -41
  271. data/lib/ea/diff/change.rb +30 -0
  272. data/lib/ea/diff/comparator.rb +82 -0
  273. data/lib/ea/diff/html_reporter.rb +91 -0
  274. data/lib/ea/diff.rb +15 -0
  275. data/lib/ea/export/json/generator.rb +42 -0
  276. data/lib/ea/export/json.rb +12 -0
  277. data/lib/ea/export/json_schema/generator.rb +77 -0
  278. data/lib/ea/export/json_schema.rb +12 -0
  279. data/lib/ea/export/plantuml/generator.rb +73 -0
  280. data/lib/ea/export/plantuml.rb +12 -0
  281. data/lib/ea/export/xmi/generator.rb +77 -0
  282. data/lib/ea/export/xmi.rb +11 -0
  283. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  284. data/lib/ea/export/xsd/generator.rb +142 -0
  285. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  286. data/lib/ea/export/xsd.rb +19 -0
  287. data/lib/ea/export.rb +14 -0
  288. data/lib/ea/image/emf_renderer.rb +42 -0
  289. data/lib/ea/image.rb +12 -0
  290. data/lib/ea/lint/engine.rb +30 -0
  291. data/lib/ea/lint/lint_rule.rb +38 -0
  292. data/lib/ea/lint/offense.rb +18 -0
  293. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  294. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  295. data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
  296. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  297. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  298. data/lib/ea/lint/rules.rb +14 -0
  299. data/lib/ea/lint.rb +13 -0
  300. data/lib/ea/mdg/document.rb +120 -0
  301. data/lib/ea/mdg/loader.rb +118 -0
  302. data/lib/ea/mdg/registry.rb +98 -0
  303. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  304. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  305. data/lib/ea/mdg/xml/apply.rb +18 -0
  306. data/lib/ea/mdg/xml/content.rb +18 -0
  307. data/lib/ea/mdg/xml/documentation.rb +24 -0
  308. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  309. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  310. data/lib/ea/mdg/xml/tag.rb +28 -0
  311. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  312. data/lib/ea/mdg/xml/technology.rb +28 -0
  313. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  314. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  315. data/lib/ea/mdg/xml.rb +24 -0
  316. data/lib/ea/mdg.rb +25 -0
  317. data/lib/ea/model/classifier.rb +4 -0
  318. data/lib/ea/model/constraint.rb +24 -0
  319. data/lib/ea/model/diagram.rb +85 -0
  320. data/lib/ea/model/diagram_connector.rb +34 -0
  321. data/lib/ea/model/diagram_element.rb +16 -0
  322. data/lib/ea/model/document.rb +47 -2
  323. data/lib/ea/model/ghost_label.rb +32 -0
  324. data/lib/ea/model/instance_specification.rb +34 -0
  325. data/lib/ea/model/legend.rb +43 -0
  326. data/lib/ea/model/legend_item.rb +26 -0
  327. data/lib/ea/model/note.rb +28 -0
  328. data/lib/ea/model/operation.rb +2 -0
  329. data/lib/ea/model/property.rb +2 -0
  330. data/lib/ea/model/relationship.rb +2 -0
  331. data/lib/ea/model/signal.rb +14 -0
  332. data/lib/ea/model/slot.rb +19 -0
  333. data/lib/ea/model.rb +8 -0
  334. data/lib/ea/ocl/evaluator.rb +94 -0
  335. data/lib/ea/ocl/nodes.rb +30 -0
  336. data/lib/ea/ocl/parser.rb +133 -0
  337. data/lib/ea/ocl/unsupported_error.rb +7 -0
  338. data/lib/ea/ocl.rb +13 -0
  339. data/lib/ea/qea/factory/document_builder.rb +24 -10
  340. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  341. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  342. data/lib/ea/qea/models/ea_author.rb +35 -0
  343. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  344. data/lib/ea/qea/models/ea_image.rb +53 -0
  345. data/lib/ea/qea/models/ea_implement.rb +32 -0
  346. data/lib/ea/qea/models/ea_list.rb +42 -0
  347. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  348. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  349. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  350. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  351. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  352. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  353. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  354. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  355. data/lib/ea/qea/models/ea_operation.rb +7 -0
  356. data/lib/ea/qea/models/ea_palette.rb +30 -0
  357. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  358. data/lib/ea/qea/models/ea_phase.rb +40 -0
  359. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  360. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  361. data/lib/ea/qea/models/ea_version.rb +39 -0
  362. data/lib/ea/qea/models/ea_xref.rb +11 -1
  363. data/lib/ea/qea/models.rb +19 -0
  364. data/lib/ea/qea/services/database_loader.rb +22 -1
  365. data/lib/ea/query/builder.rb +108 -0
  366. data/lib/ea/query.rb +9 -0
  367. data/lib/ea/render/image_converter.rb +101 -0
  368. data/lib/ea/render.rb +10 -0
  369. data/lib/ea/shapescript/parser.rb +208 -0
  370. data/lib/ea/shapescript/renderer.rb +74 -0
  371. data/lib/ea/shapescript/shape.rb +34 -0
  372. data/lib/ea/shapescript.rb +24 -0
  373. data/lib/ea/sources/qea/adapter.rb +22 -5
  374. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  375. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  376. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  377. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  378. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  379. data/lib/ea/sources/qea/note_builder.rb +58 -0
  380. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  381. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  382. data/lib/ea/sources/qea/package_builder.rb +16 -0
  383. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  384. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  385. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  386. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  387. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  388. data/lib/ea/sources/qea/xref/record.rb +41 -0
  389. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  390. data/lib/ea/sources/qea/xref.rb +27 -0
  391. data/lib/ea/sources/qea.rb +4 -0
  392. data/lib/ea/sources/xmi/adapter.rb +80 -2
  393. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  394. data/lib/ea/sources/xmi/diagram_builder.rb +95 -18
  395. data/lib/ea/sources/xmi/extension_geometry_parser.rb +19 -2
  396. data/lib/ea/sources/xmi/extension_style_parser.rb +15 -2
  397. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  398. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  399. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  400. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  401. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  402. data/lib/ea/sources/xmi.rb +4 -0
  403. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  404. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  405. data/lib/ea/spa/output/strategy.rb +34 -0
  406. data/lib/ea/svg/connector_router.rb +134 -0
  407. data/lib/ea/svg/ea_emitter/background.rb +2 -3
  408. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  409. data/lib/ea/svg/ea_emitter/canvas.rb +56 -33
  410. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  411. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  412. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  413. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  414. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  415. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  416. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  417. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  418. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  419. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  420. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  421. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  422. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  423. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  424. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  425. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  426. data/lib/ea/svg/ea_emitter/connectors.rb +46 -12
  427. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  428. data/lib/ea/svg/ea_emitter/document.rb +15 -24
  429. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  430. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  431. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  432. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  433. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  434. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  435. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  436. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  437. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  438. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  439. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  440. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  441. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  442. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  443. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  444. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  445. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  446. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  447. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  448. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  449. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  450. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  451. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  452. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  453. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  454. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  455. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  456. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  457. data/lib/ea/svg/ea_emitter/elements.rb +283 -95
  458. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  459. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  460. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  461. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  462. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  463. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  464. data/lib/ea/svg/ea_emitter/labels.rb +93 -27
  465. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  466. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  467. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  468. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  469. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  470. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  471. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  472. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  473. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  474. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  475. data/lib/ea/svg/ea_emitter/markers.rb +351 -64
  476. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  477. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  478. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  479. data/lib/ea/svg/ea_emitter.rb +33 -11
  480. data/lib/ea/svg/parity/checker.rb +105 -0
  481. data/lib/ea/svg/parity/source.rb +59 -0
  482. data/lib/ea/svg/parity/suite.rb +113 -0
  483. data/lib/ea/svg/parity.rb +14 -0
  484. data/lib/ea/svg.rb +2 -15
  485. data/lib/ea/theme/definition.rb +176 -0
  486. data/lib/ea/theme/loader.rb +125 -0
  487. data/lib/ea/theme/registry.rb +96 -0
  488. data/lib/ea/theme.rb +37 -0
  489. data/lib/ea/transformations.rb +5 -88
  490. data/lib/ea/version.rb +4 -1
  491. data/lib/ea.rb +14 -4
  492. data/lib/tasks/svg_bench.rake +87 -0
  493. data/lutaml-uml-0.5.1.gem +0 -0
  494. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  495. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  496. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  497. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  498. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  499. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  500. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  501. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  502. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  503. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  504. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  505. metadata +449 -26
  506. data/lib/ea/diagram/configuration.rb +0 -379
  507. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  508. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  509. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  510. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  511. data/lib/ea/diagram/element_renderers.rb +0 -43
  512. data/lib/ea/diagram/extractor.rb +0 -566
  513. data/lib/ea/diagram/layout_engine.rb +0 -170
  514. data/lib/ea/diagram/path_builder.rb +0 -202
  515. data/lib/ea/diagram/style_parser.rb +0 -42
  516. data/lib/ea/diagram/style_resolver.rb +0 -291
  517. data/lib/ea/diagram/svg_renderer.rb +0 -274
  518. data/lib/ea/diagram/util.rb +0 -73
  519. data/lib/ea/svg/bounds_calculator.rb +0 -74
  520. data/lib/ea/svg/connector_path.rb +0 -138
  521. data/lib/ea/svg/element_box.rb +0 -256
  522. data/lib/ea/svg/renderer.rb +0 -77
  523. data/lib/ea/svg/style_resolver.rb +0 -72
  524. data/lib/ea/transformations/configuration.rb +0 -333
  525. data/lib/ea/transformations/format_registry.rb +0 -366
  526. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  527. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  528. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  529. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Label
7
+ # Renders the role + «property» + multiplicity cluster at one
8
+ # endpoint of a UML association connector.
9
+ #
10
+ # EA encodes end-labels in two geometry boxes per endpoint:
11
+ #
12
+ # LLT/LRT - "label text" position (role name slot)
13
+ # LLB/LRB - "label box" position (multiplicity slot)
14
+ #
15
+ # Each box carries OX/OY offsets from the connector anchor.
16
+ # The «property» stereotype renders one line below the role
17
+ # name. EA records qualified role names ("pkg::role") for
18
+ # cross-package associations but suppresses label rendering
19
+ # for those — only simple role names render.
20
+ class EndLabel
21
+ PROPERTY_STEREOTYPE = "«property»"
22
+
23
+ attr_reader :canvas, :model_index, :document, :theme,
24
+ :font_family, :font_size, :font_unit
25
+
26
+ def initialize(canvas:, model_index:, document:, theme:,
27
+ font_family:, font_size:, font_unit:)
28
+ @canvas = canvas
29
+ @model_index = model_index
30
+ @document = document
31
+ @theme = theme
32
+ @font_family = font_family
33
+ @font_size = font_size
34
+ @font_unit = font_unit
35
+ end
36
+
37
+ # Returns an Array of `<text>` SVG strings for one endpoint.
38
+ #
39
+ # text_box - LLT or LRT box geometry (role position)
40
+ # mult_box - LLB or LRB box geometry (mult position)
41
+ # anchor - [x, y] of the connector endpoint
42
+ # connector - the DiagramConnector (for relationship lookup)
43
+ # end_kind - :source or :target
44
+ def texts(text_box:, mult_box:, anchor:, connector:, end_kind:)
45
+ from_property, role, mult = role_and_multiplicity(connector, end_kind)
46
+ if role_empty?(role) && mult_empty?(mult)
47
+ other_from_prop, other_role, other_mult = role_and_multiplicity(connector,
48
+ opposite(end_kind))
49
+ role = other_role if role_empty?(role)
50
+ mult ||= other_mult
51
+ from_property ||= other_from_prop
52
+ end
53
+ agg = aggregation_kind_for(connector, end_kind)
54
+ standalone = standalone_multiplicity(role, mult, aggregation: agg)
55
+ return [] if role_empty?(role) && (standalone.nil? || standalone.empty?)
56
+
57
+ text_pos = position_at(text_box, anchor)
58
+ mult_pos = position_at(mult_box, anchor) || text_pos
59
+ show_property = property_label?(connector)
60
+
61
+ build_texts(role:, mult:, standalone:,
62
+ text_pos:, mult_pos:, show_property:)
63
+ end
64
+
65
+ private
66
+
67
+ def build_texts(role:, mult:, standalone:, text_pos:, mult_pos:,
68
+ show_property:)
69
+ if role && !role.empty? && text_pos
70
+ x, y = text_pos
71
+ texts = [text_at(x, y, role)]
72
+ if show_property
73
+ texts << text_at(x, y + property_y_offset, PROPERTY_STEREOTYPE)
74
+ end
75
+ if mult && !mult.empty? && mult_pos
76
+ texts << text_at(*mult_pos, mult)
77
+ end
78
+ texts
79
+ elsif standalone && !standalone.empty? && mult_pos
80
+ [text_at(*mult_pos, standalone)]
81
+ else
82
+ []
83
+ end
84
+ end
85
+
86
+ # EA renders «property» between role name and multiplicity
87
+ # ONLY for directed associations/aggregations — those with
88
+ # t_connector.Direction set to "Source -> Destination" or
89
+ # "Destination -> Source". "Unspecified" / "Bi-Directional"
90
+ # connectors do NOT get the stereotype label.
91
+ #
92
+ # Verified against both QEAs (output by the same EA in the
93
+ # same run):
94
+ # - basic.qea: 4 Associations with Direction="Unspecified"
95
+ # and role+mult → 0 «property» labels.
96
+ # - plateau QEA: aggregations with Direction="Destination
97
+ # -> Source" → «property» rendered (737 instances across
98
+ # 137 diagrams).
99
+ UNDIRECTED_DIRECTIONS = %w[Unspecified Bi-Directional].freeze
100
+
101
+ def property_label?(connector)
102
+ direction = connector.direction.to_s
103
+ return false if direction.empty?
104
+ return false if UNDIRECTED_DIRECTIONS.include?(direction)
105
+
106
+ true
107
+ end
108
+
109
+ def role_empty?(role)
110
+ role.nil? || role.empty?
111
+ end
112
+
113
+ def mult_empty?(mult)
114
+ mult.nil? || mult.empty?
115
+ end
116
+
117
+ def opposite(end_kind)
118
+ end_kind == :source ? :target : :source
119
+ end
120
+
121
+ def role_and_multiplicity(connector, end_kind)
122
+ assoc = association_for(connector)
123
+ return [nil, nil, nil] unless assoc
124
+
125
+ prop = property_at_end(assoc, end_kind)
126
+ return [true, *property_role(prop)] if prop
127
+
128
+ [false, *association_role(assoc, end_kind)]
129
+ end
130
+
131
+ def property_at_end(association, end_kind)
132
+ id = end_kind == :source ? association.source_id : association.target_id
133
+ return nil unless id
134
+
135
+ return document.property_by_id(id) if document
136
+
137
+ fallback_property_lookup(id)
138
+ end
139
+
140
+ # Linear-scan fallback for callers that did not wire in
141
+ # the Document. Production paths go through Document's
142
+ # property_index for O(1) lookup.
143
+ def fallback_property_lookup(id)
144
+ return nil unless model_index
145
+
146
+ model_index.each_value do |obj|
147
+ next unless obj.is_a?(Ea::Model::Classifier)
148
+
149
+ found = (obj.properties || []).find { |p| p.id == id }
150
+ return found if found
151
+ end
152
+ nil
153
+ end
154
+
155
+ def property_role(property)
156
+ visibility = visibility_prefix(property)
157
+ name = property.name.to_s
158
+ role = name.empty? ? nil : "#{visibility}#{name}"
159
+ mult = multiplicity_text(property)
160
+ [role, mult]
161
+ end
162
+
163
+ def association_role(assoc, end_kind)
164
+ name = end_kind == :source ? assoc.source_role_name : assoc.target_role_name
165
+ lower = end_kind == :source ? assoc.source_multiplicity_lower : assoc.target_multiplicity_lower
166
+ upper = end_kind == :source ? assoc.source_multiplicity_upper : assoc.target_multiplicity_upper
167
+ return [nil, multiplicity_string(lower, upper)] if name.to_s.include?("::")
168
+
169
+ role = name.nil? || name.empty? ? nil : "+#{name}"
170
+ mult = multiplicity_string(lower, upper)
171
+ [role, mult]
172
+ end
173
+
174
+ def association_for(connector)
175
+ return nil unless model_index
176
+
177
+ rel = model_index[connector.relationship_ref]
178
+ return nil unless rel.is_a?(Ea::Model::Association)
179
+
180
+ rel
181
+ end
182
+
183
+ # Returns the aggregation kind for the given end (:shared,
184
+ # :composite, or nil). An aggregation marker on the OPPOSITE
185
+ # end means THIS end is the "part" end of an aggregation —
186
+ # EA shows "1" multiplicity for such ends.
187
+ def aggregation_kind_for(connector, end_kind)
188
+ assoc = association_for(connector)
189
+ return nil unless assoc
190
+
191
+ opposite_kind = opposite(end_kind)
192
+ agg = opposite_kind == :source ? assoc.source_aggregation : assoc.target_aggregation
193
+ return nil if agg.nil? || agg == "none"
194
+
195
+ agg.to_sym
196
+ end
197
+
198
+ def position_at(box, anchor)
199
+ return nil unless box
200
+
201
+ offset_x = box["ox"] || box[:ox]
202
+ offset_y = box["oy"] || box[:oy]
203
+ return nil if offset_x.nil? && offset_y.nil?
204
+
205
+ [anchor[0] + offset_x.to_i, anchor[1] + offset_y.to_i]
206
+ end
207
+
208
+ def property_y_offset
209
+ font_size + 6
210
+ end
211
+
212
+ def multiplicity_text(property)
213
+ multiplicity_string(property.multiplicity_lower,
214
+ property.multiplicity_upper)
215
+ end
216
+
217
+ def multiplicity_string(lower, upper)
218
+ return nil if lower.nil? && upper.nil?
219
+ return "1" if lower == 1 && upper == 1
220
+
221
+ upper_str = upper == -1 ? "*" : upper.to_s
222
+ "#{lower || 0}..#{upper_str}"
223
+ end
224
+
225
+ # EA suppresses standalone multiplicity when it is
226
+ # unspecified (nil). Explicit multiplicity renders
227
+ # regardless of aggregation kind. The QEA parser now
228
+ # returns nil for absent SourceCard/DestCard so default
229
+ # "1" is no longer produced — only explicit "1" reaches
230
+ # this method.
231
+ def standalone_multiplicity(role, mult, aggregation: nil)
232
+ return nil if role && !role.empty?
233
+ return nil if mult.nil?
234
+
235
+ mult
236
+ end
237
+
238
+ def visibility_prefix(property)
239
+ case property.visibility
240
+ when "private" then "-"
241
+ when "protected" then "#"
242
+ when "package" then "~"
243
+ else "+"
244
+ end
245
+ end
246
+
247
+ def text_at(x, y, content)
248
+ return nil if content.nil? || content.empty?
249
+
250
+ x_t = canvas ? canvas.translate_x(x) : x
251
+ y_t = canvas ? canvas.translate_y(y) : y
252
+ TextRenderer.new(content:, x: x_t, y: y_t,
253
+ family: font_family, size: font_size,
254
+ size_unit: font_unit,
255
+ fill: fill_color,
256
+ text_length: content.length * 6).to_svg
257
+ end
258
+
259
+ def fill_color
260
+ "#000000"
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Label
7
+ # Renders the midpoint label for a connector. EA emits one
8
+ # of two things at the midpoint, but not both:
9
+ #
10
+ # 1. «stereotype» when the relationship has an applied
11
+ # stereotype (e.g. «import», «realize»).
12
+ # 2. The relationship's Name when no stereotype is applied
13
+ # and Name is non-empty (e.g. "Association A").
14
+ #
15
+ # Stereotype wins when both are present.
16
+ class MidpointLabel
17
+ attr_reader :canvas, :model_index, :font_family, :font_size,
18
+ :font_unit
19
+
20
+ def initialize(canvas:, model_index:, font_family:, font_size:,
21
+ font_unit:)
22
+ @canvas = canvas
23
+ @model_index = model_index
24
+ @font_family = font_family
25
+ @font_size = font_size
26
+ @font_unit = font_unit
27
+ end
28
+
29
+ # Returns the SVG `<text>` for the connector's midpoint
30
+ # label, or nil if there is no label to render.
31
+ def text_for(connector, points)
32
+ content = midpoint_content(connector)
33
+ return nil if content.nil?
34
+
35
+ mx, my = midpoint(points)
36
+ render_text(mx, my, content)
37
+ end
38
+
39
+ # Returns the formatted «stereotype» string or nil. Used
40
+ # by Label::Registry to dispatch end-label vs midpoint.
41
+ def stereotype_label(connector)
42
+ rel = relationship_for(connector)
43
+ return nil unless rel
44
+
45
+ stereotype = rel.stereotype
46
+ return nil if stereotype.nil? || stereotype.to_s.empty?
47
+
48
+ "«#{stereotype}»"
49
+ end
50
+
51
+ # Returns the midpoint label content: stereotype if
52
+ # present, else the relationship Name.
53
+ def midpoint_content(connector)
54
+ stereotype = stereotype_label(connector)
55
+ return stereotype if stereotype
56
+
57
+ rel = relationship_for(connector)
58
+ return nil unless rel
59
+
60
+ name = rel.name
61
+ return nil if name.nil? || name.to_s.empty?
62
+
63
+ name.to_s
64
+ end
65
+
66
+ def relationship_for(connector)
67
+ return nil unless model_index
68
+
69
+ rel = model_index[connector.relationship_ref]
70
+ return nil unless rel.is_a?(Ea::Model::Relationship)
71
+
72
+ rel
73
+ end
74
+
75
+ private
76
+
77
+ def midpoint(points)
78
+ mid_idx = points.size / 2
79
+ if points.size.odd?
80
+ points[mid_idx]
81
+ else
82
+ a = points[mid_idx - 1]
83
+ b = points[mid_idx]
84
+ [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]
85
+ end
86
+ end
87
+
88
+ def render_text(x, y, content)
89
+ x_t = canvas ? canvas.translate_x(x) : x
90
+ y_t = canvas ? canvas.translate_y(y) : y
91
+ TextRenderer.new(content:, x: x_t, y: y_t,
92
+ family: font_family, size: font_size,
93
+ size_unit: font_unit,
94
+ fill: "#000000",
95
+ text_length: content.length * 6).to_svg
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Label
7
+ # Dispatches a connector to the appropriate label renderer(s).
8
+ #
9
+ # Rules:
10
+ # - Any relationship with an applied stereotype renders
11
+ # «stereotype» at the midpoint (Associations included —
12
+ # «import» on a Package connector is a common case).
13
+ # - Associations without a stereotype render EndLabel at
14
+ # each positioned LLT/LRT box.
15
+ # - Other relationship kinds without a stereotype render
16
+ # nothing.
17
+ #
18
+ # Open/closed: a new label kind registers here rather than
19
+ # forcing callers to edit case statements elsewhere.
20
+ class Registry
21
+ attr_reader :model_index
22
+
23
+ def initialize(model_index:)
24
+ @model_index = model_index
25
+ end
26
+
27
+ # Returns the formatted stereotype label when the
28
+ # connector should render at the midpoint, nil otherwise.
29
+ def midpoint?(connector)
30
+ MidpointLabel.new(canvas: nil, model_index: model_index,
31
+ font_family: nil, font_size: nil,
32
+ font_unit: nil).stereotype_label(connector)
33
+ end
34
+
35
+ # Returns true if the connector's relationship is an
36
+ # Association AND has no midpoint stereotype (the end-label
37
+ # path).
38
+ def end_label?(connector)
39
+ association?(connector) && !midpoint?(connector)
40
+ end
41
+
42
+ def association?(connector)
43
+ rel = model_index ? model_index[connector.relationship_ref] : nil
44
+ rel.is_a?(Ea::Model::Association)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Per-connector label renderers. Each renderer handles one
7
+ # visual label kind on a connector:
8
+ #
9
+ # EndLabel - role + «property» + multiplicity at
10
+ # LLT/LRT box positions (UML associations)
11
+ # MidpointLabel - «stereotype» at the connector midpoint
12
+ # (Package / Dependency / Realization)
13
+ #
14
+ # Registry dispatches by relationship kind so new label kinds
15
+ # register rather than editing the orchestrator (OCP).
16
+ module Label
17
+ autoload :EndLabel, "ea/svg/ea_emitter/label/end_label"
18
+ autoload :MidpointLabel, "ea/svg/ea_emitter/label/midpoint_label"
19
+ autoload :Registry, "ea/svg/ea_emitter/label/registry"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,60 +3,126 @@
3
3
  module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
- # Emits connector labels (role names, multiplicities) at the
7
- # EA-recorded offset from the connector endpoints. Each label
8
- # box has its own position decoded from the EA LLB/LLT/LRT/LRB
9
- # geometry slots.
6
+ # Emits the labels layer: connector role/multiplicity end
7
+ # labels plus midpoint stereotype labels for non-association
8
+ # connectors.
9
+ #
10
+ # This class is the orchestrator. The actual label-shaping
11
+ # logic lives in dedicated, MECE collaborators under
12
+ # `Ea::Svg::EaEmitter::Label`:
13
+ #
14
+ # Label::EndLabel - role + «property» + mult at LLT/LRT
15
+ # Label::MidpointLabel - «stereotype» at connector midpoint
16
+ # Label::Registry - dispatch by relationship kind (OCP)
17
+ #
18
+ # Theme is read once and threaded to collaborators so the
19
+ # rendering rules (font family/size, fill color) live in one
20
+ # place.
10
21
  class Labels
11
- DEFAULT_FAMILY = "Calibri"
12
- DEFAULT_SIZE = 10
22
+ DEFAULT_FAMILY = "Yu Gothic UI"
23
+ DEFAULT_SIZE = 13
13
24
 
14
- attr_reader :diagram
25
+ attr_reader :diagram, :canvas, :model_index, :theme, :document
15
26
 
16
- def initialize(diagram)
27
+ def initialize(diagram, canvas: nil, model_index: nil, theme: nil,
28
+ document: nil)
17
29
  @diagram = diagram
30
+ @canvas = canvas
31
+ @model_index = model_index
32
+ @theme = theme || Ea::Theme::Registry.default
33
+ @document = document
18
34
  end
19
35
 
20
36
  def render
21
- texts = visible_connectors.filter_map { |c| texts_for(c) }
37
+ texts = visible_connectors.flat_map { |c| texts_for(c) }
22
38
  return "" if texts.empty?
23
39
 
24
- blocks = texts.flatten
25
- %(<g style="stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00">\n#{blocks.join("\n")}\n</g>)
40
+ %(<g style="stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00">\n#{texts.join("\n")}\n</g>)
26
41
  end
27
42
 
28
43
  private
29
44
 
30
45
  def visible_connectors
31
- (diagram.connectors || []).reject(&:hidden)
46
+ (diagram.connectors || []).select(&:renderable?)
32
47
  end
33
48
 
34
49
  def texts_for(connector)
35
- points = connector.waypoints.filter_map { |w| w.position && [w.position.x, w.position.y] }
36
- return nil if points.size < 2
50
+ points = waypoint_pairs(connector)
51
+ return [] if points.size < 2
37
52
 
53
+ texts = []
54
+
55
+ # Midpoint label: stereotype «import», or relationship
56
+ # Name ("Association A"). Renders at the path midpoint.
57
+ mid_text = midpoint_renderer.text_for(connector, points)
58
+ texts << mid_text if mid_text
59
+
60
+ # End-labels: role name + «property» + multiplicity at
61
+ # positioned LLT/LRT boxes. Only for associations without
62
+ # a midpoint stereotype (stereotyped associations route
63
+ # to midpoint only).
64
+ if registry.end_label?(connector)
65
+ end_label_texts(connector, points).each { |t| texts << t }
66
+ end
67
+
68
+ texts
69
+ end
70
+
71
+ def end_label_texts(connector, points)
38
72
  source_pt = points.first
39
73
  target_pt = points.last
40
-
41
74
  boxes = connector.label_boxes || {}
75
+
42
76
  texts = []
43
- if (box = boxes["llb"]) && box["ox"] && box["oy"]
44
- texts << text_at(source_pt[0] + box["ox"], source_pt[1] + box["oy"], connector.label.to_s)
45
- end
46
- if (box = boxes["lrt"]) && box["ox"] && box["oy"]
47
- texts << text_at(target_pt[0] + box["ox"], target_pt[1] + box["oy"], connector.label.to_s)
48
- end
49
- texts.empty? ? nil : texts
77
+ end_renderer.texts(text_box: boxes[:llt], mult_box: boxes[:llb],
78
+ anchor: source_pt, connector: connector,
79
+ end_kind: :source).each { |t| texts << t }
80
+ end_renderer.texts(text_box: boxes[:lrt], mult_box: boxes[:lrb],
81
+ anchor: target_pt, connector: connector,
82
+ end_kind: :target).each { |t| texts << t }
83
+ texts
84
+ end
85
+
86
+ def registry
87
+ @registry ||= Label::Registry.new(model_index: model_index)
88
+ end
89
+
90
+ def end_renderer
91
+ @end_renderer ||= Label::EndLabel.new(
92
+ canvas: canvas,
93
+ model_index: model_index,
94
+ document: document,
95
+ theme: theme,
96
+ font_family: label_font_family,
97
+ font_size: label_font_size,
98
+ font_unit: theme.font_size_unit
99
+ )
50
100
  end
51
101
 
52
- def text_at(x, y, text)
53
- return nil if text.nil? || text.empty?
102
+ def midpoint_renderer
103
+ @midpoint_renderer ||= Label::MidpointLabel.new(
104
+ canvas: canvas,
105
+ model_index: model_index,
106
+ font_family: label_font_family,
107
+ font_size: label_font_size,
108
+ font_unit: theme.font_size_unit
109
+ )
110
+ end
111
+
112
+ def waypoint_pairs(connector)
113
+ (connector.waypoints || []).filter_map do |w|
114
+ next unless w.position
115
+
116
+ [w.position.x, w.position.y]
117
+ end
118
+ end
54
119
 
55
- %( <text x="#{format('%.2f', x)}" y="#{format('%.2f', y)}" textLength="#{text.length * 6}" style="font-family:#{DEFAULT_FAMILY}; font-weight:0; font-style:normal; font-size:#{DEFAULT_SIZE}px; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00 stroke-width:0; white-space: pre;" xml:space="preserve">#{escape(text)}</text>)
120
+ def label_font_family
121
+ theme.font_family || DEFAULT_FAMILY
56
122
  end
57
123
 
58
- def escape(text)
59
- text.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub("\"", "&quot;")
124
+ def label_font_size
125
+ theme.font_size || DEFAULT_SIZE
60
126
  end
61
127
  end
62
128
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # A single `<g>` block ready for inclusion in the SVG output.
7
+ # Carries the style string and the body (one or more child
8
+ # elements: `<path>`, `<polygon>`, `<text>`, `<rect>`).
9
+ #
10
+ # Emitters return Arrays of Layer objects. The Document
11
+ # orchestrator either joins them as-is (per-entity ordering)
12
+ # or buckets them by `style_key` for EA-style grouped output.
13
+ Layer = Struct.new(:style_key, :style, :body, keyword_init: true) do
14
+ def to_svg
15
+ %(<g style="#{style}">\n#{body}\n</g>)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end