ea 0.6.139 → 0.6.141

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ea/cli/app.rb +10 -0
  3. data/lib/ea/cli/command/diagrams.rb +4 -1
  4. data/lib/ea/cli/command/render.rb +8 -1
  5. data/lib/ea/cli/command/spa.rb +2 -1
  6. data/lib/ea/cli/command/svg.rb +9 -6
  7. data/lib/ea/fonts/metrics.rb +54 -6
  8. data/lib/ea/model/note.rb +2 -0
  9. data/lib/ea/sources/qea/diagram_builder/routing.rb +188 -8
  10. data/lib/ea/sources/qea/diagram_builder.rb +14 -1
  11. data/lib/ea/sources/qea/note_builder.rb +7 -1
  12. data/lib/ea/sources/xmi/diagram_builder/element_borders.rb +27 -0
  13. data/lib/ea/sources/xmi/diagram_builder.rb +58 -15
  14. data/lib/ea/sources/xmi/extension_style_parser.rb +1 -1
  15. data/lib/ea/sources/xmi/note_builder.rb +2 -1
  16. data/lib/ea/sources/xmi/stored_route/reversed_routes.rb +78 -0
  17. data/lib/ea/sources/xmi/stored_route.rb +83 -6
  18. data/lib/ea/spa/generator.rb +4 -2
  19. data/lib/ea/spa/projector.rb +4 -2
  20. data/lib/ea/svg/ea_emitter/bottom_margin.rb +104 -0
  21. data/lib/ea/svg/ea_emitter/bounds_calculator.rb +25 -15
  22. data/lib/ea/svg/ea_emitter/canvas.rb +80 -14
  23. data/lib/ea/svg/ea_emitter/canvas_anchor.rb +132 -0
  24. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +5 -0
  25. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +5 -0
  26. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +5 -0
  27. data/lib/ea/svg/ea_emitter/compartment/header.rb +5 -0
  28. data/lib/ea/svg/ea_emitter/compartment/header_divider.rb +5 -0
  29. data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +5 -0
  30. data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +5 -0
  31. data/lib/ea/svg/ea_emitter/compartment/marker.rb +5 -0
  32. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +11 -2
  33. data/lib/ea/svg/ea_emitter/compartment/note_text.rb +5 -0
  34. data/lib/ea/svg/ea_emitter/compartment/operations.rb +5 -0
  35. data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +5 -0
  36. data/lib/ea/svg/ea_emitter/compartment/package_from_parent.rb +34 -4
  37. data/lib/ea/svg/ea_emitter/compartment/shape.rb +30 -5
  38. data/lib/ea/svg/ea_emitter/compartment/stereotype_icon.rb +6 -0
  39. data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +5 -0
  40. data/lib/ea/svg/ea_emitter/compartment.rb +13 -0
  41. data/lib/ea/svg/ea_emitter/connector_bridges.rb +117 -29
  42. data/lib/ea/svg/ea_emitter/connectors.rb +67 -17
  43. data/lib/ea/svg/ea_emitter/content_extent.rb +104 -0
  44. data/lib/ea/svg/ea_emitter/diagram_frame.rb +7 -0
  45. data/lib/ea/svg/ea_emitter/document.rb +76 -31
  46. data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +22 -10
  47. data/lib/ea/svg/ea_emitter/element/note_shape_renderer.rb +57 -32
  48. data/lib/ea/svg/ea_emitter/elements.rb +24 -5
  49. data/lib/ea/svg/ea_emitter/font_resolver.rb +3 -1
  50. data/lib/ea/svg/ea_emitter/label/end_label.rb +134 -14
  51. data/lib/ea/svg/ea_emitter/labels.rb +16 -3
  52. data/lib/ea/svg/ea_emitter/layer_sequencer.rb +54 -33
  53. data/lib/ea/svg/ea_emitter/markers.rb +22 -10
  54. data/lib/ea/svg/ea_emitter/paint_order.rb +30 -0
  55. data/lib/ea/svg/ea_emitter/pen.rb +19 -0
  56. data/lib/ea/svg/ea_emitter/render_context.rb +14 -3
  57. data/lib/ea/svg/ea_emitter/text_renderer.rb +24 -8
  58. data/lib/ea/svg/ea_emitter.rb +5 -0
  59. data/lib/ea/svg/parity/source.rb +1 -1
  60. data/lib/ea/version.rb +1 -1
  61. metadata +9 -2
@@ -52,17 +52,28 @@ module Ea
52
52
  entries = visible_connectors.flat_map { |c| entries_for(c) }
53
53
 
54
54
  if grouped
55
- entries.group_by { |e| [e.style_key, e.stroke_width] }.map do |(key, width), grouped_entries|
55
+ entries.group_by { |e| [e.style_key, e.stroke_width, e.stroke_color] }.map do |(key, width, color), grouped_entries|
56
56
  # EA emits ONE marker per connector regardless of
57
57
  # anchor overlap. Verified against plateau Bridge
58
58
  # diagram: 9 generalizations fanning from the same
59
59
  # parent anchor produce 9 distinct triangle polygons
60
60
  # at the same coordinates. The earlier "dedup by
61
61
  # anchor" hypothesis is disproven by reference SVGs.
62
- marker_layer(key, width, grouped_entries.map(&:body).join("\n "))
62
+ marker_layer(key, width, color, grouped_entries.map(&:body).join("\n "))
63
63
  end
64
64
  else
65
- entries.map { |e| marker_layer(e.style_key, e.stroke_width, e.body) }
65
+ entries.map { |e| marker_layer(e.style_key, e.stroke_width, e.stroke_color, e.body) }
66
+ end
67
+ end
68
+
69
+ # One `[connector, Layer]` pair per marker, in diagram link
70
+ # order, so the sequencer can paint each connector's heads right
71
+ # after its line like EA does.
72
+ def connector_layers
73
+ visible_connectors.flat_map do |connector|
74
+ entries_for(connector).map do |e|
75
+ [connector, marker_layer(e.style_key, e.stroke_width, e.stroke_color, e.body)]
76
+ end
66
77
  end
67
78
  end
68
79
 
@@ -86,7 +97,7 @@ module Ea
86
97
  # drives dedup — EA emits one marker per connector.
87
98
  Entry = Struct.new(:style_key, :body, :anchor,
88
99
  :connector_waypoint_count, :stroke_width,
89
- keyword_init: true) do
100
+ :stroke_color, keyword_init: true) do
90
101
  def hash
91
102
  anchor.hash
92
103
  end
@@ -140,18 +151,18 @@ module Ea
140
151
  connector_line: { fill: "#000000", opacity: "0.00" }
141
152
  }.freeze
142
153
 
143
- def marker_layer(key, width, body)
144
- Layer.new(style_key: Connectors.layer_key(key, width, stroke_width),
145
- style: style_for(key, width), body: body)
154
+ def marker_layer(key, width, color, body)
155
+ Layer.new(style_key: Connectors.layer_key(key, width, stroke_width, color),
156
+ style: style_for(key, width, color), body: body)
146
157
  end
147
158
 
148
- def style_for(key, width = stroke_width)
159
+ def style_for(key, width = stroke_width, color = Connectors::DEFAULT_STROKE)
149
160
  spec = STYLE_MAP[key]
150
161
  return nil unless spec
151
162
 
152
163
  "stroke-width:#{width};stroke-linecap:round;stroke-linejoin:bevel; " \
153
164
  "fill:#{spec[:fill]};fill-opacity:#{spec[:opacity]}; " \
154
- "stroke:#000000; stroke-opacity:1.00"
165
+ "stroke:#{color}; stroke-opacity:1.00"
155
166
  end
156
167
 
157
168
  def visible_connectors
@@ -171,7 +182,8 @@ module Ea
171
182
  body: body,
172
183
  anchor: anchor_for(spec),
173
184
  connector_waypoint_count: waypoint_count,
174
- stroke_width: Connectors.stroke_width_for(connector, stroke_width))
185
+ stroke_width: Connectors.stroke_width_for(connector, stroke_width),
186
+ stroke_color: Connectors.stroke_color_for(connector))
175
187
  end
176
188
  end
177
189
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # EA paints connectors (each line followed by its heads) ordered by the
7
+ # start element's placement row, then the connector id: the
8
+ # `paint_order` key the diagram builders store. Verified without a
9
+ # single inversion over 12785 connector pairs of the QEA corpus.
10
+ # Connectors with an unresolvable key (empty, or with a nil part)
11
+ # follow the keyed ones, and phantoms (no stored key) come last, each
12
+ # in link order.
13
+ module PaintOrder
14
+ module_function
15
+
16
+ def connectors(diagram)
17
+ (diagram.connectors || []).each_with_index.sort_by do |connector, index|
18
+ order = connector.paint_order
19
+ rank = if order.nil?
20
+ 2
21
+ else
22
+ order.empty? || order.any?(&:nil?) ? 1 : 0
23
+ end
24
+ [rank, rank.zero? ? order : [], index]
25
+ end.map(&:first)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Svg
5
+ module EaEmitter
6
+ # The [cap, join] stroke pen a drawing leaves behind. EA never sets a
7
+ # pen on notes and legends: they take whatever the previous drawing
8
+ # left, so the emitter threads the pen from one element to the next.
9
+ module Pen
10
+ ROUND_BEVEL = %w[round bevel].freeze
11
+ SQUARE_BEVEL = %w[square bevel].freeze
12
+ SQUARE_MITER = %w[square miter].freeze
13
+
14
+ # The pen in force when nothing has been drawn yet.
15
+ INITIAL = SQUARE_MITER
16
+ end
17
+ end
18
+ end
19
+ end
@@ -19,7 +19,7 @@ module Ea
19
19
  :enum_literals, :tagged_values, :constraints, :marker_lines,
20
20
  :package_content_lines,
21
21
  :geometry, :theme, :canvas, :diagram, :model_index,
22
- :off_canvas_parent_name,
22
+ :off_canvas_parent_name, :previous_pen,
23
23
  keyword_init: true
24
24
  )
25
25
  def note_body
@@ -57,11 +57,22 @@ module Ea
57
57
  # roles; the XMI reads them as ownedAttribute), so a class whose
58
58
  # only properties are association ends has no feature compartment
59
59
  # and no divider (A59065AF urf::Administration).
60
+ #
61
+ # A diagram that suppresses a whole feature kind (Style
62
+ # HideAtts=1 / HideOps=1) has no such compartment to draw, so
63
+ # that kind never earns a divider: 4881CEEE (HideAtts=1 and
64
+ # HideOps=1) draws none over classes that own attributes,
65
+ # 8FD488EF likewise. Per-attribute visibility toggles are a
66
+ # different mechanism and keep the divider (10E3E08A).
60
67
  def hidden_features_present?
61
68
  return false unless classifier.respond_to?(:properties)
62
69
 
63
- classifier.properties.any? { |property| !property.association_id } ||
64
- classifier.operations.any?
70
+ config = diagram&.display_config
71
+ shows_attributes = config.nil? || config.show_attributes?
72
+ shows_operations = config.nil? || config.show_operations?
73
+
74
+ (shows_attributes && classifier.properties.any? { |property| !property.association_id }) ||
75
+ (shows_operations && classifier.operations.any?)
65
76
  end
66
77
  end
67
78
  end
@@ -24,14 +24,15 @@ module Ea
24
24
 
25
25
  attr_reader :content, :x, :y, :family, :size, :weight, :style,
26
26
  :fill, :text_length, :rotation, :size_unit,
27
- :stroke_in_text, :width_factor, :logical_height
27
+ :stroke_in_text, :width_factor, :logical_height, :legend_title
28
28
 
29
29
  def initialize(content:, x:, y:, family:, size:, weight: 400,
30
30
  style: "normal", fill: DEFAULT_FILL,
31
31
  text_length: nil, rotation: DEFAULT_ROTATION,
32
32
  size_unit: "px", logical_height: nil,
33
33
  stroke_in_text: DEFAULT_STROKE_IN_TEXT,
34
- width_factor: DEFAULT_WIDTH_FACTOR)
34
+ width_factor: DEFAULT_WIDTH_FACTOR, legend_title: false)
35
+ @legend_title = legend_title
35
36
  @content = content.to_s
36
37
  @x = x
37
38
  @y = y
@@ -155,23 +156,38 @@ module Ea
155
156
  GLYPH_FALLBACK = 0.61
156
157
 
157
158
  def self.estimate_width(text, size, _width_factor = nil, family: nil,
158
- weight: nil, style: nil, logical_height: nil)
159
- real = Ea::Fonts::Metrics.text_length(text, size, family: family,
160
- weight: weight,
161
- style: style, logical_height: logical_height)
159
+ weight: nil, style: nil, logical_height: nil,
160
+ legend_title: false)
161
+ opts = { family: family, weight: weight, style: style,
162
+ logical_height: logical_height, legend_title: legend_title }
163
+ real = Ea::Fonts::Metrics.text_length(text, size, **opts)
162
164
  return real if real
165
+ return piecewise_width(text, size, opts) if Ea::Fonts::Metrics.piecewise_family?(family, size)
163
166
 
167
+ text.to_s.each_char.sum { |ch| glyph_estimate(ch, size) }
168
+ end
169
+
170
+ # A Cambria run outside 7pt that is not wholly in the measured
171
+ # table: table characters are exact, the rest are estimated one
172
+ # at a time with the glyph fallback, so the
173
+ # width never shrinks when a character is appended.
174
+ def self.piecewise_width(text, size, opts)
164
175
  text.to_s.each_char.sum do |ch|
165
- (GLYPH_WIDTHS[ch] || GLYPH_FALLBACK) * size.to_f
176
+ Ea::Fonts::Metrics.text_length(ch, size, **opts) || glyph_estimate(ch, size)
166
177
  end
167
178
  end
168
179
 
180
+ def self.glyph_estimate(char, size)
181
+ (GLYPH_WIDTHS[char] || GLYPH_FALLBACK) * size.to_f
182
+ end
183
+
169
184
  def formatted_text_length
170
185
  return text_length.to_i.to_s if text_length
171
186
 
172
187
  width = self.class.estimate_width(content, size, width_factor,
173
188
  family: family, weight: weight,
174
- style: style, logical_height: logical_height)
189
+ style: style, logical_height: logical_height,
190
+ legend_title: legend_title)
175
191
  width.round.to_s
176
192
  end
177
193
 
@@ -26,10 +26,15 @@ module Ea
26
26
  #
27
27
  module EaEmitter
28
28
  autoload :Canvas, "ea/svg/ea_emitter/canvas"
29
+ autoload :CanvasAnchor, "ea/svg/ea_emitter/canvas_anchor"
30
+ autoload :BottomMargin, "ea/svg/ea_emitter/bottom_margin"
31
+ autoload :ContentExtent, "ea/svg/ea_emitter/content_extent"
29
32
  autoload :Background, "ea/svg/ea_emitter/background"
30
33
  autoload :BoundsCalculator, "ea/svg/ea_emitter/bounds_calculator"
31
34
  autoload :Style, "ea/svg/ea_emitter/style"
35
+ autoload :Pen, "ea/svg/ea_emitter/pen"
32
36
  autoload :Elements, "ea/svg/ea_emitter/elements"
37
+ autoload :PaintOrder, "ea/svg/ea_emitter/paint_order"
33
38
  autoload :Connectors, "ea/svg/ea_emitter/connectors"
34
39
  autoload :ConnectorBridges, "ea/svg/ea_emitter/connector_bridges"
35
40
  private_constant :ConnectorBridges
@@ -44,7 +44,7 @@ module Ea
44
44
  private
45
45
 
46
46
  def load_qea
47
- raise NotImplementedError, "QEA Document loading not yet wired into bench"
47
+ Ea::Sources::Qea::Adapter.from_path(path)
48
48
  end
49
49
 
50
50
  def load_xmi
data/lib/ea/version.rb CHANGED
@@ -4,5 +4,5 @@ module Ea
4
4
  # Gem release version. Extracted to its own file so the
5
5
  # `gem-release` tool's `gem bump` can find and update it via the
6
6
  # standard `lib/<gem>/version.rb` convention.
7
- VERSION = "0.6.139"
7
+ VERSION = "0.6.141"
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.139
4
+ version: 0.6.141
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-21 00:00:00.000000000 Z
11
+ date: 2026-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -854,6 +854,7 @@ files:
854
854
  - lib/ea/sources/xmi/diagram_builder.rb
855
855
  - lib/ea/sources/xmi/diagram_builder/association_class_metadata.rb
856
856
  - lib/ea/sources/xmi/diagram_builder/connector_metadata.rb
857
+ - lib/ea/sources/xmi/diagram_builder/element_borders.rb
857
858
  - lib/ea/sources/xmi/diagram_builder/font_settings.rb
858
859
  - lib/ea/sources/xmi/diagram_builder/stereotype_profiles.rb
859
860
  - lib/ea/sources/xmi/diagram_builder/umldi_edges.rb
@@ -875,6 +876,7 @@ files:
875
876
  - lib/ea/sources/xmi/relationship_type_map.rb
876
877
  - lib/ea/sources/xmi/stereotype_builder.rb
877
878
  - lib/ea/sources/xmi/stored_route.rb
879
+ - lib/ea/sources/xmi/stored_route/reversed_routes.rb
878
880
  - lib/ea/sources/xmi/tag_builder.rb
879
881
  - lib/ea/sources/xmi/type_classifier_map.rb
880
882
  - lib/ea/sources/xmi/umldi_keyword_extractor.rb
@@ -898,8 +900,10 @@ files:
898
900
  - lib/ea/svg/connector_router.rb
899
901
  - lib/ea/svg/ea_emitter.rb
900
902
  - lib/ea/svg/ea_emitter/background.rb
903
+ - lib/ea/svg/ea_emitter/bottom_margin.rb
901
904
  - lib/ea/svg/ea_emitter/bounds_calculator.rb
902
905
  - lib/ea/svg/ea_emitter/canvas.rb
906
+ - lib/ea/svg/ea_emitter/canvas_anchor.rb
903
907
  - lib/ea/svg/ea_emitter/color_resolver.rb
904
908
  - lib/ea/svg/ea_emitter/compartment.rb
905
909
  - lib/ea/svg/ea_emitter/compartment/attributes.rb
@@ -922,6 +926,7 @@ files:
922
926
  - lib/ea/svg/ea_emitter/connector_bridges.rb
923
927
  - lib/ea/svg/ea_emitter/connector_redock.rb
924
928
  - lib/ea/svg/ea_emitter/connectors.rb
929
+ - lib/ea/svg/ea_emitter/content_extent.rb
925
930
  - lib/ea/svg/ea_emitter/diagram_frame.rb
926
931
  - lib/ea/svg/ea_emitter/document.rb
927
932
  - lib/ea/svg/ea_emitter/element.rb
@@ -976,6 +981,8 @@ files:
976
981
  - lib/ea/svg/ea_emitter/marker/registry.rb
977
982
  - lib/ea/svg/ea_emitter/markers.rb
978
983
  - lib/ea/svg/ea_emitter/orthogonal_ray.rb
984
+ - lib/ea/svg/ea_emitter/paint_order.rb
985
+ - lib/ea/svg/ea_emitter/pen.rb
979
986
  - lib/ea/svg/ea_emitter/render_context.rb
980
987
  - lib/ea/svg/ea_emitter/segment_geometry.rb
981
988
  - lib/ea/svg/ea_emitter/style.rb