ea 0.6.96 → 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/config/themes/39.yml +8 -0
- data/lib/ea/sources/qea/diagram_builder/routing.rb +107 -0
- data/lib/ea/sources/qea/diagram_builder.rb +23 -10
- data/lib/ea/svg/ea_emitter/connector_redock.rb +46 -4
- data/lib/ea/svg/ea_emitter/label/end_label.rb +43 -1
- data/lib/ea/svg/ea_emitter/label/midpoint_label.rb +37 -0
- data/lib/ea/svg/ea_emitter/labels.rb +60 -6
- data/lib/ea/theme/definition.rb +23 -2
- data/lib/ea/theme/loader.rb +3 -1
- data/lib/ea/version.rb +1 -1
- metadata +2 -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,113 @@ 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
|
+
|
|
46
|
+
# Stored SY/EY use mathematical Y; ray origins use screen Y.
|
|
47
|
+
# Computed-jog origins are already normalized to their derived ports.
|
|
48
|
+
def correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
49
|
+
return model if context
|
|
50
|
+
return model unless stored_ray_candidate?(model, link_row, geom)
|
|
51
|
+
|
|
52
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
53
|
+
return model unless stored_ray_gap?(source, target, model.style[:mode], geom[:edge])
|
|
54
|
+
|
|
55
|
+
set_stored_ray_origins(model, source, target, geom)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def stored_ray_boxes(link_row, diagram_row)
|
|
59
|
+
connector = database.find_connector(link_row.connectorid)
|
|
60
|
+
[source_placement_for(connector, diagram_row),
|
|
61
|
+
target_placement_for(connector, diagram_row)].map { |placement| bounds_from_rect(placement) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def set_stored_ray_origins(model, source, target, geom)
|
|
65
|
+
model.source_anchor = stored_ray_origin(source, geom[:sx], geom[:sy])
|
|
66
|
+
model.target_anchor = stored_ray_origin(target, geom[:ex], geom[:ey])
|
|
67
|
+
model
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def stored_ray_candidate?(model, link_row, geom)
|
|
71
|
+
%w[Generalization Aggregation].include?(model.connector_type) &&
|
|
72
|
+
model.waypoints.size == 2 && stored_ray_style?(model.style, link_row) &&
|
|
73
|
+
stored_ray_geometry?(model, geom) && !normalized_port_anchors?(model)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def stored_ray_style?(style, link_row)
|
|
77
|
+
%w[1 3].include?(style[:mode]) && !style[:tree] &&
|
|
78
|
+
(style[:mode] == "1" || link_row.path.to_s.empty?)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def stored_ray_geometry?(model, geom)
|
|
82
|
+
model.has_geometry_offsets && model.source_port && model.target_port &&
|
|
83
|
+
(!geom[:sy].to_i.zero? || !geom[:ey].to_i.zero?)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def normalized_port_anchors?(model)
|
|
87
|
+
same_port_anchor?(model.source_anchor, model.source_port) &&
|
|
88
|
+
same_port_anchor?(model.target_anchor, model.target_port)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def same_port_anchor?(anchor, port)
|
|
92
|
+
anchor.x == port.x && anchor.y == port.y
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def stored_ray_gap?(source, target, mode, edge)
|
|
96
|
+
return horizontal_ray_gap?(source, target) || vertical_ray_gap?(source, target) if mode == "1"
|
|
97
|
+
|
|
98
|
+
if edge == 1
|
|
99
|
+
target.y + target.height < source.y
|
|
100
|
+
elsif edge == 4
|
|
101
|
+
target.x + target.width < source.x
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def horizontal_ray_gap?(source, target)
|
|
106
|
+
source.x + source.width < target.x || target.x + target.width < source.x
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def vertical_ray_gap?(source, target)
|
|
110
|
+
source.y + source.height < target.y || target.y + target.height < source.y
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def stored_ray_origin(bounds, x_offset, y_offset)
|
|
114
|
+
Ea::Model::Point.new(x: bounds.x + bounds.width / 2 + x_offset.to_i,
|
|
115
|
+
y: bounds.y + bounds.height / 2 - y_offset.to_i)
|
|
116
|
+
end
|
|
117
|
+
|
|
11
118
|
# Square routing keeps the stored orthogonal trunk, even when a
|
|
12
119
|
# shorter facing-edge route exists between the same boxes.
|
|
13
120
|
def stored_square_route(link_row, connector, source, target)
|
|
@@ -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) }
|
|
@@ -385,7 +396,7 @@ module Ea
|
|
|
385
396
|
geom = parse_geometry_fields(link_row.geometry)
|
|
386
397
|
context = ContextDiagramSupport.package_context(diagram_row) ||
|
|
387
398
|
ContextDiagramSupport.element_regenerate?(diagram_row)
|
|
388
|
-
collapse_computed_jog(Ea::Model::DiagramConnector.new(
|
|
399
|
+
model = collapse_computed_jog(Ea::Model::DiagramConnector.new(
|
|
389
400
|
id: IdNormalizer.synthetic("dc", diagram_row.diagram_id,
|
|
390
401
|
link_row.instance_id),
|
|
391
402
|
diagram_id: IdNormalizer.from_guid(diagram_row.ea_guid),
|
|
@@ -410,6 +421,8 @@ 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)
|
|
425
|
+
correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
413
426
|
end
|
|
414
427
|
|
|
415
428
|
# Ray origin for one connector end: the element's center
|
|
@@ -106,7 +106,7 @@ module Ea
|
|
|
106
106
|
def outline_connector?(connector, bounds)
|
|
107
107
|
return true if %w[Generalization Aggregation].include?(connector.connector_type)
|
|
108
108
|
|
|
109
|
-
return false unless stored_association?(connector)
|
|
109
|
+
return false unless stored_association?(connector) || offset_free_association?(connector)
|
|
110
110
|
|
|
111
111
|
# Diagonal Association rounding and tree docks keep their existing rules.
|
|
112
112
|
pairs = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
|
|
@@ -122,6 +122,20 @@ module Ea
|
|
|
122
122
|
end
|
|
123
123
|
private_class_method :stored_association?
|
|
124
124
|
|
|
125
|
+
def offset_free_association?(connector)
|
|
126
|
+
style = connector.style.to_h
|
|
127
|
+
connector.connector_type == "Association" && style[:mode] == "3" && !style[:tree] &&
|
|
128
|
+
%i[direct regenerated].none? { |key| style.key?(key) } && offset_free_anchors?(connector)
|
|
129
|
+
end
|
|
130
|
+
private_class_method :offset_free_association?
|
|
131
|
+
|
|
132
|
+
def offset_free_anchors?(connector)
|
|
133
|
+
!connector.has_geometry_offsets &&
|
|
134
|
+
[connector.source_port, connector.target_port].all?(&:nil?) &&
|
|
135
|
+
[connector.source_anchor, connector.target_anchor].all?
|
|
136
|
+
end
|
|
137
|
+
private_class_method :offset_free_anchors?
|
|
138
|
+
|
|
125
139
|
def clipped_pairs(bounds, anchors)
|
|
126
140
|
source, target = bounds
|
|
127
141
|
source_anchor, target_anchor = anchors
|
|
@@ -195,18 +209,46 @@ module Ea
|
|
|
195
209
|
divider_y_by_ref[target_ref]]
|
|
196
210
|
return nil unless bounds.all? && dividers.all?
|
|
197
211
|
|
|
198
|
-
|
|
212
|
+
preserve_source = explicit_horizontal_source_dock?(pairs, connector)
|
|
213
|
+
adjusted = row_slot_pairs(pairs, bounds, dividers, preserve_source: preserve_source)
|
|
199
214
|
if stroke_outline?(connector, bounds) &&
|
|
200
215
|
pairs == pairs_for(connector, diagram, model_index, bounds_map)
|
|
201
216
|
original = clipped_pairs(bounds, [connector.source_anchor, connector.target_anchor])
|
|
202
|
-
original_final = row_slot_pairs(original, bounds, dividers)
|
|
217
|
+
original_final = row_slot_pairs(original, bounds, dividers, preserve_source: preserve_source)
|
|
203
218
|
adjusted = original_final if original != pairs && !forward_route?(original_final, adjusted)
|
|
204
219
|
end
|
|
205
220
|
adjusted == pairs ? nil : adjusted
|
|
206
221
|
end
|
|
207
222
|
|
|
208
|
-
|
|
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)
|
|
209
249
|
pairs.each_with_index.map do |(x, y), index|
|
|
250
|
+
next [x, y] if preserve_source && index.zero?
|
|
251
|
+
|
|
210
252
|
row_slot?(x, y, bounds[index], dividers[index]) ? [x, y + ROW_SLOT_PITCH] : [x, y]
|
|
211
253
|
end
|
|
212
254
|
end
|
|
@@ -86,11 +86,15 @@ 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
|
|
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
|
|
97
|
+
mult_pos = target_multiplicity_seat(heading, anchor, mult_box) || mult_pos
|
|
94
98
|
end
|
|
95
99
|
|
|
96
100
|
build_texts(role:, mult:, standalone:,
|
|
@@ -199,6 +203,36 @@ module Ea
|
|
|
199
203
|
[x - SEAT_X_INSET - width, y - 6]
|
|
200
204
|
end
|
|
201
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
|
+
|
|
202
236
|
def plain_role_route?(style)
|
|
203
237
|
%w[1 3].include?(style[:mode]) && %w[0 2].include?(style[:lwidth]) &&
|
|
204
238
|
[nil, "V"].include?(style[:tree])
|
|
@@ -251,6 +285,14 @@ module Ea
|
|
|
251
285
|
[x - width - 3, y + 15] if width
|
|
252
286
|
end
|
|
253
287
|
|
|
288
|
+
def target_multiplicity_seat(heading, anchor, box)
|
|
289
|
+
return nil unless seat_font?
|
|
290
|
+
return nil unless heading && heading[0].zero? && heading[1].negative?
|
|
291
|
+
return nil unless cambria_box?(box) && stored_offsets_zero?(box)
|
|
292
|
+
|
|
293
|
+
[anchor[0] + 8, anchor[1] - 7]
|
|
294
|
+
end
|
|
295
|
+
|
|
254
296
|
def cambria_box?(box)
|
|
255
297
|
box && default_presentation?(box) && font_family.to_s.casecmp("Cambria").zero?
|
|
256
298
|
end
|
|
@@ -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 &&
|
|
@@ -97,6 +97,7 @@ module Ea
|
|
|
97
97
|
mult_box: tgt_mb,
|
|
98
98
|
anchor: target_pt, connector: connector,
|
|
99
99
|
end_kind: :target, tree: tree && tree[:target],
|
|
100
|
+
heading: stored_tgt && leg_heading(points.reverse),
|
|
100
101
|
stored_labels: stored_labels).each { |t| texts << t }
|
|
101
102
|
texts
|
|
102
103
|
end
|
|
@@ -112,11 +113,21 @@ module Ea
|
|
|
112
113
|
{ source: true, target: true }
|
|
113
114
|
end
|
|
114
115
|
|
|
115
|
-
#
|
|
116
|
-
#
|
|
116
|
+
# A diagonal clipped leg keeps the seat of its unambiguous exit face.
|
|
117
|
+
# Unknown faces retain the stored-upward fallback.
|
|
117
118
|
def source_heading(connector, points)
|
|
118
119
|
heading = leg_heading(points)
|
|
119
|
-
return heading unless points.size == 2 && heading
|
|
120
|
+
return heading unless points.size == 2 && heading.none?(&:zero?)
|
|
121
|
+
|
|
122
|
+
box = source_dock_bounds(connector)
|
|
123
|
+
face = box && dock_face_heading(points.first, box)
|
|
124
|
+
return face if face && face.zip(heading).any? { |axis, delta| (axis * delta).positive? }
|
|
125
|
+
|
|
126
|
+
stored_upward_heading(connector, heading)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def stored_upward_heading(connector, heading)
|
|
130
|
+
return heading unless heading[1].negative?
|
|
120
131
|
|
|
121
132
|
stored = waypoint_pairs(connector)
|
|
122
133
|
return heading unless stored.size == 2
|
|
@@ -125,6 +136,49 @@ module Ea
|
|
|
125
136
|
original[0].zero? && original[1].negative? ? original : heading
|
|
126
137
|
end
|
|
127
138
|
|
|
139
|
+
def source_dock_bounds(connector)
|
|
140
|
+
return nil unless model_index && drawn_bounds
|
|
141
|
+
|
|
142
|
+
relationship = model_index[connector.relationship_ref]
|
|
143
|
+
return nil unless relationship
|
|
144
|
+
|
|
145
|
+
source, target = ConnectorRedock.end_refs(relationship)
|
|
146
|
+
return nil if source == target
|
|
147
|
+
return nil unless drawn_bounds[target]
|
|
148
|
+
|
|
149
|
+
drawn_bounds[source]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def dock_face_heading(point, box)
|
|
153
|
+
return nil unless box.width.positive? && box.height.positive?
|
|
154
|
+
|
|
155
|
+
vertical_dock_heading(point, box) || horizontal_dock_heading(point, box)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def vertical_dock_heading(point, box)
|
|
159
|
+
x, y = point
|
|
160
|
+
left = box.x
|
|
161
|
+
return nil unless x > left && x < left + box.width
|
|
162
|
+
|
|
163
|
+
top = box.y
|
|
164
|
+
return [0, -1] if [top, top - 1].include?(y)
|
|
165
|
+
|
|
166
|
+
bottom = top + box.height
|
|
167
|
+
[0, 1] if [bottom, bottom + 1].include?(y)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def horizontal_dock_heading(point, box)
|
|
171
|
+
x, y = point
|
|
172
|
+
top = box.y
|
|
173
|
+
return nil unless y > top && y < top + box.height
|
|
174
|
+
|
|
175
|
+
left = box.x
|
|
176
|
+
return [-1, 0] if [left, left - 1].include?(x)
|
|
177
|
+
|
|
178
|
+
right = left + box.width
|
|
179
|
+
[1, 0] if [right, right + 1].include?(x)
|
|
180
|
+
end
|
|
181
|
+
|
|
128
182
|
def leg_heading(points)
|
|
129
183
|
[points[1][0] - points[0][0], points[1][1] - points[0][1]]
|
|
130
184
|
end
|
|
@@ -172,7 +226,7 @@ module Ea
|
|
|
172
226
|
theme: theme,
|
|
173
227
|
font_family: label_font_family,
|
|
174
228
|
font_size: label_font_size,
|
|
175
|
-
font_unit: theme.
|
|
229
|
+
font_unit: theme.label_font_size_unit
|
|
176
230
|
)
|
|
177
231
|
end
|
|
178
232
|
|
|
@@ -182,7 +236,7 @@ module Ea
|
|
|
182
236
|
model_index: model_index,
|
|
183
237
|
font_family: label_font_family,
|
|
184
238
|
font_size: label_font_size,
|
|
185
|
-
font_unit: theme.
|
|
239
|
+
font_unit: theme.label_font_size_unit
|
|
186
240
|
)
|
|
187
241
|
end
|
|
188
242
|
|
|
@@ -212,7 +266,7 @@ module Ea
|
|
|
212
266
|
end
|
|
213
267
|
|
|
214
268
|
def label_font_size
|
|
215
|
-
theme.
|
|
269
|
+
theme.label_font_size || DEFAULT_SIZE
|
|
216
270
|
end
|
|
217
271
|
end
|
|
218
272
|
end
|
data/lib/ea/theme/definition.rb
CHANGED
|
@@ -65,7 +65,8 @@ module Ea
|
|
|
65
65
|
text_y_offset: 12, line_height: 13).freeze
|
|
66
66
|
|
|
67
67
|
def initialize(id:, name: nil, font_family: nil, font_size: nil,
|
|
68
|
-
label_font_family: nil,
|
|
68
|
+
label_font_family: nil, label_font_size: nil,
|
|
69
|
+
label_font_size_unit: nil,
|
|
69
70
|
font_size_unit: "pt", text_weight_normal: 400,
|
|
70
71
|
text_weight_bold: 700,
|
|
71
72
|
element_font_size: 9, element_font_unit: "pt",
|
|
@@ -83,6 +84,8 @@ module Ea
|
|
|
83
84
|
@name = name
|
|
84
85
|
@font_family = font_family
|
|
85
86
|
@label_font_family = label_font_family
|
|
87
|
+
@label_font_size = label_font_size
|
|
88
|
+
@label_font_size_unit = label_font_size_unit
|
|
86
89
|
@font_size = font_size
|
|
87
90
|
@font_size_unit = font_size_unit
|
|
88
91
|
@text_weight_normal = text_weight_normal
|
|
@@ -125,8 +128,18 @@ module Ea
|
|
|
125
128
|
@label_font_family || font_family
|
|
126
129
|
end
|
|
127
130
|
|
|
131
|
+
def label_font_size
|
|
132
|
+
@label_font_size || font_size
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def label_font_size_unit
|
|
136
|
+
@label_font_size_unit || font_size_unit
|
|
137
|
+
end
|
|
138
|
+
|
|
128
139
|
def with(**overrides)
|
|
129
|
-
overrides
|
|
140
|
+
clear_label_override(overrides, :font_family, :label_font_family)
|
|
141
|
+
clear_label_override(overrides, :font_size, :label_font_size)
|
|
142
|
+
clear_label_override(overrides, :font_size_unit, :label_font_size_unit)
|
|
130
143
|
Definition.new(**to_h.merge(overrides))
|
|
131
144
|
end
|
|
132
145
|
|
|
@@ -159,6 +172,8 @@ module Ea
|
|
|
159
172
|
font_family: font_family, font_size: font_size,
|
|
160
173
|
font_size_unit: font_size_unit,
|
|
161
174
|
label_font_family: @label_font_family,
|
|
175
|
+
label_font_size: @label_font_size,
|
|
176
|
+
label_font_size_unit: @label_font_size_unit,
|
|
162
177
|
text_weight_normal: text_weight_normal,
|
|
163
178
|
text_weight_bold: text_weight_bold,
|
|
164
179
|
element_font_size: element_font_size,
|
|
@@ -179,6 +194,12 @@ module Ea
|
|
|
179
194
|
package: package, note: note, fills: fills
|
|
180
195
|
}
|
|
181
196
|
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
def clear_label_override(overrides, global_key, label_key)
|
|
201
|
+
overrides[label_key] = nil if overrides.key?(global_key) && !overrides.key?(label_key)
|
|
202
|
+
end
|
|
182
203
|
end
|
|
183
204
|
end
|
|
184
205
|
end
|
data/lib/ea/theme/loader.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Ea
|
|
|
10
10
|
# name: EA White Theme
|
|
11
11
|
# font: { family: Carlito, size: 7, unit: pt, weight_normal: 400, ... }
|
|
12
12
|
# element_font: { size: 9, unit: pt }
|
|
13
|
-
# label_font: { family: Cambria }
|
|
13
|
+
# label_font: { family: Cambria, size: 7, unit: pt }
|
|
14
14
|
# colors: { text: "#000000", attribute_text: "#66413F", ... }
|
|
15
15
|
# strokes: { element_border: 2, connector_line: 2, ... }
|
|
16
16
|
# text_metrics: { width_factor: 0.65 }
|
|
@@ -56,6 +56,8 @@ module Ea
|
|
|
56
56
|
name: data["name"],
|
|
57
57
|
font_family: font["family"],
|
|
58
58
|
label_font_family: label_font["family"],
|
|
59
|
+
label_font_size: label_font["size"],
|
|
60
|
+
label_font_size_unit: label_font["unit"],
|
|
59
61
|
font_size: font["size"],
|
|
60
62
|
font_size_unit: font["unit"] || "pt",
|
|
61
63
|
text_weight_normal: font["weight_normal"] || 400,
|
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.98
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -501,6 +501,7 @@ files:
|
|
|
501
501
|
- config/qea_schema.yml
|
|
502
502
|
- config/stereotype_colors.yml
|
|
503
503
|
- config/themes/119.yml
|
|
504
|
+
- config/themes/39.yml
|
|
504
505
|
- config/themes/default.yml
|
|
505
506
|
- docs/ea_to_uml_type_mapping.md
|
|
506
507
|
- docs/xmi_qea_conversion_capabilities.md
|