ea 0.6.116 → 0.6.117
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/svg/ea_emitter/compartment/instance_slots.rb +2 -2
- data/lib/ea/svg/ea_emitter/compartment/shape.rb +3 -1
- data/lib/ea/svg/ea_emitter/compartment/tagged_values.rb +1 -0
- data/lib/ea/svg/ea_emitter/document.rb +7 -2
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +1 -1
- data/lib/ea/svg/ea_emitter/element/header_lines.rb +9 -0
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +1 -8
- data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +16 -10
- data/lib/ea/svg/ea_emitter/element/package_shape_renderer.rb +16 -8
- data/lib/ea/svg/ea_emitter/element/tagged_value_renderer.rb +22 -11
- data/lib/ea/svg/ea_emitter/elements.rb +6 -7
- data/lib/ea/svg/ea_emitter/font_resolver.rb +9 -4
- 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: 473bc36a9b9e843f0eb497f2437f56932823a674d8823c44c40063f93ab2da54
|
|
4
|
+
data.tar.gz: 41b7858cf138ecc8e5a16b05762451ac98e738b9c4b13b3bfd781fb9821ab002
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c319d8f6b1c3454cd5c04495822223b4ec22e58bb1a357774b9ec3f5bbccde4eb1fa10bd94ff64a1a61cac24a70462d63915757fa6df98846d0c3fa7c5174b35
|
|
7
|
+
data.tar.gz: d9dc77d29d955d62ec4e27d102a35b41cd4e7a561f08ec91077a8cb8838f0de2438c935d3cb1176a03687eb7efab9c23654fdfbb299c7fab84dc8a92ddc7d3a1
|
|
@@ -73,7 +73,7 @@ module Ea
|
|
|
73
73
|
x: bounds.x + (context.theme.attribute_spec.content_x_offset || 26),
|
|
74
74
|
y: y_pos,
|
|
75
75
|
family: context.family, size: context.size,
|
|
76
|
-
size_unit: context.size_unit, fill: fill
|
|
76
|
+
size_unit: context.size_unit, logical_height: context.logical_height, fill: fill
|
|
77
77
|
).to_svg
|
|
78
78
|
end
|
|
79
79
|
module_function :line_text
|
|
@@ -86,7 +86,7 @@ module Ea
|
|
|
86
86
|
x: x_pos,
|
|
87
87
|
y: y_pos,
|
|
88
88
|
family: context.family, size: context.size,
|
|
89
|
-
size_unit: context.size_unit, fill: fill,
|
|
89
|
+
size_unit: context.size_unit, logical_height: context.logical_height, fill: fill,
|
|
90
90
|
style: "italic"
|
|
91
91
|
).to_svg
|
|
92
92
|
end
|
|
@@ -14,7 +14,9 @@ module Ea
|
|
|
14
14
|
Element::LegendRenderer.render(
|
|
15
15
|
context.bounds,
|
|
16
16
|
legend: context.model_element.legend,
|
|
17
|
-
family: context.family
|
|
17
|
+
family: context.family,
|
|
18
|
+
item_font: { size: context.element&.font_size&.nonzero?, unit: context.size_unit,
|
|
19
|
+
logical_height: context.logical_height }
|
|
18
20
|
)
|
|
19
21
|
elsif context.model_element.is_a?(Ea::Model::Note)
|
|
20
22
|
Element::NoteShapeRenderer.render(
|
|
@@ -12,10 +12,10 @@ module Ea
|
|
|
12
12
|
|
|
13
13
|
attr_reader :diagram, :model_index, :frame, :document
|
|
14
14
|
|
|
15
|
-
def initialize(diagram, model_index:, frame:
|
|
15
|
+
def initialize(diagram, model_index:, frame: nil, document: nil, **_options)
|
|
16
16
|
@diagram = diagram
|
|
17
17
|
@model_index = model_index
|
|
18
|
-
@frame = frame
|
|
18
|
+
@frame = frame.nil? ? !empty_diagram?(diagram) : frame
|
|
19
19
|
@document = document
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -86,6 +86,11 @@ module Ea
|
|
|
86
86
|
%(<g>#{svg}</g>)
|
|
87
87
|
end
|
|
88
88
|
private :render_image
|
|
89
|
+
|
|
90
|
+
def empty_diagram?(source_diagram)
|
|
91
|
+
source_diagram.elements.empty? && source_diagram.connectors.empty?
|
|
92
|
+
end
|
|
93
|
+
private :empty_diagram?
|
|
89
94
|
end
|
|
90
95
|
end
|
|
91
96
|
end
|
|
@@ -196,7 +196,7 @@ module Ea
|
|
|
196
196
|
def tagged_value_first_y
|
|
197
197
|
return nil unless tagged_values_count.to_i.positive?
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
content_bottom_y ? content_bottom_y + size + 5 : raw_attr_first_y
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
# The lowest content edge among attributes, enum literals,
|
|
@@ -15,6 +15,15 @@ module Ea
|
|
|
15
15
|
HeaderLinePipeline.for(classifier, **opts)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# A parent followed directly by the name has a 3px gap beyond
|
|
19
|
+
# the row pitch. A class stereotype needs 6px; interfaces use 3px.
|
|
20
|
+
def parent_ghost_gap(lines)
|
|
21
|
+
style = lines.dig(0, 1)
|
|
22
|
+
return 0 unless %i[italic italic_tight].include?(style)
|
|
23
|
+
|
|
24
|
+
style == :italic_tight || %i[bold bold_italic].include?(lines.dig(1, 1)) ? 3 : 6
|
|
25
|
+
end
|
|
26
|
+
|
|
18
27
|
# Delegate to the Name provider so historical callers and
|
|
19
28
|
# specs that test display_name keep working.
|
|
20
29
|
def display_name(classifier, *_)
|
|
@@ -20,20 +20,13 @@ module Ea
|
|
|
20
20
|
line_h = Ea::Fonts::Metrics.compartment_line_height(
|
|
21
21
|
size, family: family, unit: size_unit, offset: line_offset, logical_height: logical_height
|
|
22
22
|
)
|
|
23
|
+
ghost_shift = HeaderLines.parent_ghost_gap(lines)
|
|
23
24
|
text_blocks = lines.each_with_index.map do |(text, style), idx|
|
|
24
25
|
weight = case style
|
|
25
26
|
when :bold, :bold_italic then weight_bold
|
|
26
27
|
else weight_normal
|
|
27
28
|
end
|
|
28
29
|
font_style = %i[italic italic_tight bold_italic].include?(style) ? "italic" : "normal"
|
|
29
|
-
# EA spaces the line AFTER a right-aligned ghost 3px
|
|
30
|
-
# wider than the pitch. Interfaces use a tighter +3
|
|
31
|
-
# (936AA434/Surface: ghost 100, «interface» 116).
|
|
32
|
-
ghost_shift = case lines.first.last
|
|
33
|
-
when :italic then 6
|
|
34
|
-
when :italic_tight then 3
|
|
35
|
-
else 0
|
|
36
|
-
end
|
|
37
30
|
y = first_y + (idx * line_h) +
|
|
38
31
|
(idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
|
|
39
32
|
x = if %i[italic italic_tight].include?(style)
|
|
@@ -32,15 +32,16 @@ module Ea
|
|
|
32
32
|
LABEL_BASELINE_FROM_ICON_TOP = 12
|
|
33
33
|
DEFAULT_ITEM_FONT_SIZE = 9
|
|
34
34
|
|
|
35
|
-
def self.render(bounds, legend:, family:, canvas: nil)
|
|
36
|
-
new(bounds, legend, family, canvas).to_svg
|
|
35
|
+
def self.render(bounds, legend:, family:, canvas: nil, item_font: {})
|
|
36
|
+
new(bounds, legend, family, canvas, item_font).to_svg
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def initialize(bounds, legend, family, canvas)
|
|
39
|
+
def initialize(bounds, legend, family, canvas, item_font = {})
|
|
40
40
|
@bounds = bounds
|
|
41
41
|
@legend = legend
|
|
42
42
|
@family = family
|
|
43
43
|
@canvas = canvas
|
|
44
|
+
@item_font = item_font[:size]&.nonzero? ? item_font : {}
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
def to_svg
|
|
@@ -49,7 +50,7 @@ module Ea
|
|
|
49
50
|
|
|
50
51
|
private
|
|
51
52
|
|
|
52
|
-
attr_reader :bounds, :legend, :family, :canvas
|
|
53
|
+
attr_reader :bounds, :legend, :family, :canvas, :item_font
|
|
53
54
|
|
|
54
55
|
def container_layer
|
|
55
56
|
body = format(
|
|
@@ -151,8 +152,9 @@ module Ea
|
|
|
151
152
|
|
|
152
153
|
# Real font metrics when available; legacy per-char
|
|
153
154
|
# estimate otherwise (families without metric tables).
|
|
154
|
-
def estimated_width(text, size, weight: nil)
|
|
155
|
-
measured = Ea::Fonts::Metrics.text_length(text, size, family: family, weight: weight
|
|
155
|
+
def estimated_width(text, size, weight: nil, logical_height: nil)
|
|
156
|
+
measured = Ea::Fonts::Metrics.text_length(text, size, family: family, weight: weight,
|
|
157
|
+
logical_height: logical_height)
|
|
156
158
|
return measured if measured
|
|
157
159
|
|
|
158
160
|
text.to_s.each_char.sum do |ch|
|
|
@@ -161,7 +163,10 @@ module Ea
|
|
|
161
163
|
end
|
|
162
164
|
|
|
163
165
|
def widest_item_label_width
|
|
164
|
-
|
|
166
|
+
logical_height = item_font[:logical_height] if (item_font[:unit] || "pt") == "pt"
|
|
167
|
+
legend.items.map do |item|
|
|
168
|
+
estimated_width(item.name, item_font_size, logical_height: logical_height)
|
|
169
|
+
end.max || 0.0
|
|
165
170
|
end
|
|
166
171
|
|
|
167
172
|
def item_top_y(index)
|
|
@@ -177,7 +182,7 @@ module Ea
|
|
|
177
182
|
end
|
|
178
183
|
|
|
179
184
|
def item_font_size
|
|
180
|
-
DEFAULT_ITEM_FONT_SIZE
|
|
185
|
+
item_font[:size] || DEFAULT_ITEM_FONT_SIZE
|
|
181
186
|
end
|
|
182
187
|
|
|
183
188
|
def title_text_width
|
|
@@ -186,10 +191,11 @@ module Ea
|
|
|
186
191
|
|
|
187
192
|
def build_text(content, x:, y:, size:, weight:)
|
|
188
193
|
tx, ty = translate(x, y)
|
|
194
|
+
size_unit = weight.zero? ? (item_font[:unit] || "pt") : "pt"
|
|
189
195
|
TextRenderer.new(
|
|
190
196
|
content: content.to_s, x: tx, y: ty,
|
|
191
|
-
family: family, size: size, size_unit:
|
|
192
|
-
weight: weight, fill: label_fill
|
|
197
|
+
family: family, size: size, size_unit: size_unit,
|
|
198
|
+
weight: weight, fill: label_fill, logical_height: weight.zero? ? item_font[:logical_height] : nil
|
|
193
199
|
).to_svg
|
|
194
200
|
end
|
|
195
201
|
|
|
@@ -185,25 +185,33 @@ module Ea
|
|
|
185
185
|
end
|
|
186
186
|
|
|
187
187
|
def body_width
|
|
188
|
-
[bounds.width,
|
|
188
|
+
[bounds.width, autosized_tab_width + TAB_BODY_EXTRA].max
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def tab_width
|
|
192
|
-
[body_width,
|
|
193
|
-
|
|
192
|
+
[body_width, autosized_tab_width].min
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def autosized_tab_width
|
|
196
|
+
self.class.autosized_tab_width(
|
|
197
|
+
label: label, stereotype: stereotype, size: size, family: family
|
|
198
|
+
)
|
|
194
199
|
end
|
|
195
200
|
|
|
196
201
|
# Tab width fits the widest text line (stereotype or name)
|
|
197
202
|
# plus padding, floored at EA's minimum tab width. The body
|
|
198
203
|
# extends 15px past the tab on the right.
|
|
199
|
-
def self.autosized_tab_width(label:, stereotype:, size:)
|
|
200
|
-
lines = [label,
|
|
201
|
-
|
|
204
|
+
def self.autosized_tab_width(label:, stereotype:, size:, family: nil)
|
|
205
|
+
lines = [[label, 700]]
|
|
206
|
+
lines << ["«#{stereotype}»", 0] if stereotype && !stereotype.empty?
|
|
207
|
+
widest = lines.map do |text, weight|
|
|
208
|
+
TextRenderer.estimate_width(text, size, family: family, weight: weight)
|
|
209
|
+
end.max || 0
|
|
202
210
|
[widest + TAB_NAME_EXTRA, DEFAULT_TAB_WIDTH].max.round
|
|
203
211
|
end
|
|
204
212
|
|
|
205
|
-
def self.body_width_for(label:, stereotype:, size:)
|
|
206
|
-
autosized_tab_width(label: label, stereotype: stereotype, size: size) + TAB_BODY_EXTRA
|
|
213
|
+
def self.body_width_for(label:, stereotype:, size:, family: nil)
|
|
214
|
+
autosized_tab_width(label: label, stereotype: stereotype, size: size, family: family) + TAB_BODY_EXTRA
|
|
207
215
|
end
|
|
208
216
|
|
|
209
217
|
end
|
|
@@ -16,26 +16,37 @@ module Ea
|
|
|
16
16
|
HEADER_X_OFFSET = 5
|
|
17
17
|
LINE_X_OFFSET = 5
|
|
18
18
|
|
|
19
|
-
def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
text_blocks
|
|
23
|
-
|
|
19
|
+
def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000",
|
|
20
|
+
size_unit: "px", logical_height: nil)
|
|
21
|
+
line_h = line_height(size, family, size_unit, logical_height)
|
|
22
|
+
text_blocks = [build_text(bounds.x + centered_x_offset(bounds, size), first_y,
|
|
23
|
+
HEADER_TEXT, family, size, fill,
|
|
24
|
+
style: "italic", size_unit: size_unit, logical_height: logical_height)]
|
|
24
25
|
tagged_values.each_with_index do |tv, idx|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
text_blocks << build_text(bounds.x + LINE_X_OFFSET, first_y + ((idx + 1) * line_h),
|
|
27
|
+
"#{tv.key} = #{tv.value}", family, size, fill,
|
|
28
|
+
size_unit: size_unit, logical_height: logical_height)
|
|
28
29
|
end
|
|
29
30
|
%(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
def self.build_text(x, y, content, family, size, fill, style: "normal"
|
|
33
|
+
def self.build_text(x, y, content, family, size, fill, style: "normal",
|
|
34
|
+
size_unit: "px", logical_height: nil)
|
|
33
35
|
TextRenderer.new(content: content, x: x, y: y,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
family: family, size: size, fill: fill,
|
|
37
|
+
size_unit: size_unit, logical_height: logical_height,
|
|
38
|
+
style: style).to_svg
|
|
36
39
|
end
|
|
37
40
|
private_class_method :build_text
|
|
38
41
|
|
|
42
|
+
def self.line_height(size, family, size_unit, logical_height)
|
|
43
|
+
Ea::Fonts::Metrics.compartment_line_height(
|
|
44
|
+
size, family: family, unit: size_unit, logical_height: logical_height,
|
|
45
|
+
offset: size_unit == "pt" ? 6 : 4
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
private_class_method :line_height
|
|
49
|
+
|
|
39
50
|
# Center the "tags" header text within the bounds width.
|
|
40
51
|
def self.centered_x_offset(bounds, size)
|
|
41
52
|
text_width = HEADER_TEXT.length * (size / 2)
|
|
@@ -74,7 +74,7 @@ module Ea
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def package_stereotype(context)
|
|
77
|
-
context.
|
|
77
|
+
context.model_element.stereotype_refs.first.to_s
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def record_drawn_bounds(element, context)
|
|
@@ -84,11 +84,11 @@ module Ea
|
|
|
84
84
|
return unless raw
|
|
85
85
|
|
|
86
86
|
width = context.bounds.width
|
|
87
|
-
if classifier_package?(context.
|
|
87
|
+
if classifier_package?(context.model_element)
|
|
88
88
|
tab = Element::PackageShapeRenderer.autosized_tab_width(
|
|
89
|
-
label: context.
|
|
89
|
+
label: context.model_element.name.to_s,
|
|
90
90
|
stereotype: package_stereotype(context),
|
|
91
|
-
size: context.size
|
|
91
|
+
size: context.size, family: context.family
|
|
92
92
|
)
|
|
93
93
|
width = [raw.width, tab + Element::PackageShapeRenderer::TAB_BODY_EXTRA].max
|
|
94
94
|
end
|
|
@@ -316,8 +316,7 @@ module Ea
|
|
|
316
316
|
size_unit: size_unit, logical_height: logical_height,
|
|
317
317
|
header_ghost: header_lines.any? &&
|
|
318
318
|
%i[italic italic_tight].include?(header_lines.first.last),
|
|
319
|
-
header_ghost_tight: header_lines
|
|
320
|
-
header_lines.first.last == :italic_tight,
|
|
319
|
+
header_ghost_tight: Element::HeaderLines.parent_ghost_gap(header_lines) == 3,
|
|
321
320
|
header_lines_count: header_lines.size,
|
|
322
321
|
attr_lines_count: attr_lines.size,
|
|
323
322
|
op_lines_count: op_lines.size,
|
|
@@ -705,7 +704,7 @@ module Ea
|
|
|
705
704
|
end
|
|
706
705
|
|
|
707
706
|
def font_resolver
|
|
708
|
-
@font_resolver ||= FontResolver.new(diagram, theme: theme)
|
|
707
|
+
@font_resolver ||= FontResolver.new(diagram, theme: theme, model_index: model_index)
|
|
709
708
|
end
|
|
710
709
|
|
|
711
710
|
def element_filter
|
|
@@ -23,10 +23,11 @@ module Ea
|
|
|
23
23
|
# a use-default marker (see QEA DiagramBuilder.font_size_from).
|
|
24
24
|
DEFAULT_ELEMENT_FONT_SIZE = 7
|
|
25
25
|
|
|
26
|
-
attr_reader :diagram, :theme
|
|
26
|
+
attr_reader :diagram, :theme, :model_index
|
|
27
27
|
|
|
28
|
-
def initialize(diagram, theme: nil)
|
|
28
|
+
def initialize(diagram, theme: nil, model_index: {})
|
|
29
29
|
@diagram = diagram
|
|
30
|
+
@model_index = model_index
|
|
30
31
|
@theme = theme || Ea::Theme::Registry.default
|
|
31
32
|
end
|
|
32
33
|
|
|
@@ -84,7 +85,8 @@ module Ea
|
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
# Unthemed diagrams without a saved family use Cambria.
|
|
87
|
-
# Saved families still supply the diagram default.
|
|
88
|
+
# Saved classifier families still supply the diagram default.
|
|
89
|
+
# A note's explicit font belongs to its body, not sibling classifiers.
|
|
88
90
|
def diagram_default_family
|
|
89
91
|
families = element_families
|
|
90
92
|
return FALLBACK_DEFAULT_FAMILY if families.empty?
|
|
@@ -102,7 +104,10 @@ module Ea
|
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
def element_families
|
|
105
|
-
(diagram.elements || []).
|
|
107
|
+
placements = (diagram.elements || []).reject do |element|
|
|
108
|
+
model_index[element.model_element_ref].is_a?(Ea::Model::Note)
|
|
109
|
+
end
|
|
110
|
+
placements.map(&:font_family).compact.reject do |family|
|
|
106
111
|
family.empty? || DEFAULT_MARKER_FAMILIES.include?(family)
|
|
107
112
|
end
|
|
108
113
|
end
|
data/lib/ea/version.rb
CHANGED