ea 0.6.97 → 0.6.98

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: c2ca3f0fd4381aec39af89849d601c85defa496b697f1fe625558c8a210aef64
4
- data.tar.gz: adbc3773a202f06ad7327ace0fbea64daacea45a4719afb28959332673dafdcd
3
+ metadata.gz: b3c13e631927ad57c6168b6d7c02eaed8e933c89ad7dfcd0a055fb6b8a69e8e1
4
+ data.tar.gz: 3edb35912780be1170fce402dd141823f4c5e44c92a6f84451fa136d3eb026a8
5
5
  SHA512:
6
- metadata.gz: 99b878a17d58444e251964011cf2af59d46459223e657b8ffbf1a4602dc572af56e594bedd05c321cec0502d91e654cdd013d5b408277027d1c4f41c65885d3f
7
- data.tar.gz: 73ef9107ec26ea7b60f7c9e5d00b7fa09b4d08b10842345c589a75dfd13a62b553287b95b0ad1b71e0c45fc032ab30ff1299fecb03e2a3abac69027a69180e97
6
+ metadata.gz: 6544102e41ff1bde04737e18f93de5ea0ee657c47335f7eeb12316162ca33fbf2e0d5b0dc1572fccc5174373781b1368508d606641286c88ebe2d7f9f2232079
7
+ data.tar.gz: eb3cb4702ea44ba8759ab9774a1ca445684b9d6ce7043153b4823657c191fff45c6db267ee74b1ed20cdec2064248b60a0f5112827b92edfa4aaecaa495fa093
@@ -8,6 +8,41 @@ module Ea
8
8
  module Routing
9
9
  private
10
10
 
11
+ # An empty custom-line Path has no authored corner to preserve.
12
+ # Keep normalized port routes; replace only the computed EDGE2 jog.
13
+ def direct_empty_path_ray(model, link_row, diagram_row, geom, context)
14
+ return model if context
15
+ return model unless empty_path_ray_candidate?(model, link_row, geom)
16
+
17
+ source, target = stored_ray_boxes(link_row, diagram_row)
18
+ return model unless empty_path_ray_gap?(source, target, geom)
19
+
20
+ model.waypoints = [model.source_port, model.target_port].map do |point|
21
+ Ea::Model::Waypoint.new(position: point)
22
+ end
23
+ set_stored_ray_origins(model, source, target, geom)
24
+ end
25
+
26
+ def empty_path_ray_gap?(source, target, geom)
27
+ return false unless source.x + source.width < target.x
28
+
29
+ [stored_ray_origin(source, geom[:sx], geom[:sy]),
30
+ stored_ray_origin(target, geom[:ex], geom[:ey])].all? do |origin|
31
+ within_height?(origin.y, source) && within_height?(origin.y, target)
32
+ end
33
+ end
34
+
35
+ def empty_path_ray_candidate?(model, link_row, geom)
36
+ %w[Generalization Aggregation].include?(model.connector_type) &&
37
+ model.waypoints.size == 3 && empty_path_ray_style?(model.style, link_row, geom) &&
38
+ model.has_geometry_offsets && model.source_port && model.target_port
39
+ end
40
+
41
+ def empty_path_ray_style?(style, link_row, geom)
42
+ style[:mode] == "3" && style[:lwidth] == "2" && !style[:tree] &&
43
+ geom[:edge] == 2 && link_row.path.to_s.empty?
44
+ end
45
+
11
46
  # Stored SY/EY use mathematical Y; ray origins use screen Y.
12
47
  # Computed-jog origins are already normalized to their derived ports.
13
48
  def correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
@@ -16,16 +16,11 @@ module Ea
16
16
 
17
17
  attr_reader :database
18
18
 
19
- # Generalizations whose EA CustomProperties carry
20
- # isSubstitutable draw '{isSubstitutable=false}' at the label
21
- # seat (t_xref 'connector property' rows keyed by connector
22
- # guid; VALU 0/empty both mean false).
19
+ # This flag requests the false annotation, not the property's Boolean
20
+ # value. An explicit true (-1) must not request a false annotation.
23
21
  def substitutable_connector?(conn)
24
- @substitutable_guids ||= database.collections[:xrefs]
25
- .select do |x|
26
- x.xref_type == "connector property" &&
27
- x.name == "CustomProperties" &&
28
- x.description.to_s.include?("isSubstitutable")
22
+ @substitutable_guids ||= database.collections[:xrefs].select do |xref|
23
+ false_substitutability_xref?(xref)
29
24
  end.map(&:client).to_set
30
25
  @substitutable_guids.include?(conn.ea_guid)
31
26
  end
@@ -99,6 +94,22 @@ module Ea
99
94
 
100
95
  private
101
96
 
97
+ def false_substitutability_xref?(xref)
98
+ return false unless xref.xref_type == "connector property" && xref.name == "CustomProperties"
99
+
100
+ description = xref.description.to_s
101
+ description.include?("isSubstitutable") && !true_substitutability_property?(description)
102
+ end
103
+
104
+ def true_substitutability_property?(description)
105
+ description.scan(/@PROP=(.*?)@ENDPROP;/m).any? do |match|
106
+ property = match.first
107
+ property.include?("@NAME=isSubstitutable@ENDNAME;") &&
108
+ property.include?("@TYPE=Boolean@ENDTYPE;") &&
109
+ property[/@VALU=(.*?)@ENDVALU;/m, 1] == "-1"
110
+ end
111
+ end
112
+
102
113
  def build_elements(diagram_row)
103
114
  objs = database.diagram_objects_for(diagram_row.diagram_id) || []
104
115
  objs.map { |obj_row| build_element(obj_row, diagram_row) }
@@ -410,6 +421,7 @@ module Ea
410
421
  has_geometry_offsets: geometry_has_offsets?(link_row.geometry),
411
422
  is_substitutable: substitutable_connector?(connector)
412
423
  ), link_row, diagram_row, geom)
424
+ model = direct_empty_path_ray(model, link_row, diagram_row, geom, context)
413
425
  correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
414
426
  end
415
427
 
@@ -209,18 +209,46 @@ module Ea
209
209
  divider_y_by_ref[target_ref]]
210
210
  return nil unless bounds.all? && dividers.all?
211
211
 
212
- adjusted = row_slot_pairs(pairs, bounds, dividers)
212
+ preserve_source = explicit_horizontal_source_dock?(pairs, connector)
213
+ adjusted = row_slot_pairs(pairs, bounds, dividers, preserve_source: preserve_source)
213
214
  if stroke_outline?(connector, bounds) &&
214
215
  pairs == pairs_for(connector, diagram, model_index, bounds_map)
215
216
  original = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
216
- original_final = row_slot_pairs(original, bounds, dividers)
217
+ original_final = row_slot_pairs(original, bounds, dividers, preserve_source: preserve_source)
217
218
  adjusted = original_final if original != pairs && !forward_route?(original_final, adjusted)
218
219
  end
219
220
  adjusted == pairs ? nil : adjusted
220
221
  end
221
222
 
222
- def row_slot_pairs(pairs, bounds, dividers)
223
+ # A supplied horizontal port already specifies its row. Moving it
224
+ # to the next compartment row would discard that explicit coordinate.
225
+ def explicit_horizontal_source_dock?(pairs, connector)
226
+ return false unless explicit_horizontal_ray?(connector)
227
+
228
+ connector.source_anchor.y == connector.target_anchor.y &&
229
+ connector.source_port.y == connector.source_anchor.y &&
230
+ pairs.first[1] == connector.source_port.y
231
+ end
232
+ private_class_method :explicit_horizontal_source_dock?
233
+
234
+ def explicit_horizontal_ray?(connector)
235
+ explicit_horizontal_ray_style?(connector) && connector.has_geometry_offsets &&
236
+ connector.source_port && connector.target_port &&
237
+ connector.source_anchor && connector.target_anchor
238
+ end
239
+ private_class_method :explicit_horizontal_ray?
240
+
241
+ def explicit_horizontal_ray_style?(connector)
242
+ style = connector.style.to_h
243
+ %w[Generalization Aggregation].include?(connector.connector_type) &&
244
+ style[:mode] == "3" && !style[:tree] && !style[:direct] && !style[:regenerated]
245
+ end
246
+ private_class_method :explicit_horizontal_ray_style?
247
+
248
+ def row_slot_pairs(pairs, bounds, dividers, preserve_source: false)
223
249
  pairs.each_with_index.map do |(x, y), index|
250
+ next [x, y] if preserve_source && index.zero?
251
+
224
252
  row_slot?(x, y, bounds[index], dividers[index]) ? [x, y + ROW_SLOT_PITCH] : [x, y]
225
253
  end
226
254
  end
@@ -86,12 +86,14 @@ module Ea
86
86
  stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
87
87
  direction = leg_direction(end_kind, heading)
88
88
  if direction && !(tree && zero_offset?(text_box, mult_box))
89
- text_pos = source_property_seat(connector, direction, anchor, text_box, role) ||
89
+ text_pos = plain_vertical_role(connector, direction, anchor, text_box, role) ||
90
+ source_property_seat(connector, direction, anchor, text_box, role) ||
90
91
  plain_upward_seat(connector, direction, anchor, text_box, role) ||
91
92
  role_seat(direction, anchor, text_box, role) || text_pos
92
93
  mult_pos = directional_multiplicity(connector, direction, anchor, mult_box, mult) ||
93
94
  mult_seat(direction, anchor, mult_box) || mult_pos
94
95
  elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
96
+ text_pos = target_upward_role(connector, heading, anchor, text_box, role) || text_pos
95
97
  mult_pos = target_multiplicity_seat(heading, anchor, mult_box) || mult_pos
96
98
  end
97
99
 
@@ -201,6 +203,36 @@ module Ea
201
203
  [x - SEAT_X_INSET - width, y - 6]
202
204
  end
203
205
 
206
+ def target_upward_role(connector, heading, anchor, box, role)
207
+ return nil unless heading && heading[0].zero? && heading[1].negative?
208
+
209
+ plain_vertical_role(connector, :target_up, anchor, box, role)
210
+ end
211
+
212
+ def plain_vertical_role(connector, direction, anchor, box, role)
213
+ return nil unless %i[down target_up].include?(direction) && plain_vertical_box?(box)
214
+
215
+ end_kind = direction == :down ? :source : :target
216
+ return nil unless plain_vertical_connector?(connector, end_kind)
217
+
218
+ width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
219
+ return nil unless width
220
+
221
+ x, y = seat_corner(anchor, box)
222
+ [x - width - 8, y + (end_kind == :source ? 15 : -7)]
223
+ end
224
+
225
+ def plain_vertical_box?(box)
226
+ seat_font? && cambria_box?(box) && [0, 1].include?(box["alignment"])
227
+ end
228
+
229
+ def plain_vertical_connector?(connector, end_kind)
230
+ style = connector.style.to_h
231
+ connector.connector_type == "Association" &&
232
+ connector_source_dest_stereotype(connector, end_kind).empty? &&
233
+ style[:mode] == "3" && style[:lwidth] == "0" && !style[:tree]
234
+ end
235
+
204
236
  def plain_role_route?(style)
205
237
  %w[1 3].include?(style[:mode]) && %w[0 2].include?(style[:lwidth]) &&
206
238
  [nil, "V"].include?(style[:tree])
@@ -96,6 +96,9 @@ module Ea
96
96
  LINE_PITCH = 13
97
97
 
98
98
  def label_position(connector, points)
99
+ name_seat = stored_name_position(connector, points)
100
+ return name_seat if name_seat
101
+
99
102
  return midpoint(points) unless default_substitutable_seat?(connector) &&
100
103
  upward_four_point_route?(points)
101
104
 
@@ -103,6 +106,40 @@ module Ea
103
106
  [source[0] + 3, ((source[1] + bend[1]) / 2).floor - 1]
104
107
  end
105
108
 
109
+ # At 7pt, a centered name's stored corner is 3px below its baseline.
110
+ def stored_name_position(connector, points)
111
+ box = stored_name_box(connector)
112
+ return nil unless box
113
+
114
+ width = Ea::Fonts::Metrics.text_length(midpoint_content(connector), font_size, family: font_family)
115
+ return nil unless width
116
+
117
+ x, y = midpoint(points)
118
+ offset_x, offset_y = box.values_at("ox", "oy").map(&:to_i)
119
+ [x + offset_x - (width / 2.0).floor, y + offset_y - 3]
120
+ end
121
+
122
+ def stored_name_box(connector)
123
+ return nil unless connector.is_a?(Ea::Model::DiagramConnector) && measured_name_font?
124
+ return nil if connector.is_substitutable || stereotype_label(connector)
125
+ return nil if midpoint_content(connector).include?(": ")
126
+
127
+ box = connector.label_boxes.to_h[:lmt]
128
+ box if default_name_box?(box)
129
+ end
130
+
131
+ def measured_name_font?
132
+ font_family.to_s.casecmp("Cambria").zero? && font_size == 7 && font_unit == "pt"
133
+ end
134
+
135
+ def default_name_box?(box)
136
+ return false unless box && box["alignment"] == 1
137
+ return false unless box.values_at("ox", "oy").all?
138
+ return false unless [nil, 0, 1].include?(box["direction"]) && [nil, -1].include?(box["color"])
139
+
140
+ box["rotation"].to_i.zero? && box.values_at("bold", "italic", "underline", "hidden").none?
141
+ end
142
+
106
143
  def default_substitutable_seat?(connector)
107
144
  connector.is_a?(Ea::Model::DiagramConnector) && connector.is_substitutable &&
108
145
  connector.connector_type == "Generalization" && font_size == 7 &&
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.97"
7
+ VERSION = "0.6.98"
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.97
4
+ version: 0.6.98
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.