ea 0.6.108 → 0.6.110
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/diagram/display_config.rb +5 -0
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +12 -37
- data/lib/ea/svg/ea_emitter/compartment/operations.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment/shape.rb +1 -2
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +15 -5
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +9 -14
- data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +3 -3
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +18 -8
- data/lib/ea/svg/ea_emitter/elements.rb +6 -5
- data/lib/ea/svg/ea_emitter/label/end_label.rb +32 -5
- 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: 55380e3923a2afe6babb0b3163079cc273e7f054cc8d1549db7985ace1f45757
|
|
4
|
+
data.tar.gz: dd92a38559eb5c1688771391995247055d633530fdcb4712a62aa087a31bfbf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c98de5218163ad487eb0453bcf8fca9ea844a8cecbe3d36856ce2a44ab5728a691792d9a112c9efdf4f1e4084360a655052494af40764e339f0086692c8f6d8
|
|
7
|
+
data.tar.gz: 83fe0979cd7213d32a2615efd5d8e4f54f22c4c94761f5ec8bc15da01adf5070878fe937475a7424a8e8afc144cff1b0bc3620137bd491584d59cbb4733a75cf
|
|
@@ -101,6 +101,11 @@ module Ea
|
|
|
101
101
|
style_flags["OpParams"] != "0"
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Type-only parameter detail is stored as OpParams=1.
|
|
105
|
+
def operation_parameter_detail
|
|
106
|
+
:type_only if style_flags["OpParams"] == "1"
|
|
107
|
+
end
|
|
108
|
+
|
|
104
109
|
# Element Alias instead of Name in the header. Defaults to false
|
|
105
110
|
# — most diagrams show the Name.
|
|
106
111
|
def use_alias?
|
|
@@ -6,13 +6,11 @@ module Ea
|
|
|
6
6
|
module Compartment
|
|
7
7
|
# Enumeration literal compartment: divider line + "literals"
|
|
8
8
|
# italic header + each literal name. Skipped when the
|
|
9
|
-
# classifier has no literals or
|
|
9
|
+
# classifier has no literals or no content geometry.
|
|
10
10
|
module EnumLiterals
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
-
#
|
|
14
|
-
# 13px apart at 7pt (attr rows use 14), first row at
|
|
15
|
-
# divider + 14. No compartment divider, no header label.
|
|
13
|
+
# Caption and literal rows share the same baseline spacing.
|
|
16
14
|
LITERAL_LINE_OFFSET = 6
|
|
17
15
|
|
|
18
16
|
def render(context)
|
|
@@ -29,16 +27,8 @@ module Ea
|
|
|
29
27
|
)
|
|
30
28
|
text_blocks = []
|
|
31
29
|
row_y = context.geometry.enum_literal_first_y
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# (t_attribute.Default); bare literal lists render the
|
|
35
|
-
# rows directly. Corpus-verified (PT_TypeOfSA:
|
|
36
|
-
# "literals" caption + "ServingParcel = SP" rows;
|
|
37
|
-
# CV_SequenceType: bare rows, no caption).
|
|
38
|
-
if caption?(context)
|
|
39
|
-
text_blocks << caption_text(context, row_y)
|
|
40
|
-
row_y += line_h
|
|
41
|
-
end
|
|
30
|
+
text_blocks << caption_text(context, row_y)
|
|
31
|
+
row_y += line_h
|
|
42
32
|
context.enum_literals.each_with_index do |literal, idx|
|
|
43
33
|
y = row_y + (idx * line_h)
|
|
44
34
|
text_blocks << visibility_placeholder(context, y)
|
|
@@ -47,28 +37,6 @@ module Ea
|
|
|
47
37
|
wrap_group(text_blocks, context.theme.text_color)
|
|
48
38
|
end
|
|
49
39
|
|
|
50
|
-
def caption?(context)
|
|
51
|
-
values_caption?(context) || grown?(context)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# EA draws the 'literals' caption on every enum box it
|
|
55
|
-
# re-lays (grown height) - corpus 8/8 grown=caption. Boxes
|
|
56
|
-
# keeping stored size keep their stored caption state,
|
|
57
|
-
# captured by the stored literal values.
|
|
58
|
-
def grown?(context)
|
|
59
|
-
stored = context.element&.bounds
|
|
60
|
-
return false unless stored
|
|
61
|
-
|
|
62
|
-
context.bounds.height > stored.height + 4
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def values_caption?(context)
|
|
66
|
-
context.enum_literals.any? do |l|
|
|
67
|
-
value = l.value.to_s
|
|
68
|
-
!value.empty? && value != l.name.to_s
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
40
|
def caption_text(context, y)
|
|
73
41
|
content = "literals"
|
|
74
42
|
len = TextRenderer.estimate_width(content, context.size, nil,
|
|
@@ -101,9 +69,16 @@ module Ea
|
|
|
101
69
|
|
|
102
70
|
def literal_name_text(context, literal, y)
|
|
103
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
|
|
104
79
|
TextRenderer.new(
|
|
105
80
|
content: content,
|
|
106
|
-
x: context.bounds.x +
|
|
81
|
+
x: context.bounds.x + offset,
|
|
107
82
|
y: y,
|
|
108
83
|
family: context.family, size: context.size,
|
|
109
84
|
size_unit: context.size_unit, logical_height: context.logical_height,
|
|
@@ -28,7 +28,8 @@ module Ea
|
|
|
28
28
|
bounds: context.bounds,
|
|
29
29
|
first_y: context.geometry.op_first_y,
|
|
30
30
|
family: context.family, size: context.size,
|
|
31
|
-
size_unit: context.size_unit, logical_height: context.logical_height
|
|
31
|
+
size_unit: context.size_unit, logical_height: context.logical_height,
|
|
32
|
+
parameter_detail: context.diagram&.display_config&.operation_parameter_detail
|
|
32
33
|
)
|
|
33
34
|
parts.join("\n")
|
|
34
35
|
end
|
|
@@ -14,8 +14,7 @@ module Ea
|
|
|
14
14
|
Element::LegendRenderer.render(
|
|
15
15
|
context.bounds,
|
|
16
16
|
legend: context.model_element.legend,
|
|
17
|
-
family: context.family
|
|
18
|
-
canvas: context.canvas
|
|
17
|
+
family: context.family
|
|
19
18
|
)
|
|
20
19
|
elsif context.model_element.is_a?(Ea::Model::Package) ||
|
|
21
20
|
context.model_element.is_a?(Ea::Model::Note)
|
|
@@ -72,8 +72,19 @@ module Ea
|
|
|
72
72
|
end
|
|
73
73
|
private :line_height
|
|
74
74
|
|
|
75
|
+
# A source font can export at the same point size with a smaller
|
|
76
|
+
# logical cell. Seat its first baseline using the same height delta
|
|
77
|
+
# as its row pitch, without changing compact-font spacing.
|
|
78
|
+
def source_baseline_adjustment
|
|
79
|
+
nominal_height = Ea::Fonts::Metrics.compartment_line_height(
|
|
80
|
+
size, family: @family, unit: @size_unit, offset: 0
|
|
81
|
+
)
|
|
82
|
+
line_height(0) - nominal_height
|
|
83
|
+
end
|
|
84
|
+
private :source_baseline_adjustment
|
|
85
|
+
|
|
75
86
|
def header_first_y
|
|
76
|
-
bounds.y + size + (header_top_padding || 12)
|
|
87
|
+
bounds.y + size + (header_top_padding || 12) + source_baseline_adjustment
|
|
77
88
|
end
|
|
78
89
|
|
|
79
90
|
# Y of the divider line between header and the rest.
|
|
@@ -113,7 +124,7 @@ module Ea
|
|
|
113
124
|
end
|
|
114
125
|
|
|
115
126
|
def raw_attr_first_y
|
|
116
|
-
return
|
|
127
|
+
return header_first_y + 12 unless divider_y
|
|
117
128
|
|
|
118
129
|
divider_y + line_height((attr_first_offset || 7) - 1) + 1
|
|
119
130
|
end
|
|
@@ -152,9 +163,8 @@ module Ea
|
|
|
152
163
|
end
|
|
153
164
|
|
|
154
165
|
# EA draws a single divider under the header and seats
|
|
155
|
-
# enumeration
|
|
156
|
-
#
|
|
157
|
-
# "literals" header. Verified against EA-published SVGs.
|
|
166
|
+
# the enumeration caption directly after it (+14 at 7pt),
|
|
167
|
+
# followed by the literal rows without a second divider.
|
|
158
168
|
def enum_literal_first_y
|
|
159
169
|
return nil unless divider_y
|
|
160
170
|
|
|
@@ -11,12 +11,9 @@ module Ea
|
|
|
11
11
|
# - Foreign elements (owning package ≠ the diagram's package,
|
|
12
12
|
# gated on t_diagram.ShowForeign) render the qualified
|
|
13
13
|
# name "OwningPackage::Name".
|
|
14
|
-
# - Qualified
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# each centered independently. Corpus-verified: every
|
|
18
|
-
# wrapped header overflows its box and every single-line
|
|
19
|
-
# header fits (tightest observed slack 6px).
|
|
14
|
+
# - Qualified names wrap after their colon separator when
|
|
15
|
+
# the measured name reaches the box width minus 6px.
|
|
16
|
+
# Each resulting line retains its separator and is centered.
|
|
20
17
|
#
|
|
21
18
|
# InstanceSpecification returns [] (InstanceSpec provider
|
|
22
19
|
# already emitted the full label).
|
|
@@ -42,9 +39,8 @@ module Ea
|
|
|
42
39
|
return [[name, weight]]
|
|
43
40
|
end
|
|
44
41
|
|
|
45
|
-
if name.
|
|
46
|
-
|
|
47
|
-
[["#{qualifier}::", weight], [base, weight]]
|
|
42
|
+
if (qualified = name.match(/\A(.+?:{1,2})([^:\s].*)\z/))
|
|
43
|
+
[[qualified[1], weight], [qualified[2], weight]]
|
|
48
44
|
else
|
|
49
45
|
HeaderLinePipeline.wrap_words(name, context, weight).map do |line|
|
|
50
46
|
[line, weight]
|
|
@@ -59,11 +55,10 @@ module Ea
|
|
|
59
55
|
family: context.family, weight: "700", style: font_style,
|
|
60
56
|
logical_height: context.logical_height
|
|
61
57
|
).round
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
len > context.bounds_width.to_i - 6
|
|
58
|
+
# Qualified classifier names also wrap at the exact boundary;
|
|
59
|
+
# ordinary word wrapping keeps the shared pipeline threshold.
|
|
60
|
+
usable = context.bounds_width.to_i - 6
|
|
61
|
+
name.match?(/:{1,2}[^:\s]/) ? len >= usable : len > usable
|
|
67
62
|
end
|
|
68
63
|
|
|
69
64
|
# EA renders the plain classifier name in element headers;
|
|
@@ -9,7 +9,7 @@ module Ea
|
|
|
9
9
|
# (verified against plateau reference SVGs):
|
|
10
10
|
#
|
|
11
11
|
# ┌──────────────────────────────┐
|
|
12
|
-
# │ 凡例 │ ← title baseline
|
|
12
|
+
# │ 凡例 │ ← title baseline 20px below top
|
|
13
13
|
# │ ┌──┐ │
|
|
14
14
|
# │ └──┘ GMLに定義されたクラス │ ← item 0 (icon top + 30px)
|
|
15
15
|
# │ ┌──┐ │
|
|
@@ -24,8 +24,8 @@ module Ea
|
|
|
24
24
|
class LegendRenderer
|
|
25
25
|
CONTAINER_RX = 3
|
|
26
26
|
ICON_SIZE = 17
|
|
27
|
-
TITLE_Y_OFFSET =
|
|
28
|
-
FIRST_ITEM_Y_OFFSET =
|
|
27
|
+
TITLE_Y_OFFSET = 20
|
|
28
|
+
FIRST_ITEM_Y_OFFSET = 30
|
|
29
29
|
ITEM_SPACING = 19
|
|
30
30
|
ICON_X_OFFSET = 10
|
|
31
31
|
ICON_LABEL_GAP = 22
|
|
@@ -16,7 +16,8 @@ module Ea
|
|
|
16
16
|
VISIBILITY_X_OFFSET = 5
|
|
17
17
|
CONTENT_X_OFFSET = 22
|
|
18
18
|
|
|
19
|
-
def self.render(operations, bounds:, first_y:, family:, size:, size_unit: "px",
|
|
19
|
+
def self.render(operations, bounds:, first_y:, family:, size:, size_unit: "px",
|
|
20
|
+
logical_height: nil, parameter_detail: nil)
|
|
20
21
|
# EA's op rows pitch 13 (size + 6), same as attribute
|
|
21
22
|
# rows (6D4C665D: ops 541..658 step 13).
|
|
22
23
|
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
@@ -39,14 +40,15 @@ module Ea
|
|
|
39
40
|
y = first_y + ((idx + offset) * line_h)
|
|
40
41
|
text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
|
|
41
42
|
visibility_prefix(op), family, size, size_unit, logical_height: logical_height)
|
|
43
|
+
text = operation_text(op, parameter_detail: parameter_detail)
|
|
42
44
|
text_blocks << build_text(bounds.x + AttributeRenderer.content_x_offset(size), y,
|
|
43
|
-
|
|
45
|
+
text, family, size, size_unit, logical_height: logical_height)
|
|
44
46
|
end
|
|
45
47
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# Builds display lines for a classifier's operations.
|
|
49
|
-
def self.lines_for(classifier)
|
|
51
|
+
def self.lines_for(classifier, parameter_detail: nil)
|
|
50
52
|
ops = classifier.operations
|
|
51
53
|
return [] unless ops&.any?
|
|
52
54
|
|
|
@@ -54,7 +56,7 @@ module Ea
|
|
|
54
56
|
visibility = visibility_prefix(op).strip
|
|
55
57
|
params = (op.parameters || []).map do |p|
|
|
56
58
|
type = namespace_double_colon(p.type_name)
|
|
57
|
-
p
|
|
59
|
+
parameter_text(p, type, parameter_detail)
|
|
58
60
|
end.join(", ")
|
|
59
61
|
return_type = namespace_double_colon(op.return_type_name)
|
|
60
62
|
"#{visibility} #{op.name}(#{params}): #{return_type}".strip
|
|
@@ -66,9 +68,9 @@ module Ea
|
|
|
66
68
|
end
|
|
67
69
|
private_class_method :visibility_prefix
|
|
68
70
|
|
|
69
|
-
def self.operation_text(operation)
|
|
71
|
+
def self.operation_text(operation, parameter_detail: nil)
|
|
70
72
|
prefix = operation.is_reception ? "«signal» " : ""
|
|
71
|
-
text = "#{prefix}#{operation.name}(#{reception_params(operation)})"
|
|
73
|
+
text = "#{prefix}#{operation.name}(#{reception_params(operation, parameter_detail)})"
|
|
72
74
|
return text unless operation.return_type_name && !operation.is_reception
|
|
73
75
|
|
|
74
76
|
"#{text}: #{operation.return_type_name}"
|
|
@@ -82,11 +84,19 @@ module Ea
|
|
|
82
84
|
end
|
|
83
85
|
private_class_method :namespace_double_colon
|
|
84
86
|
|
|
85
|
-
def self.
|
|
87
|
+
def self.parameter_text(param, type, parameter_detail)
|
|
88
|
+
return "#{type}*" if param.direction == "inout"
|
|
89
|
+
return type if parameter_detail == :type_only
|
|
90
|
+
|
|
91
|
+
"#{param.name}: #{type}"
|
|
92
|
+
end
|
|
93
|
+
private_class_method :parameter_text
|
|
94
|
+
|
|
95
|
+
def self.reception_params(operation, parameter_detail)
|
|
86
96
|
params = (operation.parameters || []).filter_map { |p|
|
|
87
97
|
next nil if p.type_name.to_s.empty?
|
|
88
98
|
|
|
89
|
-
p
|
|
99
|
+
parameter_text(p, p.type_name, parameter_detail)
|
|
90
100
|
}.join(", ")
|
|
91
101
|
params
|
|
92
102
|
end
|
|
@@ -144,7 +144,10 @@ module Ea
|
|
|
144
144
|
end
|
|
145
145
|
op_lines = if is_classifier && show_operations? &&
|
|
146
146
|
(operations_visible?(element) || regenerated_context?)
|
|
147
|
-
Element::OperationRenderer.lines_for(
|
|
147
|
+
Element::OperationRenderer.lines_for(
|
|
148
|
+
classifier,
|
|
149
|
+
parameter_detail: display_config.operation_parameter_detail
|
|
150
|
+
)
|
|
148
151
|
else
|
|
149
152
|
[]
|
|
150
153
|
end
|
|
@@ -440,12 +443,10 @@ module Ea
|
|
|
440
443
|
classifier.literals || []
|
|
441
444
|
end
|
|
442
445
|
|
|
443
|
-
#
|
|
444
|
-
# literal carries a stored code value.
|
|
446
|
+
# A populated literal compartment includes its caption row.
|
|
445
447
|
def enum_row_count_for(classifier)
|
|
446
448
|
literals = enum_literals_for(classifier)
|
|
447
|
-
|
|
448
|
-
literals.size + (caption ? 1 : 0)
|
|
449
|
+
literals.empty? ? 0 : literals.size + 1
|
|
449
450
|
end
|
|
450
451
|
|
|
451
452
|
def tagged_values_for(classifier)
|
|
@@ -99,9 +99,10 @@ module Ea
|
|
|
99
99
|
horizontal_tree_multiplicity(connector, direction, anchor, mult_box, mult) ||
|
|
100
100
|
mult_seat(direction, anchor, mult_box) || mult_pos
|
|
101
101
|
elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
|
|
102
|
-
text_pos =
|
|
102
|
+
text_pos = target_property_role(connector, heading, anchor, text_box, role) ||
|
|
103
|
+
target_vertical_role(connector, heading, anchor, text_box, role) ||
|
|
103
104
|
target_rightward_role(heading, anchor, text_box, role) || text_pos
|
|
104
|
-
mult_pos = target_multiplicity_seat(heading, anchor, mult_box) ||
|
|
105
|
+
mult_pos = target_multiplicity_seat(connector, heading, anchor, mult_box) ||
|
|
105
106
|
target_horizontal_multiplicity(heading, anchor, mult_box, mult) || mult_pos
|
|
106
107
|
end
|
|
107
108
|
|
|
@@ -213,6 +214,25 @@ module Ea
|
|
|
213
214
|
[x - SEAT_X_INSET - width, y - 6]
|
|
214
215
|
end
|
|
215
216
|
|
|
217
|
+
# An upward target uses the downward property's right edge and
|
|
218
|
+
# the upward target baseline, both relative to its stored corner.
|
|
219
|
+
def target_property_role(connector, heading, anchor, box, role)
|
|
220
|
+
return nil unless upward_target_property?(connector, heading)
|
|
221
|
+
return nil unless plain_vertical_box?(box) && box["alignment"] == 1
|
|
222
|
+
|
|
223
|
+
width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
|
|
224
|
+
return nil unless width
|
|
225
|
+
|
|
226
|
+
x, y = seat_corner(anchor, box)
|
|
227
|
+
inset, = property_role_offsets(:down)
|
|
228
|
+
[x - inset - width, y - 7]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def upward_target_property?(connector, heading)
|
|
232
|
+
heading && heading[0].zero? && heading[1].negative? &&
|
|
233
|
+
property_label?(connector, :target) && downward_property_route?(connector.style.to_h)
|
|
234
|
+
end
|
|
235
|
+
|
|
216
236
|
def target_vertical_role(connector, heading, anchor, box, role)
|
|
217
237
|
return nil unless heading && heading[0].zero? && !heading[1].zero?
|
|
218
238
|
|
|
@@ -350,12 +370,19 @@ module Ea
|
|
|
350
370
|
mult_seat(:right, anchor, box)
|
|
351
371
|
end
|
|
352
372
|
|
|
353
|
-
def target_multiplicity_seat(heading, anchor, box)
|
|
373
|
+
def target_multiplicity_seat(connector, heading, anchor, box)
|
|
354
374
|
return nil unless seat_font?
|
|
355
375
|
return nil unless heading && heading[0].zero? && heading[1].negative?
|
|
356
|
-
return nil unless
|
|
376
|
+
return nil unless target_multiplicity_box?(connector, box)
|
|
377
|
+
|
|
378
|
+
x, y = seat_corner(anchor, box)
|
|
379
|
+
[x + 8, y - 7]
|
|
380
|
+
end
|
|
357
381
|
|
|
358
|
-
|
|
382
|
+
def target_multiplicity_box?(connector, box)
|
|
383
|
+
# Property targets seat at the stored corner; plain moved labels
|
|
384
|
+
# retain their explicit offsets.
|
|
385
|
+
cambria_box?(box) && (stored_offsets_zero?(box) || property_label?(connector, :target))
|
|
359
386
|
end
|
|
360
387
|
|
|
361
388
|
def cambria_box?(box)
|
data/lib/ea/version.rb
CHANGED