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,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Computes the (min_x, min_y, width, height) tuple for a
7
+ # Diagram's canvas. Encapsulates the union rules so Canvas
8
+ # stays a small value-object with just coordinate translation
9
+ # and formatting concerns.
10
+ #
11
+ # Sources contributing to the bounds:
12
+ # - ElementBounds: logical x-extent, logical+image y-extent
13
+ # - ConnectorBounds: waypoint positions
14
+ # - MarkerExtent: MARKER_EXTENT padding around end points
15
+ # - PackageTabExtent: extra space above Package elements for
16
+ # the tab polygon (which sits above the
17
+ # element's logical top).
18
+ #
19
+ # Each source is a method returning an Array<[x, y]> points.
20
+ # Composing new sources = adding a method, no modification of
21
+ # existing ones (OCP).
22
+ class BoundsCalculator
23
+ MARKER_EXTENT = 15
24
+ PACKAGE_TAB_HEIGHT = 20
25
+ # EA's canvas includes non-uniform frame insets around the
26
+ # element content. Reverse-engineered from reference SVG
27
+ # byte-diff: maintenance diagram has element 169x80 at
28
+ # logical (0,0), ref canvas is 254x177 with element at
29
+ # (35, 40). So:
30
+ # canvas_width = element_width + INSET_LEFT + INSET_RIGHT
31
+ # canvas_height = element_height + INSET_TOP + INSET_BOTTOM
32
+ # The top inset accommodates the frame tab + label space
33
+ # above the element.
34
+ INSET_LEFT = 35
35
+ INSET_RIGHT = 50
36
+ INSET_TOP = 40
37
+ INSET_BOTTOM = 57
38
+
39
+ attr_reader :diagram, :model_index
40
+
41
+ def initialize(diagram, model_index: nil)
42
+ @diagram = diagram
43
+ @model_index = model_index
44
+ end
45
+
46
+ def compute
47
+ points = []
48
+ points.concat(element_points)
49
+ points.concat(connector_points)
50
+ points.concat(marker_points)
51
+ points.concat(package_tab_points)
52
+ return [0, 0, 1, 1] if points.empty?
53
+
54
+ xs = points.map(&:first)
55
+ ys = points.map(&:last)
56
+ min_x = xs.min || 0
57
+ min_y = ys.min || 0
58
+ [
59
+ min_x,
60
+ min_y,
61
+ (xs.max - min_x) + INSET_LEFT + INSET_RIGHT,
62
+ (ys.max - min_y) + INSET_TOP + INSET_BOTTOM
63
+ ]
64
+ end
65
+
66
+ private
67
+
68
+ # Element x-extent: logical bounds only (matches EA's canvas
69
+ # left/right exactly).
70
+ # Element y-extent: union of logical + image_bounds (image
71
+ # extends below bounds for shadow / image padding).
72
+ def element_points
73
+ pts = []
74
+ (diagram.elements || []).each do |e|
75
+ primary = e.bounds || e.image_bounds
76
+ if primary
77
+ pts << [primary.x, primary.y]
78
+ pts << [primary.x + primary.width, primary.y + primary.height]
79
+ end
80
+ if e.bounds && e.image_bounds
81
+ ib = e.image_bounds
82
+ pts << [ib.x, ib.y]
83
+ pts << [ib.x + ib.width, ib.y + ib.height]
84
+ end
85
+ end
86
+ pts
87
+ end
88
+
89
+ # Package elements render with a tab polygon ABOVE the
90
+ # element's logical top edge (height PACKAGE_TAB_HEIGHT).
91
+ # Include those points so the canvas reserves space.
92
+ def package_tab_points
93
+ return [] if model_index.nil?
94
+
95
+ pts = []
96
+ (diagram.elements || []).each do |e|
97
+ ref = e.model_element_ref
98
+ next unless ref
99
+
100
+ candidate = model_index[ref]
101
+ next unless candidate.is_a?(Ea::Model::Package)
102
+
103
+ primary = e.bounds || e.image_bounds
104
+ next unless primary
105
+
106
+ pts << [primary.x, primary.y - PACKAGE_TAB_HEIGHT]
107
+ pts << [primary.x + primary.width, primary.y]
108
+ end
109
+ pts
110
+ end
111
+
112
+ def connector_points
113
+ pts = []
114
+ (diagram.connectors || []).each do |c|
115
+ (c.waypoints || []).each do |wp|
116
+ next unless wp.position
117
+
118
+ pts << [wp.position.x, wp.position.y]
119
+ end
120
+ end
121
+ pts
122
+ end
123
+
124
+ def marker_points
125
+ pts = []
126
+ (diagram.connectors || []).each do |c|
127
+ waypoints = (c.waypoints || []).map(&:position).compact
128
+ next unless waypoints.size >= 2
129
+
130
+ source = waypoints.first
131
+ target = waypoints.last
132
+ pts << [source.x - MARKER_EXTENT, source.y - MARKER_EXTENT]
133
+ pts << [source.x + MARKER_EXTENT, source.y + MARKER_EXTENT]
134
+ pts << [target.x - MARKER_EXTENT, target.y - MARKER_EXTENT]
135
+ pts << [target.x + MARKER_EXTENT, target.y + MARKER_EXTENT]
136
+ end
137
+ pts
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -3,44 +3,47 @@
3
3
  module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
- # Computes the canvas dimensions for the root <svg> element.
7
- # Union of all element image_bounds (the padded bounds EA
8
- # uses for the visual box), with a 10 px outer margin.
9
- # Emits cm dimensions (px / 28.346 at 72 DPI).
10
- Canvas = Struct.new(:min_x, :min_y, :width, :height, keyword_init: true) do
11
- PX_PER_CM = 28.3464567
6
+ # Canvas carries the diagram's computed pixel bounds plus the
7
+ # coordinate-translation helpers that align element (0,0) with
8
+ # EA's content origin (35, 40). Bound computation itself is
9
+ # delegated to BoundsCalculator (SRP).
10
+ #
11
+ # Promoted from a Struct.new + `do ... end` block to a proper
12
+ # class so the public API is explicit (attr_reader vs implicit
13
+ # struct fields) and behavior is named rather than implicit.
14
+ # Struct definitions with instance methods conflate "data" and
15
+ # "behavior" — a class keeps them separable while the values
16
+ # stay immutable.
17
+ class Canvas
18
+ # EA's diagram canvas places element (0,0) at SVG (35, 40) —
19
+ # the diagram's content origin inset from the canvas top-left
20
+ # corner. Reverse-engineered from reference SVG byte-diff
21
+ # against the maintenance diagram.
22
+ PX_PER_CM = 37.795275591
23
+ FRAME_INSET_LEFT = 35
24
+ FRAME_INSET_TOP = 40
12
25
 
13
- def self.from(diagram)
14
- points = []
15
- (diagram.elements || []).each do |e|
16
- b = e.image_bounds || e.bounds
17
- next unless b
26
+ attr_reader :min_x, :min_y, :width, :height
18
27
 
19
- points << [b.x, b.y]
20
- points << [b.x + b.width, b.y + b.height]
21
- end
22
- (diagram.connectors || []).each do |c|
23
- (c.waypoints || []).each do |wp|
24
- next unless wp.position
25
-
26
- points << [wp.position.x, wp.position.y]
27
- end
28
- end
29
- return new(min_x: 0, min_y: 0, width: 1, height: 1) if points.empty?
28
+ def initialize(min_x:, min_y:, width:, height:)
29
+ @min_x = min_x
30
+ @min_y = min_y
31
+ @width = width
32
+ @height = height
33
+ end
30
34
 
31
- xs = points.map(&:first)
32
- ys = points.map(&:last)
33
- margin = 10
34
- new(
35
- min_x: (xs.min || 0) - margin,
36
- min_y: (ys.min || 0) - margin,
37
- width: (xs.max - xs.min) + (2 * margin),
38
- height: (ys.max - ys.min) + (2 * margin)
39
- )
35
+ # Build a Canvas for a diagram by running the bound
36
+ # calculator. Defaults model_index to nil (no canvas-aware
37
+ # element bounds needed) for callers that only need a
38
+ # frame-size estimate.
39
+ def self.from(diagram, model_index: nil)
40
+ min_x, min_y, width, height =
41
+ BoundsCalculator.new(diagram, model_index: model_index).compute
42
+ new(min_x: min_x, min_y: min_y, width: width, height: height)
40
43
  end
41
44
 
42
45
  def view_box
43
- "#{min_x} #{min_y} #{width} #{height}"
46
+ "0 0 #{width} #{height}"
44
47
  end
45
48
 
46
49
  def width_cm
@@ -51,6 +54,26 @@ module Ea
51
54
  format_cm(height)
52
55
  end
53
56
 
57
+ # Translate a content-space point into SVG-space. EA insets
58
+ # the content area from the canvas top-left by
59
+ # FRAME_INSET_LEFT/TOP.
60
+ def translate_x(x)
61
+ x - min_x + FRAME_INSET_LEFT
62
+ end
63
+
64
+ def translate_y(y)
65
+ y - min_y + FRAME_INSET_TOP
66
+ end
67
+
68
+ # Compact coordinate formatter. Integers lose the decimal
69
+ # point; floats keep up to two digits with trailing zeros
70
+ # stripped (matches EA's emitted numeric format).
71
+ def self.coord(value)
72
+ return value.to_i.to_s if value == value.to_i
73
+
74
+ format("%.2f", value).sub(/\.?0+$/, "")
75
+ end
76
+
54
77
  private
55
78
 
56
79
  def format_cm(px)
@@ -62,4 +85,4 @@ module Ea
62
85
  end
63
86
  end
64
87
  end
65
- end
88
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Single source of truth for fill + stroke color decisions.
7
+ #
8
+ # Theme :119 applies:
9
+ # - Border color (#9A8484) for element strokes
10
+ # - Attribute color (#66413F) for attribute text
11
+ # - Method color for operation text
12
+ # Theme :119 does NOT apply:
13
+ # - Per-type fill colors (elements use default #FFFFFF)
14
+ # - Text color (#000000 black for non-attribute text)
15
+ #
16
+ # Precedence chain for fill:
17
+ # 1. Element's BCol int (when present and not sentinel)
18
+ # 2. Default fill (#FFFFFF)
19
+ #
20
+ # Precedence chain for stroke:
21
+ # 1. Element's LCol int (when present and not sentinel)
22
+ # 2. Theme border color (when theme is themed)
23
+ # 3. Default stroke (#000000)
24
+ class ColorResolver
25
+ DEFAULT_FILL = "#FFFFFF"
26
+ DEFAULT_STROKE = "#000000"
27
+
28
+ attr_reader :theme, :stereotype_resolver
29
+
30
+ def initialize(theme: nil, stereotype_resolver: Ea::Svg::StereotypeColorResolver.new)
31
+ @theme = theme || Ea::Theme::Registry.default
32
+ @stereotype_resolver = stereotype_resolver
33
+ end
34
+
35
+ def fill_for(element, classifier)
36
+ bcol = Element::BColDecoder.to_hex(element&.background_color)
37
+ return bcol if bcol
38
+
39
+ DEFAULT_FILL
40
+ end
41
+
42
+ def stroke_for(element)
43
+ lcol = Element::BColDecoder.to_hex(element&.line_color)
44
+ return lcol if lcol
45
+ return theme.border_color if theme.themed?
46
+
47
+ DEFAULT_STROKE
48
+ end
49
+
50
+ def attribute_text_color
51
+ theme.themed? ? (theme.attribute_color || DEFAULT_STROKE) : DEFAULT_STROKE
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Attribute compartment. Skipped when no attribute lines.
8
+ module Attributes
9
+ module_function
10
+
11
+ def render(context)
12
+ return nil if context.attr_lines.empty?
13
+
14
+ Element::AttributeRenderer.render(
15
+ context.attr_lines,
16
+ bounds: context.bounds,
17
+ first_y: context.geometry.attr_first_y,
18
+ family: context.family, size: context.size, size_unit: context.size_unit,
19
+ fill: context.theme.attribute_text_color,
20
+ visibility_x_offset: context.theme.attribute_spec.visibility_x_offset,
21
+ content_x_offset: context.theme.attribute_spec.content_x_offset
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Constraints compartment. Skipped when the classifier has no
8
+ # constraints. Renders an italic "constraints" header and one
9
+ # "{name}" line per constraint.
10
+ module Constraints
11
+ DEFAULT_TEXT_COLOR = "#000000"
12
+
13
+ module_function
14
+
15
+ def render(context)
16
+ return nil unless context.constraints&.any?
17
+
18
+ Element::ConstraintRenderer.render(
19
+ context.constraints,
20
+ bounds: context.bounds,
21
+ first_y: context.geometry.tagged_value_first_y,
22
+ family: context.family, size: context.size,
23
+ fill: DEFAULT_TEXT_COLOR
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Enumeration literal compartment: divider line + "literals"
8
+ # italic header + each literal name. Skipped when the
9
+ # classifier has no literals or the geometry cannot fit them.
10
+ module EnumLiterals
11
+ module_function
12
+
13
+ def render(context)
14
+ return nil unless context.enum_literals.any?
15
+ return nil unless context.geometry.enum_literal_first_y
16
+
17
+ [
18
+ Element::DividerRenderer.render(
19
+ context.bounds,
20
+ y: context.geometry.enum_divider_y,
21
+ stroke: context.stroke,
22
+ stroke_width: context.stroke_width
23
+ ),
24
+ render_literals_block(context)
25
+ ].join("\n")
26
+ end
27
+
28
+ def render_literals_block(context)
29
+ line_h = context.size + (context.theme.compartments.attr_line_offset || 4)
30
+ text_blocks = [literal_header_text(context)]
31
+ context.enum_literals.each_with_index do |literal, idx|
32
+ y = context.geometry.enum_literal_first_y + ((idx + 1) * line_h)
33
+ text_blocks << visibility_placeholder(context, y)
34
+ text_blocks << literal_name_text(context, literal, y)
35
+ end
36
+ wrap_group(text_blocks, context.theme.text_color)
37
+ end
38
+
39
+ def literal_header_text(context)
40
+ TextRenderer.new(
41
+ content: "literals",
42
+ x: context.bounds.x + (context.bounds.width / 2.0) -
43
+ (8 * context.size * context.theme.text_width_factor) / 2,
44
+ y: context.geometry.enum_literal_first_y,
45
+ family: context.family, size: context.size, size_unit: context.size_unit,
46
+ style: "italic", fill: context.theme.text_color,
47
+ stroke_in_text: context.theme.stroke_in_text_color,
48
+ width_factor: context.theme.text_width_factor
49
+ ).to_svg
50
+ end
51
+
52
+ def visibility_placeholder(context, y)
53
+ TextRenderer.new(
54
+ content: " ",
55
+ x: context.bounds.x + (context.theme.attribute_spec.visibility_x_offset || 5),
56
+ y: y,
57
+ family: context.family, size: context.size, size_unit: context.size_unit,
58
+ fill: context.theme.text_color,
59
+ stroke_in_text: context.theme.stroke_in_text_color,
60
+ width_factor: context.theme.text_width_factor
61
+ ).to_svg
62
+ end
63
+
64
+ def literal_name_text(context, literal, y)
65
+ TextRenderer.new(
66
+ content: literal.name.to_s,
67
+ x: context.bounds.x + (context.theme.attribute_spec.content_x_offset || 26),
68
+ y: y,
69
+ family: context.family, size: context.size, size_unit: context.size_unit,
70
+ fill: context.theme.text_color,
71
+ stroke_in_text: context.theme.stroke_in_text_color,
72
+ width_factor: context.theme.text_width_factor
73
+ ).to_svg
74
+ end
75
+
76
+ def wrap_group(text_blocks, text_color)
77
+ style = "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
78
+ "fill:#{text_color};fill-opacity:1.00; " \
79
+ "stroke:#000000; stroke-opacity:0.00"
80
+ %(<g style="#{style}">\n#{text_blocks.join("\n")}\n</g>)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Header compartment: stereotype label + class name. Skips
8
+ # rendering when the classifier produces no header lines.
9
+ module Header
10
+ module_function
11
+
12
+ def render(context)
13
+ return nil if context.header_lines.empty?
14
+
15
+ Element::HeaderRenderer.render(
16
+ context.header_lines,
17
+ bounds: context.bounds,
18
+ first_y: context.geometry.header_first_y,
19
+ family: context.family, size: context.size, size_unit: context.size_unit,
20
+ fill: context.theme.text_color,
21
+ weight_normal: context.theme.text_weight_normal,
22
+ weight_bold: context.theme.text_weight_bold,
23
+ stroke_in_text: context.theme.stroke_in_text_color,
24
+ width_factor: context.theme.text_width_factor,
25
+ line_offset: context.theme.compartments.header_line_offset
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Horizontal divider between header and the first content
8
+ # compartment (attributes / operations / literals / tagged
9
+ # values). Skipped when there is no content below.
10
+ module HeaderDivider
11
+ module_function
12
+
13
+ def render(context)
14
+ return nil unless context.geometry
15
+ return nil unless context.geometry.divider_y
16
+ return nil unless divider_required?(context)
17
+
18
+ Element::DividerRenderer.render(
19
+ context.bounds,
20
+ y: context.geometry.divider_y,
21
+ stroke: context.stroke,
22
+ stroke_width: context.stroke_width
23
+ )
24
+ end
25
+
26
+ # EA shows a header divider on Object diagrams when the
27
+ # element is large enough to hold a slot compartment (height
28
+ # > 50). The starter Object diagram uses 90×50 boxes with
29
+ # no slots and no divider; richer Object diagrams use
30
+ # 133×53+ boxes with the divider always present. On
31
+ # HandDraw diagrams (Domain Model), every element shows a
32
+ # divider regardless of content. For other diagrams the
33
+ # divider is shown only when there is content below the
34
+ # header.
35
+ def divider_required?(context)
36
+ return true if hand_draw?(context)
37
+ return object_element_with_slots?(context) if object_diagram?(context)
38
+
39
+ context.content_below_header?
40
+ end
41
+
42
+ def object_diagram?(context)
43
+ context.diagram && context.diagram.diagram_type == "Object"
44
+ end
45
+
46
+ def hand_draw?(context)
47
+ context.diagram && context.diagram.hand_draw
48
+ end
49
+
50
+ def object_element_with_slots?(context)
51
+ context.bounds && context.bounds.height.to_i > 50
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Instance slot compartment. Renders each slot on an
8
+ # InstanceSpecification as "name op value", then appends
9
+ # "(from PackageName)" in italics below the slots when the
10
+ # instance has a classifier. Skipped entirely when the model
11
+ # element is not an InstanceSpecification or has neither
12
+ # slots nor a classifier.
13
+ module InstanceSlots
14
+ ROW_OFFSET = 13
15
+ SUBTITLE_EXTRA_OFFSET = 10
16
+
17
+ module_function
18
+
19
+ def render(context)
20
+ return nil unless instance?(context)
21
+
22
+ instance = context.model_element
23
+ slots = instance.slots || []
24
+ subtitle = from_package_subtitle(instance)
25
+ return nil if slots.empty? && subtitle.nil?
26
+
27
+ first_y = context.geometry.attr_first_y
28
+ bounds = context.bounds
29
+ fill = context.theme.attribute_text_color
30
+ blocks = []
31
+ slots.each_with_index do |slot, idx|
32
+ y = first_y + (idx * ROW_OFFSET)
33
+ blocks << line_text(slot, bounds, y, context, fill)
34
+ end
35
+ if subtitle
36
+ y = first_y + (slots.size * ROW_OFFSET) + SUBTITLE_EXTRA_OFFSET
37
+ blocks << subtitle_text(subtitle, bounds, y, context, fill)
38
+ end
39
+ wrap(blocks.join("\n"), fill)
40
+ end
41
+
42
+ def instance?(context)
43
+ context.model_element.is_a?(Ea::Model::InstanceSpecification)
44
+ end
45
+ module_function :instance?
46
+
47
+ def from_package_subtitle(instance)
48
+ pkg = instance.package_name
49
+ return nil if pkg.nil? || pkg.empty?
50
+
51
+ classifier = instance.classifier_name
52
+ return nil if classifier.nil? || classifier.empty?
53
+
54
+ "(from #{pkg})"
55
+ end
56
+ module_function :from_package_subtitle
57
+
58
+ def line_text(slot, bounds, y_pos, context, fill)
59
+ content = "#{slot.name} #{slot.op} #{slot.value}"
60
+ TextRenderer.new(
61
+ content: content,
62
+ x: bounds.x + (context.theme.attribute_spec.content_x_offset || 26),
63
+ y: y_pos,
64
+ family: context.family, size: context.size,
65
+ size_unit: context.size_unit, fill: fill
66
+ ).to_svg
67
+ end
68
+ module_function :line_text
69
+
70
+ def subtitle_text(text, bounds, y_pos, context, fill)
71
+ estimated_width = text.length * context.size * 0.5
72
+ x_pos = bounds.x + (bounds.width - estimated_width) / 2.0
73
+ TextRenderer.new(
74
+ content: text,
75
+ x: x_pos,
76
+ y: y_pos,
77
+ family: context.family, size: context.size,
78
+ size_unit: context.size_unit, fill: fill,
79
+ style: "italic"
80
+ ).to_svg
81
+ end
82
+ module_function :subtitle_text
83
+
84
+ def wrap(body, fill)
85
+ group_style = "stroke-width:1;stroke-linecap:round;" \
86
+ "stroke-linejoin:bevel; fill:#{fill};" \
87
+ "fill-opacity:1.00; stroke:#000000;" \
88
+ " stroke-opacity:0.00"
89
+ %(<g style="#{group_style}">\n#{body}\n</g>)
90
+ end
91
+ module_function :wrap
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end