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,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Emits the elements layer. Orchestrates per-element rendering
7
+ # by delegating to specialized compartment renderers (shape,
8
+ # header, divider, attribute). Frame-element filtering and
9
+ # stereotype-color fallback also live in dedicated
10
+ # collaborators.
11
+ class Elements
12
+ DEFAULT_FILL = "#FFFFFF"
13
+ DEFAULT_STROKE = "#000000"
14
+ DEFAULT_STROKE_WIDTH = 2
15
+ DEFAULT_TEXT_COLOR = "#000000"
16
+
17
+ attr_reader :diagram, :model_index, :canvas, :document
18
+
19
+ def initialize(diagram, model_index:, canvas: nil, document: nil)
20
+ @diagram = diagram
21
+ @model_index = model_index
22
+ @canvas = canvas
23
+ @document = document
24
+ end
25
+
26
+ def render
27
+ groups.join("\n")
28
+ end
29
+
30
+ # Returns an Array of per-entity `<g>...</g>` strings.
31
+ # Each element contributes up to 4 groups (shape, header,
32
+ # divider, attrs) in EA's per-entity layer order.
33
+ def groups
34
+ ordered_elements.flat_map { |e| groups_for(e) }
35
+ end
36
+
37
+ private
38
+
39
+ def ordered_elements
40
+ (diagram.elements || []).sort_by { |e| e.z_order || 0 }
41
+ end
42
+
43
+ def groups_for(element)
44
+ return [] if element_filter.skip?(element)
45
+
46
+ context = build_context(element)
47
+ return [] unless context
48
+
49
+ Compartment.render_all(context).compact
50
+ end
51
+
52
+ # Build the RenderContext for one element. Returns nil when
53
+ # the element has no usable bounds (skip rendering).
54
+ def build_context(element)
55
+ raw_bounds = element.bounds || element.image_bounds
56
+ return nil unless raw_bounds
57
+
58
+ bounds = translate_bounds(raw_bounds)
59
+ model_element = model_element_for(element)
60
+ classifier = classifier_for(element)
61
+ size = font_resolver.size_for(element)
62
+ family = font_resolver.family_for(element)
63
+ size_unit = font_resolver.size_unit_for(element)
64
+ parent_name = off_canvas_parent_name_for(classifier)
65
+
66
+ header_lines = classifier ? Element::HeaderLines.for(classifier,
67
+ diagram_package_id: diagram.package_id,
68
+ visually_nested: visually_nested?(element),
69
+ umldi_keyword: element.umldi_keyword,
70
+ bounds_width: raw_bounds&.width,
71
+ font_size: size,
72
+ off_canvas_parent_name: parent_name) : []
73
+ is_classifier = classifier.is_a?(Ea::Model::Classifier)
74
+ attr_lines = (is_classifier && show_attributes?) ? Element::AttributeRenderer.lines_for(classifier, lookup: attribute_lookup) : []
75
+ op_lines = (is_classifier && show_operations?) ? Element::OperationRenderer.lines_for(classifier) : []
76
+ geometry = CompartmentGeometry.new(bounds: bounds, size: size,
77
+ header_lines_count: header_lines.size,
78
+ attr_lines_count: attr_lines.size,
79
+ op_lines_count: op_lines.size,
80
+ tagged_values_count: tagged_values_for(classifier).size,
81
+ header_top_padding: theme.compartments.header_top_padding,
82
+ header_line_offset: theme.compartments.header_line_offset,
83
+ divider_offset: theme.compartments.divider_offset,
84
+ attr_line_offset: theme.compartments.attr_line_offset,
85
+ attr_first_offset: theme.compartments.attr_first_offset)
86
+ RenderContext.new(
87
+ element: element,
88
+ bounds: bounds,
89
+ model_element: model_element,
90
+ classifier: classifier,
91
+ fill: resolve_fill(element, classifier),
92
+ stroke: resolve_stroke,
93
+ stroke_width: resolve_stroke_width,
94
+ text_fill: theme.text_color,
95
+ family: family, size: size, size_unit: size_unit,
96
+ header_lines: header_lines,
97
+ attr_lines: attr_lines,
98
+ op_lines: op_lines,
99
+ enum_literals: enum_literals_for(classifier),
100
+ tagged_values: element.show_tagged_values ? tagged_values_for(classifier) : [],
101
+ constraints: constraints_for(classifier),
102
+ package_content_lines: package_content_lines_for(model_element),
103
+ geometry: geometry,
104
+ theme: theme,
105
+ canvas: canvas,
106
+ diagram: diagram,
107
+ model_index: model_index,
108
+ off_canvas_parent_name: parent_name
109
+ )
110
+ end
111
+
112
+ def enum_literals_for(classifier)
113
+ return [] unless classifier.is_a?(Ea::Model::Enumeration)
114
+
115
+ classifier.literals || []
116
+ end
117
+
118
+ def tagged_values_for(classifier)
119
+ return [] unless classifier.is_a?(Ea::Model::Classifier)
120
+
121
+ classifier.tagged_values || []
122
+ end
123
+
124
+ def constraints_for(classifier)
125
+ return [] unless classifier.is_a?(Ea::Model::Classifier)
126
+
127
+ classifier.constraints || []
128
+ end
129
+
130
+ # EA renders the off-canvas parent classifier's name as an
131
+ # italic line at the top of a placed element's header when
132
+ # ALL of these hold:
133
+ #
134
+ # 1. The diagram's HideParents flag is 0 (the default).
135
+ # HideParents=1 suppresses the ghost line entirely.
136
+ # 2. The parent (general) of a Generalization is NOT placed
137
+ # on the current diagram.
138
+ # 3. No other element from the parent's package is placed
139
+ # on the diagram — EA treats the parent's package as
140
+ # "represented" and omits the ghost as redundant.
141
+ def off_canvas_parent_name_for(classifier)
142
+ return nil unless classifier.is_a?(Ea::Model::Classifier)
143
+ return nil unless document
144
+ return nil unless diagram.show_parents
145
+
146
+ gen = parent_generalization_for(classifier)
147
+ return nil unless gen
148
+
149
+ parent = model_index[gen.general_id]
150
+ return nil unless parent
151
+ return nil if placed_on_diagram?(gen.general_id)
152
+ return nil if parent_package_represented_on_diagram?(parent)
153
+
154
+ name = parent.name.to_s
155
+ name.empty? ? nil : name
156
+ end
157
+
158
+ def parent_generalization_for(classifier)
159
+ document.relationships.find do |rel|
160
+ rel.is_a?(Ea::Model::Generalization) &&
161
+ rel.specific_id == classifier.id
162
+ end
163
+ end
164
+
165
+ def placed_on_diagram?(model_element_ref)
166
+ (diagram.elements || []).any? { |e| e.model_element_ref == model_element_ref }
167
+ end
168
+
169
+ # Returns true when any element placed on the current diagram
170
+ # lives in the same package as `parent`. EA suppresses the
171
+ # parent-class ghost line in that case — the parent's package
172
+ # is already "represented" on the diagram, so the off-canvas
173
+ # parent is implicitly visible to the reader.
174
+ def parent_package_represented_on_diagram?(parent)
175
+ parent_pkg = parent.is_a?(Ea::Model::Classifier) ? parent.package_id : nil
176
+ return false unless parent_pkg
177
+
178
+ (diagram.elements || []).any? do |e|
179
+ sibling = model_index[e.model_element_ref]
180
+ sibling.is_a?(Ea::Model::Classifier) && sibling.package_id == parent_pkg
181
+ end
182
+ end
183
+
184
+ # EA renders a package's child classifiers and sub-packages as
185
+ # alphabetical "+ Name" rows in the body compartment, but
186
+ # only when the diagram's t_diagram.ShowPackageContents flag
187
+ # is non-zero. Returns [] when the diagram suppresses
188
+ # package contents or the model element isn't a Package.
189
+ # Each entry is a Struct(name:, kind:) so the renderer can
190
+ # pick the correct per-type icon (Enumeration vs default).
191
+ def package_content_lines_for(model_element)
192
+ return [] unless model_element.is_a?(Ea::Model::Package)
193
+ return [] unless diagram.show_package_contents
194
+
195
+ children = package_children(model_element)
196
+ return [] if children.empty?
197
+
198
+ children.sort_by { |c| c.name.to_s }.map do |c|
199
+ PackageContentRow.new(name: c.name.to_s, kind: row_icon_kind(c))
200
+ end
201
+ end
202
+
203
+ def package_children(package)
204
+ return [] unless model_index
205
+
206
+ model_index.values.select do |obj|
207
+ (obj.is_a?(Ea::Model::Classifier) && obj.package_id == package.id) ||
208
+ (obj.is_a?(Ea::Model::Package) && obj.parent_id == package.id)
209
+ end
210
+ end
211
+
212
+ # Discriminator for the per-row icon shape. EA draws a
213
+ # list-style icon for Enumeration children (or classifiers
214
+ # stereotyped "enumeration"), a small 13×9 folder icon for
215
+ # sub-Packages, and a "folded paper" icon for other
216
+ # classifiers.
217
+ def row_icon_kind(child)
218
+ if child.is_a?(Ea::Model::Package)
219
+ :package
220
+ elsif child.is_a?(Ea::Model::Enumeration) ||
221
+ enumeration_stereotype?(child)
222
+ :enumeration
223
+ else
224
+ :default
225
+ end
226
+ end
227
+
228
+ def enumeration_stereotype?(child)
229
+ return false unless child.is_a?(Ea::Model::Classifier)
230
+
231
+ refs = child.stereotype_refs
232
+ refs&.any? { |r| r.to_s.downcase == "enumeration" }
233
+ end
234
+
235
+ PackageContentRow = Struct.new(:name, :kind, keyword_init: true)
236
+
237
+ # Computes y-coordinates for header/divider/attr compartments
238
+ # given bounds, font size, and line count. See
239
+ # Element::CompartmentGeometry for the coordinate math.
240
+ CompartmentGeometry = Element::CompartmentGeometry
241
+
242
+ # Returns true when this element's bounds are geometrically
243
+ # inside another element's bounds in the same diagram. EA
244
+ # shows qualified names (ParentClass::ChildClass) for
245
+ # visually-nested classes and simple names for separately-
246
+ # placed classes.
247
+ def visually_nested?(element)
248
+ my_bounds = element.bounds || element.image_bounds
249
+ return false unless my_bounds
250
+
251
+ (diagram.elements || []).any? do |other|
252
+ next if other.id == element.id
253
+
254
+ other_bounds = other.bounds || other.image_bounds
255
+ next unless other_bounds
256
+
257
+ bounds_contain?(other_bounds, my_bounds)
258
+ end
259
+ end
260
+
261
+ def bounds_contain?(outer, inner)
262
+ outer.x <= inner.x &&
263
+ outer.y <= inner.y &&
264
+ outer.x + outer.width >= inner.x + inner.width &&
265
+ outer.y + outer.height >= inner.y + inner.height
266
+ end
267
+
268
+ def resolve_fill(element, classifier)
269
+ color_resolver.fill_for(element, classifier)
270
+ end
271
+
272
+ def resolve_stroke
273
+ color_resolver.stroke_for(nil)
274
+ end
275
+
276
+ def resolve_stroke_width
277
+ theme.themed? ? theme.stroke_width : DEFAULT_STROKE_WIDTH
278
+ end
279
+
280
+ def color_resolver
281
+ @color_resolver ||= ColorResolver.new(theme: theme)
282
+ end
283
+
284
+ def theme
285
+ @theme ||= diagram.theme
286
+ end
287
+
288
+ def display_config
289
+ @display_config ||= diagram.display_config
290
+ end
291
+
292
+ def show_attributes?
293
+ display_config.show_attributes?
294
+ end
295
+
296
+ def show_operations?
297
+ display_config.show_operations?
298
+ end
299
+
300
+ # EA only renders a header→content divider when there's
301
+ # actual content below (attributes, operations, literals, or
302
+ # tagged values). Classes with only a header (no displayed
303
+ # features) omit the divider entirely.
304
+ def has_content_below_header?(attr_lines, op_lines, enum_literals, tagged_values = [])
305
+ !attr_lines.empty? || !op_lines.empty? || enum_literals.any? || tagged_values.any?
306
+ end
307
+
308
+ def font_resolver
309
+ @font_resolver ||= FontResolver.new(diagram, theme: theme)
310
+ end
311
+
312
+ def element_filter
313
+ @element_filter ||= Element::Filter.new(model_index: model_index)
314
+ end
315
+
316
+ def classifier_for(element)
317
+ ref = element.model_element_ref
318
+ return nil unless ref
319
+
320
+ candidate = model_index[ref]
321
+ return nil unless candidate.is_a?(Ea::Model::Classifier) ||
322
+ candidate.is_a?(Ea::Model::InstanceSpecification)
323
+
324
+ candidate
325
+ end
326
+
327
+ # Lookup proc for AttributeLineBuilder: resolves a
328
+ # classifier id to the Classifier instance. Returns nil
329
+ # when not found or not a Classifier.
330
+ def attribute_lookup
331
+ lambda { |id| model_index[id] if model_index && model_index[id].is_a?(Ea::Model::Classifier) }
332
+ end
333
+
334
+ def model_element_for(element)
335
+ ref = element.model_element_ref
336
+ return nil unless ref
337
+
338
+ model_index[ref]
339
+ end
340
+
341
+ def translate_bounds(b)
342
+ return b unless canvas
343
+
344
+ # Reuse the model's Bounds value object rather than an
345
+ # ad-hoc OpenStruct. Keeps the type uniform across the
346
+ # pipeline and avoids require "ostruct".
347
+ Ea::Model::Bounds.new(
348
+ x: canvas.translate_x(b.x),
349
+ y: canvas.translate_y(b.y),
350
+ width: b.width,
351
+ height: b.height
352
+ )
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Resolves the effective font family + size for a DiagramElement,
7
+ # using EA's fallback chain:
8
+ #
9
+ # 1. element.font_family / element.font_size (per-element override)
10
+ # 2. theme.font_family / theme.font_size (when theme is themed)
11
+ # 3. diagram-level default (most common non-nil value across
12
+ # the diagram's elements)
13
+ # 4. locale fallback (Calibri 10)
14
+ #
15
+ # Lives outside the Elements emitter so the resolution rule is
16
+ # defined once (MECE) and reusable by Labels and any future
17
+ # text-emitting layer.
18
+ class FontResolver
19
+ DEFAULT_FONT_FAMILY = "Yu Gothic UI"
20
+ # EA's default element compartment font size (matches
21
+ # reference SVGs which use 9pt for classifier header,
22
+ # attributes, operations, etc. — distinct from theme's
23
+ # diagram-level font size which applies only to frame
24
+ # label and swimlanes).
25
+ DEFAULT_ELEMENT_FONT_SIZE = 9
26
+
27
+ attr_reader :diagram, :theme
28
+
29
+ def initialize(diagram, theme: nil)
30
+ @diagram = diagram
31
+ @theme = theme || Ea::Theme::Registry.default
32
+ end
33
+
34
+ def family_for(element)
35
+ explicit = element&.font_family
36
+ return explicit if explicit && !explicit.empty?
37
+ return theme.font_family if theme.themed? && theme.font_family
38
+
39
+ diagram_default_family || DEFAULT_FONT_FAMILY
40
+ end
41
+
42
+ def size_for(element)
43
+ explicit = element&.font_size
44
+ explicit = nil if explicit&.zero?
45
+ return explicit if explicit
46
+ return diagram_default_size if diagram_default_size
47
+
48
+ DEFAULT_ELEMENT_FONT_SIZE
49
+ end
50
+
51
+ def size_unit_for(_element)
52
+ theme.font_size_unit || "pt"
53
+ end
54
+
55
+ def weight_for(element)
56
+ bold = element&.font_bold ? 700 : nil
57
+ bold || (theme.themed? ? theme.text_weight_normal : 400)
58
+ end
59
+
60
+ def style_for(element)
61
+ element&.font_italic ? "italic" : "normal"
62
+ end
63
+
64
+ private
65
+
66
+ # EA's app-default font depends on the system locale:
67
+ # Japanese Windows → "Yu Gothic UI" 13, English Windows →
68
+ # "Calibri" 10. We can't query EA's runtime, so we infer
69
+ # from the diagram:
70
+ #
71
+ # - If ANY element specifies font=, use the most common.
72
+ # - If NO element specifies font= (all are "use default"),
73
+ # assume the EA English-locale default: Calibri 10.
74
+ def diagram_default_family
75
+ families = element_families
76
+ return FALLBACK_DEFAULT_FAMILY if families.empty?
77
+
78
+ most_common(families)
79
+ end
80
+
81
+ def diagram_default_size
82
+ sizes = element_sizes
83
+ return nil if sizes.empty?
84
+
85
+ most_common(sizes)
86
+ end
87
+
88
+ FALLBACK_DEFAULT_FAMILY = "Calibri"
89
+
90
+ def most_common(values)
91
+ return nil if values.empty?
92
+
93
+ tally = tally_values(values)
94
+ tally.max_by { |_, count| count }.first
95
+ end
96
+
97
+ def element_families
98
+ (diagram.elements || []).map(&:font_family).compact
99
+ end
100
+
101
+ def element_sizes
102
+ (diagram.elements || []).map(&:font_size).compact.reject(&:zero?)
103
+ end
104
+
105
+ def tally_values(values)
106
+ values.each_with_object(Hash.new(0)) { |v, acc| acc[v] += 1 }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Emits ghost classifier name `<text>` elements for connector
7
+ # endpoints that reference off-canvas classifiers. Each ghost
8
+ # label appears as italic text near the connector's on-canvas
9
+ # endpoint.
10
+ class GhostLabels
11
+ DEFAULT_FAMILY = "Carlito"
12
+ DEFAULT_SIZE = 9
13
+ DEFAULT_UNIT = "pt"
14
+ DEFAULT_FILL = "#000000"
15
+
16
+ attr_reader :diagram, :canvas, :family, :size, :size_unit, :fill
17
+
18
+ def initialize(diagram, canvas: nil, family: DEFAULT_FAMILY,
19
+ size: DEFAULT_SIZE, size_unit: DEFAULT_UNIT,
20
+ fill: DEFAULT_FILL)
21
+ @diagram = diagram
22
+ @canvas = canvas
23
+ @family = family
24
+ @size = size
25
+ @size_unit = size_unit
26
+ @fill = fill
27
+ end
28
+
29
+ def render
30
+ texts = visible_connectors.flat_map do |connector|
31
+ (connector.ghost_labels || []).map { |g| text_for(g) }
32
+ end
33
+ return "" if texts.empty?
34
+
35
+ group_style = "stroke-width:1;stroke-linecap:round;" \
36
+ "stroke-linejoin:bevel; fill:#{fill};" \
37
+ "fill-opacity:1.00; stroke:#000000;" \
38
+ " stroke-opacity:0.00"
39
+ %(<g style="#{group_style}">\n#{texts.join("\n")}\n</g>)
40
+ end
41
+
42
+ private
43
+
44
+ def visible_connectors
45
+ (diagram.connectors || []).select(&:renderable?)
46
+ end
47
+
48
+ def text_for(ghost)
49
+ tx, ty = translate(ghost.anchor_x, ghost.anchor_y)
50
+ TextRenderer.new(
51
+ content: ghost.name.to_s,
52
+ x: tx, y: ty,
53
+ family: family, size: size, size_unit: size_unit,
54
+ style: "italic", fill: fill
55
+ ).to_svg
56
+ end
57
+
58
+ def translate(x, y)
59
+ return [x, y] unless canvas
60
+
61
+ [canvas.translate_x(x), canvas.translate_y(y)]
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end