ea 0.6.89 → 0.6.90

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: bbd238ad74b281cd5cb44ae2df2b49edd56eec8ab0001f8cf1694259a974c385
4
- data.tar.gz: 978f6880bb2ffa3a9fd308047c62dd21e0869305ea832ac64b91de9f1c8303e9
3
+ metadata.gz: e78b8830cfb779be7d7724d11c984d11de76c2220aaae41502515f156e03aeca
4
+ data.tar.gz: d694be35c46f90866280700653aa35af66da37e9e3ec9bcf0724805b947550b0
5
5
  SHA512:
6
- metadata.gz: 44a797e58abb32b6434cb83eec2d84270a6499dc3c8d986a6da1a68da1074982a9217655d20e835bd5fe8812c8780a7a9775a9495d970cf0eb849694b0d6d9d3
7
- data.tar.gz: 0c410271301a481b5085d7be62a2f45de58eb7fe46cad199e69e0f0f017f7648f4646305f2853ce248c5f6c62979f31c8d7087b7526945234315b8b6b6f63833
6
+ metadata.gz: 86c6dc4296a5c63c074178cdb01f19b171403e895b02dfb6e9897da9db87288a143228a3a0d7631953ddcf19ce2199a3060440fbd7472c472507af952206496c
7
+ data.tar.gz: f0a27398ba13e4b352ccda7e3f810485577f5e2ac5154f2a4b43ab276ae6c2778877318f5c0c58e9ce92caa95ce2e27d10b11e829785cf61bb916fc1ac7bbccc
@@ -605,21 +605,15 @@ module Ea
605
605
  intermediate = intermediate_waypoints(link_row)
606
606
  if edge_out == 2 && source_port && target_port &&
607
607
  !valid_tree_path?(intermediate, source_placement)
608
- # EA recomputes tree routes at DRAW TIME from current
609
- # element positions when the stored Path no longer lands
610
- # on the source's facing edge (hand-verified: SY 8542's
611
- # stale Path vs EA-drawn route; CityObjectGroup's current
612
- # Path renders byte-exact and is kept). Computed route:
613
- # source port -> corner at (target dock x, source exit
614
- # y) -> target port.
608
+ # A stale tree Path (not on the source's facing edge) is
609
+ # recomputed: source port -> corner at (target dock x,
610
+ # source exit y) -> target port.
615
611
  points = [[source_point[0], source_point[1]],
616
612
  [target_point[0], source_point[1]],
617
613
  [target_point[0], target_point[1]]]
618
614
  elsif edge_out == 2 && intermediate.size == 1
619
- # Current tree Path (bend on the source's facing edge):
620
- # EA's draw-time route shape — source right edge at the
621
- # bend's y, corner at the bend, target facing edge at the
622
- # bend's x (byte-exact on CityObjectGroup).
615
+ # Current tree Path: source right edge at the bend's y,
616
+ # corner at the bend, target facing edge at the bend's x.
623
617
  src = bounds_from_rect(source_placement)
624
618
  tgt = bounds_from_rect(target_placement)
625
619
  bend = intermediate.first
@@ -630,21 +624,11 @@ module Ea
630
624
  [bend[0], target_y]]
631
625
  elsif intermediate.any?
632
626
  points = [source_point, *intermediate, target_point]
633
- # Generalizations with the parent ABOVE keep their stored
634
- # interior bends verbatim but re-dock the endpoints onto
635
- # the box edges AT THE ADJACENT BEND's x (hand-verified:
636
- # stored (542,975)(541,807)(543,807)(486,757) draws as
637
- # (541,975)(541,807)(543,807)(543,757) — only the two
638
- # endpoints move; the stale final x is replaced by the
639
- # last bend's trunk x).
627
+ # Parent-above Generalizations keep stored interior bends
628
+ # and re-dock only the endpoints, at the adjacent bend's x.
640
629
  if connector.connector_type == "Generalization"
641
- src_b = bounds_from_rect(source_placement)
642
- tgt_b = bounds_from_rect(target_placement)
643
- if tgt_b.y + tgt_b.height <= src_b.y
644
- points[0] = [points[1][0].clamp(src_b.x, src_b.x + src_b.width), src_b.y]
645
- points[-1] = [points[-2][0].clamp(tgt_b.x, tgt_b.x + tgt_b.width),
646
- tgt_b.y + tgt_b.height]
647
- end
630
+ redock_parent_above(points, link_row, source_placement,
631
+ target_placement)
648
632
  end
649
633
  elsif source_port || target_port
650
634
  # Exact ports are known — the SX/SY bend heuristics in
@@ -654,20 +638,22 @@ module Ea
654
638
  points = sx_sy_ex_ey_waypoints(source_point, target_point, geom)
655
639
  end
656
640
 
657
- # Associations/Aggregations between DISJOINT boxes: when the
658
- # stored route does not already dock the closest FACING edge
659
- # pair, EA redraws a straight aligned line between those
660
- # edges (corpus: edge pair = min facing gap, 85% accurate;
661
- # dock coordinate = the stored route's own coordinate
662
- # clamped into the edges' overlap; simulation on 417
663
- # identity-paired connectors: exact routes 41 -> 100).
664
- # Tree-style (EDGE=2) connectors are excluded — their
665
- # computed routes are already byte-exact.
641
+ # Between DISJOINT boxes, a stored route that does not dock the
642
+ # closest facing edge pair is redrawn straight between them.
643
+ # EDGE=2 routes are excluded; they are already exact.
666
644
  if %w[Association Aggregation].include?(connector.connector_type) &&
667
645
  edge_out != 2
668
646
  points = facing_edge_route(points,
669
647
  bounds_from_rect(source_placement),
670
648
  bounds_from_rect(target_placement))
649
+ # A width-2 Aggregation that keeps its stored bends is
650
+ # docked like a Generalization one.
651
+ if connector.connector_type == "Aggregation" &&
652
+ intermediate.any? && points.size >= 4 &&
653
+ generalization_outline_offset(link_row, points.size) == 1
654
+ redock_parent_above(points, link_row, source_placement,
655
+ target_placement, strict: true)
656
+ end
671
657
  end
672
658
 
673
659
  points.map do |x, y|
@@ -978,6 +964,35 @@ module Ea
978
964
 
979
965
  IdNormalizer.from_guid(conn.ea_guid)
980
966
  end
967
+
968
+ # Docks both ends of a bent route on the box edges at the
969
+ # adjacent bend's x when the target is above the source; interior
970
+ # bends stay. strict: skip an end whose bend is not beyond its dock.
971
+ def redock_parent_above(points, link_row, source_placement,
972
+ target_placement, strict: false)
973
+ src_b = bounds_from_rect(source_placement)
974
+ tgt_b = bounds_from_rect(target_placement)
975
+ return points unless tgt_b.y + tgt_b.height <= src_b.y
976
+
977
+ inset = generalization_outline_offset(link_row, points.size)
978
+ source_y = src_b.y
979
+ target_y = tgt_b.y + tgt_b.height
980
+ source_y -= inset if points[1][1] < source_y - inset
981
+ target_y += inset if points[-2][1] > target_y + inset
982
+ unless strict && points[1][1] >= source_y
983
+ points[0] = [points[1][0].clamp(src_b.x, src_b.x + src_b.width), source_y]
984
+ end
985
+ unless strict && points[-2][1] <= target_y
986
+ points[-1] = [points[-2][0].clamp(tgt_b.x, tgt_b.x + tgt_b.width), target_y]
987
+ end
988
+ points
989
+ end
990
+
991
+ def generalization_outline_offset(link_row, point_count)
992
+ return 0 if point_count < 4
993
+
994
+ DiagramStyleParser.parse(link_row.style)[:lwidth] == "2" ? 1 : 0
995
+ end
981
996
  end
982
997
  end
983
998
  end
@@ -36,16 +36,45 @@ module Ea
36
36
  return nil unless source_bounds && target_bounds
37
37
  return nil if source_ref == target_ref
38
38
 
39
- sa = connector.source_anchor
40
- ta = connector.target_anchor
41
- if sa && ta
42
- [anchor_exit(sa, source_bounds, ta),
43
- anchor_exit(ta, target_bounds, sa)]
39
+ anchors = [connector.source_anchor, connector.target_anchor]
40
+ bounds = [source_bounds, target_bounds]
41
+ original = clipped_pairs(bounds, anchors)
42
+ return original unless stroke_outline?(connector, bounds)
43
+
44
+ # EA docks these markers on the outer edge of the 2px box stroke.
45
+ outline = bounds.map do |box|
46
+ Ea::Model::Bounds.new(x: box.x - 1, y: box.y - 1,
47
+ width: box.width + 2, height: box.height + 2)
48
+ end
49
+ candidate = clipped_pairs(outline, anchors)
50
+ forward_route?(original, candidate) ? candidate : original
51
+ end
52
+
53
+ def stroke_outline?(connector, bounds)
54
+ connector.style.to_h[:lwidth] == "2" &&
55
+ %w[Generalization Aggregation].include?(connector.connector_type) &&
56
+ bounds.all? { |box| box.width.positive? && box.height.positive? }
57
+ end
58
+ private_class_method :stroke_outline?
59
+
60
+ def clipped_pairs(bounds, anchors)
61
+ source, target = bounds
62
+ source_anchor, target_anchor = anchors
63
+ if source_anchor && target_anchor
64
+ [anchor_exit(source_anchor, source, target_anchor),
65
+ anchor_exit(target_anchor, target, source_anchor)]
44
66
  else
45
- [edge_point(source_bounds, target_bounds),
46
- edge_point(target_bounds, source_bounds)]
67
+ [edge_point(source, target), edge_point(target, source)]
47
68
  end
48
69
  end
70
+ private_class_method :clipped_pairs
71
+
72
+ def forward_route?(original, candidate)
73
+ before = original.first.zip(original.last).map { |start, finish| finish - start }
74
+ after = candidate.first.zip(candidate.last).map { |start, finish| finish - start }
75
+ before.zip(after).sum { |old, current| old * current }.positive?
76
+ end
77
+ private_class_method :forward_route?
49
78
 
50
79
  # Exit point of the anchor-to-anchor segment through a box's
51
80
  # outline: smallest positive t whose crossing lies on the
@@ -101,21 +130,29 @@ module Ea
101
130
  divider_y_by_ref[target_ref]]
102
131
  return nil unless bounds.all? && dividers.all?
103
132
 
104
- changed = false
105
- adjusted = pairs.each_with_index.map do |(x, y), i|
106
- box = bounds[i]
107
- divider = dividers[i]
108
- at_edge = (x - box.x).abs <= 1 || (x - (box.x + box.width)).abs <= 1
109
- at_divider = (y - divider.to_f).abs <= 1
110
- if at_edge && at_divider
111
- changed = true
112
- [x, y + ROW_SLOT_PITCH]
113
- else
114
- [x, y]
115
- end
133
+ adjusted = row_slot_pairs(pairs, bounds, dividers)
134
+ if stroke_outline?(connector, bounds) &&
135
+ pairs == pairs_for(connector, diagram, model_index, bounds_map)
136
+ original = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
137
+ original_final = row_slot_pairs(original, bounds, dividers)
138
+ adjusted = original_final if original != pairs && !forward_route?(original_final, adjusted)
139
+ end
140
+ adjusted == pairs ? nil : adjusted
141
+ end
142
+
143
+ def row_slot_pairs(pairs, bounds, dividers)
144
+ pairs.each_with_index.map do |(x, y), index|
145
+ row_slot?(x, y, bounds[index], dividers[index]) ? [x, y + ROW_SLOT_PITCH] : [x, y]
116
146
  end
117
- changed ? adjusted : nil
118
147
  end
148
+ private_class_method :row_slot_pairs
149
+
150
+ def row_slot?(x_position, y_position, bounds, divider)
151
+ at_edge = (x_position - bounds.x).abs <= 1 ||
152
+ (x_position - (bounds.x + bounds.width)).abs <= 1
153
+ at_edge && (y_position - divider.to_f).abs <= 1
154
+ end
155
+ private_class_method :row_slot?
119
156
 
120
157
  def direct?(connector)
121
158
  style = connector.style || {}
@@ -75,7 +75,7 @@ module Ea
75
75
  stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
76
76
 
77
77
  build_texts(role:, mult:, standalone:,
78
- text_pos:, mult_pos:, stereo:)
78
+ text_pos:, mult_pos:, stereo:, text_box:)
79
79
  end
80
80
 
81
81
  private
@@ -110,12 +110,13 @@ module Ea
110
110
  end
111
111
 
112
112
  def build_texts(role:, mult:, standalone:, text_pos:, mult_pos:,
113
- stereo:)
113
+ stereo:, text_box:)
114
114
  if role && !role.empty? && text_pos
115
115
  x, y = text_pos
116
116
  texts = [text_at(x, y, role)]
117
117
  if stereo
118
- texts << text_at(x, y + property_y_offset, stereo)
118
+ stereo_x = x + stereotype_offset(role, stereo, text_box)
119
+ texts << text_at(stereo_x, y + property_y_offset, stereo)
119
120
  end
120
121
  if mult && !mult.empty? && mult_pos
121
122
  texts << text_at(*mult_pos, mult)
@@ -128,6 +129,16 @@ module Ea
128
129
  end
129
130
  end
130
131
 
132
+ def stereotype_offset(role, stereo, text_box)
133
+ return 0 unless text_box && text_box["alignment"] == 1
134
+
135
+ role_width = TextRenderer.estimate_width(role, font_size,
136
+ family: font_family).round
137
+ stereo_width = TextRenderer.estimate_width(stereo, font_size,
138
+ family: font_family).round
139
+ ((role_width - stereo_width) / 2.0).round
140
+ end
141
+
131
142
  # EA renders «Property» between role name and multiplicity
132
143
  # exactly on connector ends carrying
133
144
  # t_connector.SourceStereotype / DestStereotype = "Property"
@@ -32,7 +32,7 @@ module Ea
32
32
  content = midpoint_content(connector)
33
33
  return nil if content.nil?
34
34
 
35
- mx, my = midpoint(points)
35
+ mx, my = label_position(connector, points)
36
36
  # EA splits a "name: value" label at the colon-space into
37
37
  # two lines centered on the same slot (81F92FC7: "new
38
38
  # ownership:" / "LA_Right" at y 172/185, both centered on
@@ -95,6 +95,35 @@ module Ea
95
95
 
96
96
  LINE_PITCH = 13
97
97
 
98
+ def label_position(connector, points)
99
+ return midpoint(points) unless default_substitutable_seat?(connector) &&
100
+ upward_four_point_route?(points)
101
+
102
+ source, bend = points
103
+ [source[0] + 3, ((source[1] + bend[1]) / 2).floor - 1]
104
+ end
105
+
106
+ def default_substitutable_seat?(connector)
107
+ connector.is_a?(Ea::Model::DiagramConnector) && connector.is_substitutable &&
108
+ connector.connector_type == "Generalization" && font_size == 7 &&
109
+ stereotype_label(connector).nil? &&
110
+ default_constraint_box?(connector.label_boxes.to_h[:lmb])
111
+ end
112
+
113
+ def default_constraint_box?(box)
114
+ box && !box["hidden"] &&
115
+ box.values_at("ox", "oy", "rotation", "alignment") == [0, 0, 0, 0]
116
+ end
117
+
118
+ def upward_four_point_route?(points)
119
+ return false unless points.size == 4
120
+
121
+ source, first_bend, last_bend, target = points
122
+ source[0] == first_bend[0] && first_bend[1] == last_bend[1] &&
123
+ last_bend[0] == target[0] && source[1] > first_bend[1] &&
124
+ target[1] < last_bend[1]
125
+ end
126
+
98
127
  def midpoint(points)
99
128
  mid_idx = points.size / 2
100
129
  if points.size.odd?
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.89"
7
+ VERSION = "0.6.90"
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.89
4
+ version: 0.6.90
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.