ea 0.6.107 → 0.6.108
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/119.yml +3 -2
- data/lib/ea/fonts/cambria_7_styled_widths.json +293 -0
- data/lib/ea/fonts/metrics.rb +50 -7
- data/lib/ea/model/diagram_element.rb +4 -0
- data/lib/ea/sources/qea/diagram_builder/auto_routing.rb +124 -11
- data/lib/ea/sources/qea/diagram_builder/font_settings.rb +50 -0
- data/lib/ea/sources/qea/diagram_builder/routing.rb +82 -82
- data/lib/ea/sources/qea/diagram_builder.rb +8 -30
- data/lib/ea/sources/qea/instance_builder.rb +1 -0
- data/lib/ea/svg/ea_emitter/color_resolver.rb +1 -2
- data/lib/ea/svg/ea_emitter/compartment/attributes.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +11 -5
- data/lib/ea/svg/ea_emitter/compartment/header.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment/note_body.rb +6 -4
- data/lib/ea/svg/ea_emitter/compartment/note_html.rb +66 -0
- data/lib/ea/svg/ea_emitter/compartment/operations.rb +2 -1
- data/lib/ea/svg/ea_emitter/compartment.rb +1 -0
- data/lib/ea/svg/ea_emitter/connector_redock.rb +30 -14
- data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +11 -9
- data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +24 -8
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +18 -7
- data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +9 -5
- data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +8 -5
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +3 -2
- data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +10 -5
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +2 -1
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +14 -12
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +10 -7
- data/lib/ea/svg/ea_emitter/elements.rb +39 -32
- data/lib/ea/svg/ea_emitter/font_resolver.rb +14 -4
- data/lib/ea/svg/ea_emitter/ghost_labels.rb +3 -0
- data/lib/ea/svg/ea_emitter/label/end_label.rb +37 -4
- data/lib/ea/svg/ea_emitter/labels.rb +12 -4
- data/lib/ea/svg/ea_emitter/render_context.rb +1 -1
- data/lib/ea/svg/ea_emitter/text_renderer.rb +6 -5
- data/lib/ea/theme/definition.rb +5 -2
- data/lib/ea/theme/loader.rb +3 -2
- data/lib/ea/version.rb +1 -1
- metadata +4 -1
|
@@ -47,12 +47,14 @@ module Ea
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def self.render(lines, bounds:, first_y:, family:,
|
|
50
|
-
size:, size_unit: DEFAULT_FONT_UNIT,
|
|
50
|
+
size:, size_unit: DEFAULT_FONT_UNIT, logical_height: nil,
|
|
51
51
|
fill: "#000000",
|
|
52
52
|
visibility_x_offset: DEFAULT_VISIBILITY_X_OFFSET,
|
|
53
53
|
content_x_offset: nil)
|
|
54
54
|
|
|
55
|
-
line_h =
|
|
55
|
+
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
56
|
+
size, family: family, unit: size_unit, offset: ROW_LINE_OFFSET, logical_height: logical_height
|
|
57
|
+
)
|
|
56
58
|
offsets = group_lead_offsets(lines)
|
|
57
59
|
text_blocks = []
|
|
58
60
|
lines.each_with_index do |line, idx|
|
|
@@ -60,15 +62,27 @@ module Ea
|
|
|
60
62
|
visibility, rest = split_visibility(line)
|
|
61
63
|
if visibility
|
|
62
64
|
unless visibility.strip.empty?
|
|
63
|
-
text_blocks << build_text(
|
|
65
|
+
text_blocks << build_text(
|
|
66
|
+
bounds.x + visibility_x_offset, y, visibility, family, size, size_unit, fill,
|
|
67
|
+
logical_height: logical_height
|
|
68
|
+
)
|
|
64
69
|
end
|
|
65
70
|
offset = (content_x_offset || DEFAULT_CONTENT_X_OFFSET) +
|
|
66
71
|
2 * (size - 7)
|
|
67
|
-
|
|
72
|
+
offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
|
|
73
|
+
text_blocks << build_text(
|
|
74
|
+
bounds.x + offset, y, rest, family, size, size_unit, fill, logical_height: logical_height
|
|
75
|
+
)
|
|
68
76
|
elsif line.strip.start_with?("«")
|
|
69
|
-
text_blocks << build_text(
|
|
77
|
+
text_blocks << build_text(
|
|
78
|
+
bounds.x + STEREOTYPE_HEADER_X_OFFSET, y, line.strip, family, size, size_unit, fill,
|
|
79
|
+
logical_height: logical_height
|
|
80
|
+
)
|
|
70
81
|
else
|
|
71
|
-
text_blocks << build_text(
|
|
82
|
+
text_blocks << build_text(
|
|
83
|
+
bounds.x + visibility_x_offset, y, line.strip, family, size, size_unit, fill,
|
|
84
|
+
logical_height: logical_height
|
|
85
|
+
)
|
|
72
86
|
end
|
|
73
87
|
end
|
|
74
88
|
group_style = "stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; fill:#{fill};fill-opacity:1.00; stroke:#000000; stroke-opacity:0.00"
|
|
@@ -149,9 +163,11 @@ module Ea
|
|
|
149
163
|
end
|
|
150
164
|
private_class_method :split_visibility
|
|
151
165
|
|
|
152
|
-
def self.build_text(x, y, content, family, size, size_unit = DEFAULT_FONT_UNIT,
|
|
166
|
+
def self.build_text(x, y, content, family, size, size_unit = DEFAULT_FONT_UNIT,
|
|
167
|
+
fill = "#000000", logical_height: nil)
|
|
153
168
|
TextRenderer.new(content: content, x: x, y: y,
|
|
154
|
-
|
|
169
|
+
family: family, size: size, size_unit: size_unit,
|
|
170
|
+
logical_height: logical_height, fill: fill).to_svg
|
|
155
171
|
end
|
|
156
172
|
private_class_method :build_text
|
|
157
173
|
|
|
@@ -40,10 +40,14 @@ module Ea
|
|
|
40
40
|
attr_line_offset: 6,
|
|
41
41
|
attr_first_offset: 7,
|
|
42
42
|
attr_group_lead: 0,
|
|
43
|
+
family: nil, size_unit: "pt", logical_height: nil,
|
|
43
44
|
header_ghost: false,
|
|
44
45
|
header_ghost_tight: false)
|
|
45
46
|
@bounds = bounds
|
|
46
47
|
@size = size
|
|
48
|
+
@family = family
|
|
49
|
+
@size_unit = size_unit
|
|
50
|
+
@logical_height = logical_height
|
|
47
51
|
@header_lines_count = header_lines_count
|
|
48
52
|
@attr_lines_count = attr_lines_count
|
|
49
53
|
@op_lines_count = op_lines_count
|
|
@@ -61,6 +65,13 @@ module Ea
|
|
|
61
65
|
@header_ghost_tight = header_ghost_tight
|
|
62
66
|
end
|
|
63
67
|
|
|
68
|
+
def line_height(offset)
|
|
69
|
+
Ea::Fonts::Metrics.compartment_line_height(
|
|
70
|
+
size, family: @family, unit: @size_unit, offset: offset, logical_height: @logical_height
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
private :line_height
|
|
74
|
+
|
|
64
75
|
def header_first_y
|
|
65
76
|
bounds.y + size + (header_top_padding || 12)
|
|
66
77
|
end
|
|
@@ -81,9 +92,9 @@ module Ea
|
|
|
81
92
|
ghost_shift = header_ghost ? (header_ghost_tight ? 3 : 6) : 0
|
|
82
93
|
|
|
83
94
|
header_first_y +
|
|
84
|
-
([header_lines_count, 1].max - 1) * (
|
|
95
|
+
([header_lines_count, 1].max - 1) * line_height(header_line_offset || 6) +
|
|
85
96
|
ghost_shift +
|
|
86
|
-
(marker_count.to_i * (
|
|
97
|
+
(marker_count.to_i * line_height(6)) +
|
|
87
98
|
(divider_offset || 8)
|
|
88
99
|
end
|
|
89
100
|
|
|
@@ -104,13 +115,13 @@ module Ea
|
|
|
104
115
|
def raw_attr_first_y
|
|
105
116
|
return bounds.y + size + (header_top_padding || 12) + 12 unless divider_y
|
|
106
117
|
|
|
107
|
-
divider_y +
|
|
118
|
+
divider_y + line_height((attr_first_offset || 7) - 1) + 1
|
|
108
119
|
end
|
|
109
120
|
|
|
110
121
|
def attr_bottom_y
|
|
111
122
|
return attr_first_y unless attr_lines_count&.positive?
|
|
112
123
|
|
|
113
|
-
attr_first_y + (attr_lines_count - 1) * (
|
|
124
|
+
attr_first_y + (attr_lines_count - 1) * line_height(attr_line_offset || 6) +
|
|
114
125
|
@attr_group_lead.to_i
|
|
115
126
|
end
|
|
116
127
|
|
|
@@ -137,7 +148,7 @@ module Ea
|
|
|
137
148
|
def op_bottom_y
|
|
138
149
|
return op_divider_y unless op_lines_count&.positive?
|
|
139
150
|
|
|
140
|
-
op_first_y + (op_lines_count - 1) * (
|
|
151
|
+
op_first_y + (op_lines_count - 1) * line_height(6)
|
|
141
152
|
end
|
|
142
153
|
|
|
143
154
|
# EA draws a single divider under the header and seats
|
|
@@ -147,7 +158,7 @@ module Ea
|
|
|
147
158
|
def enum_literal_first_y
|
|
148
159
|
return nil unless divider_y
|
|
149
160
|
|
|
150
|
-
divider_y +
|
|
161
|
+
divider_y + line_height((attr_first_offset || 7) - 1) + 1
|
|
151
162
|
end
|
|
152
163
|
|
|
153
164
|
# OCL constraints follow the regular content with a divider and caption.
|
|
@@ -199,7 +210,7 @@ module Ea
|
|
|
199
210
|
def enum_literals_bottom_y
|
|
200
211
|
return nil unless enum_literal_first_y
|
|
201
212
|
|
|
202
|
-
enum_literal_first_y + ([enum_literals_count, 1].max - 1) * (
|
|
213
|
+
enum_literal_first_y + ([enum_literals_count, 1].max - 1) * line_height(6)
|
|
203
214
|
end
|
|
204
215
|
end
|
|
205
216
|
end
|
|
@@ -39,27 +39,31 @@ module Ea
|
|
|
39
39
|
def self.compartment_row(constraint, context, index)
|
|
40
40
|
build_text(context.bounds.x + 5,
|
|
41
41
|
context.geometry.constraints_first_y + (index + 1) * (context.size + 5),
|
|
42
|
-
"{#{constraint.name}}", context.family, context.size, context.text_fill
|
|
42
|
+
"{#{constraint.name}}", context.family, context.size, context.text_fill,
|
|
43
|
+
size_unit: context.size_unit, logical_height: context.logical_height)
|
|
43
44
|
end
|
|
44
45
|
private_class_method :compartment_row
|
|
45
46
|
|
|
46
47
|
def self.compartment_caption(context)
|
|
47
48
|
TextRenderer.new(content: "constraints", x: caption_x(context),
|
|
48
49
|
y: context.geometry.constraints_first_y, family: context.family,
|
|
49
|
-
size: context.size,
|
|
50
|
+
size: context.size, size_unit: context.size_unit, logical_height: context.logical_height,
|
|
51
|
+
fill: context.text_fill, style: "italic").to_svg
|
|
50
52
|
end
|
|
51
53
|
private_class_method :compartment_caption
|
|
52
54
|
|
|
53
55
|
def self.caption_x(context)
|
|
54
56
|
width = TextRenderer.estimate_width("constraints", context.size, nil,
|
|
55
|
-
family: context.family, style: "italic"
|
|
57
|
+
family: context.family, style: "italic",
|
|
58
|
+
logical_height: context.logical_height).round
|
|
56
59
|
context.bounds.x + ((context.bounds.width - width) / 2).floor
|
|
57
60
|
end
|
|
58
61
|
private_class_method :caption_x
|
|
59
62
|
|
|
60
|
-
def self.build_text(x, y, content, family, size, fill)
|
|
63
|
+
def self.build_text(x, y, content, family, size, fill, size_unit: "px", logical_height: nil)
|
|
61
64
|
TextRenderer.new(content: content, x: x, y: y,
|
|
62
|
-
|
|
65
|
+
family: family, size: size, size_unit: size_unit,
|
|
66
|
+
logical_height: logical_height, fill: fill).to_svg
|
|
63
67
|
end
|
|
64
68
|
private_class_method :build_text
|
|
65
69
|
end
|
|
@@ -16,19 +16,22 @@ module Ea
|
|
|
16
16
|
# EA lists literals alphabetically (case-insensitive) and
|
|
17
17
|
# spaces rows at the same 13px pitch as attributes at 7pt
|
|
18
18
|
# (size + 6) — corpus-verified against EA-published SVGs.
|
|
19
|
-
def self.render(literals, bounds:, first_y:, family:, size:)
|
|
20
|
-
line_h =
|
|
19
|
+
def self.render(literals, bounds:, first_y:, family:, size:, size_unit: "px", logical_height: nil)
|
|
20
|
+
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
21
|
+
size, family: family, unit: size_unit, logical_height: logical_height
|
|
22
|
+
)
|
|
21
23
|
sorted = literals.sort_by { |l| [l.name.to_s.downcase, l.name.to_s] }
|
|
22
24
|
text_blocks = sorted.each_with_index.map do |literal, idx|
|
|
23
25
|
y = first_y + (idx * line_h)
|
|
24
|
-
build_text(bounds.x + 5, y, literal.name.to_s, family, size)
|
|
26
|
+
build_text(bounds.x + 5, y, literal.name.to_s, family, size, size_unit, logical_height: logical_height)
|
|
25
27
|
end
|
|
26
28
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
def self.build_text(x, y, content, family, size)
|
|
31
|
+
def self.build_text(x, y, content, family, size, size_unit, logical_height: nil)
|
|
30
32
|
TextRenderer.new(content: content, x: x, y: y,
|
|
31
|
-
|
|
33
|
+
family: family, size: size, size_unit: size_unit,
|
|
34
|
+
logical_height: logical_height).to_svg
|
|
32
35
|
end
|
|
33
36
|
private_class_method :build_text
|
|
34
37
|
end
|
|
@@ -19,7 +19,7 @@ module Ea
|
|
|
19
19
|
module HeaderLinePipeline
|
|
20
20
|
Context = Struct.new(:classifier, :diagram_package_id,
|
|
21
21
|
:visually_nested, :umldi_keyword,
|
|
22
|
-
:bounds_width, :font_size, :family,
|
|
22
|
+
:bounds_width, :font_size, :family, :logical_height,
|
|
23
23
|
:off_canvas_parent_name,
|
|
24
24
|
:foreign_package_name,
|
|
25
25
|
:suppress_stereotypes,
|
|
@@ -57,7 +57,8 @@ module Ea
|
|
|
57
57
|
width = lambda do |s|
|
|
58
58
|
TextRenderer.estimate_width(
|
|
59
59
|
s, context.font_size || 9, nil,
|
|
60
|
-
family: context.family, weight: "700", style: font_style
|
|
60
|
+
family: context.family, weight: "700", style: font_style,
|
|
61
|
+
logical_height: context.logical_height
|
|
61
62
|
).round
|
|
62
63
|
end
|
|
63
64
|
return [text] if context.bounds_width.nil?
|
|
@@ -20,15 +20,20 @@ module Ea
|
|
|
20
20
|
return [] unless inst.is_a?(Ea::Model::InstanceSpecification)
|
|
21
21
|
|
|
22
22
|
lines = stereotype_lines(inst, context)
|
|
23
|
-
|
|
24
|
-
if inst.classifier_name && !inst.classifier_name.empty?
|
|
25
|
-
label = "#{label}: #{inst.classifier_name}"
|
|
26
|
-
end
|
|
27
|
-
lines + HeaderLinePipeline.wrap_words(label, context, :bold).map do |line|
|
|
23
|
+
lines + HeaderLinePipeline.wrap_words(label_for(inst), context, :bold).map do |line|
|
|
28
24
|
[line, :bold]
|
|
29
25
|
end
|
|
30
26
|
end
|
|
31
27
|
|
|
28
|
+
# "Name / alias: Classifier"; t_object.Alias is the instance role.
|
|
29
|
+
def self.label_for(inst)
|
|
30
|
+
label = inst.name.to_s
|
|
31
|
+
label = "#{label} / #{inst.role_name}" unless inst.role_name.to_s.empty?
|
|
32
|
+
return label if inst.classifier_name.to_s.empty?
|
|
33
|
+
|
|
34
|
+
"#{label}: #{inst.classifier_name}"
|
|
35
|
+
end
|
|
36
|
+
|
|
32
37
|
def self.stereotype_lines(inst, context)
|
|
33
38
|
return [] if context.suppress_stereotypes
|
|
34
39
|
|
|
@@ -56,7 +56,8 @@ module Ea
|
|
|
56
56
|
font_style = weight == :bold_italic ? "italic" : "normal"
|
|
57
57
|
len = TextRenderer.estimate_width(
|
|
58
58
|
name, context.font_size || 9, nil,
|
|
59
|
-
family: context.family, weight: "700", style: font_style
|
|
59
|
+
family: context.family, weight: "700", style: font_style,
|
|
60
|
+
logical_height: context.logical_height
|
|
60
61
|
).round
|
|
61
62
|
# Same usable width as wrap_words: box width - 6.
|
|
62
63
|
# EA wraps "Content information::MD_FeatureCatalogue-
|
|
@@ -12,17 +12,20 @@ module Ea
|
|
|
12
12
|
# x= is the start position, not the center).
|
|
13
13
|
class HeaderRenderer
|
|
14
14
|
def self.render(lines, bounds:, first_y:, family:,
|
|
15
|
-
size:, size_unit: "pt",
|
|
15
|
+
size:, size_unit: "pt", logical_height: nil,
|
|
16
16
|
fill: "#000000", weight_normal: 400, weight_bold: 700,
|
|
17
17
|
stroke_in_text: "#000000", width_factor: 0.612,
|
|
18
18
|
line_offset: 6)
|
|
19
|
-
|
|
19
|
+
logical_height = nil unless size_unit == "pt"
|
|
20
|
+
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
21
|
+
size, family: family, unit: size_unit, offset: line_offset, logical_height: logical_height
|
|
22
|
+
)
|
|
20
23
|
text_blocks = lines.each_with_index.map do |(text, style), idx|
|
|
21
24
|
weight = case style
|
|
22
25
|
when :bold, :bold_italic then weight_bold
|
|
23
26
|
else weight_normal
|
|
24
27
|
end
|
|
25
|
-
font_style =
|
|
28
|
+
font_style = %i[italic italic_tight bold_italic].include?(style) ? "italic" : "normal"
|
|
26
29
|
# EA spaces the line AFTER a right-aligned ghost 3px
|
|
27
30
|
# wider than the pitch. Interfaces use a tighter +3
|
|
28
31
|
# (936AA434/Surface: ghost 100, «interface» 116).
|
|
@@ -33,23 +36,22 @@ module Ea
|
|
|
33
36
|
end
|
|
34
37
|
y = first_y + (idx * line_h) +
|
|
35
38
|
(idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
|
|
36
|
-
x = if style
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
bounds.x + bounds.width -
|
|
39
|
+
x = if %i[italic italic_tight].include?(style)
|
|
40
|
+
# Parent ghosts end 3px inside the box's right edge.
|
|
41
|
+
bounds.x + bounds.width - 3 -
|
|
40
42
|
Ea::Svg::EaEmitter::TextRenderer.estimate_width(
|
|
41
43
|
text, size, width_factor,
|
|
42
|
-
family: family, weight: weight, style: font_style
|
|
44
|
+
family: family, weight: weight, style: font_style, logical_height: logical_height
|
|
43
45
|
).round
|
|
44
46
|
else
|
|
45
47
|
center_x_for(text, bounds, size, width_factor,
|
|
46
48
|
family: family, weight: weight,
|
|
47
|
-
style: font_style)
|
|
49
|
+
style: font_style, logical_height: logical_height)
|
|
48
50
|
end
|
|
49
51
|
TextRenderer.new(
|
|
50
52
|
content: text,
|
|
51
53
|
x: x, y: y,
|
|
52
|
-
family: family, size: size, size_unit: size_unit,
|
|
54
|
+
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
53
55
|
weight: weight, style: font_style, fill: fill,
|
|
54
56
|
stroke_in_text: stroke_in_text, width_factor: width_factor
|
|
55
57
|
).to_svg
|
|
@@ -60,9 +62,9 @@ module Ea
|
|
|
60
62
|
# EA centers text by its INTEGER textLength and floors the
|
|
61
63
|
# resulting x (verified exact against EA-published SVGs).
|
|
62
64
|
def self.center_x_for(text, bounds, size, width_factor,
|
|
63
|
-
family: nil, weight: nil, style: nil)
|
|
65
|
+
family: nil, weight: nil, style: nil, logical_height: nil)
|
|
64
66
|
len = Ea::Svg::EaEmitter::TextRenderer.estimate_width(
|
|
65
|
-
text, size, width_factor, family: family, weight: weight, style: style
|
|
67
|
+
text, size, width_factor, family: family, weight: weight, style: style, logical_height: logical_height
|
|
66
68
|
).round
|
|
67
69
|
(bounds.x + (bounds.width - len) / 2.0).floor
|
|
68
70
|
end
|
|
@@ -16,10 +16,12 @@ 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:)
|
|
19
|
+
def self.render(operations, bounds:, first_y:, family:, size:, size_unit: "px", logical_height: nil)
|
|
20
20
|
# EA's op rows pitch 13 (size + 6), same as attribute
|
|
21
21
|
# rows (6D4C665D: ops 541..658 step 13).
|
|
22
|
-
line_h =
|
|
22
|
+
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
23
|
+
size, family: family, unit: size_unit, logical_height: logical_height
|
|
24
|
+
)
|
|
23
25
|
text_blocks = []
|
|
24
26
|
has_receptions = operations.any?(&:is_reception)
|
|
25
27
|
if has_receptions
|
|
@@ -27,7 +29,7 @@ module Ea
|
|
|
27
29
|
content: "receptions",
|
|
28
30
|
x: bounds.x + (bounds.width / 2.0) - (8 * size * 0.65) / 2,
|
|
29
31
|
y: first_y,
|
|
30
|
-
family: family, size: size,
|
|
32
|
+
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
31
33
|
style: "italic", fill: "#000000"
|
|
32
34
|
).to_svg
|
|
33
35
|
end
|
|
@@ -36,9 +38,9 @@ module Ea
|
|
|
36
38
|
ops.each_with_index do |op, idx|
|
|
37
39
|
y = first_y + ((idx + offset) * line_h)
|
|
38
40
|
text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
|
|
39
|
-
|
|
41
|
+
visibility_prefix(op), family, size, size_unit, logical_height: logical_height)
|
|
40
42
|
text_blocks << build_text(bounds.x + AttributeRenderer.content_x_offset(size), y,
|
|
41
|
-
|
|
43
|
+
operation_text(op), family, size, size_unit, logical_height: logical_height)
|
|
42
44
|
end
|
|
43
45
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
44
46
|
end
|
|
@@ -90,9 +92,10 @@ module Ea
|
|
|
90
92
|
end
|
|
91
93
|
private_class_method :reception_params
|
|
92
94
|
|
|
93
|
-
def self.build_text(x, y, content, family, size)
|
|
95
|
+
def self.build_text(x, y, content, family, size, size_unit, logical_height: nil)
|
|
94
96
|
TextRenderer.new(content: content, x: x, y: y,
|
|
95
|
-
|
|
97
|
+
family: family, size: size, size_unit: size_unit,
|
|
98
|
+
logical_height: logical_height).to_svg
|
|
96
99
|
end
|
|
97
100
|
private_class_method :build_text
|
|
98
101
|
end
|
|
@@ -116,6 +116,7 @@ module Ea
|
|
|
116
116
|
size = font_resolver.size_for(element)
|
|
117
117
|
family = font_resolver.family_for(element)
|
|
118
118
|
size_unit = font_resolver.size_unit_for(element)
|
|
119
|
+
logical_height = font_resolver.logical_height_for(element)
|
|
119
120
|
parent_name = off_canvas_parent_name_for(classifier)
|
|
120
121
|
|
|
121
122
|
|
|
@@ -152,8 +153,8 @@ module Ea
|
|
|
152
153
|
# 22 + 3 (CharacterString 197+27=224, Integer 92+25=117).
|
|
153
154
|
# Titles never drive growth - they wrap instead.
|
|
154
155
|
if attr_lines.any? || op_lines.any?
|
|
155
|
-
row_w = [row_content_width(attr_lines, 5, size, family),
|
|
156
|
-
row_content_width(op_lines, 3, size, family)].compact.max
|
|
156
|
+
row_w = [row_content_width(attr_lines, 5, size, family, logical_height),
|
|
157
|
+
row_content_width(op_lines, 3, size, family, logical_height)].compact.max
|
|
157
158
|
if row_w && row_w > bounds.width
|
|
158
159
|
bounds = Ea::Model::Bounds.new(
|
|
159
160
|
x: bounds.x, y: bounds.y,
|
|
@@ -167,18 +168,19 @@ module Ea
|
|
|
167
168
|
visually_nested: visually_nested?(element),
|
|
168
169
|
umldi_keyword: element.umldi_keyword,
|
|
169
170
|
bounds_width: bounds.width,
|
|
170
|
-
font_size: size,
|
|
171
|
+
font_size: size, logical_height: logical_height,
|
|
171
172
|
family: family,
|
|
172
173
|
off_canvas_parent_name: parent_name,
|
|
173
174
|
foreign_package_name: foreign_package_name_for(classifier),
|
|
174
175
|
suppress_stereotypes: suppress_stereotypes?) : []
|
|
175
176
|
geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
tagged_values_for(classifier).size,
|
|
178
|
+
header_lines, classifier: classifier, family: family,
|
|
179
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
180
|
+
constraints_count: constraints_for(classifier).size,
|
|
181
|
+
marker_count: marker_lines_for(classifier, element).size,
|
|
182
|
+
enum_literals_count: enum_row_count_for(classifier),
|
|
183
|
+
attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
|
|
182
184
|
# EA suppresses the whole attribute compartment when its
|
|
183
185
|
# rows do not fit the stored box height (never truncates
|
|
184
186
|
# rows) - the AOS box on CityFurniture hides its
|
|
@@ -196,12 +198,13 @@ module Ea
|
|
|
196
198
|
geometry.attr_bottom_y.to_i > bounds.y + bounds.height
|
|
197
199
|
attr_lines = []
|
|
198
200
|
geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
tagged_values_for(classifier).size,
|
|
202
|
+
header_lines, classifier: classifier, family: family,
|
|
203
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
204
|
+
constraints_count: constraints_for(classifier).size,
|
|
205
|
+
marker_count: marker_lines_for(classifier, element).size,
|
|
206
|
+
enum_literals_count: enum_row_count_for(classifier),
|
|
207
|
+
attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
|
|
205
208
|
end
|
|
206
209
|
# EA suppresses the operations compartment when its rows
|
|
207
210
|
# overflow the stored box height (same rule as attributes)
|
|
@@ -216,22 +219,24 @@ module Ea
|
|
|
216
219
|
height: geometry.op_bottom_y.to_i + 8 - bounds.y
|
|
217
220
|
)
|
|
218
221
|
geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
222
|
+
tagged_values_for(classifier).size,
|
|
223
|
+
header_lines, classifier: classifier, family: family,
|
|
224
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
225
|
+
constraints_count: constraints_for(classifier).size,
|
|
226
|
+
marker_count: marker_lines_for(classifier, element).size,
|
|
227
|
+
enum_literals_count: enum_row_count_for(classifier),
|
|
228
|
+
attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
|
|
225
229
|
elsif op_lines.any? && geometry.op_first_y &&
|
|
226
230
|
geometry.op_bottom_y.to_i > bounds.y + bounds.height
|
|
227
231
|
op_lines = []
|
|
228
232
|
geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
tagged_values_for(classifier).size,
|
|
234
|
+
header_lines, classifier: classifier, family: family,
|
|
235
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
236
|
+
constraints_count: constraints_for(classifier).size,
|
|
237
|
+
marker_count: marker_lines_for(classifier, element).size,
|
|
238
|
+
enum_literals_count: enum_row_count_for(classifier),
|
|
239
|
+
attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
|
|
235
240
|
end
|
|
236
241
|
# Real attributes grow the DRAWN box past the stored
|
|
237
242
|
# height instead of being dropped (0CABA7D7: stored h44
|
|
@@ -265,7 +270,7 @@ module Ea
|
|
|
265
270
|
stroke: resolve_stroke,
|
|
266
271
|
stroke_width: resolve_stroke_width,
|
|
267
272
|
text_fill: theme.text_color,
|
|
268
|
-
family: family, size: size, size_unit: size_unit,
|
|
273
|
+
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
269
274
|
header_lines: header_lines,
|
|
270
275
|
attr_lines: attr_lines,
|
|
271
276
|
op_lines: op_lines,
|
|
@@ -283,14 +288,14 @@ module Ea
|
|
|
283
288
|
)
|
|
284
289
|
end
|
|
285
290
|
|
|
286
|
-
def row_content_width(lines, right_pad, size, family)
|
|
291
|
+
def row_content_width(lines, right_pad, size, family, logical_height)
|
|
287
292
|
return nil if lines.to_a.empty?
|
|
288
293
|
|
|
289
294
|
tl = lines.filter_map do |line|
|
|
290
295
|
text = line.sub(/\A\S /, "")
|
|
291
296
|
Ea::Fonts::Metrics.text_length(text, size,
|
|
292
297
|
family: family,
|
|
293
|
-
weight: "400")&.round
|
|
298
|
+
weight: "400", logical_height: logical_height)&.round
|
|
294
299
|
end.max
|
|
295
300
|
return nil unless tl
|
|
296
301
|
|
|
@@ -301,8 +306,9 @@ module Ea
|
|
|
301
306
|
tagged_count, header_lines, classifier: nil,
|
|
302
307
|
constraints_count: 0, marker_count: 0,
|
|
303
308
|
enum_literals_count: 0,
|
|
304
|
-
attr_group_lead: 0)
|
|
305
|
-
CompartmentGeometry.new(bounds: bounds, size: size,
|
|
309
|
+
attr_group_lead: 0, family: nil, size_unit: "pt", logical_height: nil)
|
|
310
|
+
CompartmentGeometry.new(bounds: bounds, size: size, family: family,
|
|
311
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
306
312
|
header_ghost: header_lines.any? &&
|
|
307
313
|
%i[italic italic_tight].include?(header_lines.first.last),
|
|
308
314
|
header_ghost_tight: header_lines.any? &&
|
|
@@ -496,6 +502,7 @@ module Ea
|
|
|
496
502
|
|
|
497
503
|
def foreign_package_name_for(classifier)
|
|
498
504
|
return nil unless diagram.show_foreign
|
|
505
|
+
return nil if diagram.style_ex.to_s.split(";").include?("NoFullScope=1")
|
|
499
506
|
return nil unless classifier.is_a?(Ea::Model::Classifier)
|
|
500
507
|
|
|
501
508
|
# Nested classifiers (t_object.ParentID -> a Class) qualify
|
|
@@ -41,9 +41,8 @@ module Ea
|
|
|
41
41
|
DEFAULT_MARKER_FAMILIES.include?(explicit)
|
|
42
42
|
return explicit
|
|
43
43
|
end
|
|
44
|
-
return theme.font_family if theme.themed? && theme.font_family
|
|
45
44
|
|
|
46
|
-
diagram_default_family || DEFAULT_FONT_FAMILY
|
|
45
|
+
theme_family_for(explicit) || diagram_default_family || DEFAULT_FONT_FAMILY
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
def size_for(element)
|
|
@@ -58,8 +57,12 @@ module Ea
|
|
|
58
57
|
DEFAULT_ELEMENT_FONT_SIZE
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
def size_unit_for(
|
|
62
|
-
theme.font_size_unit || "pt"
|
|
60
|
+
def size_unit_for(element)
|
|
61
|
+
element&.font_size_unit || theme.font_size_unit || "pt"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def logical_height_for(element)
|
|
65
|
+
element&.font_logical_height if size_unit_for(element) == "pt"
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
def weight_for(element)
|
|
@@ -73,6 +76,13 @@ module Ea
|
|
|
73
76
|
|
|
74
77
|
private
|
|
75
78
|
|
|
79
|
+
def theme_family_for(explicit)
|
|
80
|
+
return unless theme.themed?
|
|
81
|
+
return theme.font_family if DEFAULT_MARKER_FAMILIES.include?(explicit)
|
|
82
|
+
|
|
83
|
+
theme.element_font_family || theme.font_family
|
|
84
|
+
end
|
|
85
|
+
|
|
76
86
|
# EA's app-default font depends on the system locale:
|
|
77
87
|
# Japanese Windows → "Yu Gothic UI" 13, English Windows →
|
|
78
88
|
# "Calibri" 10. We can't query EA's runtime, so we infer
|
|
@@ -42,8 +42,11 @@ module Ea
|
|
|
42
42
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
|
+
# EA does not draw a connector whose other end is missing from
|
|
46
|
+
# the diagram, so it has no waypoints and no ghost label either.
|
|
45
47
|
def visible_connectors
|
|
46
48
|
(diagram.connectors || []).select(&:renderable?)
|
|
49
|
+
.reject { |connector| connector.waypoints.empty? }
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def text_for(ghost)
|