ea 0.6.118 → 0.6.120
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 +45 -0
- data/lib/ea/sources/qea/diagram_builder.rb +14 -1
- data/lib/ea/sources/qea/relationship_builder.rb +16 -0
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +21 -8
- data/lib/ea/svg/ea_emitter/connector_redock.rb +41 -8
- data/lib/ea/svg/ea_emitter/elements.rb +17 -17
- data/lib/ea/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e19d0258340b6c50c472c7e475acfb906207edd915df43ac4ccb521e771fbe90
|
|
4
|
+
data.tar.gz: 26358bd7c1503b5a30f65001fe3a0e380276d1f027df08ecc4db0e67f614be0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed1eb04e3b50194345017aec6611431b67e3a488bc75c219ec58fa0cc298cb6ffe5336422f0e74a3271c9bf6178a2373d85f4630dd5fcc9d467b326abda457d1
|
|
7
|
+
data.tar.gz: dc2eb0768acee7d558d9967a20875f43b077b567964e375dad22e6f69a353b0b34b1d7f85c0fe6046a4e87b868e76e73a9a87e7c670bae29d78419652b435cfb
|
|
@@ -8,6 +8,51 @@ module Ea
|
|
|
8
8
|
module Routing
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
# Offsets stay attached to semantic Start/End IDs even when the
|
|
12
|
+
# saved corner follows the opposite placement order. Clip each leg
|
|
13
|
+
# from its stored mathematical origin, retaining external origins.
|
|
14
|
+
def reversed_stored_corner(model, link_row, diagram_row, geom, context)
|
|
15
|
+
return model if context || !geom.values_at(:sx, :sy, :ex, :ey).all?
|
|
16
|
+
|
|
17
|
+
connector = database.find_connector(link_row.connectorid)
|
|
18
|
+
return model unless connector && reversed_corner_link?(link_row, diagram_row, connector)
|
|
19
|
+
|
|
20
|
+
boxes = stored_ray_boxes(link_row, diagram_row)
|
|
21
|
+
origins = [stored_ray_origin(boxes[0], geom[:sx], geom[:sy]),
|
|
22
|
+
stored_ray_origin(boxes[1], geom[:ex], geom[:ey])]
|
|
23
|
+
x, y = intermediate_waypoints(link_row).first
|
|
24
|
+
bend = Ea::Model::Point.new(x: x, y: y)
|
|
25
|
+
ends = origins.zip(boxes).map do |origin, box|
|
|
26
|
+
inside_box?(origin, box) ? diagonal_corner_exit(origin, bend, box) : origin
|
|
27
|
+
end
|
|
28
|
+
return model unless ends.all?
|
|
29
|
+
|
|
30
|
+
model.waypoints = [ends[0], bend, ends[1]].map { |point| Ea::Model::Waypoint.new(position: point) }
|
|
31
|
+
model.source_anchor, model.target_anchor = origins
|
|
32
|
+
model
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Path follows SOID/EOID placement order, independently of the
|
|
36
|
+
# relationship's Start/End IDs. Only a complete, unique DUID pair
|
|
37
|
+
# can reverse the authored single-corner route.
|
|
38
|
+
def reversed_corner_link?(link_row, diagram_row, connector)
|
|
39
|
+
style = DiagramStyleParser.parse(link_row.style)
|
|
40
|
+
return false unless connector.connector_type == "Association" &&
|
|
41
|
+
style.values_at(:mode, :lwidth, :tree) == ["3", "0", nil] &&
|
|
42
|
+
parse_geometry_fields(link_row.geometry)[:edge] == 2 &&
|
|
43
|
+
intermediate_waypoints(link_row).size == 1
|
|
44
|
+
|
|
45
|
+
ids = style.values_at(:soid, :eoid)
|
|
46
|
+
return false if ids.any? { |id| id.to_s.empty? } || ids.uniq.size != 2
|
|
47
|
+
|
|
48
|
+
placements = database.diagram_objects_for(diagram_row.diagram_id) || []
|
|
49
|
+
ends = ids.map do |id|
|
|
50
|
+
matches = placements.select { |row| DiagramStyleParser.parse(row.objectstyle)[:duid] == id }
|
|
51
|
+
matches.first.ea_object_id if matches.size == 1
|
|
52
|
+
end
|
|
53
|
+
ends == [connector.end_object_id, connector.start_object_id] && ends.uniq.size == 2
|
|
54
|
+
end
|
|
55
|
+
|
|
11
56
|
# An empty custom-line Path has no authored corner to preserve.
|
|
12
57
|
# Keep normalized port routes; replace only the computed EDGE2 jog.
|
|
13
58
|
def direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
@@ -145,6 +145,7 @@ module Ea
|
|
|
145
145
|
font_underline: truthy?(style_hash[:ul]),
|
|
146
146
|
show_tagged_values: truthy?(style_hash[:tag]),
|
|
147
147
|
line_width: effective_line_width(obj_row, style_hash),
|
|
148
|
+
line_color: effective_line_color(obj_row, style_hash),
|
|
148
149
|
background_color: style_hash[:bcol]&.to_i
|
|
149
150
|
)
|
|
150
151
|
end
|
|
@@ -161,6 +162,17 @@ module Ea
|
|
|
161
162
|
nil
|
|
162
163
|
end
|
|
163
164
|
|
|
165
|
+
def effective_line_color(obj_row, style)
|
|
166
|
+
object = database.find_object(obj_row.ea_object_id)
|
|
167
|
+
[style[:lcol], object&.bordercolor].each do |raw|
|
|
168
|
+
next if raw.nil? || raw.to_s.empty?
|
|
169
|
+
|
|
170
|
+
color = raw.to_i
|
|
171
|
+
return color if color >= 0
|
|
172
|
+
end
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
164
176
|
def truthy?(raw)
|
|
165
177
|
raw == "1" || raw == "-1"
|
|
166
178
|
end
|
|
@@ -452,7 +464,8 @@ module Ea
|
|
|
452
464
|
model = overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
453
465
|
model = auto_port_elbow(model, link_row, diagram_row, geom, context)
|
|
454
466
|
model = mark_default_square_ray(model, link_row, geom, context)
|
|
455
|
-
restore_stored_square_ports(model, link_row, diagram_row, geom, context)
|
|
467
|
+
model = restore_stored_square_ports(model, link_row, diagram_row, geom, context)
|
|
468
|
+
reversed_stored_corner(model, link_row, diagram_row, geom, context)
|
|
456
469
|
end
|
|
457
470
|
|
|
458
471
|
# Ray origin for one connector end: the element's center
|
|
@@ -23,6 +23,8 @@ module Ea
|
|
|
23
23
|
args = common_args(row)
|
|
24
24
|
args.merge!(association_args(row)) if klass == Ea::Model::Association
|
|
25
25
|
args.merge!(generalization_args(row)) if klass == Ea::Model::Generalization
|
|
26
|
+
args.merge!(dependency_args(row)) if klass == Ea::Model::Dependency
|
|
27
|
+
args.merge!(realization_args(row)) if klass == Ea::Model::Realization
|
|
26
28
|
klass.new(**args)
|
|
27
29
|
end
|
|
28
30
|
|
|
@@ -50,6 +52,20 @@ module Ea
|
|
|
50
52
|
}
|
|
51
53
|
end
|
|
52
54
|
|
|
55
|
+
def dependency_args(row)
|
|
56
|
+
{
|
|
57
|
+
client_id: id_for_object_id(row.start_object_id),
|
|
58
|
+
supplier_id: id_for_object_id(row.end_object_id)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def realization_args(row)
|
|
63
|
+
{
|
|
64
|
+
realizing_id: id_for_object_id(row.start_object_id),
|
|
65
|
+
contract_id: id_for_object_id(row.end_object_id)
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
53
69
|
def association_args(row)
|
|
54
70
|
{
|
|
55
71
|
source_id: id_for_object_id(row.start_object_id),
|
|
@@ -69,16 +69,9 @@ module Ea
|
|
|
69
69
|
|
|
70
70
|
def literal_name_text(context, literal, y)
|
|
71
71
|
content = literal_display(literal)
|
|
72
|
-
# EA uses the attribute row point-size increment for literal names.
|
|
73
|
-
offset = context.theme.attribute_spec.content_x_offset || 26
|
|
74
|
-
offset += 2 * (context.size - 7) if context.size_unit == "pt"
|
|
75
|
-
if Ea::Fonts::Metrics.compact_compartment_font?(context.size,
|
|
76
|
-
family: context.family, unit: context.size_unit)
|
|
77
|
-
offset -= 1
|
|
78
|
-
end
|
|
79
72
|
TextRenderer.new(
|
|
80
73
|
content: content,
|
|
81
|
-
x: context.bounds.x +
|
|
74
|
+
x: context.bounds.x + content_x_offset(context.theme, context.size, context.size_unit, context.family),
|
|
82
75
|
y: y,
|
|
83
76
|
family: context.family, size: context.size,
|
|
84
77
|
size_unit: context.size_unit, logical_height: context.logical_height,
|
|
@@ -88,6 +81,26 @@ module Ea
|
|
|
88
81
|
).to_svg
|
|
89
82
|
end
|
|
90
83
|
|
|
84
|
+
def content_width(literals, theme:, size:, family:, **font_options)
|
|
85
|
+
return nil if literals.empty?
|
|
86
|
+
|
|
87
|
+
logical_height = font_options[:logical_height] if font_options[:size_unit] == "pt"
|
|
88
|
+
length = literals.map do |literal|
|
|
89
|
+
TextRenderer.estimate_width(literal_display(literal), size, theme.text_width_factor,
|
|
90
|
+
family: family, weight: 400, style: "normal",
|
|
91
|
+
logical_height: logical_height).round
|
|
92
|
+
end.max
|
|
93
|
+
content_x_offset(theme, size, font_options[:size_unit], family) + length + 5
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def content_x_offset(theme, size, size_unit, family)
|
|
97
|
+
# EA uses the attribute row point-size increment for literal names.
|
|
98
|
+
offset = theme.attribute_spec.content_x_offset || 26
|
|
99
|
+
offset += 2 * (size - 7) if size_unit == "pt"
|
|
100
|
+
offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
|
|
101
|
+
offset
|
|
102
|
+
end
|
|
103
|
+
|
|
91
104
|
# EA shows "Name = Code" for literals with a stored
|
|
92
105
|
# default value; bare names otherwise.
|
|
93
106
|
def literal_display(literal)
|
|
@@ -21,7 +21,8 @@ module Ea
|
|
|
21
21
|
return nil unless bounds_map
|
|
22
22
|
points = waypoint_pairs(connector)
|
|
23
23
|
lateral = lateral_tree?(connector, points)
|
|
24
|
-
|
|
24
|
+
trunk = trunk_tree?(connector, points)
|
|
25
|
+
return nil unless points.size == 2 || lateral || trunk
|
|
25
26
|
|
|
26
27
|
rel = relationship_for(connector, model_index)
|
|
27
28
|
return nil unless rel
|
|
@@ -33,6 +34,7 @@ module Ea
|
|
|
33
34
|
target_bounds = bounds_map[target_ref]
|
|
34
35
|
return nil unless source_bounds && target_bounds
|
|
35
36
|
return nil if source_ref == target_ref
|
|
37
|
+
return trunk_pairs(points, source_bounds, target_bounds, connector.style[:tree]) if trunk
|
|
36
38
|
|
|
37
39
|
square = OrthogonalRay.pairs_for(connector, source_bounds, target_bounds)
|
|
38
40
|
return square if square
|
|
@@ -164,6 +166,37 @@ module Ea
|
|
|
164
166
|
end
|
|
165
167
|
private_class_method :lateral_tree?
|
|
166
168
|
|
|
169
|
+
# The QEA builder already docks the Generalization, Aggregation and
|
|
170
|
+
# Association trunks; the other types (Dependency, Nesting, ...) keep
|
|
171
|
+
# their stored ends. A flush (width 0) vertical or horizontal trunk
|
|
172
|
+
# runs axis-aligned into both boxes, so a stored end off that axis or
|
|
173
|
+
# on the far edge of its box is a lost dock the adjacent bend names.
|
|
174
|
+
BUILDER_TRUNK_TYPES = %w[Generalization Aggregation Association].freeze
|
|
175
|
+
|
|
176
|
+
def trunk_tree?(connector, points)
|
|
177
|
+
style = connector.style.to_h
|
|
178
|
+
points.size >= 3 && style[:mode] == "3" && style[:lwidth] == "0" &&
|
|
179
|
+
%w[V H].include?(style[:tree]) && !BUILDER_TRUNK_TYPES.include?(connector.connector_type)
|
|
180
|
+
end
|
|
181
|
+
private_class_method :trunk_tree?
|
|
182
|
+
|
|
183
|
+
def trunk_pairs(points, source, target, tree)
|
|
184
|
+
first = trunk_dock(points[0], points[1], source, tree)
|
|
185
|
+
last = trunk_dock(points[-1], points[-2], target, tree)
|
|
186
|
+
return nil if first == points[0] && last == points[-1]
|
|
187
|
+
|
|
188
|
+
[first, *points[1..-2], last]
|
|
189
|
+
end
|
|
190
|
+
private_class_method :trunk_pairs
|
|
191
|
+
|
|
192
|
+
def trunk_dock(end_point, bend, bounds, tree)
|
|
193
|
+
return end_point unless bounds.width.positive? && bounds.height.positive?
|
|
194
|
+
|
|
195
|
+
dock = tree == "V" ? vertical_target(bend, bounds) : horizontal_target(bend, bounds)
|
|
196
|
+
dock || end_point
|
|
197
|
+
end
|
|
198
|
+
private_class_method :trunk_dock
|
|
199
|
+
|
|
167
200
|
# The bend selects the near target edge even when the boxes overlap.
|
|
168
201
|
# Preserve the source leg and bend; only the final dock changes.
|
|
169
202
|
def lateral_target(bend, bounds, tree)
|
|
@@ -227,7 +260,7 @@ module Ea
|
|
|
227
260
|
def bordered_outline?(connector, diagram, refs, bounds)
|
|
228
261
|
style = connector.style.to_h
|
|
229
262
|
return false unless style[:lwidth] == "0" && !style[:tree]
|
|
230
|
-
return false unless %w[Generalization Aggregation Association].include?(connector.connector_type)
|
|
263
|
+
return false unless %w[Generalization Aggregation Association Dependency].include?(connector.connector_type)
|
|
231
264
|
return false unless bounds.all? { |box| box.width.positive? && box.height.positive? }
|
|
232
265
|
|
|
233
266
|
ends = [[refs.first, style[:soid]], [refs.last, style[:eoid]]]
|
|
@@ -508,15 +541,15 @@ module Ea
|
|
|
508
541
|
|
|
509
542
|
# Polymorphic relationship ends, in connector start→end
|
|
510
543
|
# order: Generalization child→parent (specific_id/general_id),
|
|
544
|
+
# Realization realizing_id/contract_id,
|
|
511
545
|
# Dependency client→supplier (client_id/supplier_id),
|
|
512
546
|
# Association source_id/target_id.
|
|
513
547
|
def end_refs(rel)
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
else
|
|
519
|
-
[rel.source_id, rel.target_id]
|
|
548
|
+
case rel
|
|
549
|
+
when Ea::Model::Generalization then [rel.specific_id, rel.general_id]
|
|
550
|
+
when Ea::Model::Realization then [rel.realizing_id, rel.contract_id]
|
|
551
|
+
when Ea::Model::Dependency then [rel.client_id, rel.supplier_id]
|
|
552
|
+
else [rel.source_id, rel.target_id]
|
|
520
553
|
end
|
|
521
554
|
end
|
|
522
555
|
|
|
@@ -151,20 +151,20 @@ module Ea
|
|
|
151
151
|
else
|
|
152
152
|
[]
|
|
153
153
|
end
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
# Titles wrap; content rows grow the drawn box to fit their insets.
|
|
155
|
+
row_w = [row_content_width(attr_lines, 5, size, family, logical_height),
|
|
156
|
+
row_content_width(op_lines, 3, size, family, logical_height),
|
|
157
|
+
Compartment::EnumLiterals.content_width(
|
|
158
|
+
enum_literals_for(classifier),
|
|
159
|
+
theme: theme, size: size, family: family,
|
|
160
|
+
size_unit: size_unit, logical_height: logical_height
|
|
161
|
+
)].compact.max
|
|
162
|
+
if row_w && row_w > bounds.width
|
|
163
|
+
bounds = Ea::Model::Bounds.new(
|
|
164
|
+
x: bounds.x, y: bounds.y,
|
|
165
|
+
width: row_w,
|
|
166
|
+
height: bounds.height
|
|
167
|
+
)
|
|
168
168
|
end
|
|
169
169
|
stereotype_fqname = element.style&.fetch(:stereotype_fqname, nil)
|
|
170
170
|
header_lines = classifier ? Element::HeaderLines.for(classifier,
|
|
@@ -272,7 +272,7 @@ module Ea
|
|
|
272
272
|
model_element: model_element,
|
|
273
273
|
classifier: classifier,
|
|
274
274
|
fill: resolve_fill(element, classifier),
|
|
275
|
-
stroke: resolve_stroke,
|
|
275
|
+
stroke: resolve_stroke(element),
|
|
276
276
|
stroke_width: resolve_stroke_width(element),
|
|
277
277
|
text_fill: theme.text_color,
|
|
278
278
|
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
@@ -665,8 +665,8 @@ module Ea
|
|
|
665
665
|
color_resolver.fill_for(element, classifier)
|
|
666
666
|
end
|
|
667
667
|
|
|
668
|
-
def resolve_stroke
|
|
669
|
-
color_resolver.stroke_for(
|
|
668
|
+
def resolve_stroke(element)
|
|
669
|
+
color_resolver.stroke_for(element)
|
|
670
670
|
end
|
|
671
671
|
|
|
672
672
|
def resolve_stroke_width(element)
|
data/lib/ea/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.6.120
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 0.8.114
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 0.8.114
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: lutaml-path
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|