ea 0.6.97 → 0.6.98
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 +35 -0
- data/lib/ea/sources/qea/diagram_builder.rb +21 -9
- data/lib/ea/svg/ea_emitter/connector_redock.rb +31 -3
- data/lib/ea/svg/ea_emitter/label/end_label.rb +33 -1
- data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +37 -0
- 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: b3c13e631927ad57c6168b6d7c02eaed8e933c89ad7dfcd0a055fb6b8a69e8e1
|
|
4
|
+
data.tar.gz: 3edb35912780be1170fce402dd141823f4c5e44c92a6f84451fa136d3eb026a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6544102e41ff1bde04737e18f93de5ea0ee657c47335f7eeb12316162ca33fbf2e0d5b0dc1572fccc5174373781b1368508d606641286c88ebe2d7f9f2232079
|
|
7
|
+
data.tar.gz: eb3cb4702ea44ba8759ab9774a1ca445684b9d6ce7043153b4823657c191fff45c6db267ee74b1ed20cdec2064248b60a0f5112827b92edfa4aaecaa495fa093
|
|
@@ -8,6 +8,41 @@ module Ea
|
|
|
8
8
|
module Routing
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
+
# An empty custom-line Path has no authored corner to preserve.
|
|
12
|
+
# Keep normalized port routes; replace only the computed EDGE2 jog.
|
|
13
|
+
def direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
14
|
+
return model if context
|
|
15
|
+
return model unless empty_path_ray_candidate?(model, link_row, geom)
|
|
16
|
+
|
|
17
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
18
|
+
return model unless empty_path_ray_gap?(source, target, geom)
|
|
19
|
+
|
|
20
|
+
model.waypoints = [model.source_port, model.target_port].map do |point|
|
|
21
|
+
Ea::Model::Waypoint.new(position: point)
|
|
22
|
+
end
|
|
23
|
+
set_stored_ray_origins(model, source, target, geom)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def empty_path_ray_gap?(source, target, geom)
|
|
27
|
+
return false unless source.x + source.width < target.x
|
|
28
|
+
|
|
29
|
+
[stored_ray_origin(source, geom[:sx], geom[:sy]),
|
|
30
|
+
stored_ray_origin(target, geom[:ex], geom[:ey])].all? do |origin|
|
|
31
|
+
within_height?(origin.y, source) && within_height?(origin.y, target)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def empty_path_ray_candidate?(model, link_row, geom)
|
|
36
|
+
%w[Generalization Aggregation].include?(model.connector_type) &&
|
|
37
|
+
model.waypoints.size == 3 && empty_path_ray_style?(model.style, link_row, geom) &&
|
|
38
|
+
model.has_geometry_offsets && model.source_port && model.target_port
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def empty_path_ray_style?(style, link_row, geom)
|
|
42
|
+
style[:mode] == "3" && style[:lwidth] == "2" && !style[:tree] &&
|
|
43
|
+
geom[:edge] == 2 && link_row.path.to_s.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
11
46
|
# Stored SY/EY use mathematical Y; ray origins use screen Y.
|
|
12
47
|
# Computed-jog origins are already normalized to their derived ports.
|
|
13
48
|
def correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
@@ -16,16 +16,11 @@ module Ea
|
|
|
16
16
|
|
|
17
17
|
attr_reader :database
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# seat (t_xref 'connector property' rows keyed by connector
|
|
22
|
-
# guid; VALU 0/empty both mean false).
|
|
19
|
+
# This flag requests the false annotation, not the property's Boolean
|
|
20
|
+
# value. An explicit true (-1) must not request a false annotation.
|
|
23
21
|
def substitutable_connector?(conn)
|
|
24
|
-
@substitutable_guids ||= database.collections[:xrefs]
|
|
25
|
-
|
|
26
|
-
x.xref_type == "connector property" &&
|
|
27
|
-
x.name == "CustomProperties" &&
|
|
28
|
-
x.description.to_s.include?("isSubstitutable")
|
|
22
|
+
@substitutable_guids ||= database.collections[:xrefs].select do |xref|
|
|
23
|
+
false_substitutability_xref?(xref)
|
|
29
24
|
end.map(&:client).to_set
|
|
30
25
|
@substitutable_guids.include?(conn.ea_guid)
|
|
31
26
|
end
|
|
@@ -99,6 +94,22 @@ module Ea
|
|
|
99
94
|
|
|
100
95
|
private
|
|
101
96
|
|
|
97
|
+
def false_substitutability_xref?(xref)
|
|
98
|
+
return false unless xref.xref_type == "connector property" && xref.name == "CustomProperties"
|
|
99
|
+
|
|
100
|
+
description = xref.description.to_s
|
|
101
|
+
description.include?("isSubstitutable") && !true_substitutability_property?(description)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def true_substitutability_property?(description)
|
|
105
|
+
description.scan(/@PROP=(.*?)@ENDPROP;/m).any? do |match|
|
|
106
|
+
property = match.first
|
|
107
|
+
property.include?("@NAME=isSubstitutable@ENDNAME;") &&
|
|
108
|
+
property.include?("@TYPE=Boolean@ENDTYPE;") &&
|
|
109
|
+
property[/@VALU=(.*?)@ENDVALU;/m, 1] == "-1"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
102
113
|
def build_elements(diagram_row)
|
|
103
114
|
objs = database.diagram_objects_for(diagram_row.diagram_id) || []
|
|
104
115
|
objs.map { |obj_row| build_element(obj_row, diagram_row) }
|
|
@@ -410,6 +421,7 @@ module Ea
|
|
|
410
421
|
has_geometry_offsets: geometry_has_offsets?(link_row.geometry),
|
|
411
422
|
is_substitutable: substitutable_connector?(connector)
|
|
412
423
|
), link_row, diagram_row, geom)
|
|
424
|
+
model = direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
413
425
|
correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
414
426
|
end
|
|
415
427
|
|
|
@@ -209,18 +209,46 @@ module Ea
|
|
|
209
209
|
divider_y_by_ref[target_ref]]
|
|
210
210
|
return nil unless bounds.all? && dividers.all?
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
preserve_source = explicit_horizontal_source_dock?(pairs, connector)
|
|
213
|
+
adjusted = row_slot_pairs(pairs, bounds, dividers, preserve_source: preserve_source)
|
|
213
214
|
if stroke_outline?(connector, bounds) &&
|
|
214
215
|
pairs == pairs_for(connector, diagram, model_index, bounds_map)
|
|
215
216
|
original = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
|
|
216
|
-
original_final = row_slot_pairs(original, bounds, dividers)
|
|
217
|
+
original_final = row_slot_pairs(original, bounds, dividers, preserve_source: preserve_source)
|
|
217
218
|
adjusted = original_final if original != pairs && !forward_route?(original_final, adjusted)
|
|
218
219
|
end
|
|
219
220
|
adjusted == pairs ? nil : adjusted
|
|
220
221
|
end
|
|
221
222
|
|
|
222
|
-
|
|
223
|
+
# A supplied horizontal port already specifies its row. Moving it
|
|
224
|
+
# to the next compartment row would discard that explicit coordinate.
|
|
225
|
+
def explicit_horizontal_source_dock?(pairs, connector)
|
|
226
|
+
return false unless explicit_horizontal_ray?(connector)
|
|
227
|
+
|
|
228
|
+
connector.source_anchor.y == connector.target_anchor.y &&
|
|
229
|
+
connector.source_port.y == connector.source_anchor.y &&
|
|
230
|
+
pairs.first[1] == connector.source_port.y
|
|
231
|
+
end
|
|
232
|
+
private_class_method :explicit_horizontal_source_dock?
|
|
233
|
+
|
|
234
|
+
def explicit_horizontal_ray?(connector)
|
|
235
|
+
explicit_horizontal_ray_style?(connector) && connector.has_geometry_offsets &&
|
|
236
|
+
connector.source_port && connector.target_port &&
|
|
237
|
+
connector.source_anchor && connector.target_anchor
|
|
238
|
+
end
|
|
239
|
+
private_class_method :explicit_horizontal_ray?
|
|
240
|
+
|
|
241
|
+
def explicit_horizontal_ray_style?(connector)
|
|
242
|
+
style = connector.style.to_h
|
|
243
|
+
%w[Generalization Aggregation].include?(connector.connector_type) &&
|
|
244
|
+
style[:mode] == "3" && !style[:tree] && !style[:direct] && !style[:regenerated]
|
|
245
|
+
end
|
|
246
|
+
private_class_method :explicit_horizontal_ray_style?
|
|
247
|
+
|
|
248
|
+
def row_slot_pairs(pairs, bounds, dividers, preserve_source: false)
|
|
223
249
|
pairs.each_with_index.map do |(x, y), index|
|
|
250
|
+
next [x, y] if preserve_source && index.zero?
|
|
251
|
+
|
|
224
252
|
row_slot?(x, y, bounds[index], dividers[index]) ? [x, y + ROW_SLOT_PITCH] : [x, y]
|
|
225
253
|
end
|
|
226
254
|
end
|
|
@@ -86,12 +86,14 @@ module Ea
|
|
|
86
86
|
stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
|
|
87
87
|
direction = leg_direction(end_kind, heading)
|
|
88
88
|
if direction && !(tree && zero_offset?(text_box, mult_box))
|
|
89
|
-
text_pos =
|
|
89
|
+
text_pos = plain_vertical_role(connector, direction, anchor, text_box, role) ||
|
|
90
|
+
source_property_seat(connector, direction, anchor, text_box, role) ||
|
|
90
91
|
plain_upward_seat(connector, direction, anchor, text_box, role) ||
|
|
91
92
|
role_seat(direction, anchor, text_box, role) || text_pos
|
|
92
93
|
mult_pos = directional_multiplicity(connector, direction, anchor, mult_box, mult) ||
|
|
93
94
|
mult_seat(direction, anchor, mult_box) || mult_pos
|
|
94
95
|
elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
|
|
96
|
+
text_pos = target_upward_role(connector, heading, anchor, text_box, role) || text_pos
|
|
95
97
|
mult_pos = target_multiplicity_seat(heading, anchor, mult_box) || mult_pos
|
|
96
98
|
end
|
|
97
99
|
|
|
@@ -201,6 +203,36 @@ module Ea
|
|
|
201
203
|
[x - SEAT_X_INSET - width, y - 6]
|
|
202
204
|
end
|
|
203
205
|
|
|
206
|
+
def target_upward_role(connector, heading, anchor, box, role)
|
|
207
|
+
return nil unless heading && heading[0].zero? && heading[1].negative?
|
|
208
|
+
|
|
209
|
+
plain_vertical_role(connector, :target_up, anchor, box, role)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def plain_vertical_role(connector, direction, anchor, box, role)
|
|
213
|
+
return nil unless %i[down target_up].include?(direction) && plain_vertical_box?(box)
|
|
214
|
+
|
|
215
|
+
end_kind = direction == :down ? :source : :target
|
|
216
|
+
return nil unless plain_vertical_connector?(connector, end_kind)
|
|
217
|
+
|
|
218
|
+
width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
219
|
+
return nil unless width
|
|
220
|
+
|
|
221
|
+
x, y = seat_corner(anchor, box)
|
|
222
|
+
[x - width - 8, y + (end_kind == :source ? 15 : -7)]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def plain_vertical_box?(box)
|
|
226
|
+
seat_font? && cambria_box?(box) && [0, 1].include?(box["alignment"])
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def plain_vertical_connector?(connector, end_kind)
|
|
230
|
+
style = connector.style.to_h
|
|
231
|
+
connector.connector_type == "Association" &&
|
|
232
|
+
connector_source_dest_stereotype(connector, end_kind).empty? &&
|
|
233
|
+
style[:mode] == "3" && style[:lwidth] == "0" && !style[:tree]
|
|
234
|
+
end
|
|
235
|
+
|
|
204
236
|
def plain_role_route?(style)
|
|
205
237
|
%w[1 3].include?(style[:mode]) && %w[0 2].include?(style[:lwidth]) &&
|
|
206
238
|
[nil, "V"].include?(style[:tree])
|
|
@@ -96,6 +96,9 @@ module Ea
|
|
|
96
96
|
LINE_PITCH = 13
|
|
97
97
|
|
|
98
98
|
def label_position(connector, points)
|
|
99
|
+
name_seat = stored_name_position(connector, points)
|
|
100
|
+
return name_seat if name_seat
|
|
101
|
+
|
|
99
102
|
return midpoint(points) unless default_substitutable_seat?(connector) &&
|
|
100
103
|
upward_four_point_route?(points)
|
|
101
104
|
|
|
@@ -103,6 +106,40 @@ module Ea
|
|
|
103
106
|
[source[0] + 3, ((source[1] + bend[1]) / 2).floor - 1]
|
|
104
107
|
end
|
|
105
108
|
|
|
109
|
+
# At 7pt, a centered name's stored corner is 3px below its baseline.
|
|
110
|
+
def stored_name_position(connector, points)
|
|
111
|
+
box = stored_name_box(connector)
|
|
112
|
+
return nil unless box
|
|
113
|
+
|
|
114
|
+
width = Ea::Fonts::Metrics.text_length(midpoint_content(connector), font_size, family: font_family)
|
|
115
|
+
return nil unless width
|
|
116
|
+
|
|
117
|
+
x, y = midpoint(points)
|
|
118
|
+
offset_x, offset_y = box.values_at("ox", "oy").map(&:to_i)
|
|
119
|
+
[x + offset_x - (width / 2.0).floor, y + offset_y - 3]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def stored_name_box(connector)
|
|
123
|
+
return nil unless connector.is_a?(Ea::Model::DiagramConnector) && measured_name_font?
|
|
124
|
+
return nil if connector.is_substitutable || stereotype_label(connector)
|
|
125
|
+
return nil if midpoint_content(connector).include?(": ")
|
|
126
|
+
|
|
127
|
+
box = connector.label_boxes.to_h[:lmt]
|
|
128
|
+
box if default_name_box?(box)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def measured_name_font?
|
|
132
|
+
font_family.to_s.casecmp("Cambria").zero? && font_size == 7 && font_unit == "pt"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def default_name_box?(box)
|
|
136
|
+
return false unless box && box["alignment"] == 1
|
|
137
|
+
return false unless box.values_at("ox", "oy").all?
|
|
138
|
+
return false unless [nil, 0, 1].include?(box["direction"]) && [nil, -1].include?(box["color"])
|
|
139
|
+
|
|
140
|
+
box["rotation"].to_i.zero? && box.values_at("bold", "italic", "underline", "hidden").none?
|
|
141
|
+
end
|
|
142
|
+
|
|
106
143
|
def default_substitutable_seat?(connector)
|
|
107
144
|
connector.is_a?(Ea::Model::DiagramConnector) && connector.is_substitutable &&
|
|
108
145
|
connector.connector_type == "Generalization" && font_size == 7 &&
|
data/lib/ea/version.rb
CHANGED