ea 0.6.113 → 0.6.115
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 +1 -1
- data/lib/ea/sources/qea/diagram_builder.rb +28 -0
- data/lib/ea/svg/ea_emitter/connector_redock.rb +24 -7
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +1 -0
- data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +10 -10
- data/lib/ea/svg/ea_emitter/element/legend_renderer.rb +5 -1
- data/lib/ea/svg/ea_emitter/elements.rb +7 -3
- data/lib/ea/svg/ea_emitter/font_resolver.rb +4 -10
- data/lib/ea/svg/ea_emitter/label/end_label.rb +11 -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: c4e23583eed4c1a582614c26b2c77ada31ab41af83b00b474fcdc199d807b599
|
|
4
|
+
data.tar.gz: 7c02275d511da08c73e261f53815f8d55092420a7bff501151c5b713162068d6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 378bb61902c1103887fcc9807e675fd2ebfc2dfe50c7b5266c20f2570b02a42ed0c8b120fc81ca626a6b4d9a421f45d2fd151c21b189714282a50f668ce07b79
|
|
7
|
+
data.tar.gz: 3e4edfe09c4c9235c39e47b6cd202f64bed7904d2b99567cabbff418ecbb7298a1e38b7a0c805fe2e12d760b7e2c11274cf67ee36811925dad804c9c24e61670
|
data/config/themes/119.yml
CHANGED
|
@@ -126,6 +126,8 @@ module Ea
|
|
|
126
126
|
@current_obj_row = obj_row
|
|
127
127
|
style_hash = DiagramStyleParser.parse(obj_row.objectstyle)
|
|
128
128
|
style_hash[:stored_attribute_order] = true
|
|
129
|
+
fqname = stereotype_fqname_for(obj_row)
|
|
130
|
+
style_hash[:stereotype_fqname] = fqname if fqname
|
|
129
131
|
font_style = effective_font_style(obj_row, style_hash)
|
|
130
132
|
Ea::Model::DiagramElement.new(
|
|
131
133
|
id: IdNormalizer.synthetic("de", diagram_row.diagram_id,
|
|
@@ -142,14 +144,40 @@ module Ea
|
|
|
142
144
|
font_italic: truthy?(style_hash[:italic]),
|
|
143
145
|
font_underline: truthy?(style_hash[:ul]),
|
|
144
146
|
show_tagged_values: truthy?(style_hash[:tag]),
|
|
147
|
+
line_width: effective_line_width(obj_row, style_hash),
|
|
145
148
|
background_color: style_hash[:bcol]&.to_i
|
|
146
149
|
)
|
|
147
150
|
end
|
|
148
151
|
|
|
152
|
+
# Negative widths inherit the saved object border, then the diagram theme.
|
|
153
|
+
def effective_line_width(obj_row, style)
|
|
154
|
+
object = database.find_object(obj_row.ea_object_id)
|
|
155
|
+
[style[:lwth], object&.borderwidth].each do |raw|
|
|
156
|
+
next if raw.nil? || raw.to_s.empty?
|
|
157
|
+
|
|
158
|
+
width = raw.to_i
|
|
159
|
+
return width if width >= 0
|
|
160
|
+
end
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
149
164
|
def truthy?(raw)
|
|
150
165
|
raw == "1" || raw == "-1"
|
|
151
166
|
end
|
|
152
167
|
|
|
168
|
+
def stereotype_fqname_for(placement)
|
|
169
|
+
object = database.find_object(placement.ea_object_id)
|
|
170
|
+
return nil unless object
|
|
171
|
+
|
|
172
|
+
database.xrefs_for_client(object.ea_guid).each do |xref|
|
|
173
|
+
application = Xref::Parser.parse(xref.description).stereotype
|
|
174
|
+
next unless application && application.name == object.stereotype
|
|
175
|
+
|
|
176
|
+
return application.fqname
|
|
177
|
+
end
|
|
178
|
+
nil
|
|
179
|
+
end
|
|
180
|
+
|
|
153
181
|
def build_connectors(diagram_row)
|
|
154
182
|
links = database.diagram_links_for(diagram_row.diagram_id) || []
|
|
155
183
|
context = ContextDiagramSupport.context?(diagram_row)
|
|
@@ -215,7 +215,7 @@ module Ea
|
|
|
215
215
|
private_class_method :overlap_ray?
|
|
216
216
|
|
|
217
217
|
# The QEA builder flags a model relationship drawn without any
|
|
218
|
-
# diagram link row;
|
|
218
|
+
# diagram link row; its docks follow the resolved element borders.
|
|
219
219
|
def default_model_connector?(connector)
|
|
220
220
|
connector.style.to_h[:phantom_outline] == true && !connector.has_geometry_offsets &&
|
|
221
221
|
%w[Aggregation Association].include?(connector.connector_type)
|
|
@@ -243,16 +243,32 @@ module Ea
|
|
|
243
243
|
end
|
|
244
244
|
private_class_method :border_width?
|
|
245
245
|
|
|
246
|
-
#
|
|
247
|
-
# absent or inherited widths retain the default outer-stroke docking.
|
|
246
|
+
# EA seats docks on whole pixels; a 1px border keeps the geometric edge.
|
|
248
247
|
def outline_bounds(box, diagram, ref, duid)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
width: box.width + 2, height: box.height + 2)
|
|
248
|
+
inset = (outline_width(diagram, ref, duid) / 2.0).floor
|
|
249
|
+
Ea::Model::Bounds.new(x: box.x - inset, y: box.y - inset,
|
|
250
|
+
width: box.width + 2 * inset, height: box.height + 2 * inset)
|
|
253
251
|
end
|
|
254
252
|
private_class_method :outline_bounds
|
|
255
253
|
|
|
254
|
+
def outline_width(diagram, ref, duid)
|
|
255
|
+
widths = end_placements(diagram, ref, duid).map do |element|
|
|
256
|
+
effective_border_width(element, diagram)
|
|
257
|
+
end.uniq
|
|
258
|
+
widths.size == 1 ? widths.first : diagram.theme.stroke_width
|
|
259
|
+
end
|
|
260
|
+
private_class_method :outline_width
|
|
261
|
+
|
|
262
|
+
def effective_border_width(element, diagram)
|
|
263
|
+
return element.line_width if element.line_width && element.line_width >= 0
|
|
264
|
+
|
|
265
|
+
raw = element.style.to_h[:lwth]
|
|
266
|
+
return raw.to_i if raw && raw.to_i >= 0
|
|
267
|
+
|
|
268
|
+
diagram.theme.stroke_width
|
|
269
|
+
end
|
|
270
|
+
private_class_method :effective_border_width
|
|
271
|
+
|
|
256
272
|
def end_placements(diagram, ref, duid)
|
|
257
273
|
placed = Array(diagram&.elements).select { |el| el.model_element_ref == ref }
|
|
258
274
|
named = placed.select { |el| duid && el.duid.to_s.casecmp?(duid.to_s) }
|
|
@@ -389,6 +405,7 @@ module Ea
|
|
|
389
405
|
dividers = [divider_y_by_ref[source_ref],
|
|
390
406
|
divider_y_by_ref[target_ref]]
|
|
391
407
|
return nil unless bounds.all? && dividers.all?
|
|
408
|
+
return nil if flush_lateral_line?(connector, pairs, *bounds)
|
|
392
409
|
|
|
393
410
|
preserve_source = explicit_horizontal_source_dock?(pairs, connector)
|
|
394
411
|
adjusted = row_slot_pairs(pairs, bounds, dividers, preserve_source: preserve_source)
|
|
@@ -22,7 +22,7 @@ module Ea
|
|
|
22
22
|
# render zero «...» lines, classes included).
|
|
23
23
|
return [] if context.suppress_stereotypes
|
|
24
24
|
|
|
25
|
-
labels = label_for(classifier, context.umldi_keyword)
|
|
25
|
+
labels = label_for(classifier, context.umldi_keyword, context.stereotype_fqname)
|
|
26
26
|
return [] if labels.empty?
|
|
27
27
|
|
|
28
28
|
labels.map { |l| [l, :normal] }
|
|
@@ -30,10 +30,10 @@ module Ea
|
|
|
30
30
|
|
|
31
31
|
# @return [Array<String>] «name» labels (EA renders one
|
|
32
32
|
# line per applied stereotype)
|
|
33
|
-
def self.label_for(classifier, umldi_keyword)
|
|
33
|
+
def self.label_for(classifier, umldi_keyword, stereotype_fqname = nil)
|
|
34
34
|
return ["«#{umldi_keyword}»"] if umldi_keyword && !umldi_keyword.empty?
|
|
35
35
|
|
|
36
|
-
explicit = explicit_stereotype(classifier)
|
|
36
|
+
explicit = explicit_stereotype(classifier, stereotype_fqname)
|
|
37
37
|
return explicit if explicit
|
|
38
38
|
|
|
39
39
|
case classifier
|
|
@@ -45,21 +45,21 @@ module Ea
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def self.explicit_stereotype(classifier)
|
|
48
|
+
def self.explicit_stereotype(classifier, stereotype_fqname = nil)
|
|
49
49
|
return nil unless classifier.is_a?(Ea::Model::Classifier) ||
|
|
50
50
|
classifier.is_a?(Ea::Model::Package)
|
|
51
51
|
|
|
52
52
|
refs = classifier.stereotype_refs
|
|
53
53
|
return nil unless refs&.any?
|
|
54
54
|
|
|
55
|
-
refs.map { |ref| "«#{canonical(ref)}»" }
|
|
55
|
+
refs.map { |ref| "«#{canonical(ref, stereotype_fqname)}»" }
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
-
# EA
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
# EA's standard Class stereotype displays as «type» while
|
|
59
|
+
# its semantic name remains Type. Custom profiles retain case.
|
|
60
|
+
def self.canonical(ref, stereotype_fqname = nil)
|
|
61
|
+
return "type" if ref == "Type" && stereotype_fqname == "StandardProfileL2::Type"
|
|
62
|
+
|
|
63
63
|
ref
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -169,7 +169,11 @@ module Ea
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def title_font_size
|
|
172
|
-
|
|
172
|
+
return Ea::Model::Legend::DEFAULT_HEADING_SIZE unless legend.heading_size
|
|
173
|
+
|
|
174
|
+
# HEADINGSIZE is stored in pixels; SVG legend text uses points.
|
|
175
|
+
points = legend.heading_size * 72.0 / 96
|
|
176
|
+
points == points.to_i ? points.to_i : points
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
def item_font_size
|
|
@@ -166,10 +166,12 @@ module Ea
|
|
|
166
166
|
)
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
|
+
stereotype_fqname = element.style&.fetch(:stereotype_fqname, nil)
|
|
169
170
|
header_lines = classifier ? Element::HeaderLines.for(classifier,
|
|
170
171
|
diagram_package_id: diagram.package_id,
|
|
171
172
|
visually_nested: visually_nested?(element),
|
|
172
173
|
umldi_keyword: element.umldi_keyword,
|
|
174
|
+
stereotype_fqname: stereotype_fqname,
|
|
173
175
|
bounds_width: bounds.width,
|
|
174
176
|
font_size: size, logical_height: logical_height,
|
|
175
177
|
family: family,
|
|
@@ -271,7 +273,7 @@ module Ea
|
|
|
271
273
|
classifier: classifier,
|
|
272
274
|
fill: resolve_fill(element, classifier),
|
|
273
275
|
stroke: resolve_stroke,
|
|
274
|
-
stroke_width: resolve_stroke_width,
|
|
276
|
+
stroke_width: resolve_stroke_width(element),
|
|
275
277
|
text_fill: theme.text_color,
|
|
276
278
|
family: family, size: size, size_unit: size_unit, logical_height: logical_height,
|
|
277
279
|
header_lines: header_lines,
|
|
@@ -452,7 +454,7 @@ module Ea
|
|
|
452
454
|
def tagged_values_for(classifier)
|
|
453
455
|
return [] unless classifier.is_a?(Ea::Model::Classifier)
|
|
454
456
|
|
|
455
|
-
classifier.tagged_values || []
|
|
457
|
+
(classifier.tagged_values || []).reject { |tag| tag.value.nil? || tag.value.empty? }
|
|
456
458
|
end
|
|
457
459
|
|
|
458
460
|
# Real OCL constraints render trailing, after the content
|
|
@@ -668,7 +670,9 @@ module Ea
|
|
|
668
670
|
color_resolver.stroke_for(nil)
|
|
669
671
|
end
|
|
670
672
|
|
|
671
|
-
def resolve_stroke_width
|
|
673
|
+
def resolve_stroke_width(element)
|
|
674
|
+
return element.line_width if element.line_width && element.line_width >= 0
|
|
675
|
+
|
|
672
676
|
theme.themed? ? theme.stroke_width : DEFAULT_STROKE_WIDTH
|
|
673
677
|
end
|
|
674
678
|
|
|
@@ -10,7 +10,7 @@ module Ea
|
|
|
10
10
|
# 2. theme.font_family / theme.font_size (when theme is themed)
|
|
11
11
|
# 3. diagram-level default (most common non-nil value across
|
|
12
12
|
# the diagram's elements)
|
|
13
|
-
# 4.
|
|
13
|
+
# 4. unthemed fallback (Cambria)
|
|
14
14
|
#
|
|
15
15
|
# Lives outside the Elements emitter so the resolution rule is
|
|
16
16
|
# defined once (MECE) and reusable by Labels and any future
|
|
@@ -83,14 +83,8 @@ module Ea
|
|
|
83
83
|
theme.element_font_family || theme.font_family
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# "Calibri" 10. We can't query EA's runtime, so we infer
|
|
89
|
-
# from the diagram:
|
|
90
|
-
#
|
|
91
|
-
# - If ANY element specifies font=, use the most common.
|
|
92
|
-
# - If NO element specifies font= (all are "use default"),
|
|
93
|
-
# assume the EA English-locale default: Calibri 10.
|
|
86
|
+
# Unthemed diagrams without a saved family use Cambria.
|
|
87
|
+
# Saved families still supply the diagram default.
|
|
94
88
|
def diagram_default_family
|
|
95
89
|
families = element_families
|
|
96
90
|
return FALLBACK_DEFAULT_FAMILY if families.empty?
|
|
@@ -98,7 +92,7 @@ module Ea
|
|
|
98
92
|
most_common(families)
|
|
99
93
|
end
|
|
100
94
|
|
|
101
|
-
FALLBACK_DEFAULT_FAMILY = "
|
|
95
|
+
FALLBACK_DEFAULT_FAMILY = "Cambria"
|
|
102
96
|
|
|
103
97
|
def most_common(values)
|
|
104
98
|
return nil if values.empty?
|
|
@@ -247,12 +247,15 @@ module Ea
|
|
|
247
247
|
def source_bottom_dock?(connector, heading, box)
|
|
248
248
|
source_seat?(:source, heading) && heading[0].zero? && heading[1].positive? &&
|
|
249
249
|
zero_source_property_box?(box) && default_presentation?(box) &&
|
|
250
|
-
source_bottom_property_route?(connector.style.to_h) &&
|
|
251
|
-
source_property_font?(connector, :down)
|
|
250
|
+
source_bottom_property_route?(connector.style.to_h) && source_property_cambria?(connector)
|
|
252
251
|
end
|
|
253
252
|
|
|
253
|
+
# A downward leg on the horizontal lateral tree seats an absent
|
|
254
|
+
# box like the up leg already does on the same route.
|
|
254
255
|
def source_bottom_property_route?(style)
|
|
255
|
-
|
|
256
|
+
return false unless style.values_at(:mode, :lwidth) == %w[3 2]
|
|
257
|
+
|
|
258
|
+
style[:tree] == "LH" || (style[:stored_association_ray] == true && style[:tree].nil?)
|
|
256
259
|
end
|
|
257
260
|
|
|
258
261
|
def plain_upward_seat(connector, direction, anchor, box, role)
|
|
@@ -386,11 +389,14 @@ module Ea
|
|
|
386
389
|
end
|
|
387
390
|
|
|
388
391
|
def source_property_font?(connector, direction)
|
|
389
|
-
|
|
390
|
-
property_label?(connector, :source) &&
|
|
392
|
+
source_property_cambria?(connector) &&
|
|
391
393
|
source_property_route?(connector.style.to_h, direction)
|
|
392
394
|
end
|
|
393
395
|
|
|
396
|
+
def source_property_cambria?(connector)
|
|
397
|
+
font_family.to_s.casecmp("Cambria").zero? && property_label?(connector, :source)
|
|
398
|
+
end
|
|
399
|
+
|
|
394
400
|
def source_property_route?(style, direction)
|
|
395
401
|
case direction
|
|
396
402
|
when :left then left_property_route?(style)
|
data/lib/ea/version.rb
CHANGED