ea 0.3.1 → 0.5.0

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