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,27 +3,49 @@
3
3
  module Ea
4
4
  module Svg
5
5
  # EaEmitter orchestrates SVG output that mirrors EA's layering
6
- # and naming conventions:
6
+ # and naming conventions.
7
7
  #
8
- # 1. XML declaration + DOCTYPE (SVG 1.0)
9
- # 2. Root <svg> with cm dimensions + viewBox
10
- # 3. <title></title> + <desc>Created with Enterprise Architect..</desc>
11
- # 4. Background <g> with full-canvas white rect
12
- # 5. Per-element groups, layered: shape <g>, text <g>, divider
13
- # <g>, attribute text <g>
14
- # 6. Per-connector groups: path <g> and polygon <g>
15
- # 7. Final labels <g> with all connector text
8
+ # Architecture:
9
+ #
10
+ # Document (entry point)
11
+ # └─ LayerSequencer (frame?, theme via diagram.theme)
12
+ # ├─ Background
13
+ # ├─ DiagramFrame (opt-in)
14
+ # ├─ Elements Element::* compartment renderers
15
+ # ├─ Connectors Layer
16
+ # ├─ Markers → Marker::Registry (OCP) → Layer
17
+ # └─ Labels → TextRenderer
18
+ #
19
+ # Cross-cutting:
20
+ # Canvas, BoundsCalculator, ColorResolver, FontResolver
21
+ # Ea::Theme::Definition / Registry / Loader (domain-level)
22
+ # TextRenderer, Style
23
+ #
24
+ # Theme is a top-level domain concept at Ea::Theme::*.
25
+ # See lib/ea/theme.rb for the full API.
16
26
  #
17
- # Distinct from Ea::Svg::Renderer which produces a thinner
18
- # output for diagrams that don't carry full EA style data.
19
27
  module EaEmitter
20
28
  autoload :Canvas, "ea/svg/ea_emitter/canvas"
21
29
  autoload :Background, "ea/svg/ea_emitter/background"
30
+ autoload :BoundsCalculator, "ea/svg/ea_emitter/bounds_calculator"
31
+ autoload :Style, "ea/svg/ea_emitter/style"
22
32
  autoload :Elements, "ea/svg/ea_emitter/elements"
23
33
  autoload :Connectors, "ea/svg/ea_emitter/connectors"
24
34
  autoload :Markers, "ea/svg/ea_emitter/markers"
25
35
  autoload :Labels, "ea/svg/ea_emitter/labels"
36
+ autoload :GhostLabels, "ea/svg/ea_emitter/ghost_labels"
37
+ autoload :Layer, "ea/svg/ea_emitter/layer"
38
+ autoload :LayerSequencer, "ea/svg/ea_emitter/layer_sequencer"
39
+ autoload :FontResolver, "ea/svg/ea_emitter/font_resolver"
40
+ autoload :Marker, "ea/svg/ea_emitter/marker"
41
+ autoload :Element, "ea/svg/ea_emitter/element"
42
+ autoload :DiagramFrame, "ea/svg/ea_emitter/diagram_frame"
43
+ autoload :TextRenderer, "ea/svg/ea_emitter/text_renderer"
44
+ autoload :ColorResolver, "ea/svg/ea_emitter/color_resolver"
26
45
  autoload :Document, "ea/svg/ea_emitter/document"
46
+ autoload :Label, "ea/svg/ea_emitter/label"
47
+ autoload :Compartment, "ea/svg/ea_emitter/compartment"
48
+ autoload :RenderContext, "ea/svg/ea_emitter/render_context"
27
49
  end
28
50
  end
29
51
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module Ea
6
+ module Svg
7
+ module Parity
8
+ # Compares an emitted SVG against one EA reference SVG across
9
+ # element counts, font-family, viewBox, and text overlap.
10
+ class Checker
11
+ ELEMENT_TYPES = %i[rect path polygon text group].freeze
12
+
13
+ attr_reader :ours, :reference
14
+
15
+ def initialize(ours:, reference:)
16
+ @ours = ours
17
+ @reference = reference
18
+ end
19
+
20
+ def report
21
+ Report.new(
22
+ rect: count_diff("rect"),
23
+ path: count_diff("path"),
24
+ polygon: count_diff("polygon"),
25
+ text: count_diff("text"),
26
+ group: top_level_group_diff,
27
+ font_family: font_family_match,
28
+ view_box: view_box_match,
29
+ text_overlap: text_overlap_ratio
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ def our_doc
36
+ @our_doc ||= Nokogiri::XML(@ours)
37
+ end
38
+
39
+ def ref_doc
40
+ @ref_doc ||= Nokogiri::XML(@reference)
41
+ end
42
+
43
+ def count_diff(selector)
44
+ Diff.new(ours: our_doc.css(selector).size,
45
+ reference: ref_doc.css(selector).size)
46
+ end
47
+
48
+ def top_level_group_diff
49
+ Diff.new(ours: our_doc.css("svg > g").size,
50
+ reference: ref_doc.css("svg > g").size)
51
+ end
52
+
53
+ def font_family_match
54
+ our_family = first_text_style(our_doc)[/font-family:([^;]+)/, 1]
55
+ ref_family = first_text_style(ref_doc)[/font-family:([^;]+)/, 1]
56
+ our_family == ref_family
57
+ end
58
+
59
+ def view_box_match
60
+ our_doc.root["viewBox"] == ref_doc.root["viewBox"]
61
+ end
62
+
63
+ def text_overlap_ratio
64
+ our_set = our_doc.css("text").map(&:text).map(&:strip).to_set
65
+ ref_set = ref_doc.css("text").map(&:text).map(&:strip).to_set
66
+ return 1.0 if our_set.empty? && ref_set.empty?
67
+ return 0.0 if our_set.empty? || ref_set.empty?
68
+
69
+ (our_set & ref_set).size.to_f / (our_set | ref_set).size.to_f
70
+ end
71
+
72
+ def first_text_style(doc)
73
+ first = doc.css("text").first
74
+ first ? (first["style"] || "") : ""
75
+ end
76
+
77
+ Diff = Struct.new(:ours, :reference, keyword_init: true) do
78
+ def delta
79
+ ours - reference
80
+ end
81
+
82
+ def within?(tolerance)
83
+ delta.abs <= tolerance
84
+ end
85
+ end
86
+
87
+ Report = Struct.new(:rect, :path, :polygon, :text, :group,
88
+ :font_family, :view_box, :text_overlap,
89
+ keyword_init: true) do
90
+ def shape_delta_total
91
+ [rect, path, polygon].sum(&:delta).abs
92
+ end
93
+
94
+ def text_delta
95
+ text.delta
96
+ end
97
+
98
+ def shape_within?(tolerance)
99
+ [rect, path, polygon].all? { |d| d.within?(tolerance) }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ea"
4
+ require "xmi"
5
+ require "ea/sources/xmi/adapter"
6
+
7
+ module Ea
8
+ module Svg
9
+ module Parity
10
+ # Loads an EA model Document from any supported source file
11
+ # (`.qea`, `.xmi`). Used by the bench task to keep file-format
12
+ # dispatch in one place.
13
+ class Source
14
+ EXTENSIONS = {
15
+ ".qea" => :qea,
16
+ ".xmi" => :xmi
17
+ }.freeze
18
+
19
+ attr_reader :path
20
+
21
+ def initialize(path)
22
+ @path = path
23
+ end
24
+
25
+ def self.detect(path)
26
+ ext = File.extname(path).downcase
27
+ EXTENSIONS[ext] ||
28
+ raise(ArgumentError, "unsupported source: #{path} (ext=#{ext})")
29
+ end
30
+
31
+ def load
32
+ case self.class.detect(path)
33
+ when :qea then load_qea
34
+ when :xmi then load_xmi
35
+ end
36
+ end
37
+
38
+ def document
39
+ load
40
+ end
41
+
42
+ def format
43
+ self.class.detect(path)
44
+ end
45
+
46
+ private
47
+
48
+ def load_qea
49
+ raise NotImplementedError, "QEA Document loading not yet wired into bench"
50
+ end
51
+
52
+ def load_xmi
53
+ root = ::Xmi::Sparx::Root.parse_xml(File.read(path))
54
+ Ea::Sources::Xmi::Adapter.new(root, path).to_document
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require "ea/svg/parity/checker"
5
+
6
+ module Ea
7
+ module Svg
8
+ module Parity
9
+ # Drives Parity::Checker across every diagram in a Document
10
+ # that has a matching reference SVG. Drives from any source
11
+ # the Document supports (qea or xmi) since the Document is
12
+ # format-agnostic.
13
+ class Suite
14
+ ELEMENT_TYPES = %i[rect polygon path text].freeze
15
+
16
+ attr_reader :document, :ref_dir
17
+
18
+ def initialize(document, ref_dir)
19
+ @document = document
20
+ @ref_dir = ref_dir
21
+ end
22
+
23
+ def measure
24
+ per_diagram = diagrams_with_references.map { |d| measure_one(d) }
25
+ Report.new(per_diagram: per_diagram)
26
+ end
27
+
28
+ private
29
+
30
+ def diagrams_with_references
31
+ document.diagrams.select { |d| reference_path_for(d) }
32
+ end
33
+
34
+ def reference_path_for(diagram)
35
+ # XMI sources already use the EAID_... filename. QEA
36
+ # sources use the GUID form ("F4C23F9E-..."); try both.
37
+ paths = [
38
+ File.join(ref_dir, "#{diagram.id}.svg"),
39
+ File.join(ref_dir, "#{Ea::Sources::Qea::IdNormalizer.to_eaid(diagram.id)}.svg")
40
+ ]
41
+ paths.find { |p| File.exist?(p) }
42
+ end
43
+
44
+ def measure_one(diagram)
45
+ ref_path = reference_path_for(diagram)
46
+ ours = Ea::Svg::EaEmitter::Document.new(diagram,
47
+ model_index: document.index_by_id,
48
+ document: document).render
49
+ reference = File.read(ref_path)
50
+ checker = Checker.new(ours: ours, reference: reference)
51
+ DiagramReport.new(diagram: diagram, report: checker.report)
52
+ end
53
+
54
+ DiagramReport = Struct.new(:diagram, :report, keyword_init: true) do
55
+ def id
56
+ diagram.id
57
+ end
58
+
59
+ def name
60
+ diagram.name
61
+ end
62
+
63
+ def type
64
+ diagram.diagram_type
65
+ end
66
+ end
67
+
68
+ class Report
69
+ attr_reader :per_diagram
70
+
71
+ def initialize(per_diagram:)
72
+ @per_diagram = per_diagram
73
+ end
74
+
75
+ def total
76
+ per_diagram.size
77
+ end
78
+
79
+ def matched(shape_tolerance: 5)
80
+ per_diagram.count { |dr| dr.report.shape_delta_total <= shape_tolerance }
81
+ end
82
+
83
+ def aggregate_shape_counts
84
+ ours = { rect: 0, polygon: 0, path: 0, text: 0 }
85
+ ref = { rect: 0, polygon: 0, path: 0, text: 0 }
86
+ per_diagram.each do |dr|
87
+ ours[:rect] += dr.report.rect.ours
88
+ ours[:polygon] += dr.report.polygon.ours
89
+ ours[:path] += dr.report.path.ours
90
+ ours[:text] += dr.report.text.ours
91
+ ref[:rect] += dr.report.rect.reference
92
+ ref[:polygon] += dr.report.polygon.reference
93
+ ref[:path] += dr.report.path.reference
94
+ ref[:text] += dr.report.text.reference
95
+ end
96
+ { ours: ours, reference: ref }
97
+ end
98
+
99
+ def text_overlap_avg
100
+ return 0.0 if per_diagram.empty?
101
+
102
+ overlaps = per_diagram.map(&:report).map(&:text_overlap)
103
+ overlaps.sum / overlaps.size.to_f
104
+ end
105
+
106
+ def outliers(shape_tolerance: 5)
107
+ per_diagram.select { |dr| dr.report.shape_delta_total > shape_tolerance }
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ # Render-fidelity measurement. Compares emitted SVG against
6
+ # EA-produced reference SVGs so parity can be scored on any
7
+ # source format (.qea or .xmi) from specs or a rake task.
8
+ module Parity
9
+ autoload :Checker, "ea/svg/parity/checker"
10
+ autoload :Suite, "ea/svg/parity/suite"
11
+ autoload :Source, "ea/svg/parity/source"
12
+ end
13
+ end
14
+ end
data/lib/ea/svg.rb CHANGED
@@ -1,23 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ea
4
- # Ea::Svg is a consumer adapter of Ea::Model::Diagram — it
5
- # projects the umldi (UML Diagram Interchange) content of a
6
- # diagram into standalone SVG. Coordinates are taken straight
7
- # from the source: EA's pixel space (rectleft, recttop,
8
- # rectright, rectbottom, t_diagramlinks.geometry) or XMI's
9
- # uml:Diagram owned_element bounds.
10
- #
11
- # Distinct from Ea::Diagram::SvgRenderer which operates on the
12
- # legacy Lutaml::Uml::Document pipeline. This module consumes
13
- # the canonical Ea::Model types only.
14
4
  module Svg
15
- autoload :BoundsCalculator, "ea/svg/bounds_calculator"
16
- autoload :StyleResolver, "ea/svg/style_resolver"
17
5
  autoload :StereotypeColorResolver, "ea/svg/stereotype_color_resolver"
18
- autoload :ElementBox, "ea/svg/element_box"
19
- autoload :ConnectorPath, "ea/svg/connector_path"
20
- autoload :Renderer, "ea/svg/renderer"
6
+ autoload :ConnectorRouter, "ea/svg/connector_router"
21
7
  autoload :EaEmitter, "ea/svg/ea_emitter"
8
+ autoload :Parity, "ea/svg/parity"
22
9
  end
23
10
  end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Theme
5
+ # Immutable value object describing a complete EA diagram theme.
6
+ #
7
+ # Bundles ALL appearance values that EA's SVG renderer needs:
8
+ # fonts, colors, strokes, canvas insets, compartment spacing,
9
+ # marker geometry, etc. No renderer hardcodes appearance
10
+ # numbers — every value comes from the active Definition.
11
+ class Definition
12
+ Geometry = Struct.new(:inset_left, :inset_right, :inset_top, :inset_bottom,
13
+ keyword_init: true)
14
+ FrameSpec = Struct.new(:inset, :tab_height, :tab_slant, :tab_label_x,
15
+ :tab_label_y, :tab_padding, keyword_init: true)
16
+ CompartmentSpec = Struct.new(:header_top_padding, :header_line_offset,
17
+ :divider_offset, :attr_line_offset,
18
+ :attr_first_offset, :op_line_offset,
19
+ keyword_init: true)
20
+ AttrSpec = Struct.new(:visibility_x_offset, :content_x_offset, keyword_init: true)
21
+ MarkerSpec = Struct.new(:diamond_half_width, :diamond_half_height,
22
+ :triangle_half_base, :triangle_height,
23
+ :arrow_half_base, :arrow_height, keyword_init: true)
24
+ PackageSpec = Struct.new(:tab_height, :tab_label_padding, :default_tab_width,
25
+ keyword_init: true)
26
+ NoteSpec = Struct.new(:fold_size, :text_x_offset, :text_y_offset,
27
+ :line_height, keyword_init: true)
28
+
29
+ attr_reader :id, :name,
30
+ :font_family, :font_size, :font_size_unit,
31
+ :text_weight_normal, :text_weight_bold,
32
+ :element_font_size, :element_font_unit,
33
+ :text_color, :attribute_text_color, :method_text_color,
34
+ :border_color, :fill_color, :background_color,
35
+ :stroke_in_text_color,
36
+ :element_border_width, :connector_line_width,
37
+ :divider_stroke_width, :marker_stroke_width,
38
+ :text_width_factor,
39
+ :geometry, :frame, :compartments, :attribute_spec,
40
+ :markers, :package, :note, :fills
41
+
42
+ DEFAULT_GEOMETRY = Geometry.new(inset_left: 35, inset_right: 50,
43
+ inset_top: 40, inset_bottom: 57).freeze
44
+ DEFAULT_FRAME = FrameSpec.new(inset: 6, tab_height: 20, tab_slant: 13,
45
+ tab_label_x: 11, tab_label_y: 19,
46
+ tab_padding: 7).freeze
47
+ DEFAULT_COMPARTMENTS = CompartmentSpec.new(header_top_padding: 9,
48
+ header_line_offset: 6,
49
+ divider_offset: 8,
50
+ attr_line_offset: 4,
51
+ attr_first_offset: 7,
52
+ op_line_offset: 4).freeze
53
+ DEFAULT_ATTR = AttrSpec.new(visibility_x_offset: 5,
54
+ content_x_offset: 20).freeze
55
+ DEFAULT_MARKERS = MarkerSpec.new(diamond_half_width: 5,
56
+ diamond_half_height: 10,
57
+ triangle_half_base: 6,
58
+ triangle_height: 11,
59
+ arrow_half_base: 6,
60
+ arrow_height: 11).freeze
61
+ DEFAULT_PACKAGE = PackageSpec.new(tab_height: 20,
62
+ tab_label_padding: 10,
63
+ default_tab_width: 105).freeze
64
+ DEFAULT_NOTE = NoteSpec.new(fold_size: 12, text_x_offset: 5,
65
+ text_y_offset: 12, line_height: 12).freeze
66
+
67
+ def initialize(id:, name: nil, font_family: nil, font_size: nil,
68
+ font_size_unit: "pt", text_weight_normal: 400,
69
+ text_weight_bold: 700,
70
+ element_font_size: 9, element_font_unit: "pt",
71
+ text_color: "#000000", attribute_text_color: nil,
72
+ method_text_color: nil, border_color: "#000000",
73
+ fill_color: "#FFFFFF", background_color: "#FFFFFF",
74
+ stroke_in_text_color: "#000000",
75
+ element_border_width: 2, connector_line_width: 2,
76
+ divider_stroke_width: 2, marker_stroke_width: 2,
77
+ text_width_factor: 0.65,
78
+ geometry: nil, frame: nil, compartments: nil,
79
+ attribute_spec: nil, markers: nil, package: nil,
80
+ note: nil, fills: {})
81
+ @id = id.to_s
82
+ @name = name
83
+ @font_family = font_family
84
+ @font_size = font_size
85
+ @font_size_unit = font_size_unit
86
+ @text_weight_normal = text_weight_normal
87
+ @text_weight_bold = text_weight_bold
88
+ @element_font_size = element_font_size
89
+ @element_font_unit = element_font_unit
90
+ @text_color = text_color
91
+ @attribute_text_color = attribute_text_color
92
+ @method_text_color = method_text_color
93
+ @border_color = border_color
94
+ @fill_color = fill_color
95
+ @background_color = background_color
96
+ @stroke_in_text_color = stroke_in_text_color
97
+ @element_border_width = element_border_width
98
+ @connector_line_width = connector_line_width
99
+ @divider_stroke_width = divider_stroke_width
100
+ @marker_stroke_width = marker_stroke_width
101
+ @text_width_factor = text_width_factor
102
+ @geometry = geometry || DEFAULT_GEOMETRY.dup
103
+ @frame = frame || DEFAULT_FRAME.dup
104
+ @compartments = compartments || DEFAULT_COMPARTMENTS.dup
105
+ @attribute_spec = attribute_spec || DEFAULT_ATTR.dup
106
+ @markers = markers || DEFAULT_MARKERS.dup
107
+ @package = package || DEFAULT_PACKAGE.dup
108
+ @note = note || DEFAULT_NOTE.dup
109
+ @fills = fills.dup.freeze
110
+ end
111
+
112
+ def themed?
113
+ id != "default"
114
+ end
115
+
116
+ def fill_for(classifier)
117
+ return nil if classifier.nil?
118
+
119
+ fills[classifier.class.name]
120
+ end
121
+
122
+ def with(**overrides)
123
+ Definition.new(**to_h.merge(overrides))
124
+ end
125
+
126
+ def ==(other)
127
+ other.is_a?(Definition) && id == other.id
128
+ end
129
+
130
+ def hash
131
+ id.hash
132
+ end
133
+
134
+ alias eql? ==
135
+
136
+ # Backward-compatible aliases for renamed attributes.
137
+ def stroke_width
138
+ element_border_width
139
+ end
140
+
141
+ def attribute_color
142
+ attribute_text_color
143
+ end
144
+
145
+ def method_color
146
+ method_text_color
147
+ end
148
+
149
+ def to_h
150
+ {
151
+ id: id, name: name,
152
+ font_family: font_family, font_size: font_size,
153
+ font_size_unit: font_size_unit,
154
+ text_weight_normal: text_weight_normal,
155
+ text_weight_bold: text_weight_bold,
156
+ element_font_size: element_font_size,
157
+ element_font_unit: element_font_unit,
158
+ text_color: text_color,
159
+ attribute_text_color: attribute_text_color,
160
+ method_text_color: method_text_color,
161
+ border_color: border_color, fill_color: fill_color,
162
+ background_color: background_color,
163
+ stroke_in_text_color: stroke_in_text_color,
164
+ element_border_width: element_border_width,
165
+ connector_line_width: connector_line_width,
166
+ divider_stroke_width: divider_stroke_width,
167
+ marker_stroke_width: marker_stroke_width,
168
+ text_width_factor: text_width_factor,
169
+ geometry: geometry, frame: frame, compartments: compartments,
170
+ attribute_spec: attribute_spec, markers: markers,
171
+ package: package, note: note, fills: fills
172
+ }
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Theme
5
+ # Loads theme Definition objects from YAML files.
6
+ #
7
+ # YAML format (see config/themes/119.yml):
8
+ #
9
+ # id: "119"
10
+ # name: EA White Theme
11
+ # font: { family: Carlito, size: 7, unit: pt, weight_normal: 400, ... }
12
+ # element_font: { size: 9, unit: pt }
13
+ # colors: { text: "#000000", attribute_text: "#66413F", ... }
14
+ # strokes: { element_border: 2, connector_line: 2, ... }
15
+ # text_metrics: { width_factor: 0.65 }
16
+ # canvas: { inset_left: 35, inset_right: 50, ... }
17
+ # frame: { inset: 6, tab_height: 20, ... }
18
+ # compartments: { header_line_offset: 4, ... }
19
+ # attribute: { visibility_x_offset: 5, content_x_offset: 20 }
20
+ # markers: { diamond_half_width: 5, ... }
21
+ # package: { tab_height: 20, ... }
22
+ # note: { fold_size: 12, ... }
23
+ # fills: {}
24
+ #
25
+ class Loader
26
+ class << self
27
+ def load_file(path)
28
+ data = YAML.load_file(path)
29
+ from_hash(data)
30
+ end
31
+
32
+ def load_dir(dir)
33
+ Dir.glob(File.join(dir, "*.yml")).map { |f| load_file(f) }
34
+ end
35
+
36
+ private
37
+
38
+ def from_hash(data)
39
+ font = data["font"] || {}
40
+ elem_font = data["element_font"] || {}
41
+ colors = data["colors"] || {}
42
+ strokes = data["strokes"] || {}
43
+ text_metrics = data["text_metrics"] || {}
44
+ canvas = data["canvas"] || {}
45
+ frame = data["frame"] || {}
46
+ comps = data["compartments"] || {}
47
+ attr_cfg = data["attribute"] || {}
48
+ marker_cfg = data["markers"] || {}
49
+ pkg_cfg = data["package"] || {}
50
+ note_cfg = data["note"] || {}
51
+
52
+ Definition.new(
53
+ id: data["id"],
54
+ name: data["name"],
55
+ font_family: font["family"],
56
+ font_size: font["size"],
57
+ font_size_unit: font["unit"] || "pt",
58
+ text_weight_normal: font["weight_normal"] || 400,
59
+ text_weight_bold: font["weight_bold"] || 700,
60
+ element_font_size: elem_font["size"] || 9,
61
+ element_font_unit: elem_font["unit"] || "pt",
62
+ text_color: colors["text"] || "#000000",
63
+ attribute_text_color: colors["attribute_text"],
64
+ method_text_color: colors["method_text"],
65
+ border_color: colors["border"] || "#000000",
66
+ fill_color: colors["fill"] || "#FFFFFF",
67
+ background_color: colors["background"] || "#FFFFFF",
68
+ stroke_in_text_color: colors["stroke_in_text"] || "#000000",
69
+ element_border_width: strokes["element_border"] || 2,
70
+ connector_line_width: strokes["connector_line"] || 2,
71
+ divider_stroke_width: strokes["divider"] || 2,
72
+ marker_stroke_width: strokes["marker"] || 2,
73
+ text_width_factor: text_metrics["width_factor"] || 0.65,
74
+ geometry: canvas.empty? ? nil : Definition::Geometry.new(
75
+ inset_left: canvas["inset_left"] || 35,
76
+ inset_right: canvas["inset_right"] || 50,
77
+ inset_top: canvas["inset_top"] || 40,
78
+ inset_bottom: canvas["inset_bottom"] || 57
79
+ ),
80
+ frame: frame.empty? ? nil : Definition::FrameSpec.new(
81
+ inset: frame["inset"] || 6,
82
+ tab_height: frame["tab_height"] || 20,
83
+ tab_slant: frame["tab_slant"] || 13,
84
+ tab_label_x: frame["tab_label_x"] || 11,
85
+ tab_label_y: frame["tab_label_y"] || 19,
86
+ tab_padding: frame["tab_padding"] || 7
87
+ ),
88
+ compartments: comps.empty? ? nil : Definition::CompartmentSpec.new(
89
+ header_top_padding: comps["header_top_padding"] || 9,
90
+ header_line_offset: comps["header_line_offset"] || 6,
91
+ divider_offset: comps["divider_offset"] || 8,
92
+ attr_line_offset: comps["attr_line_offset"] || 4,
93
+ attr_first_offset: comps["attr_first_offset"] || 7,
94
+ op_line_offset: comps["op_line_offset"] || 4
95
+ ),
96
+ attribute_spec: attr_cfg.empty? ? nil : Definition::AttrSpec.new(
97
+ visibility_x_offset: attr_cfg["visibility_x_offset"] || 5,
98
+ content_x_offset: attr_cfg["content_x_offset"] || 26
99
+ ),
100
+ markers: marker_cfg.empty? ? nil : Definition::MarkerSpec.new(
101
+ diamond_half_width: marker_cfg["diamond_half_width"] || 5,
102
+ diamond_half_height: marker_cfg["diamond_half_height"] || 10,
103
+ triangle_half_base: marker_cfg["triangle_half_base"] || 6,
104
+ triangle_height: marker_cfg["triangle_height"] || 11,
105
+ arrow_half_base: marker_cfg["arrow_half_base"] || 6,
106
+ arrow_height: marker_cfg["arrow_height"] || 11
107
+ ),
108
+ package: pkg_cfg.empty? ? nil : Definition::PackageSpec.new(
109
+ tab_height: pkg_cfg["tab_height"] || 20,
110
+ tab_label_padding: pkg_cfg["tab_label_padding"] || 10,
111
+ default_tab_width: pkg_cfg["default_tab_width"] || 105
112
+ ),
113
+ note: note_cfg.empty? ? nil : Definition::NoteSpec.new(
114
+ fold_size: note_cfg["fold_size"] || 12,
115
+ text_x_offset: note_cfg["text_x_offset"] || 5,
116
+ text_y_offset: note_cfg["text_y_offset"] || 12,
117
+ line_height: note_cfg["line_height"] || 12
118
+ ),
119
+ fills: data["fills"] || {}
120
+ )
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end