ea 0.4.0 → 0.5.1

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 (548) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -1
  3. data/Rakefile +3 -0
  4. data/TODO.complete/01-t-xref-parsing.md +91 -0
  5. data/TODO.complete/02-per-label-geometry.md +50 -0
  6. data/TODO.complete/03-object-properties-constraints.md +57 -0
  7. data/TODO.complete/04-operation-params.md +37 -0
  8. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  9. data/TODO.complete/06-emf-image-rendering.md +34 -0
  10. data/TODO.complete/07-auxiliary-tables.md +47 -0
  11. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  12. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  13. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  14. data/TODO.complete/20-cli-diff.md +46 -0
  15. data/TODO.complete/21-cli-render.md +26 -0
  16. data/TODO.complete/22-cli-export.md +29 -0
  17. data/TODO.complete/23-cli-mdg.md +23 -0
  18. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  19. data/TODO.complete/31-public-send-cleanup.md +61 -0
  20. data/TODO.complete/32-spec-coverage.md +33 -0
  21. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  22. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  23. data/TODO.complete/37-cli-lint.md +32 -0
  24. data/TODO.complete/38-cli-query.md +26 -0
  25. data/TODO.complete/39-cli-info.md +23 -0
  26. data/TODO.complete/40-ocl-evaluator.md +31 -0
  27. data/TODO.complete/41-round-trip-spec.md +18 -0
  28. data/TODO.complete/42-html-diff-report.md +18 -0
  29. data/TODO.complete/43-json-schema-export.md +20 -0
  30. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  31. data/TODO.complete/45-shapescript-extensions.md +25 -0
  32. data/TODO.complete/46-xsd-consolidation.md +20 -0
  33. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  34. data/TODO.complete/51-remove-respond-to.md +25 -0
  35. data/TODO.complete/52-xsd-decouple-fixtures.md +23 -0
  36. data/TODO.complete/53-xmi-public-send-cleanup.md +26 -0
  37. data/TODO.complete/54-wire-stereotype-icon-renderer.md +20 -0
  38. data/TODO.complete/55-wire-shapescript.md +20 -0
  39. data/TODO.complete/56-wire-emf-renderer.md +27 -0
  40. data/TODO.complete/57-wire-ocl-evaluator.md +23 -0
  41. data/TODO.complete/58-diff-modifications.md +21 -0
  42. data/TODO.complete/59-delete-toy-xmi-export.md +30 -0
  43. data/TODO.complete/60-curate-json-export.md +20 -0
  44. data/TODO.complete/61-plantuml-relationships.md +22 -0
  45. data/TODO.complete/62-changelog.md +17 -0
  46. data/TODO.complete/63-dependabot-fix.md +18 -0
  47. data/TODO.complete/STATUS.md +85 -0
  48. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  49. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  50. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  51. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  52. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  53. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  54. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  55. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  56. data/TODO.diagrams/12-connector-labels.md +40 -0
  57. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  58. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  59. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  60. data/TODO.diagrams/14-center-header-text.md +33 -0
  61. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  62. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  63. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  64. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  65. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  66. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  67. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  68. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  69. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  70. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  71. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  72. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  73. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  74. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  75. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  76. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  77. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  78. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  79. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  80. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  81. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  82. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  83. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  84. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  85. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  86. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  87. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  88. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  89. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  90. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  91. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  92. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  93. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  94. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  95. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  96. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  97. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  98. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  99. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  100. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  101. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  102. data/TODO.diagrams/50-examples-parity.md +85 -0
  103. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  104. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  105. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  106. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  107. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  108. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  109. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  110. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  111. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  112. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  113. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  114. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  115. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  116. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  117. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  118. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  119. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  120. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  121. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  122. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  123. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  124. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  125. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  126. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  127. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  128. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  129. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  130. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  131. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  132. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  133. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  134. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  135. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  136. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  137. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  138. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  139. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  140. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  141. data/TODO.diagrams/README.md +69 -0
  142. data/TODO.diagrams/STATUS.md +73 -0
  143. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  144. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  145. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  146. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  147. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  148. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  149. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  150. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  151. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  152. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  153. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  154. data/TODO.fixup/12-path-style-harmony.md +23 -0
  155. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  156. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  157. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  158. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  159. data/TODO.fixup/17-path-count-calibration.md +27 -0
  160. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  161. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  162. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  163. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  164. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  165. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  166. data/TODO.fixup/24-note-element-rendering.md +23 -0
  167. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  168. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  169. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  170. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  171. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  172. data/TODO.fixup/30-theme-system-stub.md +46 -0
  173. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  174. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  175. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  176. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  177. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  178. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  179. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  180. data/TODO.fixup/38-note-element-rendering.md +15 -0
  181. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  182. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  183. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  184. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  185. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  186. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  187. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  188. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  189. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  190. data/TODO.fixup/48-background-rect-format.md +15 -0
  191. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  192. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  193. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  194. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  195. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  196. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  197. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  198. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  199. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  200. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  201. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  202. data/TODO.fixup/60-cli-theme-option.md +22 -0
  203. data/TODO.fixup/61-theme-validation.md +28 -0
  204. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  205. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  206. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  207. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  208. data/TODO.fixup/66-styleex-writeback.md +22 -0
  209. data/TODO.fixup/67-delete-dead-code.md +28 -0
  210. data/TODO.fixup/68-figure-parity-status.md +82 -0
  211. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  212. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  213. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  214. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  215. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  216. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  217. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  218. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  219. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  220. data/TODO.fixup/78-note-element-rendering.md +30 -0
  221. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  222. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  223. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  224. data/config/qea_schema.yml +356 -0
  225. data/config/themes/119.yml +98 -0
  226. data/config/themes/default.yml +14 -0
  227. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  228. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  229. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  231. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  232. data/examples/exports/UmlModel_template/model.xml +23 -0
  233. data/examples/exports/basic/UML_EA.DTD +6856 -0
  234. data/examples/exports/basic/model.xml +8039 -0
  235. data/examples/exports/simple/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple/model.xml +585 -0
  237. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  238. data/examples/exports/simple_example/model.xml +23 -0
  239. data/examples/exports/test/UML_EA.DTD +6856 -0
  240. data/examples/exports/test/model.xml +1638 -0
  241. data/examples/qea/basic.qea +0 -0
  242. data/frontend/README.md +51 -0
  243. data/frontend/dist/app.iife.js +24 -0
  244. data/frontend/dist/style.css +1 -0
  245. data/frontend/index.html +26 -0
  246. data/frontend/package-lock.json +2359 -0
  247. data/frontend/package.json +26 -0
  248. data/frontend/src/App.vue +59 -0
  249. data/frontend/src/app.ts +8 -0
  250. data/frontend/src/components/AppHeader.vue +127 -0
  251. data/frontend/src/components/AppSidebar.vue +119 -0
  252. data/frontend/src/components/ClassDetails.vue +247 -0
  253. data/frontend/src/components/DiagramView.vue +79 -0
  254. data/frontend/src/components/PackageDetails.vue +92 -0
  255. data/frontend/src/components/PackageTreeNode.vue +64 -0
  256. data/frontend/src/components/SearchResults.vue +48 -0
  257. data/frontend/src/components/WelcomeView.vue +87 -0
  258. data/frontend/src/shims-vue.d.ts +5 -0
  259. data/frontend/src/stores/dataStore.ts +79 -0
  260. data/frontend/src/stores/uiStore.ts +167 -0
  261. data/frontend/src/styles/main.css +1566 -0
  262. data/frontend/src/types.ts +221 -0
  263. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  266. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  267. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  268. data/frontend/tests/e2e/render.spec.ts +188 -0
  269. data/frontend/tsconfig.json +20 -0
  270. data/frontend/vite.config.ts +26 -0
  271. data/frontend/vitest.config.ts +8 -0
  272. data/lib/ea/cli/app.rb +57 -2
  273. data/lib/ea/cli/command/diagrams.rb +37 -28
  274. data/lib/ea/cli/command/diff.rb +46 -0
  275. data/lib/ea/cli/command/export.rb +101 -0
  276. data/lib/ea/cli/command/info.rb +75 -0
  277. data/lib/ea/cli/command/lint.rb +48 -0
  278. data/lib/ea/cli/command/mdg.rb +98 -0
  279. data/lib/ea/cli/command/query.rb +34 -0
  280. data/lib/ea/cli/command/render.rb +135 -0
  281. data/lib/ea/cli/command/svg.rb +8 -15
  282. data/lib/ea/cli/command.rb +7 -0
  283. data/lib/ea/diagram/display_config.rb +184 -0
  284. data/lib/ea/diagram.rb +4 -41
  285. data/lib/ea/diff/change.rb +30 -0
  286. data/lib/ea/diff/comparator.rb +109 -0
  287. data/lib/ea/diff/html_reporter.rb +91 -0
  288. data/lib/ea/diff.rb +15 -0
  289. data/lib/ea/export/json/generator.rb +118 -0
  290. data/lib/ea/export/json.rb +12 -0
  291. data/lib/ea/export/json_schema/generator.rb +77 -0
  292. data/lib/ea/export/json_schema.rb +12 -0
  293. data/lib/ea/export/plantuml/generator.rb +140 -0
  294. data/lib/ea/export/plantuml.rb +12 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +137 -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 +13 -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 +40 -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 +34 -0
  333. data/lib/ea/model/diagram_element.rb +16 -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/qea/validation/database/ocl_constraint_validator.rb +65 -0
  378. data/lib/ea/qea/validation/database.rb +2 -0
  379. data/lib/ea/qea/validation/validation_engine.rb +2 -0
  380. data/lib/ea/qea/validation.rb +2 -0
  381. data/lib/ea/query/builder.rb +127 -0
  382. data/lib/ea/query.rb +9 -0
  383. data/lib/ea/render/image_converter.rb +101 -0
  384. data/lib/ea/render.rb +10 -0
  385. data/lib/ea/shapescript/parser.rb +208 -0
  386. data/lib/ea/shapescript/renderer.rb +74 -0
  387. data/lib/ea/shapescript/shape.rb +34 -0
  388. data/lib/ea/shapescript.rb +24 -0
  389. data/lib/ea/sources/qea/adapter.rb +22 -5
  390. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  391. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  392. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  393. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  394. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  395. data/lib/ea/sources/qea/note_builder.rb +58 -0
  396. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  397. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  398. data/lib/ea/sources/qea/package_builder.rb +16 -0
  399. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  400. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  401. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  402. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  403. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  404. data/lib/ea/sources/qea/xref/record.rb +41 -0
  405. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  406. data/lib/ea/sources/qea/xref.rb +27 -0
  407. data/lib/ea/sources/qea.rb +4 -0
  408. data/lib/ea/sources/xmi/adapter.rb +80 -2
  409. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  410. data/lib/ea/sources/xmi/diagram_builder.rb +95 -18
  411. data/lib/ea/sources/xmi/extension_geometry_parser.rb +19 -2
  412. data/lib/ea/sources/xmi/extension_style_parser.rb +15 -2
  413. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  414. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  415. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  416. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  417. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  418. data/lib/ea/sources/xmi.rb +4 -0
  419. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  420. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  421. data/lib/ea/spa/output/strategy.rb +34 -0
  422. data/lib/ea/svg/connector_router.rb +134 -0
  423. data/lib/ea/svg/ea_emitter/background.rb +2 -3
  424. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  425. data/lib/ea/svg/ea_emitter/canvas.rb +56 -33
  426. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  427. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  428. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  429. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  430. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  431. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  432. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  433. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  434. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  435. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  436. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  437. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  438. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  439. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  440. data/lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb +32 -0
  441. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  442. data/lib/ea/svg/ea_emitter/compartment.rb +60 -0
  443. data/lib/ea/svg/ea_emitter/connectors.rb +46 -12
  444. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  445. data/lib/ea/svg/ea_emitter/document.rb +49 -25
  446. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  447. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  448. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  449. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  450. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  451. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  452. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  453. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  454. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  458. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  459. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  460. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  461. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  462. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  463. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  464. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  465. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  466. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  467. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  468. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  469. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  470. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +119 -0
  471. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  472. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  473. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  474. data/lib/ea/svg/ea_emitter/elements.rb +283 -95
  475. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  476. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  477. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  478. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  479. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  480. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  481. data/lib/ea/svg/ea_emitter/labels.rb +93 -27
  482. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  483. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  484. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  485. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  486. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  487. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  488. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  489. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  490. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  491. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  492. data/lib/ea/svg/ea_emitter/markers.rb +351 -64
  493. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  494. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  495. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  496. data/lib/ea/svg/ea_emitter.rb +33 -11
  497. data/lib/ea/svg/parity/checker.rb +105 -0
  498. data/lib/ea/svg/parity/source.rb +59 -0
  499. data/lib/ea/svg/parity/suite.rb +113 -0
  500. data/lib/ea/svg/parity.rb +14 -0
  501. data/lib/ea/svg.rb +2 -15
  502. data/lib/ea/theme/definition.rb +176 -0
  503. data/lib/ea/theme/loader.rb +125 -0
  504. data/lib/ea/theme/registry.rb +96 -0
  505. data/lib/ea/theme.rb +37 -0
  506. data/lib/ea/transformations.rb +5 -88
  507. data/lib/ea/validation/xmi_parity.rb +80 -0
  508. data/lib/ea/validation.rb +12 -0
  509. data/lib/ea/version.rb +4 -1
  510. data/lib/ea.rb +15 -4
  511. data/lib/tasks/svg_bench.rake +87 -0
  512. data/lutaml-uml-0.5.1.gem +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  520. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  521. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  522. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  523. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  524. metadata +464 -26
  525. data/lib/ea/diagram/configuration.rb +0 -379
  526. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  527. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  528. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  529. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  530. data/lib/ea/diagram/element_renderers.rb +0 -43
  531. data/lib/ea/diagram/extractor.rb +0 -566
  532. data/lib/ea/diagram/layout_engine.rb +0 -170
  533. data/lib/ea/diagram/path_builder.rb +0 -202
  534. data/lib/ea/diagram/style_parser.rb +0 -42
  535. data/lib/ea/diagram/style_resolver.rb +0 -291
  536. data/lib/ea/diagram/svg_renderer.rb +0 -274
  537. data/lib/ea/diagram/util.rb +0 -73
  538. data/lib/ea/svg/bounds_calculator.rb +0 -74
  539. data/lib/ea/svg/connector_path.rb +0 -138
  540. data/lib/ea/svg/element_box.rb +0 -256
  541. data/lib/ea/svg/renderer.rb +0 -77
  542. data/lib/ea/svg/style_resolver.rb +0 -72
  543. data/lib/ea/transformations/configuration.rb +0 -333
  544. data/lib/ea/transformations/format_registry.rb +0 -366
  545. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  546. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  547. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  548. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -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
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Instance-name underline for object diagrams. EA draws a
8
+ # short horizontal line just below each instance name to
9
+ # denote that it is an instance (UML convention). Rendered
10
+ # only when the diagram's `diagram_type` is "Object".
11
+ module InstanceUnderline
12
+ Y_OFFSET = 2
13
+ WIDTH_FACTOR = 0.65
14
+
15
+ module_function
16
+
17
+ def render(context)
18
+ return nil unless context.diagram
19
+ return nil unless object_diagram?(context)
20
+ return nil if context.header_lines.empty?
21
+
22
+ name_line = context.header_lines.last
23
+ text = name_line.first.to_s
24
+ return nil if text.empty?
25
+
26
+ underline_path(context, text)
27
+ end
28
+
29
+ def object_diagram?(context)
30
+ context.diagram && context.diagram.diagram_type == "Object"
31
+ end
32
+ module_function :object_diagram?
33
+
34
+ def underline_path(context, text)
35
+ bounds = context.bounds
36
+ text_width = TextRenderer.estimate_width(text, context.size,
37
+ WIDTH_FACTOR)
38
+ x_start = bounds.x + (bounds.width - text_width) / 2.0
39
+ y_pos = context.geometry.header_first_y + Y_OFFSET
40
+ build_path_tag(x_start, y_pos, text_width)
41
+ end
42
+ module_function :underline_path
43
+
44
+ def build_path_tag(x_start, y_pos, text_width)
45
+ x_end = x_start + text_width
46
+ coords = format_coords(x_start, y_pos, x_end)
47
+ d = "M #{coords[:x1]} #{coords[:y1]} L #{coords[:x2]} #{coords[:y1]}"
48
+ style = "stroke-width:1;stroke-linecap:square;" \
49
+ "stroke-linejoin:miter; fill:none; stroke:#000000;" \
50
+ " stroke-opacity:1.00"
51
+ %(<g style="#{style}">\n <path d="#{d}" shape-rendering="auto"/>\n</g>)
52
+ end
53
+ module_function :build_path_tag
54
+
55
+ def format_coords(x_start, y_pos, x_end)
56
+ {
57
+ x1: Ea::Svg::EaEmitter::Canvas.coord(x_start),
58
+ x2: Ea::Svg::EaEmitter::Canvas.coord(x_end),
59
+ y1: Ea::Svg::EaEmitter::Canvas.coord(y_pos)
60
+ }
61
+ end
62
+ module_function :format_coords
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Note body text wrapped inside a Note element's bounds.
8
+ # Renders only when the host element is a Note with a
9
+ # non-empty body.
10
+ module NoteBody
11
+ module_function
12
+
13
+ def render(context)
14
+ return nil if context.model_element.is_a?(Ea::Model::Note) &&
15
+ context.model_element.legend
16
+
17
+ body = context.note_body
18
+ return nil unless body
19
+
20
+ text_blocks = wrapped_lines(context, body)
21
+ %(<g style="#{group_style(context)}">\n#{text_blocks.join("\n")}\n</g>)
22
+ end
23
+
24
+ def wrapped_lines(context, body)
25
+ max_chars = [(context.bounds.width / (context.size * 0.6)).floor, 10].max
26
+ body.to_s.split(/\n/).flat_map do |para|
27
+ para.gsub(/(.{1,#{max_chars}})(\s+|$)/, "\\1\n").strip.split(/\n/)
28
+ end.each_with_index.map do |line, idx|
29
+ y = context.bounds.y + context.theme.note.text_y_offset +
30
+ (idx * context.theme.note.line_height)
31
+ TextRenderer.new(
32
+ content: line,
33
+ x: context.bounds.x + context.theme.note.text_x_offset,
34
+ y: y,
35
+ family: context.family, size: context.size, size_unit: context.size_unit,
36
+ fill: context.theme.text_color,
37
+ stroke_in_text: context.theme.stroke_in_text_color,
38
+ width_factor: context.theme.text_width_factor
39
+ ).to_svg
40
+ end
41
+ end
42
+
43
+ def group_style(context)
44
+ "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
45
+ "fill:#{context.theme.text_color};fill-opacity:1.00; " \
46
+ "stroke:#000000; stroke-opacity:0.00"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Note text compartment. When diagram.show_notes is true,
8
+ # renders the classifier's documentation note (from
9
+ # t_object.Note) as wrapped text lines in the element body.
10
+ # Lines are estimated by character width and split at word
11
+ # boundaries to fit the element bounds.
12
+ module NoteText
13
+ X_PADDING = 5
14
+ Y_FIRST_OFFSET = 15
15
+ LINE_OFFSET = 13
16
+ CHAR_WIDTH_FACTOR = 0.55
17
+
18
+ module_function
19
+
20
+ def render(context)
21
+ return nil unless show_notes?(context)
22
+ return nil unless context.classifier&.is_a?(Ea::Model::Classifier)
23
+
24
+ notes = note_bodies(context.classifier)
25
+ return nil if notes.empty?
26
+
27
+ first_y = context.geometry.attr_first_y + Y_FIRST_OFFSET
28
+ bounds = context.bounds
29
+ fill = context.theme.attribute_text_color
30
+ usable_width = bounds.width - (X_PADDING * 2)
31
+ max_chars = (usable_width / (context.size * CHAR_WIDTH_FACTOR)).floor
32
+
33
+ texts = []
34
+ y = first_y
35
+ notes.each do |body|
36
+ wrapped_lines(body, max_chars).each do |line|
37
+ texts << build_text(line, bounds.x + X_PADDING, y, context, fill)
38
+ y += LINE_OFFSET
39
+ end
40
+ end
41
+ wrap_group(texts.join("\n"), fill)
42
+ end
43
+
44
+ def show_notes?(context)
45
+ context.diagram && context.diagram.show_notes
46
+ end
47
+ module_function :show_notes?
48
+
49
+ def note_bodies(classifier)
50
+ (classifier.annotations || []).filter_map do |a|
51
+ body = a.body.to_s.strip
52
+ body.empty? ? nil : body
53
+ end
54
+ end
55
+ module_function :note_bodies
56
+
57
+ # Word-wrap text at max_chars, preserving word boundaries.
58
+ def wrapped_lines(text, max_chars)
59
+ return [text] if text.length <= max_chars
60
+
61
+ words = text.split(" ")
62
+ lines = []
63
+ current = ""
64
+ words.each do |word|
65
+ if current.empty?
66
+ current = word
67
+ elsif (current + " " + word).length <= max_chars
68
+ current = "#{current} #{word}"
69
+ else
70
+ lines << current
71
+ current = word
72
+ end
73
+ end
74
+ lines << current unless current.empty?
75
+ lines
76
+ end
77
+ module_function :wrapped_lines
78
+
79
+ def build_text(content, x_pos, y_pos, context, fill)
80
+ TextRenderer.new(
81
+ content: content,
82
+ x: x_pos, y: y_pos,
83
+ family: context.family, size: context.size,
84
+ size_unit: context.size_unit, fill: fill,
85
+ style: "italic"
86
+ ).to_svg
87
+ end
88
+ module_function :build_text
89
+
90
+ def wrap_group(body, fill)
91
+ style = "stroke-width:1;stroke-linecap:round;" \
92
+ "stroke-linejoin:bevel; fill:#{fill};" \
93
+ "fill-opacity:1.00; stroke:#000000;" \
94
+ " stroke-opacity:0.00"
95
+ %(<g style="#{style}">\n#{body}\n</g>)
96
+ end
97
+ module_function :wrap_group
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Operation compartment: divider line + operation signatures.
8
+ # Skipped when no operations or when geometry cannot fit them.
9
+ module Operations
10
+ module_function
11
+
12
+ def render(context)
13
+ return nil unless context.op_lines.any?
14
+ return nil unless context.geometry.op_first_y
15
+ return nil unless context.classifier
16
+
17
+ parts = []
18
+ if context.attr_lines.any?
19
+ parts << Element::DividerRenderer.render(
20
+ context.bounds,
21
+ y: context.geometry.op_divider_y,
22
+ stroke: context.stroke,
23
+ stroke_width: context.stroke_width
24
+ )
25
+ end
26
+ parts << Element::OperationRenderer.render(
27
+ context.classifier.operations,
28
+ bounds: context.bounds,
29
+ first_y: context.geometry.op_first_y,
30
+ family: context.family, size: context.size
31
+ )
32
+ parts.join("\n")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end