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
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Instance-name underline for object diagrams. EA draws a
8
+ # short horizontal line just below each instance name to
9
+ # denote that it is an instance (UML convention). Rendered
10
+ # only when the diagram's `diagram_type` is "Object".
11
+ module InstanceUnderline
12
+ Y_OFFSET = 2
13
+ WIDTH_FACTOR = 0.65
14
+
15
+ module_function
16
+
17
+ def render(context)
18
+ return nil unless context.diagram
19
+ return nil unless object_diagram?(context)
20
+ return nil if context.header_lines.empty?
21
+
22
+ name_line = context.header_lines.last
23
+ text = name_line.first.to_s
24
+ return nil if text.empty?
25
+
26
+ underline_path(context, text)
27
+ end
28
+
29
+ def object_diagram?(context)
30
+ context.diagram && context.diagram.diagram_type == "Object"
31
+ end
32
+ module_function :object_diagram?
33
+
34
+ def underline_path(context, text)
35
+ bounds = context.bounds
36
+ text_width = TextRenderer.estimate_width(text, context.size,
37
+ WIDTH_FACTOR)
38
+ x_start = bounds.x + (bounds.width - text_width) / 2.0
39
+ y_pos = context.geometry.header_first_y + Y_OFFSET
40
+ build_path_tag(x_start, y_pos, text_width)
41
+ end
42
+ module_function :underline_path
43
+
44
+ def build_path_tag(x_start, y_pos, text_width)
45
+ x_end = x_start + text_width
46
+ coords = format_coords(x_start, y_pos, x_end)
47
+ d = "M #{coords[:x1]} #{coords[:y1]} L #{coords[:x2]} #{coords[:y1]}"
48
+ style = "stroke-width:1;stroke-linecap:square;" \
49
+ "stroke-linejoin:miter; fill:none; stroke:#000000;" \
50
+ " stroke-opacity:1.00"
51
+ %(<g style="#{style}">\n <path d="#{d}" shape-rendering="auto"/>\n</g>)
52
+ end
53
+ module_function :build_path_tag
54
+
55
+ def format_coords(x_start, y_pos, x_end)
56
+ {
57
+ x1: Ea::Svg::EaEmitter::Canvas.coord(x_start),
58
+ x2: Ea::Svg::EaEmitter::Canvas.coord(x_end),
59
+ y1: Ea::Svg::EaEmitter::Canvas.coord(y_pos)
60
+ }
61
+ end
62
+ module_function :format_coords
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Note body text wrapped inside a Note element's bounds.
8
+ # Renders only when the host element is a Note with a
9
+ # non-empty body.
10
+ module NoteBody
11
+ module_function
12
+
13
+ def render(context)
14
+ return nil if context.model_element.is_a?(Ea::Model::Note) &&
15
+ context.model_element.legend
16
+
17
+ body = context.note_body
18
+ return nil unless body
19
+
20
+ text_blocks = wrapped_lines(context, body)
21
+ %(<g style="#{group_style(context)}">\n#{text_blocks.join("\n")}\n</g>)
22
+ end
23
+
24
+ def wrapped_lines(context, body)
25
+ max_chars = [(context.bounds.width / (context.size * 0.6)).floor, 10].max
26
+ body.to_s.split(/\n/).flat_map do |para|
27
+ para.gsub(/(.{1,#{max_chars}})(\s+|$)/, "\\1\n").strip.split(/\n/)
28
+ end.each_with_index.map do |line, idx|
29
+ y = context.bounds.y + context.theme.note.text_y_offset +
30
+ (idx * context.theme.note.line_height)
31
+ TextRenderer.new(
32
+ content: line,
33
+ x: context.bounds.x + context.theme.note.text_x_offset,
34
+ y: y,
35
+ family: context.family, size: context.size, size_unit: context.size_unit,
36
+ fill: context.theme.text_color,
37
+ stroke_in_text: context.theme.stroke_in_text_color,
38
+ width_factor: context.theme.text_width_factor
39
+ ).to_svg
40
+ end
41
+ end
42
+
43
+ def group_style(context)
44
+ "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
45
+ "fill:#{context.theme.text_color};fill-opacity:1.00; " \
46
+ "stroke:#000000; stroke-opacity:0.00"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Note text compartment. When diagram.show_notes is true,
8
+ # renders the classifier's documentation note (from
9
+ # t_object.Note) as wrapped text lines in the element body.
10
+ # Lines are estimated by character width and split at word
11
+ # boundaries to fit the element bounds.
12
+ module NoteText
13
+ X_PADDING = 5
14
+ Y_FIRST_OFFSET = 15
15
+ LINE_OFFSET = 13
16
+ CHAR_WIDTH_FACTOR = 0.55
17
+
18
+ module_function
19
+
20
+ def render(context)
21
+ return nil unless show_notes?(context)
22
+ return nil unless context.classifier&.is_a?(Ea::Model::Classifier)
23
+
24
+ notes = note_bodies(context.classifier)
25
+ return nil if notes.empty?
26
+
27
+ first_y = context.geometry.attr_first_y + Y_FIRST_OFFSET
28
+ bounds = context.bounds
29
+ fill = context.theme.attribute_text_color
30
+ usable_width = bounds.width - (X_PADDING * 2)
31
+ max_chars = (usable_width / (context.size * CHAR_WIDTH_FACTOR)).floor
32
+
33
+ texts = []
34
+ y = first_y
35
+ notes.each do |body|
36
+ wrapped_lines(body, max_chars).each do |line|
37
+ texts << build_text(line, bounds.x + X_PADDING, y, context, fill)
38
+ y += LINE_OFFSET
39
+ end
40
+ end
41
+ wrap_group(texts.join("\n"), fill)
42
+ end
43
+
44
+ def show_notes?(context)
45
+ context.diagram && context.diagram.show_notes
46
+ end
47
+ module_function :show_notes?
48
+
49
+ def note_bodies(classifier)
50
+ (classifier.annotations || []).filter_map do |a|
51
+ body = a.body.to_s.strip
52
+ body.empty? ? nil : body
53
+ end
54
+ end
55
+ module_function :note_bodies
56
+
57
+ # Word-wrap text at max_chars, preserving word boundaries.
58
+ def wrapped_lines(text, max_chars)
59
+ return [text] if text.length <= max_chars
60
+
61
+ words = text.split(" ")
62
+ lines = []
63
+ current = ""
64
+ words.each do |word|
65
+ if current.empty?
66
+ current = word
67
+ elsif (current + " " + word).length <= max_chars
68
+ current = "#{current} #{word}"
69
+ else
70
+ lines << current
71
+ current = word
72
+ end
73
+ end
74
+ lines << current unless current.empty?
75
+ lines
76
+ end
77
+ module_function :wrapped_lines
78
+
79
+ def build_text(content, x_pos, y_pos, context, fill)
80
+ TextRenderer.new(
81
+ content: content,
82
+ x: x_pos, y: y_pos,
83
+ family: context.family, size: context.size,
84
+ size_unit: context.size_unit, fill: fill,
85
+ style: "italic"
86
+ ).to_svg
87
+ end
88
+ module_function :build_text
89
+
90
+ def wrap_group(body, fill)
91
+ style = "stroke-width:1;stroke-linecap:round;" \
92
+ "stroke-linejoin:bevel; fill:#{fill};" \
93
+ "fill-opacity:1.00; stroke:#000000;" \
94
+ " stroke-opacity:0.00"
95
+ %(<g style="#{style}">\n#{body}\n</g>)
96
+ end
97
+ module_function :wrap_group
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Operation compartment: divider line + operation signatures.
8
+ # Skipped when no operations or when geometry cannot fit them.
9
+ module Operations
10
+ module_function
11
+
12
+ def render(context)
13
+ return nil unless context.op_lines.any?
14
+ return nil unless context.geometry.op_first_y
15
+ return nil unless context.classifier
16
+
17
+ parts = []
18
+ if context.attr_lines.any?
19
+ parts << Element::DividerRenderer.render(
20
+ context.bounds,
21
+ y: context.geometry.op_divider_y,
22
+ stroke: context.stroke,
23
+ stroke_width: context.stroke_width
24
+ )
25
+ end
26
+ parts << Element::OperationRenderer.render(
27
+ context.classifier.operations,
28
+ bounds: context.bounds,
29
+ first_y: context.geometry.op_first_y,
30
+ family: context.family, size: context.size
31
+ )
32
+ parts.join("\n")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Package-contents compartment. Renders the package's child
8
+ # classifiers and sub-packages as a list of "+ Name" rows in
9
+ # the body, matching EA's package-on-diagram rendering. Each
10
+ # row also draws a small per-type icon to the left of the
11
+ # text (Enumeration gets a list-style icon; other classifiers
12
+ # get the "folded paper" silhouette).
13
+ module PackageContents
14
+ ROW_HEIGHT = 16
15
+ ICON_X_OFFSET = 5
16
+ TEXT_X_OFFSET = 23
17
+
18
+ module_function
19
+
20
+ def render(context)
21
+ rows = context.package_content_lines
22
+ return nil if rows.nil? || rows.empty?
23
+
24
+ bounds = context.bounds
25
+ first_y = context.geometry.attr_first_y
26
+ blocks = rows.each_with_index.flat_map do |row, idx|
27
+ y_top = first_y - 11 + (idx * ROW_HEIGHT)
28
+ y_text = first_y + (idx * ROW_HEIGHT)
29
+ [
30
+ icon_block(bounds, y_top, row.kind),
31
+ text_block(bounds, y_text, row.name, context)
32
+ ]
33
+ end
34
+ wrap_group(blocks.join("\n"))
35
+ end
36
+
37
+ def wrap_group(body)
38
+ %(<g style="#{group_style}">\n#{body}\n</g>)
39
+ end
40
+ module_function :wrap_group
41
+
42
+ def group_style
43
+ "stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel; " \
44
+ "fill-opacity:1.00; stroke-opacity:1.00"
45
+ end
46
+ module_function :group_style
47
+
48
+ def icon_block(bounds, y_top, kind)
49
+ Element::RowIconRenderer.render(
50
+ x_pos: bounds.x + ICON_X_OFFSET,
51
+ y_pos: y_top,
52
+ kind: kind || :default
53
+ )
54
+ end
55
+ module_function :icon_block
56
+
57
+ def text_block(bounds, y, name, context)
58
+ TextRenderer.new(
59
+ content: "+ #{name}",
60
+ x: bounds.x + TEXT_X_OFFSET,
61
+ y: y,
62
+ family: context.family, size: context.size,
63
+ size_unit: context.size_unit,
64
+ fill: context.theme.attribute_text_color
65
+ ).to_svg
66
+ end
67
+ module_function :text_block
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Renders the "(from ParentPackage)" italic subtitle inside
8
+ # a Package element's body. EA emits this when a package is
9
+ # placed on a diagram whose own package is NOT the package's
10
+ # parent — the subtitle shows the package's actual parent
11
+ # context so the reader can locate it in the model tree.
12
+ #
13
+ # Format: italic, small font, centered horizontally in the
14
+ # element body, positioned near the bottom of the body.
15
+ module PackageFromParent
16
+ Y_OFFSET_FROM_BOTTOM = 5
17
+
18
+ module_function
19
+
20
+ def render(context)
21
+ pkg = context.model_element
22
+ return nil unless pkg.is_a?(Ea::Model::Package)
23
+
24
+ parent_name = parent_name_for(pkg, context)
25
+ return nil if parent_name.nil? || parent_name.empty?
26
+
27
+ subtitle = "(from #{parent_name})"
28
+ bounds = context.bounds
29
+ fill = context.theme.attribute_text_color
30
+ y = bounds.y + bounds.height - Y_OFFSET_FROM_BOTTOM
31
+ estimated_width = subtitle.length * context.size * 0.5
32
+ x = bounds.x + (bounds.width - estimated_width) / 2.0
33
+ text = TextRenderer.new(
34
+ content: subtitle, x: x, y: y,
35
+ family: context.family, size: context.size,
36
+ size_unit: context.size_unit, fill: fill,
37
+ style: "italic"
38
+ ).to_svg
39
+ wrap(text, fill)
40
+ end
41
+
42
+ # Resolves the package's parent's name via the model index.
43
+ # Returns nil when:
44
+ # - the package has no parent
45
+ # - the parent IS the diagram's own package (already in context)
46
+ # - the parent is ALSO placed on the current diagram
47
+ # - every placed Package on the diagram shares the same
48
+ # parent_id (diagram is implicitly about that parent's
49
+ # children — EA omits the subtitle as redundant)
50
+ # - the parent's name equals the placed package's name
51
+ # (e.g. an inner "3D都市モデル" nested inside an outer
52
+ # "3D都市モデル" — the subtitle would be tautological)
53
+ # - the diagram's name contains any placed Package's name
54
+ # as a substring (the diagram is implicitly "about" that
55
+ # package — subtitles would be redundant). Verified
56
+ # against plateau's "3D都市モデル応用スキーマと他のスキーマとの関係"
57
+ # diagram, which contains the placed name "3D都市モデル"
58
+ # and renders 0 subtitles in EA's reference.
59
+ def parent_name_for(pkg, context)
60
+ parent_id = pkg.parent_id
61
+ return nil if parent_id.nil? || parent_id.empty?
62
+ return nil if parent_id == context.diagram&.package_id
63
+ return nil if parent_on_diagram?(parent_id, context)
64
+ return nil if uniform_package_parent?(context)
65
+ return nil if diagram_name_contains_placed_package?(context)
66
+
67
+ parent = context.model_index_for(parent_id)
68
+ return nil unless parent.is_a?(Ea::Model::Package)
69
+ return nil if parent.name.to_s == pkg.name.to_s
70
+
71
+ parent.name.to_s.empty? ? nil : parent.name
72
+ end
73
+ module_function :parent_name_for
74
+
75
+ # Returns true when the diagram has 2+ placed Packages and
76
+ # every placed Package shares the same parent_id. EA treats
77
+ # such diagrams as "children of X" overviews and suppresses
78
+ # the (from X) subtitle on every element.
79
+ def uniform_package_parent?(context)
80
+ diagram = context.diagram
81
+ return false unless diagram && context.model_index
82
+
83
+ placed_pkg_parents = (diagram.elements || []).filter_map do |el|
84
+ me = context.model_index[el.model_element_ref]
85
+ next nil unless me.is_a?(Ea::Model::Package)
86
+
87
+ me.parent_id
88
+ end
89
+ placed_pkg_parents.size > 1 && placed_pkg_parents.uniq.size == 1
90
+ end
91
+ module_function :uniform_package_parent?
92
+
93
+ def parent_on_diagram?(parent_id, context)
94
+ diagram = context.diagram
95
+ return false unless diagram
96
+
97
+ (diagram.elements || []).any? do |e|
98
+ e.model_element_ref == parent_id
99
+ end
100
+ end
101
+ module_function :parent_on_diagram?
102
+
103
+ # Returns true when the diagram's name contains any placed
104
+ # Package's name as a substring. EA omits all subtitles in
105
+ # such diagrams — the name itself signals which package is
106
+ # the subject, making subtitles redundant.
107
+ def diagram_name_contains_placed_package?(context)
108
+ diagram = context.diagram
109
+ return false unless diagram && context.model_index
110
+
111
+ name = diagram.name.to_s
112
+ return false if name.empty?
113
+
114
+ (diagram.elements || []).any? do |el|
115
+ me = context.model_index[el.model_element_ref]
116
+ next false unless me.is_a?(Ea::Model::Package)
117
+
118
+ pkg_name = me.name.to_s
119
+ !pkg_name.empty? && name.include?(pkg_name)
120
+ end
121
+ end
122
+ module_function :diagram_name_contains_placed_package?
123
+
124
+ def wrap(body, fill)
125
+ group_style = "stroke-width:1;stroke-linecap:round;" \
126
+ "stroke-linejoin:bevel; fill:#{fill};" \
127
+ "fill-opacity:1.00; stroke:#000000;" \
128
+ " stroke-opacity:0.00"
129
+ %(<g style="#{group_style}">\n#{body}\n</g>)
130
+ end
131
+ module_function :wrap
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Element shape: stroked rect for classifiers, folder
8
+ # silhouette for packages, note silhouette for notes.
9
+ module Shape
10
+ module_function
11
+
12
+ def render(context)
13
+ if legend?(context)
14
+ Element::LegendRenderer.render(
15
+ context.bounds,
16
+ legend: context.model_element.legend,
17
+ family: context.family,
18
+ canvas: context.canvas
19
+ )
20
+ elsif context.model_element.is_a?(Ea::Model::Package) ||
21
+ context.model_element.is_a?(Ea::Model::Note)
22
+ label = package_label(context.model_element)
23
+ stereotype = package_stereotype(context.model_element)
24
+ Element::PackageShapeRenderer.render(
25
+ context.bounds,
26
+ fill: context.fill, stroke: context.stroke,
27
+ stroke_width: context.stroke_width,
28
+ label: label,
29
+ stereotype: stereotype,
30
+ family: context.family, size: context.size,
31
+ size_unit: context.size_unit,
32
+ text_fill: context.text_fill
33
+ )
34
+ elsif hand_draw?(context)
35
+ Element::HandDrawShapeRenderer.render(
36
+ context.bounds,
37
+ fill: context.fill, stroke: context.stroke,
38
+ stroke_width: context.stroke_width
39
+ )
40
+ else
41
+ Element::ShapeRenderer.render(
42
+ context.bounds,
43
+ fill: context.fill, stroke: context.stroke,
44
+ stroke_width: context.stroke_width
45
+ )
46
+ end
47
+ end
48
+
49
+ # A Text element with LegendOpts= renders as the legend
50
+ # block rather than a folder silhouette. Dispatch on the
51
+ # presence of the attached Legend payload.
52
+ def legend?(context)
53
+ context.model_element.is_a?(Ea::Model::Note) &&
54
+ context.model_element.legend
55
+ end
56
+
57
+ # HandDraw=1 in the diagram StyleEx switches every element
58
+ # to EA's hand-drawn wavy silhouette.
59
+ def hand_draw?(context)
60
+ context.diagram && context.diagram.hand_draw
61
+ end
62
+
63
+ def package_label(model_element)
64
+ model_element.name.to_s
65
+ end
66
+
67
+ # EA renders a package's applied stereotype above the name
68
+ # inside the tab area when one is set. Returns nil when no
69
+ # stereotype is applied (the common case).
70
+ def package_stereotype(model_element)
71
+ return nil unless model_element.is_a?(Ea::Model::Package)
72
+
73
+ refs = model_element.stereotype_refs
74
+ return nil unless refs&.any?
75
+
76
+ refs.first.to_s
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Compartment
7
+ # Tagged value compartment. Skipped when no tagged values
8
+ # or when geometry cannot fit them.
9
+ module TaggedValues
10
+ DEFAULT_TEXT_COLOR = "#000000"
11
+
12
+ module_function
13
+
14
+ def render(context)
15
+ return nil unless context.tagged_values.any?
16
+ return nil unless context.geometry.tagged_value_first_y
17
+
18
+ Element::TaggedValueRenderer.render(
19
+ context.tagged_values,
20
+ bounds: context.bounds,
21
+ first_y: context.geometry.tagged_value_first_y,
22
+ family: context.family, size: context.size,
23
+ fill: DEFAULT_TEXT_COLOR
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # Compartment pipeline. Each Compartment is a small,
7
+ # single-purpose renderer that decides whether it applies to
8
+ # the current RenderContext and, if so, emits its SVG layer.
9
+ #
10
+ # Adding a new compartment = adding a class to `COMPARTMENTS`.
11
+ # The Elements orchestrator does not change — OCP-friendly.
12
+ module Compartment
13
+ autoload :Shape, "ea/svg/ea_emitter/compartment/shape"
14
+ autoload :NoteBody, "ea/svg/ea_emitter/compartment/note_body"
15
+ autoload :Header, "ea/svg/ea_emitter/compartment/header"
16
+ autoload :HeaderDivider, "ea/svg/ea_emitter/compartment/header_divider"
17
+ autoload :Attributes, "ea/svg/ea_emitter/compartment/attributes"
18
+ autoload :Operations, "ea/svg/ea_emitter/compartment/operations"
19
+ autoload :EnumLiterals, "ea/svg/ea_emitter/compartment/enum_literals"
20
+ autoload :TaggedValues, "ea/svg/ea_emitter/compartment/tagged_values"
21
+ autoload :Constraints, "ea/svg/ea_emitter/compartment/constraints"
22
+ autoload :PackageContents, "ea/svg/ea_emitter/compartment/package_contents"
23
+ autoload :InstanceUnderline, "ea/svg/ea_emitter/compartment/instance_underline"
24
+ autoload :InstanceSlots, "ea/svg/ea_emitter/compartment/instance_slots"
25
+ autoload :NoteText, "ea/svg/ea_emitter/compartment/note_text"
26
+ autoload :PackageFromParent, "ea/svg/ea_emitter/compartment/package_from_parent"
27
+
28
+ # Order matters: EA emits compartments top-to-bottom inside
29
+ # each element group.
30
+ ALL = [
31
+ Shape,
32
+ NoteBody,
33
+ Header,
34
+ HeaderDivider,
35
+ Attributes,
36
+ Operations,
37
+ EnumLiterals,
38
+ TaggedValues,
39
+ Constraints,
40
+ PackageContents,
41
+ InstanceSlots,
42
+ NoteText,
43
+ PackageFromParent,
44
+ InstanceUnderline
45
+ ].freeze
46
+
47
+ module_function
48
+
49
+ # Renders every applicable compartment for the context.
50
+ # Returns an Array of SVG strings (some may be nil; the
51
+ # caller compacts).
52
+ def render_all(context)
53
+ ALL.map { |compartment| compartment.render(context) }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end