ea 0.6.49 → 0.6.51
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/instance_specification.rb +2 -0
- data/lib/ea/sources/qea/instance_builder.rb +1 -0
- data/lib/ea/svg/ea_emitter/compartment/instance_slots.rb +16 -5
- data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +7 -4
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +45 -7
- data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +19 -4
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +5 -1
- data/lib/ea/svg/ea_emitter/element/header_line_provider/parent_ghost.rb +1 -1
- data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +4 -0
- data/lib/ea/svg/ea_emitter/element/header_renderer.rb +9 -4
- data/lib/ea/svg/ea_emitter/elements.rb +13 -2
- data/lib/ea/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bac390d3be1320d1679f33e3ed0e83b0982c02faea48f6ebbc2acdc31aa345b
|
|
4
|
+
data.tar.gz: c0ffb44d6f3cb775e37edbbe8fc10ddfb10a802c9555846aba5f21188901c3dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b380504257c1e1b3ad40852561d4535e4a2dd4f6894427715eeb2a192b7ca7b9d43b77bed8d9c6c28bfaacf0d62586f4204fac00d3e6642646f1f3f214f2e0e3
|
|
7
|
+
data.tar.gz: 80acb5162983cbe2052c63bbb417adb0a019be316eba63aeb54166680f6c68b4865b2da41378eff7fd9dcc5eab117a46bbc2a577e426451b95bc75a222c556d0
|
|
@@ -10,6 +10,7 @@ module Ea
|
|
|
10
10
|
attribute :name, :string
|
|
11
11
|
attribute :classifier_id, :string
|
|
12
12
|
attribute :classifier_name, :string
|
|
13
|
+
attribute :stereotype, :string
|
|
13
14
|
attribute :package_id, :string
|
|
14
15
|
attribute :package_name, :string
|
|
15
16
|
attribute :qualified_name, :string
|
|
@@ -22,6 +23,7 @@ module Ea
|
|
|
22
23
|
map "name", to: :name
|
|
23
24
|
map "classifierId", to: :classifier_id
|
|
24
25
|
map "classifierName", to: :classifier_name
|
|
26
|
+
map "stereotype", to: :stereotype
|
|
25
27
|
map "packageId", to: :package_id
|
|
26
28
|
map "packageName", to: :package_name
|
|
27
29
|
map "qualifiedName", to: :qualified_name
|
|
@@ -36,6 +36,7 @@ module Ea
|
|
|
36
36
|
name: row.name.to_s,
|
|
37
37
|
classifier_id: classifier_id_for(row),
|
|
38
38
|
classifier_name: classifier_name_for(row),
|
|
39
|
+
stereotype: row.stereotype.to_s,
|
|
39
40
|
package_id: package_id_for(row),
|
|
40
41
|
package_name: package_name_for(row),
|
|
41
42
|
qualified_name: qualified_name_for(row),
|
|
@@ -7,9 +7,10 @@ module Ea
|
|
|
7
7
|
# Instance slot compartment. Renders each slot on an
|
|
8
8
|
# InstanceSpecification as "name op value", then appends
|
|
9
9
|
# "(from PackageName)" in italics below the slots when the
|
|
10
|
-
# instance
|
|
11
|
-
# element is not an
|
|
12
|
-
# slots nor a
|
|
10
|
+
# instance is foreign to the diagram's package. Skipped
|
|
11
|
+
# entirely when the model element is not an
|
|
12
|
+
# InstanceSpecification or has neither slots nor a
|
|
13
|
+
# classifier.
|
|
13
14
|
module InstanceSlots
|
|
14
15
|
ROW_OFFSET = 13
|
|
15
16
|
SUBTITLE_EXTRA_OFFSET = 10
|
|
@@ -21,7 +22,7 @@ module Ea
|
|
|
21
22
|
|
|
22
23
|
instance = context.model_element
|
|
23
24
|
slots = instance.slots || []
|
|
24
|
-
subtitle = from_package_subtitle(instance)
|
|
25
|
+
subtitle = from_package_subtitle(instance, context.diagram)
|
|
25
26
|
return nil if slots.empty? && subtitle.nil?
|
|
26
27
|
|
|
27
28
|
first_y = context.geometry.attr_first_y
|
|
@@ -44,7 +45,17 @@ module Ea
|
|
|
44
45
|
end
|
|
45
46
|
module_function :instance?
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
# EA draws "(from PackageName)" only for instances FOREIGN
|
|
49
|
+
# to the diagram's own package: every instance diagram in
|
|
50
|
+
# the corpus has local instances and EA renders the
|
|
51
|
+
# subtitle nowhere (FC590D99: 23 local FIG2010 instances,
|
|
52
|
+
# zero "(from" lines in the published SVG).
|
|
53
|
+
def from_package_subtitle(instance, diagram)
|
|
54
|
+
if diagram && instance.package_id && diagram.package_id &&
|
|
55
|
+
instance.package_id == diagram.package_id
|
|
56
|
+
return nil
|
|
57
|
+
end
|
|
58
|
+
|
|
48
59
|
pkg = instance.package_name
|
|
49
60
|
return nil if pkg.nil? || pkg.empty?
|
|
50
61
|
|
|
@@ -18,6 +18,7 @@ module Ea
|
|
|
18
18
|
# coordinate math changes don't ripple into rendering.
|
|
19
19
|
class CompartmentGeometry
|
|
20
20
|
attr_reader :bounds, :size, :header_lines_count, :header_ghost,
|
|
21
|
+
:header_ghost_tight,
|
|
21
22
|
:attr_lines_count, :op_lines_count,
|
|
22
23
|
:tagged_values_count,
|
|
23
24
|
:constraints_count,
|
|
@@ -38,7 +39,8 @@ module Ea
|
|
|
38
39
|
divider_offset: 8,
|
|
39
40
|
attr_line_offset: 6,
|
|
40
41
|
attr_first_offset: 7,
|
|
41
|
-
header_ghost: false
|
|
42
|
+
header_ghost: false,
|
|
43
|
+
header_ghost_tight: false)
|
|
42
44
|
@bounds = bounds
|
|
43
45
|
@size = size
|
|
44
46
|
@header_lines_count = header_lines_count
|
|
@@ -54,6 +56,7 @@ module Ea
|
|
|
54
56
|
@attr_line_offset = attr_line_offset
|
|
55
57
|
@attr_first_offset = attr_first_offset
|
|
56
58
|
@header_ghost = header_ghost
|
|
59
|
+
@header_ghost_tight = header_ghost_tight
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
def header_first_y
|
|
@@ -71,9 +74,9 @@ module Ea
|
|
|
71
74
|
return nil if header_lines_count.zero?
|
|
72
75
|
|
|
73
76
|
# The off-canvas parent ghost shifts every following
|
|
74
|
-
# header line +
|
|
75
|
-
# baseline + 8 (
|
|
76
|
-
ghost_shift = header_ghost ? 6 : 0
|
|
77
|
+
# header line +3, and the divider tracks the LAST header
|
|
78
|
+
# baseline + 8 (936AA434: ghost 100, stereo 116 = +16).
|
|
79
|
+
ghost_shift = header_ghost ? (header_ghost_tight ? 3 : 6) : 0
|
|
77
80
|
|
|
78
81
|
header_first_y +
|
|
79
82
|
([header_lines_count, 1].max - 1) * (size + (header_line_offset || 6)) +
|
|
@@ -22,6 +22,7 @@ module Ea
|
|
|
22
22
|
:bounds_width, :font_size, :family,
|
|
23
23
|
:off_canvas_parent_name,
|
|
24
24
|
:foreign_package_name,
|
|
25
|
+
:suppress_stereotypes,
|
|
25
26
|
keyword_init: true)
|
|
26
27
|
|
|
27
28
|
PROVIDERS = [
|
|
@@ -39,9 +40,18 @@ module Ea
|
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
# Greedy word wrap measured with the header's own font.
|
|
42
|
-
# EA wraps overflowing headers at word boundaries
|
|
43
|
-
# (
|
|
43
|
+
# EA wraps overflowing headers at word boundaries and after
|
|
44
|
+
# "::" qualifiers (F851A657: class "Content
|
|
45
|
+
# information::MD_FeatureCatalogueDescription" renders as
|
|
46
|
+
# "Content information::" / "MD_FeatureCatalogueDescription";
|
|
47
|
+
# 81F92FC7: instance header "new ownership: LA_Right"
|
|
44
48
|
# renders as "new ownership:" / "LA_Right").
|
|
49
|
+
# Headers wrap against a USABLE width of box width - 6:
|
|
50
|
+
# EA wrapped "SU_PB2: LA_SpatialUnit" (parts 36 + 59 +
|
|
51
|
+
# space 4 = 99) inside a 100px box, while
|
|
52
|
+
# "FuelStation: LA_BAUnit" (94) stayed single in a 100px
|
|
53
|
+
# box - the corpus boundary is margin 5 wrapped vs 6
|
|
54
|
+
# unwrapped (FC590D99 vs CBC03448).
|
|
45
55
|
def self.wrap_words(text, context, weight)
|
|
46
56
|
font_style = weight == :bold_italic ? "italic" : "normal"
|
|
47
57
|
width = lambda do |s|
|
|
@@ -51,15 +61,17 @@ module Ea
|
|
|
51
61
|
).round
|
|
52
62
|
end
|
|
53
63
|
return [text] if context.bounds_width.nil?
|
|
54
|
-
|
|
64
|
+
usable = context.bounds_width.to_i - 6
|
|
65
|
+
return [text] if width.call(text) <= usable
|
|
55
66
|
|
|
67
|
+
segments = wrap_segments(text)
|
|
56
68
|
lines = []
|
|
57
69
|
current = +""
|
|
58
|
-
|
|
59
|
-
candidate = current
|
|
60
|
-
if !current.empty? && width.call(candidate) >
|
|
70
|
+
segments.each do |segment|
|
|
71
|
+
candidate = current + segment
|
|
72
|
+
if !current.empty? && width.call(candidate) > usable
|
|
61
73
|
lines << current
|
|
62
|
-
current =
|
|
74
|
+
current = +segment.lstrip
|
|
63
75
|
else
|
|
64
76
|
current = candidate
|
|
65
77
|
end
|
|
@@ -67,6 +79,32 @@ module Ea
|
|
|
67
79
|
lines << current unless current.empty?
|
|
68
80
|
lines
|
|
69
81
|
end
|
|
82
|
+
|
|
83
|
+
# Break opportunities: after each space and after each
|
|
84
|
+
# "::". Each segment carries the separator that PRECEDES
|
|
85
|
+
# it ("" after "::", " " after a word), so joining all
|
|
86
|
+
# segments reproduces the original text exactly.
|
|
87
|
+
def self.wrap_segments(text)
|
|
88
|
+
segments = []
|
|
89
|
+
pending = ""
|
|
90
|
+
text.split(" ").each do |word|
|
|
91
|
+
chunk = +""
|
|
92
|
+
word.split(/(::)/).each_slice(2) do |part, sep|
|
|
93
|
+
chunk << pending << part
|
|
94
|
+
pending = ""
|
|
95
|
+
if sep
|
|
96
|
+
chunk << sep
|
|
97
|
+
segments << chunk.dup
|
|
98
|
+
chunk.clear
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
unless chunk.empty?
|
|
102
|
+
segments << chunk.dup
|
|
103
|
+
pending = " "
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
segments
|
|
107
|
+
end
|
|
70
108
|
end
|
|
71
109
|
end
|
|
72
110
|
end
|
|
@@ -6,22 +6,37 @@ module Ea
|
|
|
6
6
|
module Element
|
|
7
7
|
module HeaderLineProvider
|
|
8
8
|
# InstanceSpecification: short-circuits the pipeline. The
|
|
9
|
-
# header for an instance is
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# header for an instance is its own «stereotype» line (the
|
|
10
|
+
# t_object.Stereotype value, verbatim - «featureType»,
|
|
11
|
+
# «FeatureType», «type» …) followed by "name: Classifier"
|
|
12
|
+
# on one bold line, word-wrapped when it overflows the box
|
|
13
|
+
# (81F92FC7: "new ownership:" / "LA_Right"). The stereotype
|
|
14
|
+
# line is suppressed by the diagram pdata flag
|
|
15
|
+
# HideEStereo=1 (FC590D99 shows it; the Annex C "Case"
|
|
16
|
+
# diagrams with HideEStereo=1 show none).
|
|
12
17
|
class InstanceSpec
|
|
13
18
|
def self.call(context)
|
|
14
19
|
inst = context.classifier
|
|
15
20
|
return [] unless inst.is_a?(Ea::Model::InstanceSpecification)
|
|
16
21
|
|
|
22
|
+
lines = stereotype_lines(inst, context)
|
|
17
23
|
label = inst.name.to_s
|
|
18
24
|
if inst.classifier_name && !inst.classifier_name.empty?
|
|
19
25
|
label = "#{label}: #{inst.classifier_name}"
|
|
20
26
|
end
|
|
21
|
-
HeaderLinePipeline.wrap_words(label, context, :bold).map do |line|
|
|
27
|
+
lines + HeaderLinePipeline.wrap_words(label, context, :bold).map do |line|
|
|
22
28
|
[line, :bold]
|
|
23
29
|
end
|
|
24
30
|
end
|
|
31
|
+
|
|
32
|
+
def self.stereotype_lines(inst, context)
|
|
33
|
+
return [] if context.suppress_stereotypes
|
|
34
|
+
|
|
35
|
+
st = inst.stereotype.to_s
|
|
36
|
+
return [] if st.empty?
|
|
37
|
+
|
|
38
|
+
[["«#{st}»", :normal]]
|
|
39
|
+
end
|
|
25
40
|
end
|
|
26
41
|
end
|
|
27
42
|
end
|
|
@@ -58,7 +58,11 @@ module Ea
|
|
|
58
58
|
name, context.font_size || 9, nil,
|
|
59
59
|
family: context.family, weight: "700", style: font_style
|
|
60
60
|
).round
|
|
61
|
-
|
|
61
|
+
# Same usable width as wrap_words: box width - 6.
|
|
62
|
+
# EA wraps "Content information::MD_FeatureCatalogue-
|
|
63
|
+
# Description" (parts 86 + 137 + space 4 = 227) inside
|
|
64
|
+
# a 230px box (F851A657).
|
|
65
|
+
len > context.bounds_width.to_i - 6
|
|
62
66
|
end
|
|
63
67
|
|
|
64
68
|
# EA renders the plain classifier name in element headers;
|
|
@@ -17,6 +17,10 @@ module Ea
|
|
|
17
17
|
def self.call(context)
|
|
18
18
|
classifier = context.classifier
|
|
19
19
|
return [] if classifier.is_a?(Ea::Model::InstanceSpecification)
|
|
20
|
+
# Diagram pdata HideEStereo=1 suppresses classifier
|
|
21
|
+
# stereotype lines too (the Annex C "Case" diagrams
|
|
22
|
+
# render zero «...» lines, classes included).
|
|
23
|
+
return [] if context.suppress_stereotypes
|
|
20
24
|
|
|
21
25
|
labels = label_for(classifier, context.umldi_keyword)
|
|
22
26
|
return [] if labels.empty?
|
|
@@ -23,11 +23,16 @@ module Ea
|
|
|
23
23
|
else weight_normal
|
|
24
24
|
end
|
|
25
25
|
font_style = (style == :italic || style == :bold_italic) ? "italic" : "normal"
|
|
26
|
-
# EA spaces the line AFTER a right-aligned ghost
|
|
27
|
-
# wider
|
|
28
|
-
#
|
|
26
|
+
# EA spaces the line AFTER a right-aligned ghost 3px
|
|
27
|
+
# wider than the pitch. Interfaces use a tighter +3
|
|
28
|
+
# (936AA434/Surface: ghost 100, «interface» 116).
|
|
29
|
+
ghost_shift = case lines.first.last
|
|
30
|
+
when :italic then 6
|
|
31
|
+
when :italic_tight then 3
|
|
32
|
+
else 0
|
|
33
|
+
end
|
|
29
34
|
y = first_y + (idx * line_h) +
|
|
30
|
-
(idx >= 1 &&
|
|
35
|
+
(idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
|
|
31
36
|
x = if style == :italic
|
|
32
37
|
# EA right-aligns the off-canvas parent ghost at
|
|
33
38
|
# the box's right edge by its integer textLength.
|
|
@@ -126,7 +126,8 @@ module Ea
|
|
|
126
126
|
font_size: size,
|
|
127
127
|
family: family,
|
|
128
128
|
off_canvas_parent_name: parent_name,
|
|
129
|
-
foreign_package_name: foreign_package_name_for(classifier)
|
|
129
|
+
foreign_package_name: foreign_package_name_for(classifier),
|
|
130
|
+
suppress_stereotypes: suppress_stereotypes?) : []
|
|
130
131
|
is_classifier = classifier.is_a?(Ea::Model::Classifier)
|
|
131
132
|
attr_lines = if is_classifier && show_attributes? &&
|
|
132
133
|
!classifier.is_a?(Ea::Model::Interface) &&
|
|
@@ -235,7 +236,9 @@ module Ea
|
|
|
235
236
|
enum_literals_count: 0)
|
|
236
237
|
CompartmentGeometry.new(bounds: bounds, size: size,
|
|
237
238
|
header_ghost: header_lines.any? &&
|
|
238
|
-
header_lines.first.last
|
|
239
|
+
%i[italic italic_tight].include?(header_lines.first.last),
|
|
240
|
+
header_ghost_tight: header_lines.any? &&
|
|
241
|
+
header_lines.first.last == :italic_tight,
|
|
239
242
|
header_lines_count: header_lines.size,
|
|
240
243
|
attr_lines_count: attr_lines.size,
|
|
241
244
|
op_lines_count: op_lines.size,
|
|
@@ -274,6 +277,14 @@ module Ea
|
|
|
274
277
|
classifier.is_a?(Ea::Model::Interface)
|
|
275
278
|
end
|
|
276
279
|
|
|
280
|
+
# t_diagram pdata HideEStereo=1 suppresses every stereotype
|
|
281
|
+
# line on the diagram - instance boxes ("Case C25" renders
|
|
282
|
+
# no «featureType» despite every instance carrying one) and
|
|
283
|
+
# classifier boxes alike.
|
|
284
|
+
def suppress_stereotypes?
|
|
285
|
+
diagram.style.to_s.match?(/HideEStereo=1/)
|
|
286
|
+
end
|
|
287
|
+
|
|
277
288
|
# A stereotype group header («Property») sits 4px below where
|
|
278
289
|
# plain rows start (divider+18 vs divider+14 at 7pt).
|
|
279
290
|
def attr_first_offset_for(attr_lines)
|
data/lib/ea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.51
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|