ea 0.6.90 → 0.6.92
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: 641441bf5b181aaadd045223f47a59069d916f48247f19ab4e6bc6c19cfeee03
|
|
4
|
+
data.tar.gz: 0eb407d782f28301f0747a2f251ce3412f97cc60882f87cc7062567d1e7e4a64
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d705861a5209c8646e01fb097b4329e634744bdeef07355d00598a45bff1134ab186c69c0088250a3a3c5ece15d2c39281653c0e667a473fea80dbfd4bb9b9c0
|
|
7
|
+
data.tar.gz: 84fce6fa28c77169812dda317ef720035c1ac9b17ac854238aa376480980308b7c506e6f3fa52e23807e7cc851546fe65b0c6b8bda72b937b1d9a99ab82953ad
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Sources
|
|
5
|
+
module Qea
|
|
6
|
+
class DiagramBuilder
|
|
7
|
+
# Internal geometry for choosing the facing edges of stored routes.
|
|
8
|
+
module Routing
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# Keep a vertical tree trunk whose bends still fit both boxes
|
|
12
|
+
# and the open gap; the closest facing edges need not share its x.
|
|
13
|
+
def preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
14
|
+
return false unless connector.connector_type == "Aggregation" && points.size == 4
|
|
15
|
+
|
|
16
|
+
style = DiagramStyleParser.parse(link_row.style)
|
|
17
|
+
return false unless style[:tree] == "V" && style[:lwidth] == "2"
|
|
18
|
+
|
|
19
|
+
tree_bends_fit?(points[1], points[-2], src, tgt)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def tree_bends_fit?(first, last, source, target)
|
|
23
|
+
first[0] != last[0] && first[1] == last[1] &&
|
|
24
|
+
within_vertical_gap?(first[1], source, target) &&
|
|
25
|
+
within_horizontal_span?(first, source) && within_horizontal_span?(last, target)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def within_vertical_gap?(height, source, target)
|
|
29
|
+
height < source.y - 1 && height > target.y + target.height + 1
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def within_horizontal_span?(point, bounds)
|
|
33
|
+
point[0].between?(bounds.x, bounds.x + bounds.width)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns a replacement 2-point route between the closest
|
|
37
|
+
# facing edges, or the original points when: the boxes overlap
|
|
38
|
+
# on both axes, the current route already docks those edges,
|
|
39
|
+
# or no aligned dock fits the edges' overlap range.
|
|
40
|
+
def facing_edge_route(points, src, tgt)
|
|
41
|
+
v_gap = v_dir = h_gap = h_dir = nil
|
|
42
|
+
if src.y >= tgt.y + tgt.height
|
|
43
|
+
v_gap = src.y - (tgt.y + tgt.height)
|
|
44
|
+
v_dir = [:t, :b]
|
|
45
|
+
elsif tgt.y >= src.y + src.height
|
|
46
|
+
v_gap = tgt.y - (src.y + src.height)
|
|
47
|
+
v_dir = [:b, :t]
|
|
48
|
+
end
|
|
49
|
+
if src.x >= tgt.x + tgt.width
|
|
50
|
+
h_gap = src.x - (tgt.x + tgt.width)
|
|
51
|
+
h_dir = [:l, :r]
|
|
52
|
+
elsif tgt.x >= src.x + src.width
|
|
53
|
+
h_gap = tgt.x - (tgt.x + tgt.width)
|
|
54
|
+
h_dir = [:r, :l]
|
|
55
|
+
end
|
|
56
|
+
return points if v_gap.nil? && h_gap.nil?
|
|
57
|
+
|
|
58
|
+
if !v_gap.nil? && (h_gap.nil? || v_gap <= h_gap)
|
|
59
|
+
src_edge, tgt_edge = v_dir
|
|
60
|
+
else
|
|
61
|
+
src_edge, tgt_edge = h_dir
|
|
62
|
+
end
|
|
63
|
+
return points if docks_edges?(points, src, src_edge, tgt, tgt_edge)
|
|
64
|
+
|
|
65
|
+
if %i[t b].include?(src_edge)
|
|
66
|
+
lo = [src.x, tgt.x].max
|
|
67
|
+
hi = [src.x + src.width, tgt.x + tgt.width].min
|
|
68
|
+
return points if lo > hi
|
|
69
|
+
|
|
70
|
+
x = points.first[0].clamp(lo, hi)
|
|
71
|
+
[[x, src_edge == :t ? src.y : src.y + src.height],
|
|
72
|
+
[x, tgt_edge == :t ? tgt.y : tgt.y + tgt.height]]
|
|
73
|
+
else
|
|
74
|
+
lo = [src.y, tgt.y].max
|
|
75
|
+
hi = [src.y + src.height, tgt.y + tgt.height].min
|
|
76
|
+
return points if lo > hi
|
|
77
|
+
|
|
78
|
+
y = points.first[1].clamp(lo, hi)
|
|
79
|
+
[[src_edge == :l ? src.x : src.x + src.width, y],
|
|
80
|
+
[tgt_edge == :l ? tgt.x : tgt.x + tgt.width, y]]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def docks_edges?(points, src, src_edge, tgt, tgt_edge)
|
|
85
|
+
on_edge?(points.first, src, src_edge) && on_edge?(points.last, tgt, tgt_edge)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def on_edge?(pt, b, edge, tol = 2)
|
|
89
|
+
x, y = pt
|
|
90
|
+
case edge
|
|
91
|
+
when :l then (x - b.x).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
|
|
92
|
+
when :r then (x - (b.x + b.width)).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
|
|
93
|
+
when :t then (y - b.y).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
|
|
94
|
+
else (y - (b.y + b.height)).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -10,6 +10,10 @@ module Ea
|
|
|
10
10
|
# DiagramConnectors (including pixel coordinates and parsed
|
|
11
11
|
# styles).
|
|
12
12
|
class DiagramBuilder
|
|
13
|
+
autoload :Routing, "ea/sources/qea/diagram_builder/routing"
|
|
14
|
+
include Routing
|
|
15
|
+
private_constant :Routing
|
|
16
|
+
|
|
13
17
|
attr_reader :database
|
|
14
18
|
|
|
15
19
|
# Generalizations whose EA CustomProperties carry
|
|
@@ -643,9 +647,11 @@ module Ea
|
|
|
643
647
|
# EDGE=2 routes are excluded; they are already exact.
|
|
644
648
|
if %w[Association Aggregation].include?(connector.connector_type) &&
|
|
645
649
|
edge_out != 2
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
650
|
+
src = bounds_from_rect(source_placement)
|
|
651
|
+
tgt = bounds_from_rect(target_placement)
|
|
652
|
+
unless preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
653
|
+
points = facing_edge_route(points, src, tgt)
|
|
654
|
+
end
|
|
649
655
|
# A width-2 Aggregation that keeps its stored bends is
|
|
650
656
|
# docked like a Generalization one.
|
|
651
657
|
if connector.connector_type == "Aggregation" &&
|
|
@@ -661,68 +667,6 @@ module Ea
|
|
|
661
667
|
end
|
|
662
668
|
end
|
|
663
669
|
|
|
664
|
-
# Returns a replacement 2-point route between the closest
|
|
665
|
-
# facing edges, or the original points when: the boxes overlap
|
|
666
|
-
# on both axes, the current route already docks those edges,
|
|
667
|
-
# or no aligned dock fits the edges' overlap range.
|
|
668
|
-
def facing_edge_route(points, src, tgt)
|
|
669
|
-
v_gap = v_dir = h_gap = h_dir = nil
|
|
670
|
-
if src.y >= tgt.y + tgt.height
|
|
671
|
-
v_gap = src.y - (tgt.y + tgt.height)
|
|
672
|
-
v_dir = [:t, :b]
|
|
673
|
-
elsif tgt.y >= src.y + src.height
|
|
674
|
-
v_gap = tgt.y - (src.y + src.height)
|
|
675
|
-
v_dir = [:b, :t]
|
|
676
|
-
end
|
|
677
|
-
if src.x >= tgt.x + tgt.width
|
|
678
|
-
h_gap = src.x - (tgt.x + tgt.width)
|
|
679
|
-
h_dir = [:l, :r]
|
|
680
|
-
elsif tgt.x >= src.x + src.width
|
|
681
|
-
h_gap = tgt.x - (tgt.x + tgt.width)
|
|
682
|
-
h_dir = [:r, :l]
|
|
683
|
-
end
|
|
684
|
-
return points if v_gap.nil? && h_gap.nil?
|
|
685
|
-
|
|
686
|
-
if !v_gap.nil? && (h_gap.nil? || v_gap <= h_gap)
|
|
687
|
-
src_edge, tgt_edge = v_dir
|
|
688
|
-
else
|
|
689
|
-
src_edge, tgt_edge = h_dir
|
|
690
|
-
end
|
|
691
|
-
return points if docks_edges?(points, src, src_edge, tgt, tgt_edge)
|
|
692
|
-
|
|
693
|
-
if %i[t b].include?(src_edge)
|
|
694
|
-
lo = [src.x, tgt.x].max
|
|
695
|
-
hi = [src.x + src.width, tgt.x + tgt.width].min
|
|
696
|
-
return points if lo > hi
|
|
697
|
-
|
|
698
|
-
x = points.first[0].clamp(lo, hi)
|
|
699
|
-
[[x, src_edge == :t ? src.y : src.y + src.height],
|
|
700
|
-
[x, tgt_edge == :t ? tgt.y : tgt.y + tgt.height]]
|
|
701
|
-
else
|
|
702
|
-
lo = [src.y, tgt.y].max
|
|
703
|
-
hi = [src.y + src.height, tgt.y + tgt.height].min
|
|
704
|
-
return points if lo > hi
|
|
705
|
-
|
|
706
|
-
y = points.first[1].clamp(lo, hi)
|
|
707
|
-
[[src_edge == :l ? src.x : src.x + src.width, y],
|
|
708
|
-
[tgt_edge == :l ? tgt.x : tgt.x + tgt.width, y]]
|
|
709
|
-
end
|
|
710
|
-
end
|
|
711
|
-
|
|
712
|
-
def docks_edges?(points, src, src_edge, tgt, tgt_edge)
|
|
713
|
-
on_edge?(points.first, src, src_edge) && on_edge?(points.last, tgt, tgt_edge)
|
|
714
|
-
end
|
|
715
|
-
|
|
716
|
-
def on_edge?(pt, b, edge, tol = 2)
|
|
717
|
-
x, y = pt
|
|
718
|
-
case edge
|
|
719
|
-
when :l then (x - b.x).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
|
|
720
|
-
when :r then (x - (b.x + b.width)).abs <= tol && y.between?(b.y - tol, b.y + b.height + tol)
|
|
721
|
-
when :t then (y - b.y).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
|
|
722
|
-
else (y - (b.y + b.height)).abs <= tol && x.between?(b.x - tol, b.x + b.width + tol)
|
|
723
|
-
end
|
|
724
|
-
end
|
|
725
|
-
|
|
726
670
|
# A tree Path is current when its bend height still falls
|
|
727
671
|
# within the source element's y-range (the exit height EA
|
|
728
672
|
# records); a bend outside the element is stale and EA
|
|
@@ -19,6 +19,8 @@ module Ea
|
|
|
19
19
|
# for those — only simple role names render.
|
|
20
20
|
class EndLabel
|
|
21
21
|
PROPERTY_STEREOTYPE = "«Property»"
|
|
22
|
+
SEAT_FONT_SIZE = 7
|
|
23
|
+
SEAT_X_INSET = 3
|
|
22
24
|
|
|
23
25
|
attr_reader :canvas, :model_index, :document, :theme,
|
|
24
26
|
:font_family, :font_size, :font_unit
|
|
@@ -41,8 +43,9 @@ module Ea
|
|
|
41
43
|
# anchor - [x, y] of the connector endpoint
|
|
42
44
|
# connector - the DiagramConnector (for relationship lookup)
|
|
43
45
|
# end_kind - :source or :target
|
|
46
|
+
# heading - [dx, dy] of the first leg leaving the anchor
|
|
44
47
|
def texts(text_box:, mult_box:, anchor:, connector:, end_kind:,
|
|
45
|
-
tree: false)
|
|
48
|
+
tree: false, heading: nil)
|
|
46
49
|
from_property, role, mult = role_and_multiplicity(connector, end_kind)
|
|
47
50
|
if role_empty?(role) && mult_empty?(mult)
|
|
48
51
|
other_from_prop, other_role, other_mult = role_and_multiplicity(connector,
|
|
@@ -73,6 +76,11 @@ module Ea
|
|
|
73
76
|
# the end has none.
|
|
74
77
|
stereo = connector_source_dest_stereotype(connector, end_kind)
|
|
75
78
|
stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
|
|
79
|
+
direction = leg_direction(end_kind, heading)
|
|
80
|
+
if direction && !(tree && zero_offset?(text_box, mult_box))
|
|
81
|
+
text_pos = role_seat(direction, anchor, text_box, role) || text_pos
|
|
82
|
+
mult_pos = mult_seat(direction, anchor, mult_box) || mult_pos
|
|
83
|
+
end
|
|
76
84
|
|
|
77
85
|
build_texts(role:, mult:, standalone:,
|
|
78
86
|
text_pos:, mult_pos:, stereo:, text_box:)
|
|
@@ -109,6 +117,63 @@ module Ea
|
|
|
109
117
|
end
|
|
110
118
|
end
|
|
111
119
|
|
|
120
|
+
# Stored source-end boxes on a 7pt label whose first leg
|
|
121
|
+
# leaves straight up or straight right are seated at the box
|
|
122
|
+
# corner, anchor + (OX, OY) with no sign flip, 3px to its
|
|
123
|
+
# right: the multiplicity 6px above it on an upward leg and
|
|
124
|
+
# 15px below it on a rightward one, the role 7px above it on
|
|
125
|
+
# a rightward one. Other sizes, headings, the target end,
|
|
126
|
+
# custom presentation keep their offsets; zero multiplicity boxes keep their default seat.
|
|
127
|
+
def leg_direction(end_kind, heading)
|
|
128
|
+
return nil unless source_seat?(end_kind, heading)
|
|
129
|
+
|
|
130
|
+
dx, dy = heading
|
|
131
|
+
return :up if dx.zero? && dy.negative?
|
|
132
|
+
return :right if dy.zero? && dx.positive?
|
|
133
|
+
|
|
134
|
+
nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Role text on a rightward leg is left-aligned at the corner,
|
|
138
|
+
# 7px above it. Upward legs keep the plain role offset.
|
|
139
|
+
def role_seat(direction, anchor, box, role)
|
|
140
|
+
return nil unless direction == :right
|
|
141
|
+
return nil if box.nil? || role_empty?(role)
|
|
142
|
+
return nil unless default_presentation?(box)
|
|
143
|
+
|
|
144
|
+
x, y = seat_corner(anchor, box)
|
|
145
|
+
[x + SEAT_X_INSET, y - 7]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def mult_seat(direction, anchor, box)
|
|
149
|
+
return nil if box.nil? || stored_offsets_zero?(box)
|
|
150
|
+
return nil unless default_presentation?(box)
|
|
151
|
+
|
|
152
|
+
x, y = seat_corner(anchor, box)
|
|
153
|
+
[x + SEAT_X_INSET, direction == :right ? y + 15 : y - 6]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def source_seat?(end_kind, heading)
|
|
157
|
+
end_kind == :source && heading &&
|
|
158
|
+
font_size == SEAT_FONT_SIZE && font_unit == "pt"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Boxes with explicit string-keyed OX/OY (the QEA shape) that
|
|
162
|
+
# are unrotated, unhidden, unstyled and left/center aligned.
|
|
163
|
+
def default_presentation?(box)
|
|
164
|
+
return false unless box["ox"] && box["oy"]
|
|
165
|
+
return false unless [nil, 0, 1].include?(box["alignment"])
|
|
166
|
+
return false unless [nil, -1].include?(box["color"])
|
|
167
|
+
|
|
168
|
+
box["rotation"].to_i.zero? && box["direction"].to_i.zero? &&
|
|
169
|
+
box.values_at("hidden", "bold", "italic", "underline")
|
|
170
|
+
.none?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def seat_corner(anchor, box)
|
|
174
|
+
[anchor[0] + box["ox"].to_i, anchor[1] + box["oy"].to_i]
|
|
175
|
+
end
|
|
176
|
+
|
|
112
177
|
def build_texts(role:, mult:, standalone:, text_pos:, mult_pos:,
|
|
113
178
|
stereo:, text_box:)
|
|
114
179
|
if role && !role.empty? && text_pos
|
|
@@ -78,15 +78,16 @@ module Ea
|
|
|
78
78
|
boxes = connector.label_boxes || {}
|
|
79
79
|
tree = tree_shaped?(points)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
stored_src = end_boxes(boxes, :llt, :llb)
|
|
82
|
+
src_tb, src_mb = stored_src || default_label_boxes(points, :source)
|
|
83
83
|
tgt_tb, tgt_mb = end_boxes(boxes, :lrt, :lrb) ||
|
|
84
84
|
default_label_boxes(points, :target)
|
|
85
85
|
|
|
86
86
|
texts = []
|
|
87
87
|
end_renderer.texts(text_box: src_tb, mult_box: src_mb,
|
|
88
88
|
anchor: source_pt, connector: connector,
|
|
89
|
-
end_kind: :source, tree: tree && tree[:source]
|
|
89
|
+
end_kind: :source, tree: tree && tree[:source],
|
|
90
|
+
heading: stored_src && leg_heading(points)).each { |t| texts << t }
|
|
90
91
|
end_renderer.texts(text_box: tgt_tb,
|
|
91
92
|
mult_box: tgt_mb,
|
|
92
93
|
anchor: target_pt, connector: connector,
|
|
@@ -105,6 +106,10 @@ module Ea
|
|
|
105
106
|
{ source: true, target: true }
|
|
106
107
|
end
|
|
107
108
|
|
|
109
|
+
def leg_heading(points)
|
|
110
|
+
[points[1][0] - points[0][0], points[1][1] - points[0][1]]
|
|
111
|
+
end
|
|
112
|
+
|
|
108
113
|
def end_boxes(boxes, text_key, mult_key)
|
|
109
114
|
return nil if boxes[text_key].nil? && boxes[mult_key].nil?
|
|
110
115
|
|
data/lib/ea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.92
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -815,6 +815,7 @@ files:
|
|
|
815
815
|
- lib/ea/sources/qea/connector_relationship_map.rb
|
|
816
816
|
- lib/ea/sources/qea/context_diagram_support.rb
|
|
817
817
|
- lib/ea/sources/qea/diagram_builder.rb
|
|
818
|
+
- lib/ea/sources/qea/diagram_builder/routing.rb
|
|
818
819
|
- lib/ea/sources/qea/diagram_style_parser.rb
|
|
819
820
|
- lib/ea/sources/qea/id_normalizer.rb
|
|
820
821
|
- lib/ea/sources/qea/instance_builder.rb
|