ea 0.6.117 → 0.6.119

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 473bc36a9b9e843f0eb497f2437f56932823a674d8823c44c40063f93ab2da54
4
- data.tar.gz: 41b7858cf138ecc8e5a16b05762451ac98e738b9c4b13b3bfd781fb9821ab002
3
+ metadata.gz: 2b80d2500a79d8c410643f012044e96045355f11475b70d9fdfd32522eb974f7
4
+ data.tar.gz: c36ab5e3816b5970f77cff1159eb6cab83972f655c7a22eef20f780fcb369900
5
5
  SHA512:
6
- metadata.gz: c319d8f6b1c3454cd5c04495822223b4ec22e58bb1a357774b9ec3f5bbccde4eb1fa10bd94ff64a1a61cac24a70462d63915757fa6df98846d0c3fa7c5174b35
7
- data.tar.gz: d9dc77d29d955d62ec4e27d102a35b41cd4e7a561f08ec91077a8cb8838f0de2438c935d3cb1176a03687eb7efab9c23654fdfbb299c7fab84dc8a92ddc7d3a1
6
+ metadata.gz: '0092fc3cc3572c4e9ce6fad78fd06bf20f0ed8f94885dfc1d69c04a439ed6e7853ee42969fbb3eae40489387e07584897fc22d29fe7c63357cb307155fda4798'
7
+ data.tar.gz: b428d62df7fdfd930a1a06614b5b80784a755de543a8fb1513addba4807d122c7d4bdaffa26553b854e7c53f3d4e9d2822f209f24943da4079a263f76b13328f
@@ -8,6 +8,51 @@ module Ea
8
8
  module Routing
9
9
  private
10
10
 
11
+ # Offsets stay attached to semantic Start/End IDs even when the
12
+ # saved corner follows the opposite placement order. Clip each leg
13
+ # from its stored mathematical origin, retaining external origins.
14
+ def reversed_stored_corner(model, link_row, diagram_row, geom, context)
15
+ return model if context || !geom.values_at(:sx, :sy, :ex, :ey).all?
16
+
17
+ connector = database.find_connector(link_row.connectorid)
18
+ return model unless connector && reversed_corner_link?(link_row, diagram_row, connector)
19
+
20
+ boxes = stored_ray_boxes(link_row, diagram_row)
21
+ origins = [stored_ray_origin(boxes[0], geom[:sx], geom[:sy]),
22
+ stored_ray_origin(boxes[1], geom[:ex], geom[:ey])]
23
+ x, y = intermediate_waypoints(link_row).first
24
+ bend = Ea::Model::Point.new(x: x, y: y)
25
+ ends = origins.zip(boxes).map do |origin, box|
26
+ inside_box?(origin, box) ? diagonal_corner_exit(origin, bend, box) : origin
27
+ end
28
+ return model unless ends.all?
29
+
30
+ model.waypoints = [ends[0], bend, ends[1]].map { |point| Ea::Model::Waypoint.new(position: point) }
31
+ model.source_anchor, model.target_anchor = origins
32
+ model
33
+ end
34
+
35
+ # Path follows SOID/EOID placement order, independently of the
36
+ # relationship's Start/End IDs. Only a complete, unique DUID pair
37
+ # can reverse the authored single-corner route.
38
+ def reversed_corner_link?(link_row, diagram_row, connector)
39
+ style = DiagramStyleParser.parse(link_row.style)
40
+ return false unless connector.connector_type == "Association" &&
41
+ style.values_at(:mode, :lwidth, :tree) == ["3", "0", nil] &&
42
+ parse_geometry_fields(link_row.geometry)[:edge] == 2 &&
43
+ intermediate_waypoints(link_row).size == 1
44
+
45
+ ids = style.values_at(:soid, :eoid)
46
+ return false if ids.any? { |id| id.to_s.empty? } || ids.uniq.size != 2
47
+
48
+ placements = database.diagram_objects_for(diagram_row.diagram_id) || []
49
+ ends = ids.map do |id|
50
+ matches = placements.select { |row| DiagramStyleParser.parse(row.objectstyle)[:duid] == id }
51
+ matches.first.ea_object_id if matches.size == 1
52
+ end
53
+ ends == [connector.end_object_id, connector.start_object_id] && ends.uniq.size == 2
54
+ end
55
+
11
56
  # An empty custom-line Path has no authored corner to preserve.
12
57
  # Keep normalized port routes; replace only the computed EDGE2 jog.
13
58
  def direct_empty_path_ray(model, link_row, diagram_row, geom, context)
@@ -145,6 +145,7 @@ module Ea
145
145
  font_underline: truthy?(style_hash[:ul]),
146
146
  show_tagged_values: truthy?(style_hash[:tag]),
147
147
  line_width: effective_line_width(obj_row, style_hash),
148
+ line_color: effective_line_color(obj_row, style_hash),
148
149
  background_color: style_hash[:bcol]&.to_i
149
150
  )
150
151
  end
@@ -161,6 +162,17 @@ module Ea
161
162
  nil
162
163
  end
163
164
 
165
+ def effective_line_color(obj_row, style)
166
+ object = database.find_object(obj_row.ea_object_id)
167
+ [style[:lcol], object&.bordercolor].each do |raw|
168
+ next if raw.nil? || raw.to_s.empty?
169
+
170
+ color = raw.to_i
171
+ return color if color >= 0
172
+ end
173
+ nil
174
+ end
175
+
164
176
  def truthy?(raw)
165
177
  raw == "1" || raw == "-1"
166
178
  end
@@ -452,7 +464,8 @@ module Ea
452
464
  model = overlap_ray_origins(model, link_row, diagram_row, geom, context)
453
465
  model = auto_port_elbow(model, link_row, diagram_row, geom, context)
454
466
  model = mark_default_square_ray(model, link_row, geom, context)
455
- restore_stored_square_ports(model, link_row, diagram_row, geom, context)
467
+ model = restore_stored_square_ports(model, link_row, diagram_row, geom, context)
468
+ reversed_stored_corner(model, link_row, diagram_row, geom, context)
456
469
  end
457
470
 
458
471
  # Ray origin for one connector end: the element's center
@@ -23,6 +23,8 @@ module Ea
23
23
  args = common_args(row)
24
24
  args.merge!(association_args(row)) if klass == Ea::Model::Association
25
25
  args.merge!(generalization_args(row)) if klass == Ea::Model::Generalization
26
+ args.merge!(dependency_args(row)) if klass == Ea::Model::Dependency
27
+ args.merge!(realization_args(row)) if klass == Ea::Model::Realization
26
28
  klass.new(**args)
27
29
  end
28
30
 
@@ -50,6 +52,20 @@ module Ea
50
52
  }
51
53
  end
52
54
 
55
+ def dependency_args(row)
56
+ {
57
+ client_id: id_for_object_id(row.start_object_id),
58
+ supplier_id: id_for_object_id(row.end_object_id)
59
+ }
60
+ end
61
+
62
+ def realization_args(row)
63
+ {
64
+ realizing_id: id_for_object_id(row.start_object_id),
65
+ contract_id: id_for_object_id(row.end_object_id)
66
+ }
67
+ end
68
+
53
69
  def association_args(row)
54
70
  {
55
71
  source_id: id_for_object_id(row.start_object_id),
@@ -69,16 +69,9 @@ module Ea
69
69
 
70
70
  def literal_name_text(context, literal, y)
71
71
  content = literal_display(literal)
72
- # EA uses the attribute row point-size increment for literal names.
73
- offset = context.theme.attribute_spec.content_x_offset || 26
74
- offset += 2 * (context.size - 7) if context.size_unit == "pt"
75
- if Ea::Fonts::Metrics.compact_compartment_font?(context.size,
76
- family: context.family, unit: context.size_unit)
77
- offset -= 1
78
- end
79
72
  TextRenderer.new(
80
73
  content: content,
81
- x: context.bounds.x + offset,
74
+ x: context.bounds.x + content_x_offset(context.theme, context.size, context.size_unit, context.family),
82
75
  y: y,
83
76
  family: context.family, size: context.size,
84
77
  size_unit: context.size_unit, logical_height: context.logical_height,
@@ -88,6 +81,26 @@ module Ea
88
81
  ).to_svg
89
82
  end
90
83
 
84
+ def content_width(literals, theme:, size:, family:, **font_options)
85
+ return nil if literals.empty?
86
+
87
+ logical_height = font_options[:logical_height] if font_options[:size_unit] == "pt"
88
+ length = literals.map do |literal|
89
+ TextRenderer.estimate_width(literal_display(literal), size, theme.text_width_factor,
90
+ family: family, weight: 400, style: "normal",
91
+ logical_height: logical_height).round
92
+ end.max
93
+ content_x_offset(theme, size, font_options[:size_unit], family) + length + 5
94
+ end
95
+
96
+ def content_x_offset(theme, size, size_unit, family)
97
+ # EA uses the attribute row point-size increment for literal names.
98
+ offset = theme.attribute_spec.content_x_offset || 26
99
+ offset += 2 * (size - 7) if size_unit == "pt"
100
+ offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
101
+ offset
102
+ end
103
+
91
104
  # EA shows "Name = Code" for literals with a stored
92
105
  # default value; bare names otherwise.
93
106
  def literal_display(literal)
@@ -21,7 +21,8 @@ module Ea
21
21
  return nil unless bounds_map
22
22
  points = waypoint_pairs(connector)
23
23
  lateral = lateral_tree?(connector, points)
24
- return nil unless points.size == 2 || lateral
24
+ trunk = trunk_tree?(connector, points)
25
+ return nil unless points.size == 2 || lateral || trunk
25
26
 
26
27
  rel = relationship_for(connector, model_index)
27
28
  return nil unless rel
@@ -33,6 +34,7 @@ module Ea
33
34
  target_bounds = bounds_map[target_ref]
34
35
  return nil unless source_bounds && target_bounds
35
36
  return nil if source_ref == target_ref
37
+ return trunk_pairs(points, source_bounds, target_bounds, connector.style[:tree]) if trunk
36
38
 
37
39
  square = OrthogonalRay.pairs_for(connector, source_bounds, target_bounds)
38
40
  return square if square
@@ -164,6 +166,37 @@ module Ea
164
166
  end
165
167
  private_class_method :lateral_tree?
166
168
 
169
+ # The QEA builder already docks the Generalization, Aggregation and
170
+ # Association trunks; the other types (Dependency, Nesting, ...) keep
171
+ # their stored ends. A flush (width 0) vertical or horizontal trunk
172
+ # runs axis-aligned into both boxes, so a stored end off that axis or
173
+ # on the far edge of its box is a lost dock the adjacent bend names.
174
+ BUILDER_TRUNK_TYPES = %w[Generalization Aggregation Association].freeze
175
+
176
+ def trunk_tree?(connector, points)
177
+ style = connector.style.to_h
178
+ points.size >= 3 && style[:mode] == "3" && style[:lwidth] == "0" &&
179
+ %w[V H].include?(style[:tree]) && !BUILDER_TRUNK_TYPES.include?(connector.connector_type)
180
+ end
181
+ private_class_method :trunk_tree?
182
+
183
+ def trunk_pairs(points, source, target, tree)
184
+ first = trunk_dock(points[0], points[1], source, tree)
185
+ last = trunk_dock(points[-1], points[-2], target, tree)
186
+ return nil if first == points[0] && last == points[-1]
187
+
188
+ [first, *points[1..-2], last]
189
+ end
190
+ private_class_method :trunk_pairs
191
+
192
+ def trunk_dock(end_point, bend, bounds, tree)
193
+ return end_point unless bounds.width.positive? && bounds.height.positive?
194
+
195
+ dock = tree == "V" ? vertical_target(bend, bounds) : horizontal_target(bend, bounds)
196
+ dock || end_point
197
+ end
198
+ private_class_method :trunk_dock
199
+
167
200
  # The bend selects the near target edge even when the boxes overlap.
168
201
  # Preserve the source leg and bend; only the final dock changes.
169
202
  def lateral_target(bend, bounds, tree)
@@ -227,7 +260,7 @@ module Ea
227
260
  def bordered_outline?(connector, diagram, refs, bounds)
228
261
  style = connector.style.to_h
229
262
  return false unless style[:lwidth] == "0" && !style[:tree]
230
- return false unless %w[Generalization Aggregation Association].include?(connector.connector_type)
263
+ return false unless %w[Generalization Aggregation Association Dependency].include?(connector.connector_type)
231
264
  return false unless bounds.all? { |box| box.width.positive? && box.height.positive? }
232
265
 
233
266
  ends = [[refs.first, style[:soid]], [refs.last, style[:eoid]]]
@@ -508,15 +541,15 @@ module Ea
508
541
 
509
542
  # Polymorphic relationship ends, in connector start→end
510
543
  # order: Generalization child→parent (specific_id/general_id),
544
+ # Realization realizing_id/contract_id,
511
545
  # Dependency client→supplier (client_id/supplier_id),
512
546
  # Association source_id/target_id.
513
547
  def end_refs(rel)
514
- if rel.respond_to?(:specific_id)
515
- [rel.specific_id, rel.general_id]
516
- elsif rel.respond_to?(:client_id)
517
- [rel.client_id, rel.supplier_id]
518
- else
519
- [rel.source_id, rel.target_id]
548
+ case rel
549
+ when Ea::Model::Generalization then [rel.specific_id, rel.general_id]
550
+ when Ea::Model::Realization then [rel.realizing_id, rel.contract_id]
551
+ when Ea::Model::Dependency then [rel.client_id, rel.supplier_id]
552
+ else [rel.source_id, rel.target_id]
520
553
  end
521
554
  end
522
555
 
@@ -151,20 +151,20 @@ module Ea
151
151
  else
152
152
  []
153
153
  end
154
- # Regenerated contexts grow the DRAWN width to fit the
155
- # widest row: attr rows inset 22 + 5 right, op rows inset
156
- # 22 + 3 (CharacterString 197+27=224, Integer 92+25=117).
157
- # Titles never drive growth - they wrap instead.
158
- if attr_lines.any? || op_lines.any?
159
- row_w = [row_content_width(attr_lines, 5, size, family, logical_height),
160
- row_content_width(op_lines, 3, size, family, logical_height)].compact.max
161
- if row_w && row_w > bounds.width
162
- bounds = Ea::Model::Bounds.new(
163
- x: bounds.x, y: bounds.y,
164
- width: row_w,
165
- height: bounds.height
166
- )
167
- end
154
+ # Titles wrap; content rows grow the drawn box to fit their insets.
155
+ row_w = [row_content_width(attr_lines, 5, size, family, logical_height),
156
+ row_content_width(op_lines, 3, size, family, logical_height),
157
+ Compartment::EnumLiterals.content_width(
158
+ enum_literals_for(classifier),
159
+ theme: theme, size: size, family: family,
160
+ size_unit: size_unit, logical_height: logical_height
161
+ )].compact.max
162
+ if row_w && row_w > bounds.width
163
+ bounds = Ea::Model::Bounds.new(
164
+ x: bounds.x, y: bounds.y,
165
+ width: row_w,
166
+ height: bounds.height
167
+ )
168
168
  end
169
169
  stereotype_fqname = element.style&.fetch(:stereotype_fqname, nil)
170
170
  header_lines = classifier ? Element::HeaderLines.for(classifier,
@@ -272,7 +272,7 @@ module Ea
272
272
  model_element: model_element,
273
273
  classifier: classifier,
274
274
  fill: resolve_fill(element, classifier),
275
- stroke: resolve_stroke,
275
+ stroke: resolve_stroke(element),
276
276
  stroke_width: resolve_stroke_width(element),
277
277
  text_fill: theme.text_color,
278
278
  family: family, size: size, size_unit: size_unit, logical_height: logical_height,
@@ -665,8 +665,8 @@ module Ea
665
665
  color_resolver.fill_for(element, classifier)
666
666
  end
667
667
 
668
- def resolve_stroke
669
- color_resolver.stroke_for(nil)
668
+ def resolve_stroke(element)
669
+ color_resolver.stroke_for(element)
670
670
  end
671
671
 
672
672
  def resolve_stroke_width(element)
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.117"
7
+ VERSION = "0.6.119"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.117
4
+ version: 0.6.119
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.8'
19
+ version: 0.8.114
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.8'
26
+ version: 0.8.114
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: lutaml-path
29
29
  requirement: !ruby/object:Gem::Requirement