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,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the auto-generated legend block that EA renders from
8
+ # a Text element whose StyleEx carries LegendOpts=. Layout
9
+ # (verified against plateau reference SVGs):
10
+ #
11
+ # ┌──────────────────────────────┐
12
+ # │ 凡例 │ ← title baseline 19px below top
13
+ # │ ┌──┐ │
14
+ # │ └──┘ GMLに定義されたクラス │ ← item 0 (icon top + 30px)
15
+ # │ ┌──┐ │
16
+ # │ └──┘ CityGMLに定義されたクラス
17
+ # │ ┌──┐ │
18
+ # │ └──┘ i-URに定義されたクラス │
19
+ # └──────────────────────────────┘
20
+ #
21
+ # Container: bounds.width × bounds.height, rx=3.
22
+ # Each item: 17×17 icon rect + label text on the same line.
23
+ # Item spacing is 19px (icon height 17 + 2 padding).
24
+ class LegendRenderer
25
+ CONTAINER_RX = 3
26
+ ICON_SIZE = 17
27
+ TITLE_Y_OFFSET = 19
28
+ FIRST_ITEM_Y_OFFSET = 30
29
+ ITEM_SPACING = 19
30
+ ICON_X_OFFSET = 10
31
+ ICON_LABEL_GAP = 22
32
+ LABEL_BASELINE_FROM_ICON_TOP = 12
33
+ DEFAULT_ITEM_FONT_SIZE = 9
34
+
35
+ def self.render(bounds, legend:, family:, canvas: nil)
36
+ new(bounds, legend, family, canvas).to_svg
37
+ end
38
+
39
+ def initialize(bounds, legend, family, canvas)
40
+ @bounds = bounds
41
+ @legend = legend
42
+ @family = family
43
+ @canvas = canvas
44
+ end
45
+
46
+ def to_svg
47
+ [container_layer, items_layer, title_layer].compact.join("\n")
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :bounds, :legend, :family, :canvas
53
+
54
+ def container_layer
55
+ body = format(
56
+ '<rect x="%<x>s" y="%<y>s" width="%<w>s" height="%<h>s" rx="%<r>.2f" shape-rendering="auto" />',
57
+ x: fmt(bounds.x), y: fmt(bounds.y),
58
+ w: fmt(bounds.width), h: fmt(bounds.height),
59
+ r: CONTAINER_RX
60
+ )
61
+ %(<g style="#{container_style}">\n #{body}\n</g>)
62
+ end
63
+
64
+ # One <g> per item — each item carries its own fill, so we
65
+ # cannot consolidate them into a single group.
66
+ def items_layer
67
+ return nil if legend.items.empty?
68
+
69
+ legend.items.map.with_index do |item, index|
70
+ icon = icon_rect(item, index)
71
+ label = item_label(item, index)
72
+ [
73
+ %(<g style="#{icon_style(item)}">\n #{icon}\n</g>),
74
+ %(<g style="#{label_style}">\n #{label}\n</g>)
75
+ ].join("\n")
76
+ end.join("\n")
77
+ end
78
+
79
+ def title_layer
80
+ %(<g style="#{label_style}">\n #{title_text}\n</g>)
81
+ end
82
+
83
+ def container_style
84
+ "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
85
+ "fill:#{container_fill};fill-opacity:1.00; " \
86
+ "stroke:#{container_stroke}; stroke-opacity:1.00"
87
+ end
88
+
89
+ def icon_style(item)
90
+ "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
91
+ "fill:#{icon_fill(item)};fill-opacity:1.00; " \
92
+ "stroke:#000000; stroke-opacity:1.00"
93
+ end
94
+
95
+ def label_style
96
+ "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
97
+ "fill:#{label_fill};fill-opacity:1.00; " \
98
+ "stroke:#000000; stroke-opacity:0.00"
99
+ end
100
+
101
+ def container_fill
102
+ BColDecoder.to_hex(legend.background_color) || "#F0F0F0"
103
+ end
104
+
105
+ def container_stroke
106
+ BColDecoder.to_hex(legend.border_color) || "#D7D7D7"
107
+ end
108
+
109
+ def icon_fill(item)
110
+ BColDecoder.to_hex(item.background_color) || "#FFFFFF"
111
+ end
112
+
113
+ def label_fill
114
+ BColDecoder.to_hex(legend.font_color) || "#003060"
115
+ end
116
+
117
+ def icon_rect(_item, index)
118
+ x = bounds.x + ICON_X_OFFSET
119
+ y = item_top_y(index)
120
+ format(
121
+ '<rect x="%<x>s" y="%<y>s" width="%<w>d" height="%<h>d" rx="0.00" shape-rendering="auto" />',
122
+ x: fmt(x), y: fmt(y), w: ICON_SIZE, h: ICON_SIZE
123
+ )
124
+ end
125
+
126
+ def item_label(item, index)
127
+ x = bounds.x + ICON_X_OFFSET + ICON_LABEL_GAP
128
+ y = item_top_y(index) + LABEL_BASELINE_FROM_ICON_TOP
129
+ build_text(item.name, x: x, y: y, size: item_font_size, weight: 0)
130
+ end
131
+
132
+ def title_text
133
+ x = bounds.x + (bounds.width / 2) - (title_text_width / 2)
134
+ y = bounds.y + TITLE_Y_OFFSET
135
+ build_text(legend.title, x: x, y: y, size: title_font_size, weight: 700)
136
+ end
137
+
138
+ def item_top_y(index)
139
+ bounds.y + FIRST_ITEM_Y_OFFSET + (index * ITEM_SPACING)
140
+ end
141
+
142
+ def title_font_size
143
+ legend.heading_size || Ea::Model::Legend::DEFAULT_HEADING_SIZE
144
+ end
145
+
146
+ def item_font_size
147
+ DEFAULT_ITEM_FONT_SIZE
148
+ end
149
+
150
+ def title_text_width
151
+ legend.title.to_s.length * title_font_size * 0.6
152
+ end
153
+
154
+ def build_text(content, x:, y:, size:, weight:)
155
+ tx, ty = translate(x, y)
156
+ TextRenderer.new(
157
+ content: content.to_s, x: tx, y: ty,
158
+ family: family, size: size, size_unit: "pt",
159
+ weight: weight, fill: label_fill,
160
+ text_length: (content.to_s.length * size * 0.65).round
161
+ ).to_svg
162
+ end
163
+
164
+ def translate(x, y)
165
+ return [x, y] unless canvas
166
+
167
+ [canvas.translate_x(x), canvas.translate_y(y)]
168
+ end
169
+
170
+ def fmt(value)
171
+ Canvas.coord(value)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the shape `<g>` for a Note element: a folded-corner
8
+ # rect plus body text. EA renders every Note element as a
9
+ # dog-eared rectangle (top-right corner folded in) with the
10
+ # note text wrapped inside.
11
+ #
12
+ # Layout:
13
+ #
14
+ # ┌──────────────────┐
15
+ # │ ┐
16
+ # │ body text lines │
17
+ # │ │
18
+ # └──────────────────-┘
19
+ #
20
+ class NoteShapeRenderer
21
+ FOLD_SIZE = 12
22
+ TEXT_X_OFFSET = 5
23
+ TEXT_Y_OFFSET = 12
24
+ LINE_HEIGHT = 12
25
+
26
+ def self.render(bounds, body:, fill:, stroke:, stroke_width:, family:, size:, text_fill:)
27
+ new(bounds, body, fill, stroke, stroke_width, family, size, text_fill).to_svg
28
+ end
29
+
30
+ def initialize(bounds, body, fill, stroke, stroke_width, family, size, text_fill)
31
+ @bounds = bounds
32
+ @body = body.to_s
33
+ @fill = fill
34
+ @stroke = stroke
35
+ @stroke_width = stroke_width
36
+ @family = family
37
+ @size = size
38
+ @text_fill = text_fill
39
+ end
40
+
41
+ def to_svg
42
+ %(<g style="#{group_style}">\n #{path_body}\n #{fold_line}\n #{text_block}\n</g>)
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :bounds, :body, :fill, :stroke, :stroke_width,
48
+ :family, :size, :text_fill
49
+
50
+ def group_style
51
+ "stroke-width:#{stroke_width};stroke-linecap:square;stroke-linejoin:bevel; " \
52
+ "fill:#{fill};fill-opacity:1.00; stroke:#{stroke}; stroke-opacity:1.00"
53
+ end
54
+
55
+ # Folded-corner body emitted as a closed <path> matching
56
+ # EA's encoding (not <polygon>). EA uses M..L..L..Z with
57
+ # stroke-linejoin=bevel for the dog-eared silhouette.
58
+ def path_body
59
+ x = bounds.x
60
+ y = bounds.y
61
+ w = bounds.width
62
+ h = bounds.height
63
+ fold = [FOLD_SIZE, w / 3, h / 3].min
64
+ d = "M #{Canvas.coord(x)} #{Canvas.coord(y)} " \
65
+ "L #{Canvas.coord(x + w - fold)} #{Canvas.coord(y)} " \
66
+ "L #{Canvas.coord(x + w)} #{Canvas.coord(y + fold)} " \
67
+ "L #{Canvas.coord(x + w)} #{Canvas.coord(y + h)} " \
68
+ "L #{Canvas.coord(x)} #{Canvas.coord(y + h)} " \
69
+ "L #{Canvas.coord(x)} #{Canvas.coord(y)} Z"
70
+ %(<path d="#{d}" shape-rendering="auto"/>)
71
+ end
72
+
73
+ # Diagonal line marking the fold.
74
+ def fold_line
75
+ x = bounds.x
76
+ y = bounds.y
77
+ w = bounds.width
78
+ fold = [FOLD_SIZE, w / 3, bounds.height / 3].min
79
+ %(<path d="M #{Canvas.coord(x + w - fold)} #{Canvas.coord(y)} L #{Canvas.coord(x + w)} #{Canvas.coord(y + fold)}" shape-rendering="auto"/>)
80
+ end
81
+
82
+ def text_block
83
+ lines = body_lines
84
+ return "" if lines.empty?
85
+
86
+ texts = lines.each_with_index.map do |line, idx|
87
+ y = bounds.y + TEXT_Y_OFFSET + (idx * LINE_HEIGHT)
88
+ TextRenderer.new(content: line,
89
+ x: bounds.x + TEXT_X_OFFSET,
90
+ y: y,
91
+ family: family,
92
+ size: size,
93
+ fill: text_fill).to_svg
94
+ end
95
+ texts.join("\n ")
96
+ end
97
+
98
+ # Naive word-wrap based on character count. EA's actual
99
+ # wrapping uses GDI text metrics; this is an approximation.
100
+ def body_lines
101
+ return [] if body.empty?
102
+
103
+ max_chars = [(bounds.width / (size.to_f * 0.6)).floor, 10].max
104
+ body.split(/\n/).flat_map do |para|
105
+ para.gsub(/(.{1,#{max_chars}})(\s+|$)/, "\\1\n").strip.split(/\n/)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the operation compartment text `<g>`. Each operation
8
+ # renders as TWO `<text>` elements matching EA's encoding:
9
+ #
10
+ # <text>+ </text>
11
+ # <text>methodName(params): ReturnType</text>
12
+ #
13
+ # Rendered as a third compartment below the attribute
14
+ # compartment for Klass / Interface classifiers.
15
+ class OperationRenderer
16
+ VISIBILITY_X_OFFSET = 5
17
+ CONTENT_X_OFFSET = 20
18
+
19
+ def self.render(operations, bounds:, first_y:, family:, size:)
20
+ line_h = size + 4
21
+ text_blocks = []
22
+ has_receptions = operations.any?(&:is_reception)
23
+ if has_receptions
24
+ text_blocks << TextRenderer.new(
25
+ content: "receptions",
26
+ x: bounds.x + (bounds.width / 2.0) - (8 * size * 0.65) / 2,
27
+ y: first_y,
28
+ family: family, size: size,
29
+ style: "italic", fill: "#000000"
30
+ ).to_svg
31
+ end
32
+ ops = has_receptions ? operations : operations
33
+ offset = has_receptions ? 1 : 0
34
+ ops.each_with_index do |op, idx|
35
+ y = first_y + ((idx + offset) * line_h)
36
+ text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
37
+ visibility_prefix(op), family, size)
38
+ text_blocks << build_text(bounds.x + CONTENT_X_OFFSET, y,
39
+ operation_text(op), family, size)
40
+ end
41
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
42
+ end
43
+
44
+ # Builds display lines for a classifier's operations.
45
+ def self.lines_for(classifier)
46
+ ops = classifier.operations
47
+ return [] unless ops&.any?
48
+
49
+ ops.map do |op|
50
+ visibility = visibility_prefix(op).strip
51
+ params = (op.parameters || []).map { |p| "#{p.name}: #{namespace_double_colon(p.type_name)}" }.join(", ")
52
+ return_type = namespace_double_colon(op.return_type_name)
53
+ "#{visibility} #{op.name}(#{params}): #{return_type}".strip
54
+ end
55
+ end
56
+
57
+ def self.visibility_prefix(operation)
58
+ case operation.visibility
59
+ when "private" then "- "
60
+ when "protected" then "# "
61
+ when "package" then "~ "
62
+ else "+ "
63
+ end
64
+ end
65
+ private_class_method :visibility_prefix
66
+
67
+ def self.operation_text(operation)
68
+ prefix = operation.is_reception ? "«signal» " : ""
69
+ text = "#{prefix}#{operation.name}(#{reception_params(operation)})"
70
+ return text unless operation.return_type_name && !operation.is_reception
71
+
72
+ "#{text}: #{operation.return_type_name}"
73
+ end
74
+ private_class_method :operation_text
75
+
76
+ def self.namespace_double_colon(type_name)
77
+ return "" if type_name.nil? || type_name.empty?
78
+
79
+ type_name.to_s.gsub(/([A-Za-z0-9_]):([A-Za-z])/, '\1::\2')
80
+ end
81
+ private_class_method :namespace_double_colon
82
+
83
+ def self.reception_params(operation)
84
+ params = (operation.parameters || []).filter_map { |p|
85
+ p.type_name.to_s.empty? ? nil : p.type_name.to_s
86
+ }.join(", ")
87
+ params
88
+ end
89
+ private_class_method :reception_params
90
+
91
+ def self.build_text(x, y, content, family, size)
92
+ TextRenderer.new(content: content, x: x, y: y,
93
+ family: family, size: size).to_svg
94
+ end
95
+ private_class_method :build_text
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ module Element
7
+ # Emits the shape `<g>` for a Package element: a rectangular
8
+ # body polygon plus a "tab" polygon on top. EA renders every
9
+ # Package as this folder-like silhouette regardless of diagram
10
+ # type.
11
+ #
12
+ # Layout (element bounds cover tab + body together):
13
+ #
14
+ # ┌───┐
15
+ # │tab│ ← top TAB_HEIGHT_SINGLE rows ABOVE element bounds;
16
+ # │ │ when a stereotype is present, the tab grows to
17
+ # └───┴──────────┐ TAB_HEIGHT_DOUBLE rows and extends INTO
18
+ # │ │ the bounds; the name renders bold,
19
+ # │ body │ left-aligned. The body always starts
20
+ # │ │ where the tab ends.
21
+ # └───────────────┘
22
+ #
23
+ class PackageShapeRenderer
24
+ TAB_HEIGHT_SINGLE = 20
25
+ TAB_HEIGHT_DOUBLE = 36
26
+ TAB_LABEL_PADDING = 10
27
+ TAB_LABEL_X_OFFSET = 5
28
+ TAB_LABEL_Y_OFFSET = 13
29
+ TAB_LINE_OFFSET = 13
30
+ DEFAULT_TAB_WIDTH = 105
31
+
32
+ def self.render(bounds, fill:, stroke:, stroke_width:, label: nil,
33
+ stereotype: nil, family: "Carlito", size: 7,
34
+ size_unit: "pt", text_fill: "#000000")
35
+ new(bounds, fill, stroke, stroke_width, label, stereotype,
36
+ family, size, size_unit, text_fill).to_svg
37
+ end
38
+
39
+ def initialize(bounds, fill, stroke, stroke_width, label,
40
+ stereotype, family, size, size_unit, text_fill)
41
+ @bounds = bounds
42
+ @fill = fill
43
+ @stroke = stroke
44
+ @stroke_width = stroke_width
45
+ @label = label
46
+ @stereotype = stereotype
47
+ @family = family
48
+ @size = size
49
+ @size_unit = size_unit
50
+ @text_fill = text_fill
51
+ end
52
+
53
+ def to_svg
54
+ parts = [" #{body_polygon}", " #{tab_polygon}"]
55
+ parts += header_text_blocks if has_label?
56
+ %(<g style="#{group_style}">\n#{parts.join("\n")}\n</g>)
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :bounds, :fill, :stroke, :stroke_width, :label,
62
+ :stereotype, :family, :size, :size_unit, :text_fill
63
+
64
+ def has_label?
65
+ label && !label.empty?
66
+ end
67
+
68
+ def has_stereotype?
69
+ stereotype && !stereotype.empty?
70
+ end
71
+
72
+ def group_style
73
+ "stroke-width:#{stroke_width};stroke-linecap:round;stroke-linejoin:bevel; " \
74
+ "fill:#{fill};fill-opacity:1.00; stroke:#{stroke}; stroke-opacity:1.00"
75
+ end
76
+
77
+ def body_polygon
78
+ pts = body_points.map { |x, y| "#{Canvas.coord(x)} #{Canvas.coord(y)}" }.join(" ")
79
+ %(<polygon points="#{pts}" shape-rendering="auto" style="fill-rule:evenodd;"/>)
80
+ end
81
+
82
+ def tab_polygon
83
+ pts = tab_points.map { |x, y| "#{Canvas.coord(x)} #{Canvas.coord(y)}" }.join(" ")
84
+ %(<polygon points="#{pts}" shape-rendering="auto" style="fill-rule:evenodd;"/>)
85
+ end
86
+
87
+ def header_text_blocks
88
+ blocks = []
89
+ if has_stereotype?
90
+ blocks << " #{text_at(label_x, stereotype_y, "«#{stereotype}»", weight: 0)}"
91
+ blocks << " #{text_at(label_x, name_y, label, weight: 700)}"
92
+ else
93
+ blocks << " #{text_at(label_x, name_y_single, label, weight: 700)}"
94
+ end
95
+ blocks
96
+ end
97
+
98
+ def text_at(x, y, content, weight:)
99
+ TextRenderer.new(content: content,
100
+ x: x, y: y,
101
+ family: family, size: size, size_unit: size_unit,
102
+ weight: weight, fill: text_fill,
103
+ text_length: content.length * 6).to_svg
104
+ end
105
+
106
+ def label_x
107
+ bounds.x + TAB_LABEL_X_OFFSET
108
+ end
109
+
110
+ # Stereotype line baseline: 13px below the tab top.
111
+ def stereotype_y
112
+ tab_top_y + TAB_LABEL_Y_OFFSET
113
+ end
114
+
115
+ # Name baseline when stereotype is also rendered: 26px below
116
+ # the tab top (one line below the stereotype).
117
+ def name_y
118
+ tab_top_y + TAB_LABEL_Y_OFFSET + TAB_LINE_OFFSET
119
+ end
120
+
121
+ # Name baseline when only the name renders (no stereotype):
122
+ # 13px below the tab top.
123
+ def name_y_single
124
+ tab_top_y + TAB_LABEL_Y_OFFSET
125
+ end
126
+
127
+ # Tab anchored 20px above the bounds top — matches EA's
128
+ # folder silhouette regardless of whether the tab holds one
129
+ # line or two.
130
+ def tab_top_y
131
+ bounds.y - TAB_HEIGHT_SINGLE
132
+ end
133
+
134
+ def tab_height
135
+ has_stereotype? ? TAB_HEIGHT_DOUBLE : TAB_HEIGHT_SINGLE
136
+ end
137
+
138
+ def body_points
139
+ x = bounds.x
140
+ y = tab_top_y + tab_height
141
+ w = bounds.width
142
+ h = body_height(y)
143
+ [
144
+ [x, y],
145
+ [x + w, y],
146
+ [x + w, y + h],
147
+ [x, y + h],
148
+ [x, y]
149
+ ]
150
+ end
151
+
152
+ # Body extends from tab.bottom to (bounds.bottom - 20px
153
+ # bottom margin). Total body height = bounds.height -
154
+ # tab_height (because tab takes (tab_height - 20) from the
155
+ # top of the bounds plus 20 above the bounds).
156
+ def body_height(body_top)
157
+ bounds.y + bounds.height - TAB_HEIGHT_SINGLE - body_top
158
+ end
159
+
160
+ def tab_points
161
+ x = bounds.x
162
+ y = tab_top_y
163
+ w = tab_width
164
+ h = tab_height
165
+ [
166
+ [x, y],
167
+ [x + w, y],
168
+ [x + w, y + h],
169
+ [x, y + h],
170
+ [x, y]
171
+ ]
172
+ end
173
+
174
+ def tab_width
175
+ DEFAULT_TAB_WIDTH
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end