ea 0.6.92 → 0.6.94
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 +4 -4
- data/lib/ea/sources/qea/diagram_builder/routing.rb +59 -0
- data/lib/ea/sources/qea/diagram_builder.rb +14 -10
- data/lib/ea/svg/ea_emitter/connector_redock.rb +52 -6
- data/lib/ea/svg/ea_emitter/label/end_label.rb +38 -5
- data/lib/ea/svg/ea_emitter/labels.rb +7 -4
- data/lib/ea/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2c872554c2534df450bc335593fb39ed9363f3fe20053ef001b1621c5cdec6b
|
|
4
|
+
data.tar.gz: 79cecbb5a25a07a38d5bac1df1aaf413ffbc471cdd39938cd64cdf8bca822ce7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d24168a33acbf362b4feff14508abb00562139e6602caed154c7f4b5e8bfa2b0f773b045fd27585f0610946f8fb2e4e9e6dbf2b8667ab785fc1bd71f18e9aa83
|
|
7
|
+
data.tar.gz: 1559678bc826ecdb82d710a72723709fa195907e8a11bb138eed728ba8b9804b30fcab7f75a024f27f8df34558ba52ec7f373e18bf1f46e6c7a89f07af8cf70b
|
|
@@ -8,6 +8,21 @@ module Ea
|
|
|
8
8
|
module Routing
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
# A lateral tree leaves the side edge before turning upward.
|
|
12
|
+
# Keep that dock only when its first leg actually points outward.
|
|
13
|
+
def lateral_source_exit?(points, link_row, bounds)
|
|
14
|
+
return false unless points.size == 3 && DiagramStyleParser.parse(link_row.style)[:tree] == "LV"
|
|
15
|
+
|
|
16
|
+
anchor, bend = points.take(2)
|
|
17
|
+
anchor[1] == bend[1] && outward_side_exit?(anchor[0], bend[0], bounds)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def outward_side_exit?(anchor_x, bend_x, bounds)
|
|
21
|
+
left = bounds.x
|
|
22
|
+
right = left + bounds.width
|
|
23
|
+
(anchor_x == left && bend_x < left) || (anchor_x == right && bend_x > right)
|
|
24
|
+
end
|
|
25
|
+
|
|
11
26
|
# Keep a vertical tree trunk whose bends still fit both boxes
|
|
12
27
|
# and the open gap; the closest facing edges need not share its x.
|
|
13
28
|
def preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
@@ -19,6 +34,50 @@ module Ea
|
|
|
19
34
|
tree_bends_fit?(points[1], points[-2], src, tgt)
|
|
20
35
|
end
|
|
21
36
|
|
|
37
|
+
# A width-2 Mode 3 horizontal tree keeps its stored vertical trunk
|
|
38
|
+
# and docks both ends on the facing sides' 1px outer outlines at
|
|
39
|
+
# the bend heights. Returns nil unless the trunk sits strictly
|
|
40
|
+
# inside the open gap and each bend lies inside its own box.
|
|
41
|
+
def horizontal_tree_route(points, link_row, connector, src, tgt)
|
|
42
|
+
return nil unless points.size == 4 && horizontal_tree_style?(link_row, connector)
|
|
43
|
+
|
|
44
|
+
first, last = points.values_at(1, 2)
|
|
45
|
+
src_x, tgt_x = facing_outline_xs(src, tgt)
|
|
46
|
+
return nil unless src_x && trunk_in_gap?(first, last, src_x, tgt_x)
|
|
47
|
+
return nil unless inside_height?(first[1], src) && inside_height?(last[1], tgt)
|
|
48
|
+
|
|
49
|
+
[[src_x, first[1]], first, last, [tgt_x, last[1]]]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def horizontal_tree_style?(link_row, connector)
|
|
53
|
+
style = DiagramStyleParser.parse(link_row.style)
|
|
54
|
+
%w[Generalization Aggregation].include?(connector.connector_type) &&
|
|
55
|
+
style[:tree] == "H" && style[:lwidth] == "2" && style[:mode] == "3"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def trunk_in_gap?(first, last, src_x, tgt_x)
|
|
59
|
+
first[0] == last[0] && first[1] != last[1] &&
|
|
60
|
+
first[0] > [src_x, tgt_x].min && first[0] < [src_x, tgt_x].max
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def inside_height?(height, bounds)
|
|
64
|
+
height > bounds.y && height < bounds.y + bounds.height
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Source and target x on the facing sides' outer outlines, or nil
|
|
68
|
+
# when the boxes are not horizontally separated.
|
|
69
|
+
def facing_outline_xs(src, tgt)
|
|
70
|
+
source_left = src.x
|
|
71
|
+
source_right = source_left + src.width
|
|
72
|
+
target_left = tgt.x
|
|
73
|
+
target_right = target_left + tgt.width
|
|
74
|
+
if target_right < source_left
|
|
75
|
+
[source_left - 1, target_right + 1]
|
|
76
|
+
elsif source_right < target_left
|
|
77
|
+
[source_right + 1, target_left - 1]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
22
81
|
def tree_bends_fit?(first, last, source, target)
|
|
23
82
|
first[0] != last[0] && first[1] == last[1] &&
|
|
24
83
|
within_vertical_gap?(first[1], source, target) &&
|
|
@@ -577,9 +577,8 @@ module Ea
|
|
|
577
577
|
# describe the source-side and target-side bend offsets
|
|
578
578
|
# relative to the edge attachment points.
|
|
579
579
|
#
|
|
580
|
-
# Path takes precedence when present
|
|
581
|
-
#
|
|
582
|
-
# connectors with at most one bend on each end.
|
|
580
|
+
# Custom-line Path takes precedence when present. Direct mode
|
|
581
|
+
# ignores old custom bends; SX/SY/EX/EY supply its endpoint ports.
|
|
583
582
|
#
|
|
584
583
|
# If we can't resolve the source or target element placement
|
|
585
584
|
# for this diagram, we emit no waypoints (the connector will
|
|
@@ -607,8 +606,10 @@ module Ea
|
|
|
607
606
|
element_edge_point(target_placement, :target, edge_out)
|
|
608
607
|
|
|
609
608
|
intermediate = intermediate_waypoints(link_row)
|
|
610
|
-
if
|
|
611
|
-
|
|
609
|
+
if DiagramStyleParser.parse(link_row.style)[:mode] == "1"
|
|
610
|
+
points = [source_point, target_point]
|
|
611
|
+
elsif edge_out == 2 && source_port && target_port &&
|
|
612
|
+
!valid_tree_path?(intermediate, source_placement)
|
|
612
613
|
# A stale tree Path (not on the source's facing edge) is
|
|
613
614
|
# recomputed: source port -> corner at (target dock x,
|
|
614
615
|
# source exit y) -> target port.
|
|
@@ -645,10 +646,13 @@ module Ea
|
|
|
645
646
|
# Between DISJOINT boxes, a stored route that does not dock the
|
|
646
647
|
# closest facing edge pair is redrawn straight between them.
|
|
647
648
|
# EDGE=2 routes are excluded; they are already exact.
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
649
|
+
src = bounds_from_rect(source_placement)
|
|
650
|
+
tgt = bounds_from_rect(target_placement)
|
|
651
|
+
tree_route = horizontal_tree_route(points, link_row, connector, src, tgt)
|
|
652
|
+
if tree_route
|
|
653
|
+
points = tree_route
|
|
654
|
+
elsif %w[Association Aggregation].include?(connector.connector_type) &&
|
|
655
|
+
edge_out != 2
|
|
652
656
|
unless preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
653
657
|
points = facing_edge_route(points, src, tgt)
|
|
654
658
|
end
|
|
@@ -923,7 +927,7 @@ module Ea
|
|
|
923
927
|
target_y = tgt_b.y + tgt_b.height
|
|
924
928
|
source_y -= inset if points[1][1] < source_y - inset
|
|
925
929
|
target_y += inset if points[-2][1] > target_y + inset
|
|
926
|
-
unless strict && points[1][1] >= source_y
|
|
930
|
+
unless lateral_source_exit?(points, link_row, src_b) || (strict && points[1][1] >= source_y)
|
|
927
931
|
points[0] = [points[1][0].clamp(src_b.x, src_b.x + src_b.width), source_y]
|
|
928
932
|
end
|
|
929
933
|
unless strict && points[-2][1] <= target_y
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Ea
|
|
4
4
|
module Svg
|
|
5
5
|
module EaEmitter
|
|
6
|
-
# Re-docks
|
|
6
|
+
# Re-docks direct connectors and lateral tree target legs
|
|
7
7
|
# against the DRAWN element outlines captured during the element
|
|
8
8
|
# render pass. EA clips connector rays at drawn boxes — grown
|
|
9
9
|
# classifier heights and autosized package bodies — never the
|
|
@@ -13,17 +13,15 @@ module Ea
|
|
|
13
13
|
module ConnectorRedock
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
|
-
# Returns replacement waypoint pairs
|
|
16
|
+
# Returns replacement waypoint pairs for direct or lateral tree routes,
|
|
17
17
|
# for the connector, or nil when the connector must keep its
|
|
18
18
|
# parsed waypoints (not direct, not 2-point, unresolved ends,
|
|
19
19
|
# self-loop, or no drawn bounds for either end).
|
|
20
20
|
def pairs_for(connector, diagram, model_index, bounds_map)
|
|
21
21
|
return nil unless bounds_map
|
|
22
|
-
return nil unless direct?(connector) || two_point?(connector)
|
|
23
|
-
|
|
24
22
|
points = waypoint_pairs(connector)
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
lateral = lateral_tree?(connector, points)
|
|
24
|
+
return nil unless points.size == 2 || lateral
|
|
27
25
|
|
|
28
26
|
rel = relationship_for(connector, model_index)
|
|
29
27
|
return nil unless rel
|
|
@@ -36,6 +34,11 @@ module Ea
|
|
|
36
34
|
return nil unless source_bounds && target_bounds
|
|
37
35
|
return nil if source_ref == target_ref
|
|
38
36
|
|
|
37
|
+
if lateral
|
|
38
|
+
target = lateral_target(points[1], target_bounds, connector.style[:tree])
|
|
39
|
+
return target && [points[0], points[1], target]
|
|
40
|
+
end
|
|
41
|
+
|
|
39
42
|
anchors = [connector.source_anchor, connector.target_anchor]
|
|
40
43
|
bounds = [source_bounds, target_bounds]
|
|
41
44
|
original = clipped_pairs(bounds, anchors)
|
|
@@ -50,6 +53,49 @@ module Ea
|
|
|
50
53
|
forward_route?(original, candidate) ? candidate : original
|
|
51
54
|
end
|
|
52
55
|
|
|
56
|
+
def lateral_tree?(connector, points)
|
|
57
|
+
style = connector.style.to_h
|
|
58
|
+
points.size == 3 && style[:mode] == "3" && style[:lwidth] == "2" &&
|
|
59
|
+
%w[LV LH].include?(style[:tree]) &&
|
|
60
|
+
%w[Generalization Aggregation].include?(connector.connector_type)
|
|
61
|
+
end
|
|
62
|
+
private_class_method :lateral_tree?
|
|
63
|
+
|
|
64
|
+
# The bend selects the near target edge even when the boxes overlap.
|
|
65
|
+
# Preserve the source leg and bend; only the final dock changes.
|
|
66
|
+
def lateral_target(bend, bounds, tree)
|
|
67
|
+
return nil unless bounds.width.positive? && bounds.height.positive?
|
|
68
|
+
|
|
69
|
+
tree == "LV" ? vertical_target(bend, bounds) : horizontal_target(bend, bounds)
|
|
70
|
+
end
|
|
71
|
+
private_class_method :lateral_target
|
|
72
|
+
|
|
73
|
+
def vertical_target(bend, bounds)
|
|
74
|
+
x, y = bend
|
|
75
|
+
return nil unless x.between?(bounds.x, bounds.x + bounds.width)
|
|
76
|
+
|
|
77
|
+
edge = facing_coordinate(y, bounds.y, bounds.height)
|
|
78
|
+
[x, edge] if edge
|
|
79
|
+
end
|
|
80
|
+
private_class_method :vertical_target
|
|
81
|
+
|
|
82
|
+
def horizontal_target(bend, bounds)
|
|
83
|
+
x, y = bend
|
|
84
|
+
return nil unless y.between?(bounds.y, bounds.y + bounds.height)
|
|
85
|
+
|
|
86
|
+
edge = facing_coordinate(x, bounds.x, bounds.width)
|
|
87
|
+
[edge, y] if edge
|
|
88
|
+
end
|
|
89
|
+
private_class_method :horizontal_target
|
|
90
|
+
|
|
91
|
+
def facing_coordinate(bend, start, extent)
|
|
92
|
+
return start if bend < start
|
|
93
|
+
return start + extent if bend > start + extent
|
|
94
|
+
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
private_class_method :facing_coordinate
|
|
98
|
+
|
|
53
99
|
def stroke_outline?(connector, bounds)
|
|
54
100
|
connector.style.to_h[:lwidth] == "2" &&
|
|
55
101
|
%w[Generalization Aggregation].include?(connector.connector_type) &&
|
|
@@ -21,6 +21,7 @@ module Ea
|
|
|
21
21
|
PROPERTY_STEREOTYPE = "«Property»"
|
|
22
22
|
SEAT_FONT_SIZE = 7
|
|
23
23
|
SEAT_X_INSET = 3
|
|
24
|
+
SEAT_STEREOTYPE_GAP = 12
|
|
24
25
|
|
|
25
26
|
attr_reader :canvas, :model_index, :document, :theme,
|
|
26
27
|
:font_family, :font_size, :font_unit
|
|
@@ -44,12 +45,22 @@ module Ea
|
|
|
44
45
|
# connector - the DiagramConnector (for relationship lookup)
|
|
45
46
|
# end_kind - :source or :target
|
|
46
47
|
# heading - [dx, dy] of the first leg leaving the anchor
|
|
48
|
+
# stored_labels - the connector has stored label boxes at
|
|
49
|
+
# either end; false keeps the opposite-end
|
|
50
|
+
# fallback
|
|
47
51
|
def texts(text_box:, mult_box:, anchor:, connector:, end_kind:,
|
|
48
|
-
tree: false, heading: nil)
|
|
52
|
+
tree: false, heading: nil, stored_labels: false)
|
|
49
53
|
from_property, role, mult = role_and_multiplicity(connector, end_kind)
|
|
50
54
|
if role_empty?(role) && mult_empty?(mult)
|
|
51
55
|
other_from_prop, other_role, other_mult = role_and_multiplicity(connector,
|
|
52
56
|
opposite(end_kind))
|
|
57
|
+
if stored_labels
|
|
58
|
+
return [] if role_empty?(other_role) && mult_empty?(other_mult)
|
|
59
|
+
|
|
60
|
+
return own_stereotype_only(text_box, anchor, connector,
|
|
61
|
+
end_kind)
|
|
62
|
+
end
|
|
63
|
+
|
|
53
64
|
role = other_role if role_empty?(role)
|
|
54
65
|
mult ||= other_mult
|
|
55
66
|
from_property ||= other_from_prop
|
|
@@ -88,6 +99,19 @@ module Ea
|
|
|
88
99
|
|
|
89
100
|
private
|
|
90
101
|
|
|
102
|
+
# With stored boxes on the connector, an end that carries no
|
|
103
|
+
# data of its own draws only its own stereotype; the data end
|
|
104
|
+
# draws the role and multiplicity; with no data anywhere nothing
|
|
105
|
+
# is drawn. Connectors with no stored box keep the opposite-end
|
|
106
|
+
# fallback.
|
|
107
|
+
def own_stereotype_only(text_box, anchor, connector, end_kind)
|
|
108
|
+
stereo = connector_source_dest_stereotype(connector, end_kind)
|
|
109
|
+
pos = position_at(text_box, anchor)
|
|
110
|
+
return [] if stereo.nil? || stereo.empty? || pos.nil?
|
|
111
|
+
|
|
112
|
+
[text_at(pos[0], pos[1], "«#{stereo}»")]
|
|
113
|
+
end
|
|
114
|
+
|
|
91
115
|
# EA's tree-route label clusters (hand-verified against the
|
|
92
116
|
# EA-published reference, SY 8542 with canvas=(0,0)):
|
|
93
117
|
# source-end pairs stack at the exit point (role +3/-23,
|
|
@@ -154,8 +178,11 @@ module Ea
|
|
|
154
178
|
end
|
|
155
179
|
|
|
156
180
|
def source_seat?(end_kind, heading)
|
|
157
|
-
end_kind == :source && heading &&
|
|
158
|
-
|
|
181
|
+
end_kind == :source && heading && seat_font?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def seat_font?
|
|
185
|
+
font_size == SEAT_FONT_SIZE && font_unit == "pt"
|
|
159
186
|
end
|
|
160
187
|
|
|
161
188
|
# Boxes with explicit string-keyed OX/OY (the QEA shape) that
|
|
@@ -181,7 +208,7 @@ module Ea
|
|
|
181
208
|
texts = [text_at(x, y, role)]
|
|
182
209
|
if stereo
|
|
183
210
|
stereo_x = x + stereotype_offset(role, stereo, text_box)
|
|
184
|
-
texts << text_at(stereo_x, y + property_y_offset, stereo)
|
|
211
|
+
texts << text_at(stereo_x, y + property_y_offset(text_box), stereo)
|
|
185
212
|
end
|
|
186
213
|
if mult && !mult.empty? && mult_pos
|
|
187
214
|
texts << text_at(*mult_pos, mult)
|
|
@@ -368,7 +395,13 @@ module Ea
|
|
|
368
395
|
[anchor[0] + offset_x.to_i, anchor[1] - offset_y.to_i]
|
|
369
396
|
end
|
|
370
397
|
|
|
371
|
-
|
|
398
|
+
# The stereotype line sits 12px below the role on 7pt labels
|
|
399
|
+
# with default presentation;
|
|
400
|
+
# other sizes and custom presentation keep size + 6.
|
|
401
|
+
def property_y_offset(text_box)
|
|
402
|
+
return SEAT_STEREOTYPE_GAP if seat_font? && text_box &&
|
|
403
|
+
default_presentation?(text_box)
|
|
404
|
+
|
|
372
405
|
font_size + 6
|
|
373
406
|
end
|
|
374
407
|
|
|
@@ -79,19 +79,22 @@ module Ea
|
|
|
79
79
|
tree = tree_shaped?(points)
|
|
80
80
|
|
|
81
81
|
stored_src = end_boxes(boxes, :llt, :llb)
|
|
82
|
+
stored_tgt = end_boxes(boxes, :lrt, :lrb)
|
|
83
|
+
stored_labels = !(stored_src.nil? && stored_tgt.nil?)
|
|
82
84
|
src_tb, src_mb = stored_src || default_label_boxes(points, :source)
|
|
83
|
-
tgt_tb, tgt_mb =
|
|
84
|
-
default_label_boxes(points, :target)
|
|
85
|
+
tgt_tb, tgt_mb = stored_tgt || default_label_boxes(points, :target)
|
|
85
86
|
|
|
86
87
|
texts = []
|
|
87
88
|
end_renderer.texts(text_box: src_tb, mult_box: src_mb,
|
|
88
89
|
anchor: source_pt, connector: connector,
|
|
89
90
|
end_kind: :source, tree: tree && tree[:source],
|
|
90
|
-
heading: stored_src && leg_heading(points)
|
|
91
|
+
heading: stored_src && leg_heading(points),
|
|
92
|
+
stored_labels: stored_labels).each { |t| texts << t }
|
|
91
93
|
end_renderer.texts(text_box: tgt_tb,
|
|
92
94
|
mult_box: tgt_mb,
|
|
93
95
|
anchor: target_pt, connector: connector,
|
|
94
|
-
end_kind: :target, tree: tree && tree[:target]
|
|
96
|
+
end_kind: :target, tree: tree && tree[:target],
|
|
97
|
+
stored_labels: stored_labels).each { |t| texts << t }
|
|
95
98
|
texts
|
|
96
99
|
end
|
|
97
100
|
|
data/lib/ea/version.rb
CHANGED