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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e78b8830cfb779be7d7724d11c984d11de76c2220aaae41502515f156e03aeca
|
|
4
|
+
data.tar.gz: d694be35c46f90866280700653aa35af66da37e9e3ec9bcf0724805b947550b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
#
|
|
609
|
-
#
|
|
610
|
-
#
|
|
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
|
|
620
|
-
#
|
|
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
|
|
634
|
-
#
|
|
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
|
-
|
|
642
|
-
|
|
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
|
-
#
|
|
658
|
-
#
|
|
659
|
-
#
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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(
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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 =
|
|
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