ea 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (544) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/TODO.complete/01-t-xref-parsing.md +91 -0
  4. data/TODO.complete/02-per-label-geometry.md +50 -0
  5. data/TODO.complete/03-object-properties-constraints.md +57 -0
  6. data/TODO.complete/04-operation-params.md +37 -0
  7. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  8. data/TODO.complete/06-emf-image-rendering.md +34 -0
  9. data/TODO.complete/07-auxiliary-tables.md +47 -0
  10. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  11. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  12. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  13. data/TODO.complete/20-cli-diff.md +46 -0
  14. data/TODO.complete/21-cli-render.md +26 -0
  15. data/TODO.complete/22-cli-export.md +29 -0
  16. data/TODO.complete/23-cli-mdg.md +23 -0
  17. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  18. data/TODO.complete/31-public-send-cleanup.md +61 -0
  19. data/TODO.complete/32-spec-coverage.md +33 -0
  20. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  21. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  22. data/TODO.complete/37-cli-lint.md +32 -0
  23. data/TODO.complete/38-cli-query.md +26 -0
  24. data/TODO.complete/39-cli-info.md +23 -0
  25. data/TODO.complete/40-ocl-evaluator.md +31 -0
  26. data/TODO.complete/41-round-trip-spec.md +18 -0
  27. data/TODO.complete/42-html-diff-report.md +18 -0
  28. data/TODO.complete/43-json-schema-export.md +20 -0
  29. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  30. data/TODO.complete/45-shapescript-extensions.md +25 -0
  31. data/TODO.complete/46-xsd-consolidation.md +20 -0
  32. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  33. data/TODO.complete/STATUS.md +109 -0
  34. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  35. data/TODO.diagrams/01-xmi-extension-elements-parsing.md +67 -0
  36. data/TODO.diagrams/02-diagram-element-rich-model.md +61 -0
  37. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  38. data/TODO.diagrams/03-ea-style-svg-emitter.md +70 -0
  39. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  40. data/TODO.diagrams/04-connector-routing-from-soid-eoid.md +55 -0
  41. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  42. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  43. data/TODO.diagrams/05-marker-rendering-per-relationship-type.md +49 -0
  44. data/TODO.diagrams/06-connector-labels.md +49 -0
  45. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  46. data/TODO.diagrams/07-canvas-size-and-viewbox.md +39 -0
  47. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  48. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  49. data/TODO.diagrams/08-visual-regression-harness.md +34 -0
  50. data/TODO.diagrams/09-cli-render-all-diagrams.md +25 -0
  51. data/TODO.diagrams/10-stereotype-color-config.md +29 -0
  52. data/TODO.diagrams/11-elkrb-auto-layout-integration.md +53 -0
  53. data/TODO.diagrams/12-connector-labels.md +40 -0
  54. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  55. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  56. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  57. data/TODO.diagrams/14-center-header-text.md +33 -0
  58. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  59. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  60. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  61. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  62. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  63. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  64. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  65. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  66. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  67. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  68. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  69. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  70. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  71. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  72. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  73. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  74. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  75. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  76. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  77. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  78. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  79. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  80. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  81. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  82. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  83. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  84. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  85. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  86. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  87. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  88. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  89. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  90. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  91. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  92. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  93. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  94. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  95. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  96. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  97. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  98. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  99. data/TODO.diagrams/50-examples-parity.md +85 -0
  100. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  101. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  102. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  103. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  104. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  105. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  106. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  107. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  108. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  109. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  110. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  111. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  112. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  113. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  114. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  115. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  116. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  117. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  118. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  119. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  120. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  121. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  122. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  123. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  124. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  125. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  126. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  127. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  128. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  129. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  130. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  131. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  132. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  133. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  134. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  135. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  136. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  137. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  138. data/TODO.diagrams/README.md +69 -0
  139. data/TODO.diagrams/STATUS.md +73 -0
  140. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  141. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  142. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  143. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  144. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  145. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  146. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  147. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  148. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  149. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  150. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  151. data/TODO.fixup/12-path-style-harmony.md +23 -0
  152. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  153. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  154. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  155. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  156. data/TODO.fixup/17-path-count-calibration.md +27 -0
  157. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  158. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  159. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  160. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  161. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  162. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  163. data/TODO.fixup/24-note-element-rendering.md +23 -0
  164. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  165. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  166. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  167. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  168. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  169. data/TODO.fixup/30-theme-system-stub.md +46 -0
  170. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  171. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  172. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  173. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  174. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  175. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  176. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  177. data/TODO.fixup/38-note-element-rendering.md +15 -0
  178. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  179. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  180. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  181. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  182. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  183. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  184. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  185. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  186. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  187. data/TODO.fixup/48-background-rect-format.md +15 -0
  188. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  189. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  190. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  191. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  192. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  193. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  194. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  195. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  196. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  197. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  198. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  199. data/TODO.fixup/60-cli-theme-option.md +22 -0
  200. data/TODO.fixup/61-theme-validation.md +28 -0
  201. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  202. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  203. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  204. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  205. data/TODO.fixup/66-styleex-writeback.md +22 -0
  206. data/TODO.fixup/67-delete-dead-code.md +28 -0
  207. data/TODO.fixup/68-figure-parity-status.md +82 -0
  208. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  209. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  210. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  211. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  212. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  213. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  214. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  215. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  216. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  217. data/TODO.fixup/78-note-element-rendering.md +30 -0
  218. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  219. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  220. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  221. data/config/qea_schema.yml +356 -0
  222. data/config/stereotype_colors.yml +15 -0
  223. data/config/themes/119.yml +98 -0
  224. data/config/themes/default.yml +14 -0
  225. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  226. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  227. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  228. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  229. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  230. data/examples/exports/UmlModel_template/model.xml +23 -0
  231. data/examples/exports/basic/UML_EA.DTD +6856 -0
  232. data/examples/exports/basic/model.xml +8039 -0
  233. data/examples/exports/simple/UML_EA.DTD +6856 -0
  234. data/examples/exports/simple/model.xml +585 -0
  235. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  236. data/examples/exports/simple_example/model.xml +23 -0
  237. data/examples/exports/test/UML_EA.DTD +6856 -0
  238. data/examples/exports/test/model.xml +1638 -0
  239. data/examples/qea/basic.qea +0 -0
  240. data/frontend/README.md +51 -0
  241. data/frontend/dist/app.iife.js +24 -0
  242. data/frontend/dist/style.css +1 -0
  243. data/frontend/index.html +26 -0
  244. data/frontend/package-lock.json +2359 -0
  245. data/frontend/package.json +26 -0
  246. data/frontend/src/App.vue +59 -0
  247. data/frontend/src/app.ts +8 -0
  248. data/frontend/src/components/AppHeader.vue +127 -0
  249. data/frontend/src/components/AppSidebar.vue +119 -0
  250. data/frontend/src/components/ClassDetails.vue +247 -0
  251. data/frontend/src/components/DiagramView.vue +79 -0
  252. data/frontend/src/components/PackageDetails.vue +92 -0
  253. data/frontend/src/components/PackageTreeNode.vue +64 -0
  254. data/frontend/src/components/SearchResults.vue +48 -0
  255. data/frontend/src/components/WelcomeView.vue +87 -0
  256. data/frontend/src/shims-vue.d.ts +5 -0
  257. data/frontend/src/stores/dataStore.ts +79 -0
  258. data/frontend/src/stores/uiStore.ts +167 -0
  259. data/frontend/src/styles/main.css +1566 -0
  260. data/frontend/src/types.ts +221 -0
  261. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  262. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  263. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  264. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  265. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  266. data/frontend/tests/e2e/render.spec.ts +188 -0
  267. data/frontend/tsconfig.json +20 -0
  268. data/frontend/vite.config.ts +26 -0
  269. data/frontend/vitest.config.ts +8 -0
  270. data/lib/ea/cli/app.rb +62 -1
  271. data/lib/ea/cli/command/diagrams.rb +37 -28
  272. data/lib/ea/cli/command/diff.rb +46 -0
  273. data/lib/ea/cli/command/export.rb +80 -0
  274. data/lib/ea/cli/command/info.rb +75 -0
  275. data/lib/ea/cli/command/lint.rb +48 -0
  276. data/lib/ea/cli/command/mdg.rb +98 -0
  277. data/lib/ea/cli/command/query.rb +34 -0
  278. data/lib/ea/cli/command/render.rb +135 -0
  279. data/lib/ea/cli/command/svg.rb +43 -6
  280. data/lib/ea/cli/command.rb +7 -0
  281. data/lib/ea/diagram/display_config.rb +184 -0
  282. data/lib/ea/diagram.rb +4 -41
  283. data/lib/ea/diff/change.rb +30 -0
  284. data/lib/ea/diff/comparator.rb +82 -0
  285. data/lib/ea/diff/html_reporter.rb +91 -0
  286. data/lib/ea/diff.rb +15 -0
  287. data/lib/ea/export/json/generator.rb +42 -0
  288. data/lib/ea/export/json.rb +12 -0
  289. data/lib/ea/export/json_schema/generator.rb +77 -0
  290. data/lib/ea/export/json_schema.rb +12 -0
  291. data/lib/ea/export/plantuml/generator.rb +73 -0
  292. data/lib/ea/export/plantuml.rb +12 -0
  293. data/lib/ea/export/xmi/generator.rb +77 -0
  294. data/lib/ea/export/xmi.rb +11 -0
  295. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  296. data/lib/ea/export/xsd/generator.rb +142 -0
  297. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  298. data/lib/ea/export/xsd.rb +19 -0
  299. data/lib/ea/export.rb +14 -0
  300. data/lib/ea/image/emf_renderer.rb +42 -0
  301. data/lib/ea/image.rb +12 -0
  302. data/lib/ea/lint/engine.rb +30 -0
  303. data/lib/ea/lint/lint_rule.rb +38 -0
  304. data/lib/ea/lint/offense.rb +18 -0
  305. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  306. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  307. data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
  308. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  309. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  310. data/lib/ea/lint/rules.rb +14 -0
  311. data/lib/ea/lint.rb +13 -0
  312. data/lib/ea/mdg/document.rb +120 -0
  313. data/lib/ea/mdg/loader.rb +118 -0
  314. data/lib/ea/mdg/registry.rb +98 -0
  315. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  316. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  317. data/lib/ea/mdg/xml/apply.rb +18 -0
  318. data/lib/ea/mdg/xml/content.rb +18 -0
  319. data/lib/ea/mdg/xml/documentation.rb +24 -0
  320. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  321. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  322. data/lib/ea/mdg/xml/tag.rb +28 -0
  323. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  324. data/lib/ea/mdg/xml/technology.rb +28 -0
  325. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  326. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  327. data/lib/ea/mdg/xml.rb +24 -0
  328. data/lib/ea/mdg.rb +25 -0
  329. data/lib/ea/model/classifier.rb +4 -0
  330. data/lib/ea/model/constraint.rb +24 -0
  331. data/lib/ea/model/diagram.rb +85 -0
  332. data/lib/ea/model/diagram_connector.rb +53 -1
  333. data/lib/ea/model/diagram_element.rb +44 -0
  334. data/lib/ea/model/document.rb +47 -2
  335. data/lib/ea/model/ghost_label.rb +32 -0
  336. data/lib/ea/model/instance_specification.rb +34 -0
  337. data/lib/ea/model/legend.rb +43 -0
  338. data/lib/ea/model/legend_item.rb +26 -0
  339. data/lib/ea/model/note.rb +28 -0
  340. data/lib/ea/model/operation.rb +2 -0
  341. data/lib/ea/model/property.rb +2 -0
  342. data/lib/ea/model/relationship.rb +2 -0
  343. data/lib/ea/model/signal.rb +14 -0
  344. data/lib/ea/model/slot.rb +19 -0
  345. data/lib/ea/model.rb +8 -0
  346. data/lib/ea/ocl/evaluator.rb +94 -0
  347. data/lib/ea/ocl/nodes.rb +30 -0
  348. data/lib/ea/ocl/parser.rb +133 -0
  349. data/lib/ea/ocl/unsupported_error.rb +7 -0
  350. data/lib/ea/ocl.rb +13 -0
  351. data/lib/ea/qea/factory/document_builder.rb +24 -10
  352. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  353. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  354. data/lib/ea/qea/models/ea_author.rb +35 -0
  355. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  356. data/lib/ea/qea/models/ea_image.rb +53 -0
  357. data/lib/ea/qea/models/ea_implement.rb +32 -0
  358. data/lib/ea/qea/models/ea_list.rb +42 -0
  359. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  360. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  361. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  362. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  363. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  364. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  365. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  366. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  367. data/lib/ea/qea/models/ea_operation.rb +7 -0
  368. data/lib/ea/qea/models/ea_palette.rb +30 -0
  369. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  370. data/lib/ea/qea/models/ea_phase.rb +40 -0
  371. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  372. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  373. data/lib/ea/qea/models/ea_version.rb +39 -0
  374. data/lib/ea/qea/models/ea_xref.rb +11 -1
  375. data/lib/ea/qea/models.rb +19 -0
  376. data/lib/ea/qea/services/database_loader.rb +22 -1
  377. data/lib/ea/query/builder.rb +108 -0
  378. data/lib/ea/query.rb +9 -0
  379. data/lib/ea/render/image_converter.rb +101 -0
  380. data/lib/ea/render.rb +10 -0
  381. data/lib/ea/shapescript/parser.rb +208 -0
  382. data/lib/ea/shapescript/renderer.rb +74 -0
  383. data/lib/ea/shapescript/shape.rb +34 -0
  384. data/lib/ea/shapescript.rb +24 -0
  385. data/lib/ea/sources/qea/adapter.rb +22 -5
  386. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  387. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  388. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  389. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  390. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  391. data/lib/ea/sources/qea/note_builder.rb +58 -0
  392. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  393. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  394. data/lib/ea/sources/qea/package_builder.rb +16 -0
  395. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  396. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  397. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  398. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  399. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  400. data/lib/ea/sources/qea/xref/record.rb +41 -0
  401. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  402. data/lib/ea/sources/qea/xref.rb +27 -0
  403. data/lib/ea/sources/qea.rb +4 -0
  404. data/lib/ea/sources/xmi/adapter.rb +80 -2
  405. data/lib/ea/sources/xmi/annotation_builder.rb +1 -1
  406. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  407. data/lib/ea/sources/xmi/diagram_builder.rb +242 -45
  408. data/lib/ea/sources/xmi/extension_elements.rb +74 -0
  409. data/lib/ea/sources/xmi/extension_geometry_parser.rb +124 -0
  410. data/lib/ea/sources/xmi/extension_style_parser.rb +91 -0
  411. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  412. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  413. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  414. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  415. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  416. data/lib/ea/sources/xmi.rb +7 -0
  417. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  418. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  419. data/lib/ea/spa/output/strategy.rb +34 -0
  420. data/lib/ea/svg/connector_router.rb +134 -0
  421. data/lib/ea/svg/ea_emitter/background.rb +15 -0
  422. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  423. data/lib/ea/svg/ea_emitter/canvas.rb +88 -0
  424. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  425. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  426. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  427. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  428. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  429. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  430. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  431. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  432. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  433. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  434. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  435. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  436. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  437. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  438. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  439. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  440. data/lib/ea/svg/ea_emitter/connectors.rb +84 -0
  441. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  442. data/lib/ea/svg/ea_emitter/document.rb +35 -0
  443. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  444. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  445. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  446. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  447. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  448. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  449. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  450. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  451. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  452. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  453. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  454. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  455. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  456. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  457. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  458. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  459. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  460. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  461. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  462. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  463. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  464. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  465. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  466. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  467. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  468. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  469. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  470. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  471. data/lib/ea/svg/ea_emitter/elements.rb +357 -0
  472. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  473. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  474. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  475. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  476. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  477. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  478. data/lib/ea/svg/ea_emitter/labels.rb +130 -0
  479. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  480. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  481. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  482. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  483. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  484. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  485. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  486. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  487. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  488. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  489. data/lib/ea/svg/ea_emitter/markers.rb +404 -0
  490. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  491. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  492. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  493. data/lib/ea/svg/ea_emitter.rb +51 -0
  494. data/lib/ea/svg/parity/checker.rb +105 -0
  495. data/lib/ea/svg/parity/source.rb +59 -0
  496. data/lib/ea/svg/parity/suite.rb +113 -0
  497. data/lib/ea/svg/parity.rb +14 -0
  498. data/lib/ea/svg/stereotype_color_resolver.rb +50 -0
  499. data/lib/ea/svg.rb +4 -15
  500. data/lib/ea/theme/definition.rb +176 -0
  501. data/lib/ea/theme/loader.rb +125 -0
  502. data/lib/ea/theme/registry.rb +96 -0
  503. data/lib/ea/theme.rb +37 -0
  504. data/lib/ea/transformations.rb +5 -88
  505. data/lib/ea/version.rb +4 -1
  506. data/lib/ea.rb +14 -4
  507. data/lib/tasks/svg_bench.rake +87 -0
  508. data/lutaml-uml-0.5.1.gem +0 -0
  509. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  510. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  511. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  512. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  513. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  514. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  515. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  516. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  517. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  518. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  519. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  520. metadata +473 -26
  521. data/lib/ea/diagram/configuration.rb +0 -379
  522. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  523. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  524. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  525. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  526. data/lib/ea/diagram/element_renderers.rb +0 -43
  527. data/lib/ea/diagram/extractor.rb +0 -566
  528. data/lib/ea/diagram/layout_engine.rb +0 -170
  529. data/lib/ea/diagram/path_builder.rb +0 -202
  530. data/lib/ea/diagram/style_parser.rb +0 -42
  531. data/lib/ea/diagram/style_resolver.rb +0 -291
  532. data/lib/ea/diagram/svg_renderer.rb +0 -274
  533. data/lib/ea/diagram/util.rb +0 -73
  534. data/lib/ea/svg/bounds_calculator.rb +0 -74
  535. data/lib/ea/svg/connector_path.rb +0 -138
  536. data/lib/ea/svg/element_box.rb +0 -256
  537. data/lib/ea/svg/renderer.rb +0 -77
  538. data/lib/ea/svg/style_resolver.rb +0 -72
  539. data/lib/ea/transformations/configuration.rb +0 -333
  540. data/lib/ea/transformations/format_registry.rb +0 -366
  541. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  542. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  543. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  544. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Emits the diagram frame: outer border + name tab. EA renders
7
+ # every diagram with a 1px black border forming a rectangle
8
+ # inset 6px from the canvas edge, plus a "tab" polygon in the
9
+ # upper-left containing the diagram type + name text.
10
+ #
11
+ # Layout:
12
+ # ┌───┐
13
+ # │tab│──────────────────┐
14
+ # └───┘ │
15
+ # │
16
+ # (diagram contents) │
17
+ # │
18
+ # ┌──────────────────────┘
19
+ #
20
+ # Format observed in EA reference SVGs:
21
+ # <g style="...stroke-width:1;stroke-linecap:square;
22
+ # stroke-linejoin:miter; fill:#000000;
23
+ # fill-opacity:0.00; stroke:#000000;
24
+ # stroke-opacity:1.00">
25
+ # <path d="M 6 6 L 6 H-6 W-6 W-6 6 L 6 6"/>
26
+ # </g>
27
+ # <g style="...fill:#FFFFFF;...">
28
+ # <polygon points="6 26 X 26 X+13 12 X+13 6 6 6 6 26"/>
29
+ # </g>
30
+ # <g style="...">
31
+ # <text x="11" y="19" ...>class DiagramName</text>
32
+ # </g>
33
+ class DiagramFrame
34
+ attr_reader :canvas, :theme
35
+
36
+ def initialize(canvas:, theme: nil)
37
+ @canvas = canvas
38
+ @theme = theme || Ea::Theme::Registry.default
39
+ end
40
+
41
+ def layers(diagram)
42
+ return [] if canvas.nil?
43
+
44
+ [
45
+ border_layer(canvas),
46
+ tab_layer(canvas, diagram),
47
+ tab_label_layer(canvas, diagram)
48
+ ].compact
49
+ end
50
+
51
+ private
52
+
53
+ def border_layer(canvas)
54
+ d = border_path_d(canvas)
55
+ body = %(<path d="#{d}" shape-rendering="auto"/>)
56
+ Layer.new(style_key: :frame_border, style: BORDER_STYLE, body: body)
57
+ end
58
+
59
+ def tab_layer(canvas, diagram)
60
+ points = tab_points(canvas, diagram)
61
+ body = %(<polygon points="#{points}" shape-rendering="auto" style="fill-rule:evenodd;"/>)
62
+ Layer.new(style_key: :frame_tab, style: TAB_STYLE, body: body)
63
+ end
64
+
65
+ def tab_label_layer(canvas, diagram)
66
+ label = tab_label(diagram)
67
+ return nil unless label
68
+
69
+ body = build_label_text(label)
70
+ Layer.new(style_key: :frame_label, style: LABEL_STYLE, body: body)
71
+ end
72
+
73
+ def border_path_d(canvas)
74
+ inset = theme.frame.inset
75
+ right = canvas.width - inset
76
+ bottom = canvas.height - inset
77
+ "M #{inset} #{inset} L #{inset} #{bottom} L #{right} #{bottom} L #{right} #{inset} L #{inset} #{inset}"
78
+ end
79
+
80
+ def tab_points(canvas, diagram)
81
+ label_width = tab_label_width(diagram)
82
+ x1 = theme.frame.inset
83
+ x2 = x1 + label_width
84
+ x3 = x2 + theme.frame.tab_slant
85
+ top1 = theme.frame.inset
86
+ top2 = theme.frame.inset + theme.frame.tab_height
87
+ "#{x1} #{top2} #{x2} #{top2} #{x3} #{top1 + theme.frame.tab_height - theme.frame.tab_slant} #{x3} #{top1} #{x1} #{top1} #{x1} #{top2}"
88
+ end
89
+
90
+ def tab_label(diagram)
91
+ type = diagram_label_prefix(diagram)
92
+ return nil unless type
93
+
94
+ "#{type} #{diagram.name}"
95
+ end
96
+
97
+ def diagram_label_prefix(diagram)
98
+ case diagram.diagram_type&.downcase
99
+ when "logical" then "class"
100
+ when "package" then "pkg"
101
+ when "usecase" then "uc"
102
+ when "sequence" then "seq"
103
+ when "activity" then "act"
104
+ when "statechart" then "sm"
105
+ when "deployment" then "dep"
106
+ when "component" then "comp"
107
+ when "object" then "object"
108
+ when "custom", "conceptual" then nil
109
+ else diagram.diagram_type&.downcase
110
+ end
111
+ end
112
+
113
+ def tab_label_width(diagram)
114
+ label = tab_label(diagram)
115
+ return theme.package.default_tab_width unless label
116
+
117
+ text_width = Ea::Svg::EaEmitter::TextRenderer.estimate_width(
118
+ label, theme.font_size || 7, theme.text_width_factor
119
+ ).round
120
+ text_width + theme.frame.tab_padding
121
+ end
122
+
123
+ def build_label_text(label)
124
+ TextRenderer.new(
125
+ content: label,
126
+ x: theme.frame.tab_label_x, y: theme.frame.tab_label_y,
127
+ family: theme.font_family || "Calibri",
128
+ size: theme.font_size || 7,
129
+ weight: theme.text_weight_bold,
130
+ size_unit: theme.font_size_unit,
131
+ fill: "#000000",
132
+ stroke_in_text: theme.stroke_in_text_color,
133
+ width_factor: theme.text_width_factor
134
+ ).to_svg
135
+ end
136
+
137
+ BORDER_STYLE = "stroke-width:1;stroke-linecap:square;stroke-linejoin:miter; fill:#000000;fill-opacity:0.00; stroke:#000000; stroke-opacity:1.00"
138
+ TAB_STYLE = "stroke-width:1;stroke-linecap:square;stroke-linejoin:miter; fill:#FFFFFF;fill-opacity:1.00; stroke:#000000; stroke-opacity:1.00"
139
+ LABEL_STYLE = "stroke-width:1;stroke-linecap:square;stroke-linejoin:miter; fill:#000000;fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00"
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Top-level orchestrator: emits a complete standalone SVG
7
+ # document matching EA's encoding. Bound computation and
8
+ # layer sequencing delegated to BoundsCalculator and
9
+ # LayerSequencer respectively (SRP).
10
+ class Document
11
+ BUILD_ID = "1624"
12
+
13
+ attr_reader :diagram, :model_index, :frame, :document
14
+
15
+ def initialize(diagram, model_index:, frame: true, document: nil, **_options)
16
+ @diagram = diagram
17
+ @model_index = model_index
18
+ @frame = frame
19
+ @document = document
20
+ end
21
+
22
+ def render
23
+ canvas = Canvas.from(diagram, model_index: model_index)
24
+ layers = LayerSequencer.new(diagram, model_index: model_index,
25
+ canvas: canvas, frame: frame,
26
+ document: document)
27
+ .layers
28
+ .reject { |s| s.nil? || s.empty? }
29
+
30
+ %(<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">\n\n<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="#{canvas.width_cm}" height="#{canvas.height_cm}" viewBox="#{canvas.view_box}">\n<title></title>\n<desc>Created with Enterprise Architect (Build: #{BUILD_ID}) 2</desc>\n#{layers.join("\n")}\n</svg>)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Builds the text representation of a single property line
8
+ # for the attribute compartment. Encapsulates the four EA
9
+ # rendering rules:
10
+ #
11
+ # 1. Visibility marker (+, -, #, ~).
12
+ # 2. Namespace prefix on properties inherited from a
13
+ # classifier in a different package ("core::mimeType").
14
+ # 3. Primitive type name stripping ("xs::double" → "double").
15
+ # 4. Multiplicity shorthand ("[2..2]" → "[2]") and
16
+ # `{ordered}` modifier.
17
+ #
18
+ # Stateless: each call returns a fresh line String. Pass a
19
+ # `Lookup` to enable cross-classifier resolution (namespace
20
+ # prefix). Without a lookup, the line is rendered as-is
21
+ # (backwards-compatible).
22
+ class AttributeLineBuilder
23
+ # XSD primitive types whose namespace prefix EA drops in
24
+ # the rendered attribute line. The full set is not yet
25
+ # reverse-engineered — leave empty for now so we never
26
+ # wrongly strip a prefix. TODO-D 47 will populate this
27
+ # once verified per-diagram.
28
+ PRIMITIVE_TYPES = Set.new([]).freeze
29
+
30
+ attr_reader :property, :host, :lookup
31
+
32
+ # property - the Ea::Model::Property to render
33
+ # host - the Ea::Model::Classifier whose compartment
34
+ # we're rendering (determines "inherited" status)
35
+ # lookup - object responding to `#call(id) -> Classifier`
36
+ # used to resolve the property's declaring
37
+ # classifier (defaults to no lookup).
38
+ def initialize(property, host: nil, lookup: nil)
39
+ @property = property
40
+ @host = host
41
+ @lookup = lookup
42
+ end
43
+
44
+ def to_s
45
+ parts = ["#{visibility_marker} "]
46
+ parts << namespaced_name
47
+ type_str = formatted_type
48
+ parts << ": #{type_str}" unless type_str.empty?
49
+ mult = multiplicity
50
+ parts << " #{mult}" unless mult.empty?
51
+ parts << " = #{property.default_value}" if property.default_value && !property.default_value.to_s.empty?
52
+ parts.join.rstrip
53
+ end
54
+
55
+ private
56
+
57
+ def visibility_marker
58
+ case property.visibility
59
+ when "private" then "-"
60
+ when "protected" then "#"
61
+ when "package" then "~"
62
+ else "+"
63
+ end
64
+ end
65
+
66
+ # Property name, prefixed with the declaring classifier's
67
+ # package name when the property is inherited from a
68
+ # different package than the host classifier.
69
+ def namespaced_name
70
+ prefix = inherited_namespace_prefix
71
+ base = property.name.to_s
72
+ prefix && !prefix.empty? ? "#{prefix}::#{base}" : base
73
+ end
74
+
75
+ # Returns the declaring classifier's package name when it
76
+ # differs from the host's package; nil otherwise.
77
+ def inherited_namespace_prefix
78
+ return nil unless host && lookup
79
+ return nil if property.owner_id.nil? || property.owner_id == host.id
80
+
81
+ owner = lookup.call(property.owner_id)
82
+ return nil unless owner.is_a?(Ea::Model::Classifier)
83
+ return nil if owner.package_id.nil?
84
+ return nil if owner.package_id == host.package_id
85
+
86
+ owner.package_name
87
+ end
88
+
89
+ # Format the type name. Primitive types from the PRIMITIVE_TYPES
90
+ # set render bare; everything else renders verbatim from the
91
+ # source. The source already provides the correct colon form
92
+ # (single for XSD, double for UML package qualifiers).
93
+ def formatted_type
94
+ raw = property.type_name.to_s
95
+ return "" if raw.empty?
96
+
97
+ simple = raw.split(":").last.to_s.split("::").last.to_s
98
+ PRIMITIVE_TYPES.member?(simple) ? simple : raw
99
+ end
100
+
101
+ def multiplicity
102
+ lower = property.multiplicity_lower
103
+ upper = property.multiplicity_upper
104
+ return "" if lower.nil? && upper.nil?
105
+ return "" if lower == 1 && upper == 1
106
+
107
+ "[#{lower || 0}..#{bound_text(upper)}]"
108
+ end
109
+
110
+ def bound_text(value)
111
+ value == -1 ? "*" : value.to_s
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the attribute compartment text `<g>`. Each attribute
8
+ # renders as TWO `<text>` elements (visibility marker +
9
+ # content) matching EA's encoding.
10
+ class AttributeRenderer
11
+ DEFAULT_VISIBILITY_X_OFFSET = 5
12
+ DEFAULT_CONTENT_X_OFFSET = 26
13
+ DEFAULT_FONT_UNIT = "pt"
14
+
15
+ def self.render(lines, bounds:, first_y:, family:,
16
+ size:, size_unit: DEFAULT_FONT_UNIT,
17
+ fill: "#000000",
18
+ visibility_x_offset: DEFAULT_VISIBILITY_X_OFFSET,
19
+ content_x_offset: DEFAULT_CONTENT_X_OFFSET)
20
+ line_h = size + 4
21
+ text_blocks = []
22
+ lines.each_with_index do |line, idx|
23
+ y = first_y + (idx * line_h)
24
+ visibility, rest = split_visibility(line)
25
+ if visibility
26
+ text_blocks << build_text(bounds.x + visibility_x_offset, y, visibility, family, size, size_unit, fill)
27
+ text_blocks << build_text(bounds.x + content_x_offset, y, rest, family, size, size_unit, fill)
28
+ else
29
+ text_blocks << build_text(bounds.x + visibility_x_offset, y, line.strip, family, size, size_unit, fill)
30
+ end
31
+ end
32
+ group_style = "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#{fill};fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00"
33
+ %(<g style="#{group_style}">\n#{text_blocks.join("\n")}\n</g>)
34
+ end
35
+
36
+ # Builds attribute display lines from classifier properties,
37
+ # hiding properties that are navigable association ends
38
+ # (those are rendered as connector lines).
39
+ #
40
+ # When a property carries a stereotype (e.g. «voidable»),
41
+ # EA renders the stereotype label as a separate line above
42
+ # the property's own line. We insert those labels here so
43
+ # the renderer's per-line spacing naturally places them.
44
+ #
45
+ # Pass a `lookup` (any callable returning a Classifier for
46
+ # an id) to enable inherited-property namespace prefixing.
47
+ def self.lines_for(classifier, lookup: nil)
48
+ props = displayable_properties(classifier)
49
+ return [] unless props
50
+
51
+ props.flat_map do |prop|
52
+ lines = []
53
+ stereotype = property_stereotype(prop)
54
+ lines << "«#{stereotype}»" if stereotype
55
+ lines << AttributeLineBuilder.new(prop, host: classifier,
56
+ lookup: lookup).to_s
57
+ end
58
+ end
59
+
60
+ # Internal helpers
61
+
62
+ def self.split_visibility(line)
63
+ stripped = line.strip
64
+ return [nil, stripped] unless stripped.match?(/^[-+~#]\s/)
65
+
66
+ visibility = "#{stripped[0]} "
67
+ rest = stripped[2..].to_s.strip
68
+ [visibility, rest]
69
+ end
70
+ private_class_method :split_visibility
71
+
72
+ def self.build_text(x, y, content, family, size, size_unit = DEFAULT_FONT_UNIT, fill = "#000000")
73
+ TextRenderer.new(content: content, x: x, y: y,
74
+ family: family, size: size, size_unit: size_unit, fill: fill).to_svg
75
+ end
76
+ private_class_method :build_text
77
+
78
+ def self.displayable_properties(classifier)
79
+ return nil unless classifier.properties
80
+
81
+ classifier.properties.reject(&:association_id)
82
+ end
83
+ private_class_method :displayable_properties
84
+
85
+ # Returns the first stereotype ref for a property, or nil.
86
+ # Used to emit «voidable» (and similar) labels above the
87
+ # property's own line.
88
+ def self.property_stereotype(property)
89
+ refs = property.stereotype_refs
90
+ return nil unless refs&.any?
91
+
92
+ refs.first.to_s
93
+ end
94
+ private_class_method :property_stereotype
95
+
96
+ # EA renders namespace separators as "::" (UML standard)
97
+ # even when the source XMI stores them as ":" (XML style).
98
+ def self.namespace_double_colon(type_name)
99
+ return nil if type_name.nil? || type_name.empty?
100
+
101
+ type_name.to_s.gsub(/([A-Za-z0-9_]):([A-Za-z])/, '\1::\2')
102
+ end
103
+ private_class_method :namespace_double_colon
104
+
105
+ def self.multiplicity_text(property)
106
+ lower = property.multiplicity_lower
107
+ upper = property.multiplicity_upper
108
+ return "" if lower.nil? && upper.nil?
109
+ return "" if lower == 1 && upper == 1
110
+ return "[#{upper == -1 ? "*" : upper}]" if lower == upper
111
+
112
+ "[#{lower || 0}..#{upper == -1 ? "*" : upper}]"
113
+ end
114
+ private_class_method :multiplicity_text
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Decodes EA's BGR-encoded BCol/LCol integers into "#RRGGBB"
8
+ # hex strings. EA stores color as a Win32 COLORREF: R in low
9
+ # byte, G in mid byte, B in high byte.
10
+ #
11
+ # Returns nil when bgr_int is nil OR equals -1 (EA's sentinel
12
+ # for "no color override" — callers fall through to a
13
+ # stereotype-derived default).
14
+ class BColDecoder
15
+ SENTINEL = -1
16
+
17
+ def self.to_hex(bgr_int)
18
+ return nil if bgr_int.nil? || bgr_int == SENTINEL
19
+
20
+ r = bgr_int & 0xff
21
+ g = (bgr_int >> 8) & 0xff
22
+ b = (bgr_int >> 16) & 0xff
23
+ format("#%02X%02X%02X", r, g, b)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Y-coordinate calculator for the compartments of an element
8
+ # box: header text, header divider, attribute rows, operation
9
+ # rows, enum literals, tagged-value rows.
10
+ #
11
+ # Owns its own coordinate math; the rest of Elements only
12
+ # reads `header_first_y`, `divider_y`, `attr_first_y`, etc.
13
+ # Construction takes the bounds, font size, line counts,
14
+ # and per-region offsets from the theme.
15
+ #
16
+ # Extracted from Elements.rb to keep each concern in one
17
+ # place (MECE). Element rendering reads these values;
18
+ # coordinate math changes don't ripple into rendering.
19
+ class CompartmentGeometry
20
+ attr_reader :bounds, :size, :header_lines_count,
21
+ :attr_lines_count, :op_lines_count,
22
+ :tagged_values_count,
23
+ :header_top_padding, :header_line_offset,
24
+ :divider_offset, :attr_line_offset,
25
+ :attr_first_offset
26
+
27
+ def initialize(bounds:, size:, header_lines_count:,
28
+ attr_lines_count:, op_lines_count:,
29
+ tagged_values_count:,
30
+ header_top_padding: 9,
31
+ header_line_offset: 6,
32
+ divider_offset: 8,
33
+ attr_line_offset: 4,
34
+ attr_first_offset: 7)
35
+ @bounds = bounds
36
+ @size = size
37
+ @header_lines_count = header_lines_count
38
+ @attr_lines_count = attr_lines_count
39
+ @op_lines_count = op_lines_count
40
+ @tagged_values_count = tagged_values_count
41
+ @header_top_padding = header_top_padding
42
+ @header_line_offset = header_line_offset
43
+ @divider_offset = divider_offset
44
+ @attr_line_offset = attr_line_offset
45
+ @attr_first_offset = attr_first_offset
46
+ end
47
+
48
+ def header_first_y
49
+ bounds.y + size + (header_top_padding || 9)
50
+ end
51
+
52
+ # Y of the divider line between header and the rest.
53
+ # Returns nil when there's no header content to separate
54
+ # from empty below-header content.
55
+ def divider_y
56
+ return nil if header_lines_count.zero?
57
+
58
+ header_first_y +
59
+ ([header_lines_count, 1].max - 1) * (size + (header_line_offset || 6)) +
60
+ (divider_offset || 8)
61
+ end
62
+
63
+ def attr_first_y
64
+ return bounds.y + size + (header_top_padding || 9) + 12 unless divider_y
65
+
66
+ divider_y + size + (attr_first_offset || 7)
67
+ end
68
+
69
+ def attr_bottom_y
70
+ return attr_first_y unless attr_lines_count&.positive?
71
+
72
+ attr_first_y + (attr_lines_count - 1) * (size + (attr_line_offset || 4))
73
+ end
74
+
75
+ def op_divider_y
76
+ attr_bottom_y + size + 5
77
+ end
78
+
79
+ def op_first_y
80
+ return nil unless op_lines_count&.positive?
81
+
82
+ op_divider_y + size + 5
83
+ end
84
+
85
+ def op_bottom_y
86
+ return op_divider_y unless op_lines_count&.positive?
87
+
88
+ op_first_y + (op_lines_count - 1) * (size + 4)
89
+ end
90
+
91
+ def enum_divider_y
92
+ op_bottom_y + size + 5
93
+ end
94
+
95
+ def enum_literal_first_y
96
+ enum_divider_y + size + 5
97
+ end
98
+
99
+ # Tagged values appear after attributes (or after ops if
100
+ # present). EA does not emit a separate divider — the
101
+ # italic "tags" header marks the compartment.
102
+ def tagged_value_first_y
103
+ return nil unless tagged_values_count&.positive?
104
+
105
+ base = attr_bottom_y
106
+ base = op_bottom_y if op_lines_count&.positive?
107
+ base + size + 5
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the constraints compartment text `<g>`. EA renders
8
+ # constraints as a sub-compartment with an italic "constraints"
9
+ # header followed by `{name}` lines:
10
+ #
11
+ # <text style="...font-style:italic;...">constraints</text>
12
+ # <text style="...font-style:normal;...">{pattern}</text>
13
+ class ConstraintRenderer
14
+ HEADER_TEXT = "constraints"
15
+ HEADER_X_OFFSET = 5
16
+ LINE_X_OFFSET = 5
17
+
18
+ def self.render(constraints, bounds:, first_y:, family:, size:,
19
+ fill: "#000000")
20
+ line_h = size + 4
21
+ text_blocks = []
22
+ text_blocks << build_text(
23
+ bounds.x + centered_x_offset(bounds, size), first_y,
24
+ HEADER_TEXT, family, size, fill, style: "italic"
25
+ )
26
+ constraints.each_with_index do |constraint, idx|
27
+ y = first_y + ((idx + 1) * line_h)
28
+ text_blocks << build_text(
29
+ bounds.x + LINE_X_OFFSET, y,
30
+ "{#{constraint.name}}", family, size, fill
31
+ )
32
+ end
33
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
34
+ end
35
+
36
+ def self.build_text(x, y, content, family, size, fill, style: "normal")
37
+ TextRenderer.new(content: content, x: x, y: y,
38
+ family: family, size: size, fill: fill,
39
+ style: style).to_svg
40
+ end
41
+ private_class_method :build_text
42
+
43
+ def self.centered_x_offset(bounds, size)
44
+ text_width = HEADER_TEXT.length * (size / 2)
45
+ centered = (bounds.width / 2 - text_width / 2).floor
46
+ [HEADER_X_OFFSET, centered].max
47
+ end
48
+ private_class_method :centered_x_offset
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the horizontal divider path between header and
8
+ # attribute compartments.
9
+ class DividerRenderer
10
+ def self.render(bounds, y:, stroke:, stroke_width:)
11
+ %(<g style="stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#{stroke}; stroke-opacity:1.00">\n <path d="M #{Canvas.coord(bounds.x)} #{Canvas.coord(y)} L #{Canvas.coord(bounds.x + bounds.width)} #{Canvas.coord(y)}" shape-rendering="auto"/>\n</g>)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the enumeration literal compartment text `<g>`. Each
8
+ # literal appears as a separate text element matching EA's
9
+ # encoding:
10
+ #
11
+ # <text>literal_name</text>
12
+ #
13
+ # Rendered as a third compartment below the attribute
14
+ # compartment for Enumeration classifiers.
15
+ class EnumerationLiteralRenderer
16
+ def self.render(literals, bounds:, first_y:, family:, size:)
17
+ line_h = size + 4
18
+ text_blocks = literals.each_with_index.map do |literal, idx|
19
+ y = first_y + (idx * line_h)
20
+ build_text(bounds.x + 5, y, literal.name.to_s, family, size)
21
+ end
22
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
23
+ end
24
+
25
+ def self.build_text(x, y, content, family, size)
26
+ TextRenderer.new(content: content, x: x, y: y,
27
+ family: family, size: size).to_svg
28
+ end
29
+ private_class_method :build_text
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end