ea 0.6.122 → 0.6.124
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/model/legend.rb +2 -0
- data/lib/ea/sources/qea/legend_builder.rb +2 -0
- data/lib/ea/svg/ea_emitter/compartment/instance_underline.rb +22 -18
- data/lib/ea/svg/ea_emitter/compartment/package_contents.rb +10 -1
- data/lib/ea/svg/ea_emitter/connector_redock.rb +5 -3
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +18 -11
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +44 -29
- data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +15 -5
- data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +14 -2
- data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +17 -5
- data/lib/ea/svg/ea_emitter/elements.rb +25 -2
- 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: dd02b1a50cdc130c8d08643767666068e9272158df4592e4bea690c1b1e7e778
|
|
4
|
+
data.tar.gz: dc499639ec487f46cc5ee952b1fa16ce58a7eb5fc57747ca4b9ff7584488fed4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 537cd073276c4eb487d6848c28e5522df1c87cdf55170d2e94e4142f4ed3e6b2dd2544fc8220cbc75373fd33ac47349e25f0f979b126791d567946be3a3a2007
|
|
7
|
+
data.tar.gz: e07055d655ee0667d604a9089bf243ea3dc7d4e602035ec1a2a58e2db27f744e410dc4b095b3d85f0fdffd02a8068675c24c172bd44062a82d1259191242359d
|
data/lib/ea/model/legend.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Ea
|
|
|
21
21
|
|
|
22
22
|
attribute :title, :string, default: -> { DEFAULT_TITLE }
|
|
23
23
|
attribute :heading_size, :integer
|
|
24
|
+
attribute :heading_family, :string
|
|
24
25
|
attribute :font_color, :integer, default: -> { DEFAULT_FONT_COLOR }
|
|
25
26
|
attribute :background_color, :integer, default: -> { DEFAULT_BACKGROUND_COLOR }
|
|
26
27
|
attribute :border_color, :integer, default: -> { DEFAULT_BORDER_COLOR }
|
|
@@ -32,6 +33,7 @@ module Ea
|
|
|
32
33
|
map "name", to: :name
|
|
33
34
|
map "title", to: :title
|
|
34
35
|
map "headingSize", to: :heading_size
|
|
36
|
+
map "headingFamily", to: :heading_family
|
|
35
37
|
map "fontColor", to: :font_color
|
|
36
38
|
map "backgroundColor", to: :background_color
|
|
37
39
|
map "borderColor", to: :border_color
|
|
@@ -12,6 +12,7 @@ module Ea
|
|
|
12
12
|
# Returns nil when no matching xref exists (the Text element
|
|
13
13
|
# is not a legend — caller should render it as a plain note).
|
|
14
14
|
class LegendBuilder
|
|
15
|
+
DEFAULT_HEADING_FAMILY = "Cambria"
|
|
15
16
|
LEGEND_XREF_NAME = "CustomProperties"
|
|
16
17
|
LEGEND_TYPE_OBJECT_STYLE = "LEGEND_OBJECTSTYLE"
|
|
17
18
|
LEGEND_TYPE_STYLE_SETTINGS = "LEGEND_STYLE_SETTINGS"
|
|
@@ -77,6 +78,7 @@ module Ea
|
|
|
77
78
|
Ea::Model::Legend.new(
|
|
78
79
|
title: style["TITLE"] || Ea::Model::Legend::DEFAULT_TITLE,
|
|
79
80
|
heading_size: int_or_nil(style["HEADINGSIZE"]),
|
|
81
|
+
heading_family: DEFAULT_HEADING_FAMILY,
|
|
80
82
|
font_color: int_or(style["FONTCOLOR"],
|
|
81
83
|
Ea::Model::Legend::DEFAULT_FONT_COLOR),
|
|
82
84
|
background_color: int_or(style["BACKGROUNDCOLOR"],
|
|
@@ -5,12 +5,10 @@ module Ea
|
|
|
5
5
|
module EaEmitter
|
|
6
6
|
module Compartment
|
|
7
7
|
# Instance-name underline for object diagrams. EA draws a
|
|
8
|
-
# short horizontal line
|
|
9
|
-
#
|
|
10
|
-
# only when the diagram's `diagram_type` is "Object".
|
|
8
|
+
# short horizontal line below each instance name to denote
|
|
9
|
+
# that it is an instance (UML convention).
|
|
11
10
|
module InstanceUnderline
|
|
12
|
-
Y_OFFSET =
|
|
13
|
-
WIDTH_FACTOR = 0.65
|
|
11
|
+
Y_OFFSET = 1
|
|
14
12
|
|
|
15
13
|
module_function
|
|
16
14
|
|
|
@@ -19,11 +17,22 @@ module Ea
|
|
|
19
17
|
return nil unless object_diagram?(context)
|
|
20
18
|
return nil if context.header_lines.empty?
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
header = Element::HeaderRenderer.line_layout(
|
|
21
|
+
context.header_lines,
|
|
22
|
+
bounds: context.bounds,
|
|
23
|
+
first_y: context.geometry.header_first_y,
|
|
24
|
+
family: context.family,
|
|
25
|
+
size: context.size,
|
|
26
|
+
size_unit: context.size_unit,
|
|
27
|
+
logical_height: context.logical_height,
|
|
28
|
+
weight_normal: context.theme.text_weight_normal,
|
|
29
|
+
weight_bold: context.theme.text_weight_bold,
|
|
30
|
+
width_factor: context.theme.text_width_factor,
|
|
31
|
+
line_offset: context.theme.compartments.header_line_offset
|
|
32
|
+
).last
|
|
33
|
+
return nil unless header && !header.text.to_s.empty?
|
|
25
34
|
|
|
26
|
-
underline_path(
|
|
35
|
+
underline_path(header)
|
|
27
36
|
end
|
|
28
37
|
|
|
29
38
|
def object_diagram?(context)
|
|
@@ -31,18 +40,13 @@ module Ea
|
|
|
31
40
|
end
|
|
32
41
|
module_function :object_diagram?
|
|
33
42
|
|
|
34
|
-
def underline_path(
|
|
35
|
-
|
|
36
|
-
text_width = TextRenderer.estimate_width(text, context.size,
|
|
37
|
-
WIDTH_FACTOR)
|
|
38
|
-
x_start = bounds.x + (bounds.width - text_width) / 2.0
|
|
39
|
-
y_pos = context.geometry.header_first_y + Y_OFFSET
|
|
40
|
-
build_path_tag(x_start, y_pos, text_width)
|
|
43
|
+
def underline_path(header)
|
|
44
|
+
build_path_tag(header.x, header.y + Y_OFFSET, header.text_length)
|
|
41
45
|
end
|
|
42
46
|
module_function :underline_path
|
|
43
47
|
|
|
44
|
-
def build_path_tag(x_start, y_pos,
|
|
45
|
-
x_end = x_start +
|
|
48
|
+
def build_path_tag(x_start, y_pos, text_length)
|
|
49
|
+
x_end = x_start + text_length
|
|
46
50
|
coords = format_coords(x_start, y_pos, x_end)
|
|
47
51
|
d = "M #{coords[:x1]} #{coords[:y1]} L #{coords[:x2]} #{coords[:y1]}"
|
|
48
52
|
style = "stroke-width:1;stroke-linecap:square;" \
|
|
@@ -14,6 +14,7 @@ module Ea
|
|
|
14
14
|
ROW_HEIGHT = 16
|
|
15
15
|
ICON_X_OFFSET = 5
|
|
16
16
|
TEXT_X_OFFSET = 23
|
|
17
|
+
FIRST_BASELINE_OFFSET = 15
|
|
17
18
|
|
|
18
19
|
module_function
|
|
19
20
|
|
|
@@ -22,7 +23,7 @@ module Ea
|
|
|
22
23
|
return nil if rows.nil? || rows.empty?
|
|
23
24
|
|
|
24
25
|
bounds = context.bounds
|
|
25
|
-
first_y = context
|
|
26
|
+
first_y = first_baseline(context)
|
|
26
27
|
blocks = rows.each_with_index.flat_map do |row, idx|
|
|
27
28
|
y_top = first_y - 11 + (idx * ROW_HEIGHT)
|
|
28
29
|
y_text = first_y + (idx * ROW_HEIGHT)
|
|
@@ -34,6 +35,14 @@ module Ea
|
|
|
34
35
|
wrap_group(blocks.join("\n"))
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def first_baseline(context)
|
|
39
|
+
context.bounds.y + Element::PackageShapeRenderer.tab_height_for(
|
|
40
|
+
stereotype: Shape.package_stereotype(context.model_element),
|
|
41
|
+
family: context.family, size: context.size, size_unit: context.size_unit
|
|
42
|
+
) + FIRST_BASELINE_OFFSET
|
|
43
|
+
end
|
|
44
|
+
private_class_method :first_baseline
|
|
45
|
+
|
|
37
46
|
def wrap_group(body)
|
|
38
47
|
%(<g style="#{group_style}">\n#{body}\n</g>)
|
|
39
48
|
end
|
|
@@ -256,12 +256,14 @@ module Ea
|
|
|
256
256
|
end
|
|
257
257
|
private_class_method :default_model_connector?
|
|
258
258
|
|
|
259
|
-
#
|
|
260
|
-
#
|
|
259
|
+
# Thin connectors between explicitly bordered elements stop on the outer
|
|
260
|
+
# edge of their 2px borders, including package imports.
|
|
261
261
|
def bordered_outline?(connector, diagram, refs, bounds)
|
|
262
262
|
style = connector.style.to_h
|
|
263
263
|
return false unless style[:lwidth] == "0" && !style[:tree]
|
|
264
|
-
return false unless %w[Generalization Aggregation Association Dependency].include?(
|
|
264
|
+
return false unless %w[Generalization Aggregation Association Dependency Package].include?(
|
|
265
|
+
connector.connector_type
|
|
266
|
+
)
|
|
265
267
|
return false unless bounds.all? { |box| box.width.positive? && box.height.positive? }
|
|
266
268
|
|
|
267
269
|
ends = [[refs.first, style[:soid]], [refs.last, style[:eoid]]]
|
|
@@ -40,6 +40,7 @@ module Ea
|
|
|
40
40
|
attr_line_offset: 6,
|
|
41
41
|
attr_first_offset: 7,
|
|
42
42
|
attr_group_lead: 0,
|
|
43
|
+
compact_standard_operations: false,
|
|
43
44
|
family: nil, size_unit: "pt", logical_height: nil,
|
|
44
45
|
header_ghost: false,
|
|
45
46
|
header_ghost_tight: false)
|
|
@@ -61,6 +62,7 @@ module Ea
|
|
|
61
62
|
@attr_line_offset = attr_line_offset
|
|
62
63
|
@attr_first_offset = attr_first_offset
|
|
63
64
|
@attr_group_lead = attr_group_lead
|
|
65
|
+
@compact_standard_operations = compact_standard_operations
|
|
64
66
|
@header_ghost = header_ghost
|
|
65
67
|
@header_ghost_tight = header_ghost_tight
|
|
66
68
|
end
|
|
@@ -137,24 +139,29 @@ module Ea
|
|
|
137
139
|
end
|
|
138
140
|
|
|
139
141
|
def op_divider_y
|
|
142
|
+
return attr_bottom_y + line_height(attr_line_offset || 6) / 2.0 if compact_operation_spacing?
|
|
143
|
+
|
|
140
144
|
attr_bottom_y + size + 5
|
|
141
145
|
end
|
|
142
146
|
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# attrs AND ops (6D4C665D: attrs ..520, ops 541..658 step 13).
|
|
146
|
-
# Attribute-empty boxes anchor on the divider instead:
|
|
147
|
-
# first op at divider + 14 (001A64C1: EA ops 104 vs our
|
|
148
|
-
# 125 when attrs were absent).
|
|
147
|
+
# Compact standard classes end attributes with a half row cell;
|
|
148
|
+
# operations then use the same inset as an attribute-empty box.
|
|
149
149
|
def op_first_y
|
|
150
150
|
return nil unless op_lines_count&.positive?
|
|
151
151
|
|
|
152
|
-
if attr_lines_count.to_i.positive?
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
anchor = if attr_lines_count.to_i.positive?
|
|
153
|
+
compact_operation_spacing? ? op_divider_y : attr_bottom_y + 7
|
|
154
|
+
else
|
|
155
|
+
divider_y
|
|
156
|
+
end
|
|
157
|
+
anchor + size + 7
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def compact_operation_spacing?
|
|
161
|
+
@compact_standard_operations && attr_lines_count.to_i.positive? &&
|
|
162
|
+
Ea::Fonts::Metrics.compact_compartment_font?(size, family: @family, unit: @size_unit)
|
|
157
163
|
end
|
|
164
|
+
private :compact_operation_spacing?
|
|
158
165
|
|
|
159
166
|
def op_bottom_y
|
|
160
167
|
return op_divider_y unless op_lines_count&.positive?
|
|
@@ -11,55 +11,70 @@ module Ea
|
|
|
11
11
|
# the left edge from text-width approximation since SVG text
|
|
12
12
|
# x= is the start position, not the center).
|
|
13
13
|
class HeaderRenderer
|
|
14
|
+
HeaderLine = Struct.new(:text, :style, :x, :y, :text_length,
|
|
15
|
+
:weight, :font_style, keyword_init: true)
|
|
16
|
+
|
|
14
17
|
def self.render(lines, bounds:, first_y:, family:,
|
|
15
18
|
size:, size_unit: "pt", logical_height: nil,
|
|
16
19
|
fill: "#000000", weight_normal: 400, weight_bold: 700,
|
|
17
20
|
stroke_in_text: "#000000", width_factor: 0.612,
|
|
18
21
|
line_offset: 6)
|
|
19
22
|
logical_height = nil unless size_unit == "pt"
|
|
23
|
+
layout = line_layout(
|
|
24
|
+
lines, bounds: bounds, first_y: first_y, family: family,
|
|
25
|
+
size: size, size_unit: size_unit, logical_height: logical_height,
|
|
26
|
+
weight_normal: weight_normal, weight_bold: weight_bold,
|
|
27
|
+
width_factor: width_factor, line_offset: line_offset
|
|
28
|
+
)
|
|
29
|
+
text_blocks = layout.map do |line|
|
|
30
|
+
TextRenderer.new(
|
|
31
|
+
content: line.text,
|
|
32
|
+
x: line.x, y: line.y,
|
|
33
|
+
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
34
|
+
weight: line.weight, style: line.font_style, fill: fill,
|
|
35
|
+
stroke_in_text: stroke_in_text, width_factor: width_factor,
|
|
36
|
+
text_length: line.text_length
|
|
37
|
+
).to_svg
|
|
38
|
+
end
|
|
39
|
+
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the exact integer placement and width used for each
|
|
43
|
+
# emitted header line so dependent compartments can align to it.
|
|
44
|
+
def self.line_layout(lines, bounds:, first_y:, family:, size:,
|
|
45
|
+
size_unit: "pt", logical_height: nil,
|
|
46
|
+
weight_normal: 400, weight_bold: 700,
|
|
47
|
+
width_factor: 0.612, line_offset: 6)
|
|
48
|
+
logical_height = nil unless size_unit == "pt"
|
|
20
49
|
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
21
|
-
size, family: family, unit: size_unit, offset: line_offset,
|
|
50
|
+
size, family: family, unit: size_unit, offset: line_offset,
|
|
51
|
+
logical_height: logical_height
|
|
22
52
|
)
|
|
23
53
|
ghost_shift = HeaderLines.parent_ghost_gap(lines)
|
|
24
|
-
|
|
25
|
-
weight =
|
|
26
|
-
when :bold, :bold_italic then weight_bold
|
|
27
|
-
else weight_normal
|
|
28
|
-
end
|
|
54
|
+
lines.each_with_index.map do |(text, style), idx|
|
|
55
|
+
weight = %i[bold bold_italic].include?(style) ? weight_bold : weight_normal
|
|
29
56
|
font_style = %i[italic italic_tight bold_italic].include?(style) ? "italic" : "normal"
|
|
57
|
+
text_length = TextRenderer.estimate_width(
|
|
58
|
+
text, size, width_factor, family: family, weight: weight,
|
|
59
|
+
style: font_style, logical_height: logical_height
|
|
60
|
+
).round
|
|
30
61
|
y = first_y + (idx * line_h) +
|
|
31
62
|
(idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
|
|
32
63
|
x = if %i[italic italic_tight].include?(style)
|
|
33
|
-
|
|
34
|
-
bounds.x + bounds.width - 3 -
|
|
35
|
-
Ea::Svg::EaEmitter::TextRenderer.estimate_width(
|
|
36
|
-
text, size, width_factor,
|
|
37
|
-
family: family, weight: weight, style: font_style, logical_height: logical_height
|
|
38
|
-
).round
|
|
64
|
+
bounds.x + bounds.width - 3 - text_length
|
|
39
65
|
else
|
|
40
|
-
center_x_for(
|
|
41
|
-
family: family, weight: weight,
|
|
42
|
-
style: font_style, logical_height: logical_height)
|
|
66
|
+
center_x_for(text_length, bounds)
|
|
43
67
|
end
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
48
|
-
weight: weight, style: font_style, fill: fill,
|
|
49
|
-
stroke_in_text: stroke_in_text, width_factor: width_factor
|
|
50
|
-
).to_svg
|
|
68
|
+
HeaderLine.new(text: text, style: style, x: x, y: y,
|
|
69
|
+
text_length: text_length, weight: weight,
|
|
70
|
+
font_style: font_style)
|
|
51
71
|
end
|
|
52
|
-
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
53
72
|
end
|
|
54
73
|
|
|
55
74
|
# EA centers text by its INTEGER textLength and floors the
|
|
56
75
|
# resulting x (verified exact against EA-published SVGs).
|
|
57
|
-
def self.center_x_for(
|
|
58
|
-
|
|
59
|
-
len = Ea::Svg::EaEmitter::TextRenderer.estimate_width(
|
|
60
|
-
text, size, width_factor, family: family, weight: weight, style: style, logical_height: logical_height
|
|
61
|
-
).round
|
|
62
|
-
(bounds.x + (bounds.width - len) / 2.0).floor
|
|
76
|
+
def self.center_x_for(text_length, bounds)
|
|
77
|
+
(bounds.x + (bounds.width - text_length) / 2.0).floor
|
|
63
78
|
end
|
|
64
79
|
private_class_method :center_x_for
|
|
65
80
|
end
|
|
@@ -126,10 +126,16 @@ module Ea
|
|
|
126
126
|
|
|
127
127
|
def item_label(item, index)
|
|
128
128
|
x = bounds.x + ICON_X_OFFSET + ICON_LABEL_GAP
|
|
129
|
-
y = item_top_y(index) +
|
|
129
|
+
y = item_top_y(index) + item_label_baseline
|
|
130
130
|
build_text(item.name, x: x, y: y, size: item_font_size, weight: 0)
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
+
def item_label_baseline
|
|
134
|
+
return LABEL_BASELINE_FROM_ICON_TOP unless (item_font[:unit] || "pt") == "pt"
|
|
135
|
+
|
|
136
|
+
item_font[:logical_height] || LABEL_BASELINE_FROM_ICON_TOP
|
|
137
|
+
end
|
|
138
|
+
|
|
133
139
|
def title_text
|
|
134
140
|
x = title_x
|
|
135
141
|
y = bounds.y + TITLE_Y_OFFSET
|
|
@@ -152,8 +158,8 @@ module Ea
|
|
|
152
158
|
|
|
153
159
|
# Real font metrics when available; legacy per-char
|
|
154
160
|
# estimate otherwise (families without metric tables).
|
|
155
|
-
def estimated_width(text, size, weight: nil, logical_height: nil)
|
|
156
|
-
measured = Ea::Fonts::Metrics.text_length(text, size, family:
|
|
161
|
+
def estimated_width(text, size, weight: nil, logical_height: nil, text_family: family)
|
|
162
|
+
measured = Ea::Fonts::Metrics.text_length(text, size, family: text_family, weight: weight,
|
|
157
163
|
logical_height: logical_height)
|
|
158
164
|
return measured if measured
|
|
159
165
|
|
|
@@ -186,7 +192,11 @@ module Ea
|
|
|
186
192
|
end
|
|
187
193
|
|
|
188
194
|
def title_text_width
|
|
189
|
-
estimated_width(legend.title, title_font_size, weight: 700)
|
|
195
|
+
estimated_width(legend.title, title_font_size, weight: 700, text_family: heading_family)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def heading_family
|
|
199
|
+
legend.heading_family || family
|
|
190
200
|
end
|
|
191
201
|
|
|
192
202
|
def build_text(content, x:, y:, size:, weight:)
|
|
@@ -194,7 +204,7 @@ module Ea
|
|
|
194
204
|
size_unit = weight.zero? ? (item_font[:unit] || "pt") : "pt"
|
|
195
205
|
TextRenderer.new(
|
|
196
206
|
content: content.to_s, x: tx, y: ty,
|
|
197
|
-
family: family, size: size, size_unit: size_unit,
|
|
207
|
+
family: weight.zero? ? family : heading_family, size: size, size_unit: size_unit,
|
|
198
208
|
weight: weight, fill: label_fill, logical_height: weight.zero? ? item_font[:logical_height] : nil
|
|
199
209
|
).to_svg
|
|
200
210
|
end
|
|
@@ -26,9 +26,10 @@ module Ea
|
|
|
26
26
|
text_blocks = []
|
|
27
27
|
has_receptions = operations.any?(&:is_reception)
|
|
28
28
|
if has_receptions
|
|
29
|
+
heading_width = reception_heading_width(family, size, size_unit, logical_height)
|
|
29
30
|
text_blocks << TextRenderer.new(
|
|
30
31
|
content: "receptions",
|
|
31
|
-
x: bounds.x + (bounds.width / 2.0) -
|
|
32
|
+
x: bounds.x + (bounds.width / 2.0) - heading_width / 2.0,
|
|
32
33
|
y: first_y,
|
|
33
34
|
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
34
35
|
style: "italic", fill: "#000000"
|
|
@@ -36,12 +37,14 @@ module Ea
|
|
|
36
37
|
end
|
|
37
38
|
ops = has_receptions ? operations : operations
|
|
38
39
|
offset = has_receptions ? 1 : 0
|
|
40
|
+
content_x_offset = CONTENT_X_OFFSET + 2 * (size - 7)
|
|
41
|
+
content_x_offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
|
|
39
42
|
ops.each_with_index do |op, idx|
|
|
40
43
|
y = first_y + ((idx + offset) * line_h)
|
|
41
44
|
text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
|
|
42
45
|
visibility_prefix(op), family, size, size_unit, logical_height: logical_height)
|
|
43
46
|
text = operation_text(op, parameter_detail: parameter_detail)
|
|
44
|
-
text_blocks << build_text(bounds.x +
|
|
47
|
+
text_blocks << build_text(bounds.x + content_x_offset, y,
|
|
45
48
|
text, family, size, size_unit, logical_height: logical_height)
|
|
46
49
|
end
|
|
47
50
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
@@ -102,6 +105,15 @@ module Ea
|
|
|
102
105
|
end
|
|
103
106
|
private_class_method :reception_params
|
|
104
107
|
|
|
108
|
+
def self.reception_heading_width(family, size, size_unit, logical_height)
|
|
109
|
+
TextRenderer.estimate_width(
|
|
110
|
+
"receptions", size,
|
|
111
|
+
family: family, weight: 400, style: "italic",
|
|
112
|
+
logical_height: size_unit == "pt" ? logical_height : nil
|
|
113
|
+
).round
|
|
114
|
+
end
|
|
115
|
+
private_class_method :reception_heading_width
|
|
116
|
+
|
|
105
117
|
def self.build_text(x, y, content, family, size, size_unit, logical_height: nil)
|
|
106
118
|
TextRenderer.new(content: content, x: x, y: y,
|
|
107
119
|
family: family, size: size, size_unit: size_unit,
|
|
@@ -45,6 +45,21 @@ module Ea
|
|
|
45
45
|
family, size, size_unit, text_fill).to_svg
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def self.tab_height_for(stereotype:, family:, size:, size_unit: "pt")
|
|
49
|
+
rows = stereotype && !stereotype.empty? ? 2 : 1
|
|
50
|
+
nominal_height = rows == 2 ? TAB_HEIGHT_DOUBLE : TAB_HEIGHT_SINGLE
|
|
51
|
+
line_height = if size_unit == "pt"
|
|
52
|
+
Ea::Fonts::Metrics.compartment_line_height(
|
|
53
|
+
size, family: family, unit: size_unit, offset: TAB_TEXT_PADDING
|
|
54
|
+
)
|
|
55
|
+
else
|
|
56
|
+
TAB_LABEL_Y_OFFSET
|
|
57
|
+
end
|
|
58
|
+
# Preserve the nominal padding while each row follows the same
|
|
59
|
+
# font line height used to place the package header text.
|
|
60
|
+
nominal_height + rows * (line_height - TAB_LABEL_Y_OFFSET)
|
|
61
|
+
end
|
|
62
|
+
|
|
48
63
|
def initialize(bounds, fill, stroke, stroke_width, label,
|
|
49
64
|
stereotype, family, size, size_unit, text_fill)
|
|
50
65
|
@bounds = bounds
|
|
@@ -147,11 +162,8 @@ module Ea
|
|
|
147
162
|
end
|
|
148
163
|
|
|
149
164
|
def tab_height
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# Preserve the nominal padding while each row follows the same
|
|
153
|
-
# font line height used to place the package header text.
|
|
154
|
-
nominal_height + rows * (tab_line_height - TAB_LABEL_Y_OFFSET)
|
|
165
|
+
self.class.tab_height_for(stereotype: stereotype, family: family,
|
|
166
|
+
size: size, size_unit: size_unit)
|
|
155
167
|
end
|
|
156
168
|
|
|
157
169
|
def body_points
|
|
@@ -332,8 +332,22 @@ module Ea
|
|
|
332
332
|
constraints_count: 0, marker_count: 0,
|
|
333
333
|
enum_literals_count: 0,
|
|
334
334
|
attr_group_lead: 0, family: nil, size_unit: "pt", logical_height: nil)
|
|
335
|
+
divider_offset = divider_offset_for(header_lines, classifier)
|
|
336
|
+
attr_first_offset = attr_first_offset_for(attr_lines)
|
|
337
|
+
compact_standard = compact_header_offsets?(
|
|
338
|
+
classifier, header_lines, size,
|
|
339
|
+
family: family, size_unit: size_unit, logical_height: logical_height,
|
|
340
|
+
marker_count: marker_count, enum_literals_count: enum_literals_count,
|
|
341
|
+
divider_offset: divider_offset, attr_first_offset: attr_first_offset
|
|
342
|
+
)
|
|
343
|
+
if compact_standard
|
|
344
|
+
divider_offset = 7
|
|
345
|
+
# Attribute baselines retain their anchor when the divider moves up.
|
|
346
|
+
attr_first_offset = 8
|
|
347
|
+
end
|
|
335
348
|
CompartmentGeometry.new(bounds: bounds, size: size, family: family,
|
|
336
349
|
size_unit: size_unit, logical_height: logical_height,
|
|
350
|
+
compact_standard_operations: compact_standard,
|
|
337
351
|
header_ghost: header_lines.any? &&
|
|
338
352
|
%i[italic italic_tight].include?(header_lines.first.last),
|
|
339
353
|
header_ghost_tight: Element::HeaderLines.parent_ghost_gap(header_lines) == 3,
|
|
@@ -346,12 +360,21 @@ module Ea
|
|
|
346
360
|
enum_literals_count: enum_literals_count,
|
|
347
361
|
header_top_padding: header_padding_for(header_lines, classifier),
|
|
348
362
|
header_line_offset: theme.compartments.header_line_offset,
|
|
349
|
-
divider_offset:
|
|
363
|
+
divider_offset: divider_offset,
|
|
350
364
|
attr_line_offset: theme.compartments.attr_line_offset,
|
|
351
|
-
attr_first_offset:
|
|
365
|
+
attr_first_offset: attr_first_offset,
|
|
352
366
|
attr_group_lead: attr_group_lead)
|
|
353
367
|
end
|
|
354
368
|
|
|
369
|
+
def compact_header_offsets?(classifier, header_lines, size, **options)
|
|
370
|
+
classifier.is_a?(Ea::Model::Klass) &&
|
|
371
|
+
Ea::Fonts::Metrics.compact_compartment_font?(size, family: options[:family], unit: options[:size_unit]) &&
|
|
372
|
+
!%i[italic italic_tight].include?(header_lines.dig(0, 1)) &&
|
|
373
|
+
options.values_at(:logical_height, :marker_count, :enum_literals_count,
|
|
374
|
+
:divider_offset, :attr_first_offset) == [nil, 0, 0, 8, 7] &&
|
|
375
|
+
theme.compartments.header_line_offset == 6 && theme.compartments.attr_line_offset == 6
|
|
376
|
+
end
|
|
377
|
+
|
|
355
378
|
# EA seats a stereotype line 3px higher than a bare class name
|
|
356
379
|
# (first baseline +16 vs +19 at 7pt) - and Enumeration /
|
|
357
380
|
# Interface elements seat their stereotype 3px higher still
|
data/lib/ea/version.rb
CHANGED