ea 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (529) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/TODO.complete/01-t-xref-parsing.md +91 -0
  4. data/TODO.complete/02-per-label-geometry.md +50 -0
  5. data/TODO.complete/03-object-properties-constraints.md +57 -0
  6. data/TODO.complete/04-operation-params.md +37 -0
  7. data/TODO.complete/05-pdata-flag-matrix.md +52 -0
  8. data/TODO.complete/06-emf-image-rendering.md +34 -0
  9. data/TODO.complete/07-auxiliary-tables.md +47 -0
  10. data/TODO.complete/10-mdg-stereotype-registry.md +40 -0
  11. data/TODO.complete/11-gml-xsd-generation.md +42 -0
  12. data/TODO.complete/13-shapescript-interpreter.md +28 -0
  13. data/TODO.complete/20-cli-diff.md +46 -0
  14. data/TODO.complete/21-cli-render.md +26 -0
  15. data/TODO.complete/22-cli-export.md +29 -0
  16. data/TODO.complete/23-cli-mdg.md +23 -0
  17. data/TODO.complete/30-headerlines-ocp-refactor.md +72 -0
  18. data/TODO.complete/31-public-send-cleanup.md +61 -0
  19. data/TODO.complete/32-spec-coverage.md +33 -0
  20. data/TODO.complete/33-blocked-parity-gaps.md +41 -0
  21. data/TODO.complete/36-stereotype-icon-wiring.md +32 -0
  22. data/TODO.complete/37-cli-lint.md +32 -0
  23. data/TODO.complete/38-cli-query.md +26 -0
  24. data/TODO.complete/39-cli-info.md +23 -0
  25. data/TODO.complete/40-ocl-evaluator.md +31 -0
  26. data/TODO.complete/41-round-trip-spec.md +18 -0
  27. data/TODO.complete/42-html-diff-report.md +18 -0
  28. data/TODO.complete/43-json-schema-export.md +20 -0
  29. data/TODO.complete/44-remaining-qea-tables.md +40 -0
  30. data/TODO.complete/45-shapescript-extensions.md +25 -0
  31. data/TODO.complete/46-xsd-consolidation.md +20 -0
  32. data/TODO.complete/47-headerlines-legacy-removal.md +20 -0
  33. data/TODO.complete/STATUS.md +109 -0
  34. data/TODO.diagrams/01-svg-measurement-harness.md +31 -0
  35. data/TODO.diagrams/02-polygon-over-rendering.md +29 -0
  36. data/TODO.diagrams/03-text-over-rendering.md +30 -0
  37. data/TODO.diagrams/04-rect-under-rendering.md +33 -0
  38. data/TODO.diagrams/05-canvas-dimension-parity.md +51 -0
  39. data/TODO.diagrams/06-group-layering-parity.md +39 -0
  40. data/TODO.diagrams/07-text-content-fidelity.md +65 -0
  41. data/TODO.diagrams/08-qea-direct-pipeline.md +46 -0
  42. data/TODO.diagrams/12-connector-labels.md +40 -0
  43. data/TODO.diagrams/12-per-entity-svg-layer-ordering.md +24 -0
  44. data/TODO.diagrams/13-marker-polygon-vs-path-dispatch.md +20 -0
  45. data/TODO.diagrams/13-per-text-type-font-sizes.md +33 -0
  46. data/TODO.diagrams/14-center-header-text.md +33 -0
  47. data/TODO.diagrams/14-filter-diagram-frame-elements.md +18 -0
  48. data/TODO.diagrams/15-deep-dead-code-audit.md +47 -0
  49. data/TODO.diagrams/15-per-element-font-from-style.md +24 -0
  50. data/TODO.diagrams/16-canvas-marker-extent.md +30 -0
  51. data/TODO.diagrams/16-document-remaining-parity-gaps.md +91 -0
  52. data/TODO.diagrams/17-italic-abstract-classes.md +28 -0
  53. data/TODO.diagrams/17-remaining-parity-gaps.md +90 -0
  54. data/TODO.diagrams/18-canvas-from-image-bounds.md +20 -0
  55. data/TODO.diagrams/18-property-lookup-index.md +42 -0
  56. data/TODO.diagrams/19-connector-sx-sy-routing.md +27 -0
  57. data/TODO.diagrams/19-replace-respond-to-with-is-a.md +31 -0
  58. data/TODO.diagrams/20-split-labels-emitter.md +56 -0
  59. data/TODO.diagrams/20-visibility-glyph-font-encoding.md +27 -0
  60. data/TODO.diagrams/21-aggregation-navigability-arrow.md +21 -0
  61. data/TODO.diagrams/21-auto-generated-legend-block.md +65 -0
  62. data/TODO.diagrams/22-ghost-element-discriminator.md +57 -0
  63. data/TODO.diagrams/22-style-based-path-grouping.md +28 -0
  64. data/TODO.diagrams/23-marker-polygon-deduplication.md +47 -0
  65. data/TODO.diagrams/23-multiplicity-label-positioning.md +29 -0
  66. data/TODO.diagrams/24-marker-anchor-structural.md +35 -0
  67. data/TODO.diagrams/24-track-marker-anchors-structurally.md +66 -0
  68. data/TODO.diagrams/24-truncate-properties-to-fit-box.md +24 -0
  69. data/TODO.diagrams/26-phantom-general-parent-rendering.md +59 -0
  70. data/TODO.diagrams/27-path-grouping-mode-detection.md +43 -0
  71. data/TODO.diagrams/28-architecture-cleanup-batch.md +47 -0
  72. data/TODO.diagrams/30-decompose-elements-groups-for.md +52 -0
  73. data/TODO.diagrams/32-examples-coverage-batch.md +78 -0
  74. data/TODO.diagrams/36-instance-specification-model.md +42 -0
  75. data/TODO.diagrams/37-element-icon-decorations.md +41 -0
  76. data/TODO.diagrams/38-html-note-and-receptions.md +32 -0
  77. data/TODO.diagrams/39-property-stereotype-discriminator.md +40 -0
  78. data/TODO.diagrams/40-standalone-1-discriminator.md +38 -0
  79. data/TODO.diagrams/41-xmi-to-svg-pipeline.md +113 -0
  80. data/TODO.diagrams/42-close-all-parity-deltas.md +69 -0
  81. data/TODO.diagrams/43-marker-deduplication-strategy.md +54 -0
  82. data/TODO.diagrams/44-element-icon-decorations.md +34 -0
  83. data/TODO.diagrams/45-phantom-parent-class-boxes.md +40 -0
  84. data/TODO.diagrams/46-user-drawn-legend-swatches.md +44 -0
  85. data/TODO.diagrams/47-attribute-line-fidelity.md +72 -0
  86. data/TODO.diagrams/48-spec-coverage-critical-emitter-components.md +45 -0
  87. data/TODO.diagrams/49-spec-coverage-orchestrators.md +25 -0
  88. data/TODO.diagrams/50-examples-parity.md +85 -0
  89. data/TODO.diagrams/51-property-oy-gap-and-remaining-gaps.md +48 -0
  90. data/TODO.diagrams/52-marker-registration-architecture.md +89 -0
  91. data/TODO.diagrams/53-remaining-example-gaps.md +86 -0
  92. data/TODO.diagrams/54-display-name-nested-classes.md +40 -0
  93. data/TODO.diagrams/55-visibility-icon-discriminator.md +58 -0
  94. data/TODO.diagrams/56-package-contents-rendering.md +148 -0
  95. data/TODO.diagrams/57-instance-specification-model.md +100 -0
  96. data/TODO.diagrams/58-domain-model-note-text.md +67 -0
  97. data/TODO.diagrams/59-classes-arrow-discriminator.md +57 -0
  98. data/TODO.diagrams/60-testschema-association-marker.md +40 -0
  99. data/TODO.diagrams/61-legend-block-rendering.md +55 -0
  100. data/TODO.diagrams/61-plateau-parity-gaps.md +59 -0
  101. data/TODO.diagrams/62-ghost-element-rendering.md +56 -0
  102. data/TODO.diagrams/63-qualified-name-wrap.md +36 -0
  103. data/TODO.diagrams/64-property-stereotype-labels.md +22 -0
  104. data/TODO.diagrams/65-path-coordinate-frame.md +62 -0
  105. data/TODO.diagrams/67-path-coordinate-frame.md +62 -0
  106. data/TODO.diagrams/68-inherited-attributes.md +65 -0
  107. data/TODO.diagrams/69-package-anchor-conditional.md +29 -0
  108. data/TODO.diagrams/70-mdg-technology-support.md +68 -0
  109. data/TODO.diagrams/71-off-canvas-parent-ghost.md +45 -0
  110. data/TODO.diagrams/72-basic-attr-divider-paths.md +39 -0
  111. data/TODO.diagrams/73-plateau-polygon-gap.md +69 -0
  112. data/TODO.diagrams/74-plateau-rect-gap.md +22 -0
  113. data/TODO.diagrams/75-plateau-text-gap.md +39 -0
  114. data/TODO.diagrams/76-basic-poly-path-gaps.md +41 -0
  115. data/TODO.diagrams/77-basic-text-overrender.md +40 -0
  116. data/TODO.diagrams/78-headerlines-ocp-refactor.md +65 -0
  117. data/TODO.diagrams/79-architecture-backlog.md +65 -0
  118. data/TODO.diagrams/80-basic-triangle-polygons.md +51 -0
  119. data/TODO.diagrams/81-plateau-path-overrender.md +53 -0
  120. data/TODO.diagrams/82-appearance-canvas-offset.md +54 -0
  121. data/TODO.diagrams/83-3d-diagram-subtitle.md +58 -0
  122. data/TODO.diagrams/84-canvas-offset-mismatches.md +61 -0
  123. data/TODO.diagrams/85-phantom-property-overrender.md +95 -0
  124. data/TODO.diagrams/86-foreign-package-rule.md +62 -0
  125. data/TODO.diagrams/87-internal-tech-encryption.md +80 -0
  126. data/TODO.diagrams/EXAMPLES_STATUS.md +88 -0
  127. data/TODO.diagrams/README.md +69 -0
  128. data/TODO.diagrams/STATUS.md +73 -0
  129. data/TODO.fixup/01-default-grouped-paths.md +34 -0
  130. data/TODO.fixup/02-marker-consolidation-per-connector.md +20 -0
  131. data/TODO.fixup/03-diagram-font-inference.md +28 -0
  132. data/TODO.fixup/04-extract-bounds-calculator.md +25 -0
  133. data/TODO.fixup/05-marker-registry-ocp.md +32 -0
  134. data/TODO.fixup/06-connector-path-bend-routing.md +28 -0
  135. data/TODO.fixup/07-edge-attachment-formula.md +22 -0
  136. data/TODO.fixup/08-visual-diff-harness.md +28 -0
  137. data/TODO.fixup/09-extract-element-compartments.md +34 -0
  138. data/TODO.fixup/10-canvas-width-overshoot-fix.md +20 -0
  139. data/TODO.fixup/11-layer-sequencer-extraction.md +18 -0
  140. data/TODO.fixup/12-path-style-harmony.md +23 -0
  141. data/TODO.fixup/13-datatype-stereotype-fallback.md +28 -0
  142. data/TODO.fixup/14-ea-exe-reverse-engineering-notes.md +56 -0
  143. data/TODO.fixup/15-enumeration-literal-rendering.md +30 -0
  144. data/TODO.fixup/16-operation-compartment-rendering.md +42 -0
  145. data/TODO.fixup/17-path-count-calibration.md +27 -0
  146. data/TODO.fixup/18-polygon-count-calibration.md +32 -0
  147. data/TODO.fixup/19-rect-count-undershoot-investigation.md +40 -0
  148. data/TODO.fixup/20-stereotype-color-from-classifier.md +18 -0
  149. data/TODO.fixup/21-visibility-filter-for-properties.md +24 -0
  150. data/TODO.fixup/22-multiplicity-display-rule-calibration.md +25 -0
  151. data/TODO.fixup/23-diagram-frame-title-element.md +31 -0
  152. data/TODO.fixup/24-note-element-rendering.md +23 -0
  153. data/TODO.fixup/25-ea-exe-disassembly-findings.md +177 -0
  154. data/TODO.fixup/26-svg-renderer-architecture.md +78 -0
  155. data/TODO.fixup/27-svg-element-emitters-disassembly.md +135 -0
  156. data/TODO.fixup/28-geometry-parser-identification.md +57 -0
  157. data/TODO.fixup/29-qea-svg-xmi-triple-comparison.md +185 -0
  158. data/TODO.fixup/30-theme-system-stub.md +46 -0
  159. data/TODO.fixup/31-theme-value-object-registry.md +17 -0
  160. data/TODO.fixup/32-styleex-on-diagram-model.md +22 -0
  161. data/TODO.fixup/33-text-encoding-decimals-rotation.md +29 -0
  162. data/TODO.fixup/34-per-type-fill-via-theme.md +18 -0
  163. data/TODO.fixup/35-stroke-width-per-theme.md +7 -0
  164. data/TODO.fixup/36-qea-rect-coord-normalization.md +24 -0
  165. data/TODO.fixup/37-package-shape-rendering.md +22 -0
  166. data/TODO.fixup/38-note-element-rendering.md +15 -0
  167. data/TODO.fixup/39-styleex-flag-honoring.md +25 -0
  168. data/TODO.fixup/40-connector-dashed-lines.md +15 -0
  169. data/TODO.fixup/41-diagram-frame-showborder-detection.md +23 -0
  170. data/TODO.fixup/42-textrenderer-centralization.md +10 -0
  171. data/TODO.fixup/43-colorresolver-unification.md +24 -0
  172. data/TODO.fixup/44-theme-aware-textrenderer.md +17 -0
  173. data/TODO.fixup/45-theme-aware-fontresolver.md +22 -0
  174. data/TODO.fixup/46-style-parameterized-by-theme.md +21 -0
  175. data/TODO.fixup/47-canvas-frame-inset.md +19 -0
  176. data/TODO.fixup/48-background-rect-format.md +15 -0
  177. data/TODO.fixup/49-all-samples-integration-test.md +17 -0
  178. data/TODO.fixup/50-svg-emitter-architecture-docs.md +33 -0
  179. data/TODO.fixup/51-promote-theme-to-ea-namespace.md +28 -0
  180. data/TODO.fixup/52-theme-definition-integrated-fills.md +26 -0
  181. data/TODO.fixup/53-theme-registry-yaml-loading.md +50 -0
  182. data/TODO.fixup/54-diagram-theme-api.md +50 -0
  183. data/TODO.fixup/55-update-eaemitter-to-ea-theme.md +23 -0
  184. data/TODO.fixup/56-theme-yaml-files.md +65 -0
  185. data/TODO.fixup/57-theme-specs-and-integration.md +27 -0
  186. data/TODO.fixup/58-ea-entry-point-theme-autoload.md +20 -0
  187. data/TODO.fixup/59-thread-theme-through-all-renderers.md +29 -0
  188. data/TODO.fixup/60-cli-theme-option.md +22 -0
  189. data/TODO.fixup/61-theme-validation.md +28 -0
  190. data/TODO.fixup/62-theme-builder-dsl.md +31 -0
  191. data/TODO.fixup/63-theme-integration-rendering-spec.md +24 -0
  192. data/TODO.fixup/64-theme-writeback-styleex.md +21 -0
  193. data/TODO.fixup/65-remove-dead-eaemitter-theme-files.md +30 -0
  194. data/TODO.fixup/66-styleex-writeback.md +22 -0
  195. data/TODO.fixup/67-delete-dead-code.md +28 -0
  196. data/TODO.fixup/68-figure-parity-status.md +82 -0
  197. data/TODO.fixup/69-package-polygon-rendering.md +54 -0
  198. data/TODO.fixup/70-tagged-values-compartment.md +39 -0
  199. data/TODO.fixup/71-auto-detect-theme-from-xmi.md +37 -0
  200. data/TODO.fixup/72-sample-exports-as-regression-specs.md +37 -0
  201. data/TODO.fixup/73-delete-dead-transformations.md +14 -0
  202. data/TODO.fixup/74-delete-dead-diagram-renderers.md +18 -0
  203. data/TODO.fixup/75-visibility-legend-icons.md +23 -0
  204. data/TODO.fixup/76-plateau-text-undershoot.md +27 -0
  205. data/TODO.fixup/77-canvas-frame-margins.md +28 -0
  206. data/TODO.fixup/78-note-element-rendering.md +30 -0
  207. data/TODO.fixup/79-operation-compartment-rendering.md +27 -0
  208. data/TODO.fixup/80-tagged-value-stereotype-visibility.md +37 -0
  209. data/TODO.fixup/81-figure-parity-current-state.md +63 -0
  210. data/config/qea_schema.yml +356 -0
  211. data/config/themes/119.yml +98 -0
  212. data/config/themes/default.yml +14 -0
  213. data/examples/exports/20251010_current_plateau_v5/UML_EA.DTD +6856 -0
  214. data/examples/exports/20251010_current_plateau_v5/model.xml +96709 -0
  215. data/examples/exports/ArcGISWorkspace_template/UML_EA.DTD +6856 -0
  216. data/examples/exports/ArcGISWorkspace_template/model.xml +1427 -0
  217. data/examples/exports/UmlModel_template/UML_EA.DTD +6856 -0
  218. data/examples/exports/UmlModel_template/model.xml +23 -0
  219. data/examples/exports/basic/UML_EA.DTD +6856 -0
  220. data/examples/exports/basic/model.xml +8039 -0
  221. data/examples/exports/simple/UML_EA.DTD +6856 -0
  222. data/examples/exports/simple/model.xml +585 -0
  223. data/examples/exports/simple_example/UML_EA.DTD +6856 -0
  224. data/examples/exports/simple_example/model.xml +23 -0
  225. data/examples/exports/test/UML_EA.DTD +6856 -0
  226. data/examples/exports/test/model.xml +1638 -0
  227. data/examples/qea/basic.qea +0 -0
  228. data/frontend/README.md +51 -0
  229. data/frontend/dist/app.iife.js +24 -0
  230. data/frontend/dist/style.css +1 -0
  231. data/frontend/index.html +26 -0
  232. data/frontend/package-lock.json +2359 -0
  233. data/frontend/package.json +26 -0
  234. data/frontend/src/App.vue +59 -0
  235. data/frontend/src/app.ts +8 -0
  236. data/frontend/src/components/AppHeader.vue +127 -0
  237. data/frontend/src/components/AppSidebar.vue +119 -0
  238. data/frontend/src/components/ClassDetails.vue +247 -0
  239. data/frontend/src/components/DiagramView.vue +79 -0
  240. data/frontend/src/components/PackageDetails.vue +92 -0
  241. data/frontend/src/components/PackageTreeNode.vue +64 -0
  242. data/frontend/src/components/SearchResults.vue +48 -0
  243. data/frontend/src/components/WelcomeView.vue +87 -0
  244. data/frontend/src/shims-vue.d.ts +5 -0
  245. data/frontend/src/stores/dataStore.ts +79 -0
  246. data/frontend/src/stores/uiStore.ts +167 -0
  247. data/frontend/src/styles/main.css +1566 -0
  248. data/frontend/src/types.ts +221 -0
  249. data/frontend/tests/e2e/__screenshots__/new-dark-mode.png +0 -0
  250. data/frontend/tests/e2e/__screenshots__/new-package-details.png +0 -0
  251. data/frontend/tests/e2e/__screenshots__/new-search-modal.png +0 -0
  252. data/frontend/tests/e2e/__screenshots__/new-sidebar-collapsed.png +0 -0
  253. data/frontend/tests/e2e/__screenshots__/new-welcome.png +0 -0
  254. data/frontend/tests/e2e/render.spec.ts +188 -0
  255. data/frontend/tsconfig.json +20 -0
  256. data/frontend/vite.config.ts +26 -0
  257. data/frontend/vitest.config.ts +8 -0
  258. data/lib/ea/cli/app.rb +57 -2
  259. data/lib/ea/cli/command/diagrams.rb +37 -28
  260. data/lib/ea/cli/command/diff.rb +46 -0
  261. data/lib/ea/cli/command/export.rb +80 -0
  262. data/lib/ea/cli/command/info.rb +75 -0
  263. data/lib/ea/cli/command/lint.rb +48 -0
  264. data/lib/ea/cli/command/mdg.rb +98 -0
  265. data/lib/ea/cli/command/query.rb +34 -0
  266. data/lib/ea/cli/command/render.rb +135 -0
  267. data/lib/ea/cli/command/svg.rb +8 -15
  268. data/lib/ea/cli/command.rb +7 -0
  269. data/lib/ea/diagram/display_config.rb +184 -0
  270. data/lib/ea/diagram.rb +4 -41
  271. data/lib/ea/diff/change.rb +30 -0
  272. data/lib/ea/diff/comparator.rb +82 -0
  273. data/lib/ea/diff/html_reporter.rb +91 -0
  274. data/lib/ea/diff.rb +15 -0
  275. data/lib/ea/export/json/generator.rb +42 -0
  276. data/lib/ea/export/json.rb +12 -0
  277. data/lib/ea/export/json_schema/generator.rb +77 -0
  278. data/lib/ea/export/json_schema.rb +12 -0
  279. data/lib/ea/export/plantuml/generator.rb +73 -0
  280. data/lib/ea/export/plantuml.rb +12 -0
  281. data/lib/ea/export/xmi/generator.rb +77 -0
  282. data/lib/ea/export/xmi.rb +11 -0
  283. data/lib/ea/export/xsd/class_mapping.rb +65 -0
  284. data/lib/ea/export/xsd/generator.rb +142 -0
  285. data/lib/ea/export/xsd/namespace_registry.rb +82 -0
  286. data/lib/ea/export/xsd.rb +19 -0
  287. data/lib/ea/export.rb +14 -0
  288. data/lib/ea/image/emf_renderer.rb +42 -0
  289. data/lib/ea/image.rb +12 -0
  290. data/lib/ea/lint/engine.rb +30 -0
  291. data/lib/ea/lint/lint_rule.rb +38 -0
  292. data/lib/ea/lint/offense.rb +18 -0
  293. data/lib/ea/lint/rules/cyclic_generalization.rb +62 -0
  294. data/lib/ea/lint/rules/duplicate_name.rb +28 -0
  295. data/lib/ea/lint/rules/missing_stereotype.rb +35 -0
  296. data/lib/ea/lint/rules/naming_convention.rb +44 -0
  297. data/lib/ea/lint/rules/orphan_element.rb +28 -0
  298. data/lib/ea/lint/rules.rb +14 -0
  299. data/lib/ea/lint.rb +13 -0
  300. data/lib/ea/mdg/document.rb +120 -0
  301. data/lib/ea/mdg/loader.rb +118 -0
  302. data/lib/ea/mdg/registry.rb +98 -0
  303. data/lib/ea/mdg/stereotype_alias_registry.rb +77 -0
  304. data/lib/ea/mdg/xml/applies_to.rb +18 -0
  305. data/lib/ea/mdg/xml/apply.rb +18 -0
  306. data/lib/ea/mdg/xml/content.rb +18 -0
  307. data/lib/ea/mdg/xml/documentation.rb +24 -0
  308. data/lib/ea/mdg/xml/stereotype.rb +28 -0
  309. data/lib/ea/mdg/xml/stereotypes.rb +18 -0
  310. data/lib/ea/mdg/xml/tag.rb +28 -0
  311. data/lib/ea/mdg/xml/tagged_values.rb +18 -0
  312. data/lib/ea/mdg/xml/technology.rb +28 -0
  313. data/lib/ea/mdg/xml/uml_profile.rb +22 -0
  314. data/lib/ea/mdg/xml/uml_profiles.rb +18 -0
  315. data/lib/ea/mdg/xml.rb +24 -0
  316. data/lib/ea/mdg.rb +25 -0
  317. data/lib/ea/model/classifier.rb +4 -0
  318. data/lib/ea/model/constraint.rb +24 -0
  319. data/lib/ea/model/diagram.rb +85 -0
  320. data/lib/ea/model/diagram_connector.rb +34 -0
  321. data/lib/ea/model/diagram_element.rb +16 -0
  322. data/lib/ea/model/document.rb +47 -2
  323. data/lib/ea/model/ghost_label.rb +32 -0
  324. data/lib/ea/model/instance_specification.rb +34 -0
  325. data/lib/ea/model/legend.rb +43 -0
  326. data/lib/ea/model/legend_item.rb +26 -0
  327. data/lib/ea/model/note.rb +28 -0
  328. data/lib/ea/model/operation.rb +2 -0
  329. data/lib/ea/model/property.rb +2 -0
  330. data/lib/ea/model/relationship.rb +2 -0
  331. data/lib/ea/model/signal.rb +14 -0
  332. data/lib/ea/model/slot.rb +19 -0
  333. data/lib/ea/model.rb +8 -0
  334. data/lib/ea/ocl/evaluator.rb +94 -0
  335. data/lib/ea/ocl/nodes.rb +30 -0
  336. data/lib/ea/ocl/parser.rb +133 -0
  337. data/lib/ea/ocl/unsupported_error.rb +7 -0
  338. data/lib/ea/ocl.rb +13 -0
  339. data/lib/ea/qea/factory/document_builder.rb +24 -10
  340. data/lib/ea/qea/factory/generalization_builder.rb +9 -11
  341. data/lib/ea/qea/infrastructure/table_reader.rb +9 -0
  342. data/lib/ea/qea/models/ea_author.rb +35 -0
  343. data/lib/ea/qea/models/ea_glossary.rb +34 -0
  344. data/lib/ea/qea/models/ea_image.rb +53 -0
  345. data/lib/ea/qea/models/ea_implement.rb +32 -0
  346. data/lib/ea/qea/models/ea_list.rb +42 -0
  347. data/lib/ea/qea/models/ea_object_effort.rb +34 -0
  348. data/lib/ea/qea/models/ea_object_problem.rb +37 -0
  349. data/lib/ea/qea/models/ea_object_require.rb +34 -0
  350. data/lib/ea/qea/models/ea_object_resource.rb +34 -0
  351. data/lib/ea/qea/models/ea_object_risk.rb +34 -0
  352. data/lib/ea/qea/models/ea_object_scenario.rb +34 -0
  353. data/lib/ea/qea/models/ea_object_test.rb +35 -0
  354. data/lib/ea/qea/models/ea_object_trx.rb +34 -0
  355. data/lib/ea/qea/models/ea_operation.rb +7 -0
  356. data/lib/ea/qea/models/ea_palette.rb +30 -0
  357. data/lib/ea/qea/models/ea_palette_item.rb +36 -0
  358. data/lib/ea/qea/models/ea_phase.rb +40 -0
  359. data/lib/ea/qea/models/ea_role_constraint.rb +32 -0
  360. data/lib/ea/qea/models/ea_secrypt.rb +34 -0
  361. data/lib/ea/qea/models/ea_version.rb +39 -0
  362. data/lib/ea/qea/models/ea_xref.rb +11 -1
  363. data/lib/ea/qea/models.rb +19 -0
  364. data/lib/ea/qea/services/database_loader.rb +22 -1
  365. data/lib/ea/query/builder.rb +108 -0
  366. data/lib/ea/query.rb +9 -0
  367. data/lib/ea/render/image_converter.rb +101 -0
  368. data/lib/ea/render.rb +10 -0
  369. data/lib/ea/shapescript/parser.rb +208 -0
  370. data/lib/ea/shapescript/renderer.rb +74 -0
  371. data/lib/ea/shapescript/shape.rb +34 -0
  372. data/lib/ea/shapescript.rb +24 -0
  373. data/lib/ea/sources/qea/adapter.rb +22 -5
  374. data/lib/ea/sources/qea/classifier_builder.rb +184 -7
  375. data/lib/ea/sources/qea/diagram_builder.rb +352 -24
  376. data/lib/ea/sources/qea/id_normalizer.rb +16 -1
  377. data/lib/ea/sources/qea/instance_builder.rb +113 -0
  378. data/lib/ea/sources/qea/legend_builder.rb +137 -0
  379. data/lib/ea/sources/qea/note_builder.rb +58 -0
  380. data/lib/ea/sources/qea/object_classifier_map.rb +6 -1
  381. data/lib/ea/sources/qea/operation_builder.rb +1 -0
  382. data/lib/ea/sources/qea/package_builder.rb +16 -0
  383. data/lib/ea/sources/qea/relationship_builder.rb +21 -5
  384. data/lib/ea/sources/qea/tagged_value_builder.rb +28 -9
  385. data/lib/ea/sources/qea/xref/legend_definition.rb +35 -0
  386. data/lib/ea/sources/qea/xref/parser.rb +150 -0
  387. data/lib/ea/sources/qea/xref/property_definition.rb +29 -0
  388. data/lib/ea/sources/qea/xref/record.rb +41 -0
  389. data/lib/ea/sources/qea/xref/stereotype_application.rb +37 -0
  390. data/lib/ea/sources/qea/xref.rb +27 -0
  391. data/lib/ea/sources/qea.rb +4 -0
  392. data/lib/ea/sources/xmi/adapter.rb +80 -2
  393. data/lib/ea/sources/xmi/classifier_builder.rb +10 -1
  394. data/lib/ea/sources/xmi/diagram_builder.rb +95 -18
  395. data/lib/ea/sources/xmi/extension_geometry_parser.rb +19 -2
  396. data/lib/ea/sources/xmi/extension_style_parser.rb +15 -2
  397. data/lib/ea/sources/xmi/note_builder.rb +46 -0
  398. data/lib/ea/sources/xmi/property_builder.rb +9 -0
  399. data/lib/ea/sources/xmi/stereotype_builder.rb +54 -0
  400. data/lib/ea/sources/xmi/tag_builder.rb +45 -0
  401. data/lib/ea/sources/xmi/umldi_keyword_extractor.rb +87 -0
  402. data/lib/ea/sources/xmi.rb +4 -0
  403. data/lib/ea/spa/output/sharded_multi_file_strategy.rb +18 -3
  404. data/lib/ea/spa/output/single_file_strategy.rb +8 -2
  405. data/lib/ea/spa/output/strategy.rb +34 -0
  406. data/lib/ea/svg/connector_router.rb +134 -0
  407. data/lib/ea/svg/ea_emitter/background.rb +2 -3
  408. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +142 -0
  409. data/lib/ea/svg/ea_emitter/canvas.rb +56 -33
  410. data/lib/ea/svg/ea_emitter/color_resolver.rb +56 -0
  411. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +28 -0
  412. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +30 -0
  413. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +86 -0
  414. data/lib/ea/svg/ea_emitter/compartment/header.rb +32 -0
  415. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +57 -0
  416. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +96 -0
  417. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +67 -0
  418. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +52 -0
  419. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +102 -0
  420. data/lib/ea/svg/ea_emitter/compartment/operations.rb +38 -0
  421. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +72 -0
  422. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +136 -0
  423. data/lib/ea/svg/ea_emitter/compartment/shape.rb +82 -0
  424. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +30 -0
  425. data/lib/ea/svg/ea_emitter/compartment.rb +58 -0
  426. data/lib/ea/svg/ea_emitter/connectors.rb +46 -12
  427. data/lib/ea/svg/ea_emitter/diagram_frame.rb +143 -0
  428. data/lib/ea/svg/ea_emitter/document.rb +15 -24
  429. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +117 -0
  430. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +119 -0
  431. data/lib/ea/svg/ea_emitter/element/bcol_decoder.rb +29 -0
  432. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +113 -0
  433. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +53 -0
  434. data/lib/ea/svg/ea_emitter/element/divider_renderer.rb +17 -0
  435. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +34 -0
  436. data/lib/ea/svg/ea_emitter/element/filter.rb +44 -0
  437. data/lib/ea/svg/ea_emitter/element/hand_draw_shape_renderer.rb +77 -0
  438. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +43 -0
  439. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +27 -0
  440. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +76 -0
  441. data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +25 -0
  442. data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +68 -0
  443. data/lib/ea/svg/ea_emitter/element/header_line_provider.rb +19 -0
  444. data/lib/ea/svg/ea_emitter/element/header_lines.rb +27 -0
  445. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +48 -0
  446. data/lib/ea/svg/ea_emitter/element/icon_renderer.rb +109 -0
  447. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +177 -0
  448. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +112 -0
  449. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +100 -0
  450. data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +181 -0
  451. data/lib/ea/svg/ea_emitter/element/row_icon_renderer.rb +156 -0
  452. data/lib/ea/svg/ea_emitter/element/shape_renderer.rb +17 -0
  453. data/lib/ea/svg/ea_emitter/element/stereotype_icon_renderer.rb +82 -0
  454. data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +50 -0
  455. data/lib/ea/svg/ea_emitter/element/text_escape.rb +20 -0
  456. data/lib/ea/svg/ea_emitter/element.rb +37 -0
  457. data/lib/ea/svg/ea_emitter/elements.rb +283 -95
  458. data/lib/ea/svg/ea_emitter/font_resolver.rb +111 -0
  459. data/lib/ea/svg/ea_emitter/ghost_labels.rb +66 -0
  460. data/lib/ea/svg/ea_emitter/label/end_label.rb +266 -0
  461. data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +101 -0
  462. data/lib/ea/svg/ea_emitter/label/registry.rb +50 -0
  463. data/lib/ea/svg/ea_emitter/label.rb +23 -0
  464. data/lib/ea/svg/ea_emitter/labels.rb +93 -27
  465. data/lib/ea/svg/ea_emitter/layer.rb +20 -0
  466. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +93 -0
  467. data/lib/ea/svg/ea_emitter/marker/arrow_path.rb +52 -0
  468. data/lib/ea/svg/ea_emitter/marker/diamond.rb +57 -0
  469. data/lib/ea/svg/ea_emitter/marker/kind.rb +35 -0
  470. data/lib/ea/svg/ea_emitter/marker/open_triangle.rb +27 -0
  471. data/lib/ea/svg/ea_emitter/marker/package_import.rb +39 -0
  472. data/lib/ea/svg/ea_emitter/marker/plus.rb +40 -0
  473. data/lib/ea/svg/ea_emitter/marker/registry.rb +42 -0
  474. data/lib/ea/svg/ea_emitter/marker.rb +37 -0
  475. data/lib/ea/svg/ea_emitter/markers.rb +351 -64
  476. data/lib/ea/svg/ea_emitter/render_context.rb +52 -0
  477. data/lib/ea/svg/ea_emitter/style.rb +15 -0
  478. data/lib/ea/svg/ea_emitter/text_renderer.rb +92 -0
  479. data/lib/ea/svg/ea_emitter.rb +33 -11
  480. data/lib/ea/svg/parity/checker.rb +105 -0
  481. data/lib/ea/svg/parity/source.rb +59 -0
  482. data/lib/ea/svg/parity/suite.rb +113 -0
  483. data/lib/ea/svg/parity.rb +14 -0
  484. data/lib/ea/svg.rb +2 -15
  485. data/lib/ea/theme/definition.rb +176 -0
  486. data/lib/ea/theme/loader.rb +125 -0
  487. data/lib/ea/theme/registry.rb +96 -0
  488. data/lib/ea/theme.rb +37 -0
  489. data/lib/ea/transformations.rb +5 -88
  490. data/lib/ea/version.rb +4 -1
  491. data/lib/ea.rb +14 -4
  492. data/lib/tasks/svg_bench.rake +87 -0
  493. data/lutaml-uml-0.5.1.gem +0 -0
  494. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png +0 -0
  495. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png +0 -0
  496. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png +0 -0
  497. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-3-colors.png +0 -0
  498. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance-fonts.png +0 -0
  499. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-4-appearance.png +0 -0
  500. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-5-behavior.png +0 -0
  501. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-6-sequence.png +0 -0
  502. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram.png +0 -0
  503. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-2-objects.png +0 -0
  504. data/reference-docs/enterprise-architect-appearance-dialogs/appearance-3-links.png +0 -0
  505. metadata +449 -26
  506. data/lib/ea/diagram/configuration.rb +0 -379
  507. data/lib/ea/diagram/element_renderers/base_renderer.rb +0 -77
  508. data/lib/ea/diagram/element_renderers/class_renderer.rb +0 -323
  509. data/lib/ea/diagram/element_renderers/connector_renderer.rb +0 -41
  510. data/lib/ea/diagram/element_renderers/package_renderer.rb +0 -61
  511. data/lib/ea/diagram/element_renderers.rb +0 -43
  512. data/lib/ea/diagram/extractor.rb +0 -566
  513. data/lib/ea/diagram/layout_engine.rb +0 -170
  514. data/lib/ea/diagram/path_builder.rb +0 -202
  515. data/lib/ea/diagram/style_parser.rb +0 -42
  516. data/lib/ea/diagram/style_resolver.rb +0 -291
  517. data/lib/ea/diagram/svg_renderer.rb +0 -274
  518. data/lib/ea/diagram/util.rb +0 -73
  519. data/lib/ea/svg/bounds_calculator.rb +0 -74
  520. data/lib/ea/svg/connector_path.rb +0 -138
  521. data/lib/ea/svg/element_box.rb +0 -256
  522. data/lib/ea/svg/renderer.rb +0 -77
  523. data/lib/ea/svg/style_resolver.rb +0 -72
  524. data/lib/ea/transformations/configuration.rb +0 -333
  525. data/lib/ea/transformations/format_registry.rb +0 -366
  526. data/lib/ea/transformations/parsers/base_parser.rb +0 -482
  527. data/lib/ea/transformations/parsers/qea_parser.rb +0 -401
  528. data/lib/ea/transformations/parsers/xmi_parser.rb +0 -243
  529. data/lib/ea/transformations/transformation_engine.rb +0 -390
@@ -3,28 +3,55 @@
3
3
  module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
- # Emits the connectors layer: for each DiagramConnector, emits
7
- # one <g> containing the <path d="M x y L x2 y2..."/> through
8
- # its waypoints. Markers (arrowheads, diamonds) are emitted
9
- # separately by the Markers emitter.
6
+ # Emits connector line `<path>` elements grouped per line-style.
7
+ # Returns an Array of `Layer` structs.
8
+ #
9
+ # Default mode (`grouped: true`) consolidates all same-style
10
+ # paths into ONE `<g>` element — matching EA's encoding for
11
+ # diagrams with many similarly-styled connectors.
12
+ #
13
+ # Pass `grouped: false` for per-connector grouping (one `<g>`
14
+ # per line, used when strict per-entity interleaving with
15
+ # markers is required).
10
16
  class Connectors
11
- attr_reader :diagram
17
+ LINE_STYLE_KEY = :connector_line
12
18
 
13
- def initialize(diagram)
19
+ attr_reader :diagram, :canvas, :grouped, :stroke_width
20
+
21
+ def initialize(diagram, canvas: nil, grouped: true, stroke_width: 2)
14
22
  @diagram = diagram
23
+ @canvas = canvas
24
+ @grouped = grouped
25
+ @stroke_width = stroke_width
15
26
  end
16
27
 
17
- def render
28
+ def layers
18
29
  paths = visible_connectors.filter_map { |c| path_for(c) }
19
- return "" if paths.empty?
30
+ return [] if paths.empty?
31
+
32
+ if grouped
33
+ [Layer.new(style_key: LINE_STYLE_KEY, style: line_style,
34
+ body: paths.join("\n "))]
35
+ else
36
+ paths.map do |path|
37
+ Layer.new(style_key: LINE_STYLE_KEY, style: line_style, body: path)
38
+ end
39
+ end
40
+ end
20
41
 
21
- %(<g style="stroke-width:2;stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#000000; stroke-opacity:1.00">\n#{paths.join("\n")}\n</g>)
42
+ def line_style
43
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; fill:#000000;fill-opacity:0.00; stroke:#000000; stroke-opacity:1.00"
44
+ end
45
+
46
+ # Backwards-compatible render — joins all layers' `<g>`.
47
+ def render
48
+ layers.map(&:to_svg).join("\n")
22
49
  end
23
50
 
24
51
  private
25
52
 
26
53
  def visible_connectors
27
- (diagram.connectors || []).reject(&:hidden)
54
+ (diagram.connectors || []).select(&:renderable?)
28
55
  end
29
56
 
30
57
  def path_for(connector)
@@ -32,9 +59,10 @@ module Ea
32
59
  return nil if pts.size < 2
33
60
 
34
61
  d = pts.each_with_index.map do |p, idx|
35
- "#{idx.zero? ? 'M' : 'L'} #{p[0]} #{p[1]}"
62
+ x, y = translate_point(p)
63
+ "#{idx.zero? ? 'M' : 'L'} #{Canvas.coord(x)} #{Canvas.coord(y)}"
36
64
  end.join(" ")
37
- %( <path d="#{d}" shape-rendering="auto"/>)
65
+ %(<path d="#{d}" shape-rendering="auto"/>)
38
66
  end
39
67
 
40
68
  def waypoints_for(connector)
@@ -44,6 +72,12 @@ module Ea
44
72
  [wp.position.x, wp.position.y]
45
73
  end
46
74
  end
75
+
76
+ def translate_point(p)
77
+ return p unless @canvas
78
+
79
+ [@canvas.translate_x(p[0]), @canvas.translate_y(p[1])]
80
+ end
47
81
  end
48
82
  end
49
83
  end
@@ -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
@@ -4,39 +4,30 @@ module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
6
  # Top-level orchestrator: emits a complete standalone SVG
7
- # document matching EA's structure. Calls the layer emitters
8
- # in EA's canonical order.
7
+ # document matching EA's encoding. Bound computation and
8
+ # layer sequencing delegated to BoundsCalculator and
9
+ # LayerSequencer respectively (SRP).
9
10
  class Document
10
- BUILD_ID = "1628"
11
+ BUILD_ID = "1624"
11
12
 
12
- attr_reader :diagram, :model_index, :options
13
+ attr_reader :diagram, :model_index, :frame, :document
13
14
 
14
- def initialize(diagram, model_index:, **_options)
15
+ def initialize(diagram, model_index:, frame: true, document: nil, **_options)
15
16
  @diagram = diagram
16
17
  @model_index = model_index
18
+ @frame = frame
19
+ @document = document
17
20
  end
18
21
 
19
22
  def render
20
- canvas = Canvas.from(diagram)
21
- layers = [
22
- Background.render(canvas),
23
- Elements.new(diagram, model_index: model_index).render,
24
- Connectors.new(diagram).render,
25
- Markers.new(diagram, model_index: model_index).render,
26
- Labels.new(diagram).render
27
- ].reject { |s| s.nil? || s.empty? }
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? }
28
29
 
29
- <<~SVG
30
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
31
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
32
-
33
- <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}">
34
- <title></title>
35
- <desc>Created with Enterprise Architect (Build: #{BUILD_ID}) 2</desc>
36
-
37
- #{layers.join("\n\n")}
38
- </svg>
39
- SVG
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>)
40
31
  end
41
32
  end
42
33
  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