ea 0.6.127 → 0.6.128
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/auto_routing.rb +26 -4
- data/lib/ea/sources/qea/diagram_builder/routing.rb +33 -1
- data/lib/ea/sources/qea/diagram_builder.rb +24 -3
- data/lib/ea/svg/ea_emitter/compartment/attributes.rb +1 -1
- data/lib/ea/svg/ea_emitter/compartment/header.rb +10 -1
- data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +24 -5
- data/lib/ea/svg/ea_emitter/connector_redock.rb +12 -3
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +13 -3
- data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +14 -5
- data/lib/ea/svg/ea_emitter/elements.rb +13 -1
- data/lib/ea/svg/ea_emitter/label/end_label.rb +92 -5
- data/lib/ea/svg/ea_emitter/label/plain_source_seat.rb +29 -0
- data/lib/ea/svg/ea_emitter/label/plain_target_seat.rb +28 -0
- data/lib/ea/svg/ea_emitter/label/source_stair_seat.rb +60 -0
- data/lib/ea/svg/ea_emitter/label/target_bend_seat.rb +45 -0
- data/lib/ea/svg/ea_emitter/label.rb +4 -0
- data/lib/ea/svg/ea_emitter/labels.rb +1 -0
- data/lib/ea/theme/definition.rb +9 -1
- data/lib/ea/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 306887ac0320a438c1248971e4143fab06ab22982e08f2c61b19c31b7b64cc37
|
|
4
|
+
data.tar.gz: 2a73ad0edf0d4eac704f563f679946f19615e906c3d28dc30b9f25815fd8ed83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eef820b29153599ad35fc3de5bb047fa18fc824e6a09b8eb6f95c17a27d2c5126e350341c27649ebe9e861676bbb0b3437de0d9db7f4046ab964e11ce3923fa1
|
|
7
|
+
data.tar.gz: 32e7e0d157472804ada0ddb04a487d26180f14bf1e5c1254dff1b3de68353cff4d22270aee4a8fa9b28e6d103c27f0fe449ec5b0c65411579434a22f1f5bdb67
|
|
@@ -34,13 +34,14 @@ module Ea
|
|
|
34
34
|
# A Mode 2 Association with equal SX/EX (vertical EDGE 1/3) or
|
|
35
35
|
# equal SY/EY (horizontal EDGE 2/4) and no custom Path docks as a
|
|
36
36
|
# straight line through the middle of the boxes' overlap, moved
|
|
37
|
-
# by the shared stored offset.
|
|
37
|
+
# by the shared stored offset. Empty flush Mode 3 routes facing
|
|
38
|
+
# right use the same row. Stored SY counts upward.
|
|
38
39
|
def overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
39
40
|
return model if context
|
|
40
41
|
return model unless overlap_ray_candidate?(model, link_row, geom)
|
|
41
42
|
|
|
42
43
|
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
43
|
-
origins = overlap_origins(source, target, geom)
|
|
44
|
+
origins = overlap_origins(source, target, geom, model.style[:mode])
|
|
44
45
|
return model unless origins && overlap_correction?(source, target, geom)
|
|
45
46
|
|
|
46
47
|
model.source_anchor, model.target_anchor = origins.map do |ox, oy|
|
|
@@ -50,9 +51,20 @@ module Ea
|
|
|
50
51
|
model.waypoints = port_waypoints(model, [model.source_anchor, model.target_anchor])
|
|
51
52
|
end
|
|
52
53
|
model.style[:overlap_ray] = true
|
|
54
|
+
flush_overlap_ports(model, source, target)
|
|
53
55
|
model
|
|
54
56
|
end
|
|
55
57
|
|
|
58
|
+
def flush_overlap_ports(model, source, target)
|
|
59
|
+
return unless model.style[:mode] == "3"
|
|
60
|
+
|
|
61
|
+
row = model.source_anchor.y
|
|
62
|
+
model.source_port = Ea::Model::Point.new(x: source.x + source.width, y: row)
|
|
63
|
+
model.target_port = Ea::Model::Point.new(x: target.x, y: row)
|
|
64
|
+
model.waypoints = port_waypoints(model, [model.source_port, model.target_port])
|
|
65
|
+
model.style[:flush_overlap_ray] = true
|
|
66
|
+
end
|
|
67
|
+
|
|
56
68
|
# Aligned, zero-offset EDGE 2 routes already have their legacy docks.
|
|
57
69
|
def overlap_correction?(source, target, geom)
|
|
58
70
|
geom[:edge] != 2 || !geom[:sy].zero? ||
|
|
@@ -61,7 +73,9 @@ module Ea
|
|
|
61
73
|
|
|
62
74
|
# Both ray origins share the overlap coordinate on the axis the
|
|
63
75
|
# offsets move and sit at each box's centre on the other axis.
|
|
64
|
-
def overlap_origins(source, target, geom)
|
|
76
|
+
def overlap_origins(source, target, geom, mode = nil)
|
|
77
|
+
return nil if mode == "3" && !source_row_faces_target?(source, target, geom[:edge])
|
|
78
|
+
|
|
65
79
|
along, across, offset = overlap_axes(geom)
|
|
66
80
|
coordinate = overlap_ray_coordinate(*box_span(source, along), *box_span(target, along), offset)
|
|
67
81
|
return nil unless coordinate && disjoint_span?(*box_span(source, across), *box_span(target, across))
|
|
@@ -85,11 +99,19 @@ module Ea
|
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
def overlap_ray_candidate?(model, link_row, geom)
|
|
88
|
-
model.connector_type == "Association" && model.style
|
|
102
|
+
model.connector_type == "Association" && overlap_ray_style?(model.style, geom) &&
|
|
89
103
|
[2, 3].include?(model.waypoints.size) && link_row.path.to_s.empty? && !model.style[:tree] &&
|
|
90
104
|
model.has_geometry_offsets && uniform_overlap_offsets?(geom)
|
|
91
105
|
end
|
|
92
106
|
|
|
107
|
+
def overlap_ray_style?(style, geom)
|
|
108
|
+
return true if style[:mode] == "2"
|
|
109
|
+
|
|
110
|
+
# All-zero offsets retain the default center ray.
|
|
111
|
+
style[:mode] == "3" && style[:lwidth] == "0" && geom[:edge] == 2 &&
|
|
112
|
+
geom.values_at(:sx, :sy, :ex, :ey).any? { |offset| offset.to_i.nonzero? }
|
|
113
|
+
end
|
|
114
|
+
|
|
93
115
|
def uniform_overlap_offsets?(geom)
|
|
94
116
|
return false unless geom.values_at(:sx, :sy, :ex, :ey).all?
|
|
95
117
|
|
|
@@ -164,9 +164,23 @@ module Ea
|
|
|
164
164
|
|
|
165
165
|
set_stored_ray_origins(model, source, target, geom)
|
|
166
166
|
model.style[:association_ray_outline] = true if model.connector_type == "Association"
|
|
167
|
+
model.style[:stored_origin_ray] = true if stored_origin_ray?(model, source, target)
|
|
167
168
|
model
|
|
168
169
|
end
|
|
169
170
|
|
|
171
|
+
# Certify only a straight, width-2 custom-line ray whose corrected
|
|
172
|
+
# stored origins lie inside their own outlines. Its clipped row is
|
|
173
|
+
# authored geometry, even when it coincides with a header divider.
|
|
174
|
+
def stored_origin_ray?(model, source, target)
|
|
175
|
+
style = model.style.to_h
|
|
176
|
+
return false unless model.connector_type == "Generalization" &&
|
|
177
|
+
style[:lwidth] == "2" && association_ray_style?(style)
|
|
178
|
+
|
|
179
|
+
[model.source_anchor, model.target_anchor].zip([source, target]).all? do |origin, box|
|
|
180
|
+
inside_box?(origin, grown_bounds(box, 1))
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
170
184
|
# An empty-Path Mode 3 Association follows the line between its
|
|
171
185
|
# stored endpoint origins; an omitted offset defaults to the center.
|
|
172
186
|
# The target origin must lie inside its own stroked outline.
|
|
@@ -176,6 +190,8 @@ module Ea
|
|
|
176
190
|
return model unless association_ray_candidate?(model, link_row)
|
|
177
191
|
|
|
178
192
|
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
193
|
+
return model if default_center_ray?(model) && !horizontal_ray_gap?(source, target)
|
|
194
|
+
|
|
179
195
|
width = association_outline_width(model, link_row, diagram_row)
|
|
180
196
|
origins = association_ray_origins(source, target, geom, width)
|
|
181
197
|
return model unless origins
|
|
@@ -287,10 +303,26 @@ module Ea
|
|
|
287
303
|
def association_ray_candidate?(model, link_row)
|
|
288
304
|
model.connector_type == "Association" && link_row.path.to_s.empty? &&
|
|
289
305
|
model.source_anchor && model.target_anchor &&
|
|
290
|
-
|
|
306
|
+
association_ray_ports?(model) &&
|
|
291
307
|
association_ray_style?(model.style.to_h)
|
|
292
308
|
end
|
|
293
309
|
|
|
310
|
+
def association_ray_ports?(model)
|
|
311
|
+
model.source_port || model.target_port || aligned_default_origins?(model) || default_center_ray?(model)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Without stored offsets or ports, a width-2 custom line between
|
|
315
|
+
# horizontally separated boxes follows their centers to the stroke.
|
|
316
|
+
def default_center_ray?(model)
|
|
317
|
+
!model.has_geometry_offsets && model.style[:lwidth] == "2" && model.waypoints.size == 2 &&
|
|
318
|
+
[model.source_port, model.target_port].all?(&:nil?) &&
|
|
319
|
+
diagonal_origins?(model.source_anchor, model.target_anchor)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def diagonal_origins?(first, last)
|
|
323
|
+
first.x != last.x && first.y != last.y
|
|
324
|
+
end
|
|
325
|
+
|
|
294
326
|
# Zero offsets omit ports, but an explicitly stored axis-aligned
|
|
295
327
|
# width-2 ray still uses the logical centers and stroked outlines.
|
|
296
328
|
def aligned_default_origins?(model)
|
|
@@ -396,9 +396,10 @@ module Ea
|
|
|
396
396
|
# Internal producer flag, like :direct. A model relationship with no
|
|
397
397
|
# link row is stroked at the 2px box outline, so its ends dock there.
|
|
398
398
|
def phantom_style(connector_type)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
%w[Aggregation Association].include?(connector_type)
|
|
399
|
+
style = { lwidth: "1" }
|
|
400
|
+
style[:direct] = true if PHANTOM_DOUBLE_BACK_TYPES.include?(connector_type)
|
|
401
|
+
style[:phantom_outline] = true if %w[Aggregation Association].include?(connector_type)
|
|
402
|
+
style
|
|
402
403
|
end
|
|
403
404
|
|
|
404
405
|
# Computes the edge attachment point on source bounds that
|
|
@@ -424,6 +425,24 @@ module Ea
|
|
|
424
425
|
end
|
|
425
426
|
end
|
|
426
427
|
|
|
428
|
+
# Preserve original Path absence: authored two-point paths can look
|
|
429
|
+
# identical after routing but retain their stored label offsets.
|
|
430
|
+
def mark_plain_source_seat(model, link_row, context)
|
|
431
|
+
return if context || !link_row.path.to_s.empty? || model.connector_type != "Aggregation"
|
|
432
|
+
return unless model.style.values_at(:mode, :lwidth, :tree) == ["3", "2", nil]
|
|
433
|
+
return unless model.source_stereotype.to_s.empty?
|
|
434
|
+
|
|
435
|
+
model.style[:plain_source_seat] = true
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def mark_plain_target_seat(model, link_row, context)
|
|
439
|
+
return if context || !link_row.path.to_s.empty? || model.connector_type != "Association"
|
|
440
|
+
return unless model.style.values_at(:mode, :lwidth, :tree) == ["3", "2", nil]
|
|
441
|
+
return unless model.target_stereotype.to_s.empty?
|
|
442
|
+
|
|
443
|
+
model.style[:plain_target_seat] = true
|
|
444
|
+
end
|
|
445
|
+
|
|
427
446
|
def build_connector(link_row, diagram_row)
|
|
428
447
|
connector = database.find_connector(link_row.connectorid)
|
|
429
448
|
geom = parse_geometry_fields(link_row.geometry)
|
|
@@ -454,6 +473,8 @@ module Ea
|
|
|
454
473
|
has_geometry_offsets: geometry_has_offsets?(link_row.geometry),
|
|
455
474
|
is_substitutable: substitutable_connector?(connector)
|
|
456
475
|
), link_row, diagram_row, geom)
|
|
476
|
+
mark_plain_source_seat(model, link_row, context)
|
|
477
|
+
mark_plain_target_seat(model, link_row, context)
|
|
457
478
|
model = collapse_partial_port_jog(model, link_row, diagram_row)
|
|
458
479
|
model = correct_partial_port_origins(model, link_row, diagram_row, geom, context)
|
|
459
480
|
model = direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
@@ -20,7 +20,7 @@ module Ea
|
|
|
20
20
|
size_unit: context.size_unit, logical_height: context.logical_height,
|
|
21
21
|
fill: context.theme.attribute_text_color,
|
|
22
22
|
visibility_x_offset: context.theme.attribute_spec.visibility_x_offset,
|
|
23
|
-
content_x_offset: context.theme.
|
|
23
|
+
content_x_offset: context.theme.attribute_content_x_offset
|
|
24
24
|
)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -24,9 +24,18 @@ module Ea
|
|
|
24
24
|
weight_bold: context.theme.text_weight_bold,
|
|
25
25
|
stroke_in_text: context.theme.stroke_in_text_color,
|
|
26
26
|
width_factor: context.theme.text_width_factor,
|
|
27
|
-
line_offset: context.theme.compartments.header_line_offset
|
|
27
|
+
line_offset: context.theme.compartments.header_line_offset,
|
|
28
|
+
default_signal_header: default_signal_header?(context)
|
|
28
29
|
)
|
|
29
30
|
end
|
|
31
|
+
|
|
32
|
+
def default_signal_header?(context)
|
|
33
|
+
context.classifier.is_a?(Ea::Model::Signal) &&
|
|
34
|
+
context.classifier.stereotype_refs.empty? &&
|
|
35
|
+
context.element.font_size_inherited == true &&
|
|
36
|
+
context.element.font_family.nil? &&
|
|
37
|
+
!context.element.font_bold && !context.element.font_italic
|
|
38
|
+
end
|
|
30
39
|
end
|
|
31
40
|
end
|
|
32
41
|
end
|
|
@@ -13,7 +13,7 @@ module Ea
|
|
|
13
13
|
# classifier.
|
|
14
14
|
module InstanceSlots
|
|
15
15
|
ROW_OFFSET = 13
|
|
16
|
-
|
|
16
|
+
SUBTITLE_OFFSET_FROM_BOTTOM = 15
|
|
17
17
|
|
|
18
18
|
module_function
|
|
19
19
|
|
|
@@ -30,11 +30,11 @@ module Ea
|
|
|
30
30
|
fill = context.theme.attribute_text_color
|
|
31
31
|
blocks = []
|
|
32
32
|
slots.each_with_index do |slot, idx|
|
|
33
|
-
y = first_y + (idx *
|
|
33
|
+
y = first_y + (idx * row_offset(context))
|
|
34
34
|
blocks << line_text(slot, bounds, y, context, fill)
|
|
35
35
|
end
|
|
36
36
|
if subtitle
|
|
37
|
-
y =
|
|
37
|
+
y = bounds.y + bounds.height + SUBTITLE_OFFSET_FROM_BOTTOM
|
|
38
38
|
blocks << subtitle_text(subtitle, bounds, y, context, fill)
|
|
39
39
|
end
|
|
40
40
|
wrap(blocks.join("\n"), fill)
|
|
@@ -88,9 +88,19 @@ module Ea
|
|
|
88
88
|
end
|
|
89
89
|
module_function :content_x_offset
|
|
90
90
|
|
|
91
|
+
def row_offset(context)
|
|
92
|
+
return ROW_OFFSET unless Ea::Fonts::Metrics.compact_compartment_font?(
|
|
93
|
+
context.size, family: context.family, unit: context.size_unit
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
Ea::Fonts::Metrics.compartment_line_height(
|
|
97
|
+
context.size, family: context.family, unit: context.size_unit
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
module_function :row_offset
|
|
101
|
+
|
|
91
102
|
def subtitle_text(text, bounds, y_pos, context, fill)
|
|
92
|
-
|
|
93
|
-
x_pos = bounds.x + (bounds.width - estimated_width) / 2.0
|
|
103
|
+
x_pos = (bounds.x + (bounds.width - subtitle_width(text, context)) / 2.0).floor
|
|
94
104
|
TextRenderer.new(
|
|
95
105
|
content: text,
|
|
96
106
|
x: x_pos,
|
|
@@ -102,6 +112,15 @@ module Ea
|
|
|
102
112
|
end
|
|
103
113
|
module_function :subtitle_text
|
|
104
114
|
|
|
115
|
+
def subtitle_width(text, context)
|
|
116
|
+
TextRenderer.estimate_width(
|
|
117
|
+
text, context.size, nil,
|
|
118
|
+
family: context.family, style: "italic",
|
|
119
|
+
logical_height: context.size_unit == "pt" ? context.logical_height : nil
|
|
120
|
+
).round
|
|
121
|
+
end
|
|
122
|
+
module_function :subtitle_width
|
|
123
|
+
|
|
105
124
|
def wrap(body, fill)
|
|
106
125
|
group_style = "stroke-width:1;stroke-linecap:round;" \
|
|
107
126
|
"stroke-linejoin:bevel; fill:#{fill};" \
|
|
@@ -34,6 +34,9 @@ module Ea
|
|
|
34
34
|
target_bounds = bounds_map[target_ref]
|
|
35
35
|
return nil unless source_bounds && target_bounds
|
|
36
36
|
return nil if source_ref == target_ref
|
|
37
|
+
if flush_overlap_ray?(connector)
|
|
38
|
+
return clipped_pairs([source_bounds, target_bounds], [connector.source_anchor, connector.target_anchor])
|
|
39
|
+
end
|
|
37
40
|
return trunk_pairs(points, source_bounds, target_bounds, connector.style[:tree]) if trunk
|
|
38
41
|
|
|
39
42
|
square = OrthogonalRay.pairs_for(connector, source_bounds, target_bounds)
|
|
@@ -240,9 +243,13 @@ module Ea
|
|
|
240
243
|
end
|
|
241
244
|
private_class_method :stroke_outline?
|
|
242
245
|
|
|
243
|
-
# The
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
# The builder certifies the empty flush route's shared row and nominal docks.
|
|
247
|
+
def flush_overlap_ray?(connector)
|
|
248
|
+
connector.connector_type == "Association" && connector.style.to_h[:flush_overlap_ray] == true
|
|
249
|
+
end
|
|
250
|
+
private_class_method :flush_overlap_ray?
|
|
251
|
+
|
|
252
|
+
# The QEA builder marks Mode 2 overlap rays for outer-stroke docking.
|
|
246
253
|
def overlap_ray?(connector)
|
|
247
254
|
connector.connector_type == "Association" && connector.style.to_h[:overlap_ray] == true
|
|
248
255
|
end
|
|
@@ -429,7 +436,9 @@ module Ea
|
|
|
429
436
|
bounds_map, divider_y_by_ref)
|
|
430
437
|
return nil unless bounds_map && divider_y_by_ref
|
|
431
438
|
return nil unless pairs.size == 2
|
|
439
|
+
return nil if flush_overlap_ray?(connector)
|
|
432
440
|
return nil if explicit_association_ray?(pairs, connector)
|
|
441
|
+
return nil if connector.style.to_h[:stored_origin_ray] == true
|
|
433
442
|
|
|
434
443
|
rel = relationship_for(connector, model_index)
|
|
435
444
|
return nil unless rel
|
|
@@ -18,13 +18,14 @@ module Ea
|
|
|
18
18
|
size:, size_unit: "pt", logical_height: nil,
|
|
19
19
|
fill: "#000000", weight_normal: 400, weight_bold: 700,
|
|
20
20
|
stroke_in_text: "#000000", width_factor: 0.612,
|
|
21
|
-
line_offset: 6)
|
|
21
|
+
line_offset: 6, default_signal_header: false)
|
|
22
22
|
logical_height = nil unless size_unit == "pt"
|
|
23
23
|
layout = line_layout(
|
|
24
24
|
lines, bounds: bounds, first_y: first_y, family: family,
|
|
25
25
|
size: size, size_unit: size_unit, logical_height: logical_height,
|
|
26
26
|
weight_normal: weight_normal, weight_bold: weight_bold,
|
|
27
|
-
width_factor: width_factor, line_offset: line_offset
|
|
27
|
+
width_factor: width_factor, line_offset: line_offset,
|
|
28
|
+
default_signal_header: default_signal_header
|
|
28
29
|
)
|
|
29
30
|
text_blocks = layout.map do |line|
|
|
30
31
|
TextRenderer.new(
|
|
@@ -44,12 +45,21 @@ module Ea
|
|
|
44
45
|
def self.line_layout(lines, bounds:, first_y:, family:, size:,
|
|
45
46
|
size_unit: "pt", logical_height: nil,
|
|
46
47
|
weight_normal: 400, weight_bold: 700,
|
|
47
|
-
width_factor: 0.612, line_offset: 6)
|
|
48
|
+
width_factor: 0.612, line_offset: 6, default_signal_header: false)
|
|
48
49
|
logical_height = nil unless size_unit == "pt"
|
|
49
50
|
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
50
51
|
size, family: family, unit: size_unit, offset: line_offset,
|
|
51
52
|
logical_height: logical_height
|
|
52
53
|
)
|
|
54
|
+
# Native default Cambria 7pt Signals use a 15px header pitch.
|
|
55
|
+
# Keep explicit fonts, logical heights and layout overrides intact.
|
|
56
|
+
if default_signal_header && family == "Cambria" && size == 7 &&
|
|
57
|
+
size_unit == "pt" && logical_height.nil? && line_offset == 6 &&
|
|
58
|
+
first_y == bounds.y + 13 && lines.size == 2 &&
|
|
59
|
+
lines.first == ["«signal»", :normal] && lines.last.last == :bold
|
|
60
|
+
first_y -= 1
|
|
61
|
+
line_h = 15
|
|
62
|
+
end
|
|
53
63
|
ghost_shift = HeaderLines.parent_ghost_gap(lines)
|
|
54
64
|
lines.each_with_index.map do |(text, style), idx|
|
|
55
65
|
weight = %i[bold bold_italic].include?(style) ? weight_bold : weight_normal
|
|
@@ -19,8 +19,8 @@ module Ea
|
|
|
19
19
|
def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000",
|
|
20
20
|
size_unit: "px", logical_height: nil)
|
|
21
21
|
line_h = line_height(size, family, size_unit, logical_height)
|
|
22
|
-
text_blocks = [build_text(bounds.x + centered_x_offset(bounds, size
|
|
23
|
-
HEADER_TEXT, family, size, fill,
|
|
22
|
+
text_blocks = [build_text(bounds.x + centered_x_offset(bounds, family, size, size_unit, logical_height),
|
|
23
|
+
first_y, HEADER_TEXT, family, size, fill,
|
|
24
24
|
style: "italic", size_unit: size_unit, logical_height: logical_height)]
|
|
25
25
|
tagged_values.each_with_index do |tv, idx|
|
|
26
26
|
text_blocks << build_text(bounds.x + LINE_X_OFFSET, first_y + ((idx + 1) * line_h),
|
|
@@ -48,9 +48,18 @@ module Ea
|
|
|
48
48
|
private_class_method :line_height
|
|
49
49
|
|
|
50
50
|
# Center the "tags" header text within the bounds width.
|
|
51
|
-
def self.centered_x_offset(bounds, size)
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
def self.centered_x_offset(bounds, family, size, size_unit, logical_height)
|
|
52
|
+
# QEA point captions center on the same integer width emitted in SVG.
|
|
53
|
+
# Retain the legacy pixel API's placement.
|
|
54
|
+
text_width = if size_unit == "pt"
|
|
55
|
+
TextRenderer.estimate_width(
|
|
56
|
+
HEADER_TEXT, size, family: family, weight: 400,
|
|
57
|
+
style: "italic", logical_height: logical_height
|
|
58
|
+
).round
|
|
59
|
+
else
|
|
60
|
+
HEADER_TEXT.length * (size / 2)
|
|
61
|
+
end
|
|
62
|
+
centered = ((bounds.width - text_width) / 2.0).floor
|
|
54
63
|
[HEADER_X_OFFSET, centered].max
|
|
55
64
|
end
|
|
56
65
|
private_class_method :centered_x_offset
|
|
@@ -156,6 +156,7 @@ module Ea
|
|
|
156
156
|
logical_height: logical_height,
|
|
157
157
|
content_x_offset: attribute_content_x_offset(size, family, size_unit)),
|
|
158
158
|
row_content_width(op_lines, 3, size, family, logical_height: logical_height),
|
|
159
|
+
tag_content_width(element, classifier, size, family, logical_height: logical_height),
|
|
159
160
|
Compartment::EnumLiterals.content_width(
|
|
160
161
|
enum_literals_for(classifier),
|
|
161
162
|
theme: theme, size: size, family: family,
|
|
@@ -302,7 +303,7 @@ module Ea
|
|
|
302
303
|
end
|
|
303
304
|
|
|
304
305
|
def attribute_content_x_offset(size, family, size_unit)
|
|
305
|
-
offset = (theme.
|
|
306
|
+
offset = (theme.attribute_content_x_offset || Element::AttributeRenderer::DEFAULT_CONTENT_X_OFFSET) +
|
|
306
307
|
2 * (size - 7)
|
|
307
308
|
offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
|
|
308
309
|
offset
|
|
@@ -312,6 +313,17 @@ module Ea
|
|
|
312
313
|
Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit) ? 7 : 8
|
|
313
314
|
end
|
|
314
315
|
|
|
316
|
+
def tag_content_width(element, classifier, size, family, logical_height: nil)
|
|
317
|
+
return nil unless element.show_tagged_values
|
|
318
|
+
|
|
319
|
+
tagged_values_for(classifier).map do |tag|
|
|
320
|
+
TextRenderer.estimate_width("#{tag.key} = #{tag.value}", size,
|
|
321
|
+
family: family, weight: 400, style: "normal",
|
|
322
|
+
logical_height: logical_height).round +
|
|
323
|
+
2 * Element::TaggedValueRenderer::LINE_X_OFFSET
|
|
324
|
+
end.max
|
|
325
|
+
end
|
|
326
|
+
|
|
315
327
|
def row_content_width(lines, right_pad, size, family, **font_options)
|
|
316
328
|
return nil if lines.to_a.empty?
|
|
317
329
|
|
|
@@ -49,11 +49,12 @@ module Ea
|
|
|
49
49
|
# connector - the DiagramConnector (for relationship lookup)
|
|
50
50
|
# end_kind - :source or :target
|
|
51
51
|
# heading - [dx, dy] of the first leg leaving the anchor
|
|
52
|
+
# bend - first bend of a saved descending vertical stair
|
|
52
53
|
# stored_labels - the connector has stored label boxes at
|
|
53
54
|
# either end; false keeps the opposite-end
|
|
54
55
|
# fallback
|
|
55
56
|
def texts(text_box:, mult_box:, anchor:, connector:, end_kind:,
|
|
56
|
-
tree: false, heading: nil, stored_labels: false)
|
|
57
|
+
tree: false, heading: nil, stored_labels: false, bend: nil)
|
|
57
58
|
from_property, role, mult = role_and_multiplicity(connector, end_kind)
|
|
58
59
|
if role_empty?(role) && mult_empty?(mult)
|
|
59
60
|
other_from_prop, other_role, other_mult = role_and_multiplicity(connector,
|
|
@@ -92,7 +93,8 @@ module Ea
|
|
|
92
93
|
stereo = stereo.nil? || stereo.empty? ? nil : "«#{stereo}»"
|
|
93
94
|
direction = leg_direction(end_kind, heading)
|
|
94
95
|
if direction && !(tree && zero_offset?(text_box, mult_box))
|
|
95
|
-
text_pos =
|
|
96
|
+
text_pos = plain_leftward_source_role(connector, direction, anchor, text_box, role) ||
|
|
97
|
+
plain_vertical_role(connector, direction, anchor, text_box, role) ||
|
|
96
98
|
source_property_seat(connector, direction, anchor, text_box, role) ||
|
|
97
99
|
plain_upward_seat(connector, direction, anchor, text_box, role) ||
|
|
98
100
|
role_seat(direction, anchor, text_box, role) || text_pos
|
|
@@ -100,7 +102,9 @@ module Ea
|
|
|
100
102
|
horizontal_tree_multiplicity(connector, direction, anchor, mult_box, mult) ||
|
|
101
103
|
mult_seat(direction, anchor, mult_box) || mult_pos
|
|
102
104
|
elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
|
|
103
|
-
text_pos =
|
|
105
|
+
text_pos = plain_leftward_target_role(connector, heading, anchor, text_box, role) ||
|
|
106
|
+
target_leftward_property_role(connector, heading, anchor, text_box, role) ||
|
|
107
|
+
target_property_role(connector, heading, anchor, text_box, role) ||
|
|
104
108
|
target_diagonal_property_role(connector, heading, anchor, text_box, role) ||
|
|
105
109
|
target_vertical_role(connector, heading, anchor, text_box, role) ||
|
|
106
110
|
target_rightward_role(heading, anchor, text_box, role) || text_pos
|
|
@@ -115,12 +119,69 @@ module Ea
|
|
|
115
119
|
|
|
116
120
|
text_box = text_box.merge("alignment" => 1) if phantom_property_box?(connector, direction, text_box)
|
|
117
121
|
|
|
122
|
+
if end_kind == :target && !from_property
|
|
123
|
+
bend_positions = target_bend_positions(connector, bend, text_box, mult_box, role)
|
|
124
|
+
text_pos, mult_pos = bend_positions if bend_positions
|
|
125
|
+
end
|
|
126
|
+
if end_kind == :source && !from_property
|
|
127
|
+
stair_positions = source_stair_positions(connector, anchor, text_box, mult_box, role)
|
|
128
|
+
text_pos, mult_pos = stair_positions if stair_positions
|
|
129
|
+
end
|
|
130
|
+
|
|
118
131
|
build_texts(role:, mult:, standalone:,
|
|
119
132
|
text_pos:, mult_pos:, stereo:, text_box:)
|
|
120
133
|
end
|
|
121
134
|
|
|
122
135
|
private
|
|
123
136
|
|
|
137
|
+
def plain_leftward_target_role(connector, heading, anchor, box, role)
|
|
138
|
+
return nil unless heading && heading[0].negative? && heading[1].zero?
|
|
139
|
+
return nil unless plain_target_role?(connector, box, role)
|
|
140
|
+
|
|
141
|
+
PlainTargetSeat.position(
|
|
142
|
+
connector: connector, anchor: anchor, box: box,
|
|
143
|
+
width: Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def plain_target_role?(connector, box, role)
|
|
148
|
+
seat_font? && cambria_box?(box) && !role_empty?(role) &&
|
|
149
|
+
connector_source_dest_stereotype(connector, :target).to_s.empty?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def plain_leftward_source_role(connector, direction, anchor, box, role)
|
|
153
|
+
return nil unless direction == :left && seat_font? && cambria_box?(box) && !role_empty?(role)
|
|
154
|
+
return nil unless connector_source_dest_stereotype(connector, :source).to_s.empty?
|
|
155
|
+
|
|
156
|
+
PlainSourceSeat.position(
|
|
157
|
+
connector: connector, anchor: anchor, box: box,
|
|
158
|
+
width: Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def source_stair_positions(connector, anchor, text_box, mult_box, role)
|
|
163
|
+
return nil unless seat_font? && cambria_box?(text_box) && cambria_box?(mult_box)
|
|
164
|
+
return nil if role_empty?(role)
|
|
165
|
+
|
|
166
|
+
property = property_label?(connector, :source)
|
|
167
|
+
return nil unless property || connector_source_dest_stereotype(connector, :source).to_s.empty?
|
|
168
|
+
|
|
169
|
+
SourceStairSeat.positions(
|
|
170
|
+
connector: connector, anchor: anchor, text_box: text_box, mult_box: mult_box,
|
|
171
|
+
width: Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def target_bend_positions(connector, bend, text_box, mult_box, role)
|
|
176
|
+
return nil unless bend && seat_font? && cambria_box?(text_box) && cambria_box?(mult_box)
|
|
177
|
+
return nil unless connector_source_dest_stereotype(connector, :target).to_s.empty? && !role_empty?(role)
|
|
178
|
+
|
|
179
|
+
TargetBendSeat.positions(
|
|
180
|
+
connector: connector, bend: bend, text_box: text_box, mult_box: mult_box,
|
|
181
|
+
width: Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
|
|
124
185
|
# With stored boxes on the connector, an end that carries no
|
|
125
186
|
# data of its own draws only its own stereotype; the data end
|
|
126
187
|
# draws the role and multiplicity; with no data anywhere nothing
|
|
@@ -281,7 +342,7 @@ module Ea
|
|
|
281
342
|
return nil unless direction == :up && cambria_box?(box)
|
|
282
343
|
return nil unless [0, 1].include?(box["alignment"])
|
|
283
344
|
return nil unless connector_source_dest_stereotype(connector, :source).empty?
|
|
284
|
-
return nil unless plain_role_route?(connector.style.to_h)
|
|
345
|
+
return nil unless plain_role_route?(connector.style.to_h, box)
|
|
285
346
|
|
|
286
347
|
width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
287
348
|
return nil unless width
|
|
@@ -290,6 +351,29 @@ module Ea
|
|
|
290
351
|
[x - SEAT_X_INSET - width, y - 6]
|
|
291
352
|
end
|
|
292
353
|
|
|
354
|
+
# Moved target Property clusters on horizontal arrivals share
|
|
355
|
+
# the leftward source seat relative to their stored corner.
|
|
356
|
+
def target_leftward_property_role(connector, heading, anchor, box, role)
|
|
357
|
+
return nil unless heading && heading[0].negative? && heading[1].zero?
|
|
358
|
+
return nil unless moved_target_property_box?(connector, box)
|
|
359
|
+
return nil unless horizontal_target_property_route?(connector.style.to_h)
|
|
360
|
+
|
|
361
|
+
width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
362
|
+
return nil unless width
|
|
363
|
+
|
|
364
|
+
x, y = seat_corner(anchor, box)
|
|
365
|
+
[x - property_role_offsets(:left).first - width, y - 7]
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def moved_target_property_box?(connector, box)
|
|
369
|
+
plain_vertical_box?(box) && box["alignment"] == 1 && [31, 35].include?(box["cy"]) &&
|
|
370
|
+
!stored_offsets_zero?(box) && property_label?(connector, :target)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def horizontal_target_property_route?(style)
|
|
374
|
+
style.values_at(:mode, :lwidth) == %w[3 2] && [nil, "LH"].include?(style[:tree])
|
|
375
|
+
end
|
|
376
|
+
|
|
293
377
|
# An upward target uses the downward property's right edge and
|
|
294
378
|
# the upward target baseline, both relative to its stored corner.
|
|
295
379
|
def target_property_role(connector, heading, anchor, box, role)
|
|
@@ -389,7 +473,9 @@ module Ea
|
|
|
389
473
|
end
|
|
390
474
|
end
|
|
391
475
|
|
|
392
|
-
def plain_role_route?(style)
|
|
476
|
+
def plain_role_route?(style, box)
|
|
477
|
+
return box["cy"] == 16 if orthogonal_stub_route?(style)
|
|
478
|
+
|
|
393
479
|
%w[1 3].include?(style[:mode]) && %w[0 2].include?(style[:lwidth]) &&
|
|
394
480
|
[nil, "V"].include?(style[:tree])
|
|
395
481
|
end
|
|
@@ -473,6 +559,7 @@ module Ea
|
|
|
473
559
|
|
|
474
560
|
def left_property_route?(style)
|
|
475
561
|
downward_property_route?(style) || mode_two_route?(style) ||
|
|
562
|
+
(style[:overlap_ray] == true && style.values_at(:mode, :lwidth, :tree) == ["2", "0", nil]) ||
|
|
476
563
|
(style[:mode] == "3" && %w[0 2].include?(style[:lwidth]) &&
|
|
477
564
|
[nil, "H", "LV"].include?(style[:tree]))
|
|
478
565
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
module EaEmitter
|
|
6
|
+
module Label
|
|
7
|
+
# Plain leftward roles end before the stored corner, mirroring
|
|
8
|
+
# the 3px gutter and baseline of the rightward role seat.
|
|
9
|
+
module PlainSourceSeat
|
|
10
|
+
def self.position(connector:, anchor:, box:, width:)
|
|
11
|
+
return nil unless width && eligible_connector?(connector) && moved_role_box?(box)
|
|
12
|
+
|
|
13
|
+
[anchor[0] + box["ox"].to_i - width - 3, anchor[1] + box["oy"].to_i - 7]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.eligible_connector?(connector)
|
|
17
|
+
connector.connector_type == "Aggregation" &&
|
|
18
|
+
connector.style.to_h.values_at(:mode, :lwidth, :tree, :plain_source_seat) == ["3", "2", nil, true]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.moved_role_box?(box)
|
|
22
|
+
box.values_at("alignment", "cy") == [1, 18] &&
|
|
23
|
+
box.values_at("ox", "oy").map(&:to_i) != [0, 0]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
module EaEmitter
|
|
6
|
+
module Label
|
|
7
|
+
# Plain leftward target roles end three pixels before the anchor.
|
|
8
|
+
module PlainTargetSeat
|
|
9
|
+
def self.position(connector:, anchor:, box:, width:)
|
|
10
|
+
return nil unless width && eligible_connector?(connector) && zero_role_box?(box)
|
|
11
|
+
|
|
12
|
+
[anchor[0] - width - 3, anchor[1] - 7]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.eligible_connector?(connector)
|
|
16
|
+
connector.connector_type == "Association" &&
|
|
17
|
+
connector.style.to_h.values_at(:mode, :lwidth, :tree, :plain_target_seat) == ["3", "2", nil, true]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.zero_role_box?(box)
|
|
21
|
+
box.values_at("alignment", "cy") == [1, 18] &&
|
|
22
|
+
box.values_at("ox", "oy").map(&:to_i) == [0, 0]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
module EaEmitter
|
|
6
|
+
module Label
|
|
7
|
+
# Moved source pairs on descending stairs share a baseline 18px
|
|
8
|
+
# below the stored source dock. Property clusters reserve the
|
|
9
|
+
# wider role gutter; multiplicities keep the plain 3px gutter.
|
|
10
|
+
module SourceStairSeat
|
|
11
|
+
def self.positions(connector:, anchor:, text_box:, mult_box:, width:)
|
|
12
|
+
return nil unless width && connector.connector_type == "Aggregation"
|
|
13
|
+
return nil unless connector.style.to_h.values_at(:mode, :lwidth, :tree) == %w[3 0 V]
|
|
14
|
+
return nil unless descending_stair?(connector.waypoints)
|
|
15
|
+
|
|
16
|
+
height, inset = role_geometry(connector)
|
|
17
|
+
return nil unless moved_box?(text_box, height) && moved_box?(mult_box, 18)
|
|
18
|
+
|
|
19
|
+
[position(anchor, text_box, -width - inset), position(anchor, mult_box, 3)]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.role_geometry(connector)
|
|
23
|
+
connector.source_stereotype.to_s.strip.casecmp("property").zero? ? [35, 60] : [18, 3]
|
|
24
|
+
end
|
|
25
|
+
private_class_method :role_geometry
|
|
26
|
+
|
|
27
|
+
def self.descending_stair?(waypoints)
|
|
28
|
+
return false unless waypoints.size == 4 && waypoints.all?(&:position)
|
|
29
|
+
|
|
30
|
+
first, bend, corner, last = waypoints.map(&:position)
|
|
31
|
+
descending_leg?(first, bend) && descending_leg?(corner, last) &&
|
|
32
|
+
horizontal_leg?(bend, corner)
|
|
33
|
+
end
|
|
34
|
+
private_class_method :descending_stair?
|
|
35
|
+
|
|
36
|
+
def self.descending_leg?(first, last)
|
|
37
|
+
first.x == last.x && first.y < last.y
|
|
38
|
+
end
|
|
39
|
+
private_class_method :descending_leg?
|
|
40
|
+
|
|
41
|
+
def self.horizontal_leg?(first, last)
|
|
42
|
+
first.y == last.y && first.x != last.x
|
|
43
|
+
end
|
|
44
|
+
private_class_method :horizontal_leg?
|
|
45
|
+
|
|
46
|
+
def self.position(anchor, box, inset)
|
|
47
|
+
[anchor[0] + box["ox"].to_i + inset, anchor[1] + box["oy"].to_i + 18]
|
|
48
|
+
end
|
|
49
|
+
private_class_method :position
|
|
50
|
+
|
|
51
|
+
def self.moved_box?(box, height)
|
|
52
|
+
box["alignment"] == 1 && box["cy"] == height &&
|
|
53
|
+
[box["ox"].to_i, box["oy"].to_i] != [0, 0]
|
|
54
|
+
end
|
|
55
|
+
private_class_method :moved_box?
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
module EaEmitter
|
|
6
|
+
module Label
|
|
7
|
+
# Moved plain target pairs on saved vertical stairs use the first
|
|
8
|
+
# bend, opposing 8px gutters and a baseline 3px above the saved OY.
|
|
9
|
+
module TargetBendSeat
|
|
10
|
+
def self.reference(points)
|
|
11
|
+
return nil unless points.size == 4
|
|
12
|
+
|
|
13
|
+
first, bend, corner, last = points
|
|
14
|
+
bend if descending_leg?(first, bend) && descending_leg?(corner, last) &&
|
|
15
|
+
bend[1] == corner[1] && bend[0] != corner[0]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.descending_leg?(first, last)
|
|
19
|
+
first[0] == last[0] && first[1] < last[1]
|
|
20
|
+
end
|
|
21
|
+
private_class_method :descending_leg?
|
|
22
|
+
|
|
23
|
+
def self.positions(connector:, bend:, text_box:, mult_box:, width:)
|
|
24
|
+
return nil unless bend && width && connector.connector_type == "Association"
|
|
25
|
+
return nil unless connector.style.to_h.values_at(:mode, :lwidth, :tree) == %w[3 0 V]
|
|
26
|
+
return nil unless moved_box?(text_box, 31) && moved_box?(mult_box, 16)
|
|
27
|
+
|
|
28
|
+
[position(bend, text_box, -width - 8), position(bend, mult_box, 8)]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.position(bend, box, inset)
|
|
32
|
+
[bend[0] + box["ox"].to_i + inset, bend[1] + box["oy"].to_i - 3]
|
|
33
|
+
end
|
|
34
|
+
private_class_method :position
|
|
35
|
+
|
|
36
|
+
def self.moved_box?(box, height)
|
|
37
|
+
box["alignment"] == 1 && box["cy"] == height &&
|
|
38
|
+
[box["ox"].to_i, box["oy"].to_i] != [0, 0]
|
|
39
|
+
end
|
|
40
|
+
private_class_method :moved_box?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -15,6 +15,10 @@ module Ea
|
|
|
15
15
|
# register rather than editing the orchestrator (OCP).
|
|
16
16
|
module Label
|
|
17
17
|
autoload :EndLabel, "ea/svg/ea_emitter/label/end_label"
|
|
18
|
+
autoload :TargetBendSeat, "ea/svg/ea_emitter/label/target_bend_seat"
|
|
19
|
+
autoload :SourceStairSeat, "ea/svg/ea_emitter/label/source_stair_seat"
|
|
20
|
+
autoload :PlainSourceSeat, "ea/svg/ea_emitter/label/plain_source_seat"
|
|
21
|
+
autoload :PlainTargetSeat, "ea/svg/ea_emitter/label/plain_target_seat"
|
|
18
22
|
autoload :MidpointLabel, "ea/svg/ea_emitter/label/midpoint_label"
|
|
19
23
|
autoload :Registry, "ea/svg/ea_emitter/label/registry"
|
|
20
24
|
end
|
|
@@ -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
|
+
bend: Label::TargetBendSeat.reference(points),
|
|
100
101
|
heading: (stored_tgt || stored_labels) && target_heading(connector, points),
|
|
101
102
|
stored_labels: stored_labels).each { |t| texts << t }
|
|
102
103
|
texts
|
data/lib/ea/theme/definition.rb
CHANGED
|
@@ -108,6 +108,7 @@ module Ea
|
|
|
108
108
|
@geometry = geometry || DEFAULT_GEOMETRY.dup
|
|
109
109
|
@frame = frame || DEFAULT_FRAME.dup
|
|
110
110
|
@compartments = compartments || DEFAULT_COMPARTMENTS.dup
|
|
111
|
+
@attribute_spec_override = attribute_spec
|
|
111
112
|
@attribute_spec = attribute_spec || DEFAULT_ATTR.dup
|
|
112
113
|
@markers = markers || DEFAULT_MARKERS.dup
|
|
113
114
|
@package = package || DEFAULT_PACKAGE.dup
|
|
@@ -137,6 +138,12 @@ module Ea
|
|
|
137
138
|
@label_font_size_unit || font_size_unit
|
|
138
139
|
end
|
|
139
140
|
|
|
141
|
+
# Attribute cells have a different inherited seat from enum literals,
|
|
142
|
+
# which continue to consume the legacy attribute_spec default.
|
|
143
|
+
def attribute_content_x_offset
|
|
144
|
+
@attribute_spec_override ? attribute_spec.content_x_offset : 22
|
|
145
|
+
end
|
|
146
|
+
|
|
140
147
|
def with(**overrides)
|
|
141
148
|
clear_label_override(overrides, :font_family, :label_font_family)
|
|
142
149
|
clear_label_override(overrides, :font_family, :element_font_family)
|
|
@@ -193,7 +200,8 @@ module Ea
|
|
|
193
200
|
marker_stroke_width: marker_stroke_width,
|
|
194
201
|
text_width_factor: text_width_factor,
|
|
195
202
|
geometry: geometry, frame: frame, compartments: compartments,
|
|
196
|
-
|
|
203
|
+
# Preserve omission so reconstruction retains the attribute-cell default.
|
|
204
|
+
attribute_spec: @attribute_spec_override, markers: markers,
|
|
197
205
|
package: package, note: note, fills: fills
|
|
198
206
|
}
|
|
199
207
|
end
|
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.128
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -940,7 +940,11 @@ files:
|
|
|
940
940
|
- lib/ea/svg/ea_emitter/label.rb
|
|
941
941
|
- lib/ea/svg/ea_emitter/label/end_label.rb
|
|
942
942
|
- lib/ea/svg/ea_emitter/label/midpoint_label.rb
|
|
943
|
+
- lib/ea/svg/ea_emitter/label/plain_source_seat.rb
|
|
944
|
+
- lib/ea/svg/ea_emitter/label/plain_target_seat.rb
|
|
943
945
|
- lib/ea/svg/ea_emitter/label/registry.rb
|
|
946
|
+
- lib/ea/svg/ea_emitter/label/source_stair_seat.rb
|
|
947
|
+
- lib/ea/svg/ea_emitter/label/target_bend_seat.rb
|
|
944
948
|
- lib/ea/svg/ea_emitter/labels.rb
|
|
945
949
|
- lib/ea/svg/ea_emitter/layer.rb
|
|
946
950
|
- lib/ea/svg/ea_emitter/layer_sequencer.rb
|