ea 0.6.17 → 0.6.18
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/diagram_connector.rb +7 -0
- data/lib/ea/sources/qea/diagram_builder.rb +4 -0
- data/lib/ea/svg/ea_emitter/bounds_calculator.rb +11 -71
- data/lib/ea/svg/ea_emitter/compartment/note_body.rb +28 -2
- data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +5 -1
- data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +6 -2
- data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +4 -3
- data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +21 -15
- data/lib/ea/svg/ea_emitter/element/header_line_provider/stereotype_label.rb +5 -13
- data/lib/ea/svg/ea_emitter/elements.rb +23 -1
- data/lib/ea/svg/ea_emitter/label/end_label.rb +13 -22
- data/lib/ea/svg/ea_emitter/labels.rb +4 -0
- data/lib/ea/theme/definition.rb +1 -1
- 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: 951615c9e10080cf517cdae2a6052582c8a61dfa83f5607306471073665742ee
|
|
4
|
+
data.tar.gz: c496317d449df3482decf289edcd71c625cff4fcae28ccb7c5bf7bb81cfdb91d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: accef1410192459bf992364629e34c414d96f4938e78b3776232b36287f88e1b12e029e42bc1c29ca9ca6496f62e88118f399416eda8db872e32e67d953c523f
|
|
7
|
+
data.tar.gz: b21a6f9a663ebbd335f6b3bf44841b5679c3c2b80b79f35e959e52bfc06fe75988cb350f5281a42046a1de5b6ceb8739dbea9df0e6596d41fc7b092fa0092d0b
|
|
@@ -19,6 +19,11 @@ module Ea
|
|
|
19
19
|
attribute :target_edge, :integer
|
|
20
20
|
attribute :connector_type, :string
|
|
21
21
|
attribute :direction, :string
|
|
22
|
+
# t_connector.SourceStereotype / DestStereotype — EA renders
|
|
23
|
+
# the «Property» end-label exactly on the stereotyped end
|
|
24
|
+
# (corpus-verified; direction alone does not decide).
|
|
25
|
+
attribute :source_stereotype, :string
|
|
26
|
+
attribute :target_stereotype, :string
|
|
22
27
|
attribute :waypoints, Waypoint, collection: true, initialize_empty: true
|
|
23
28
|
attribute :label, :string
|
|
24
29
|
attribute :style, :hash, default: -> { {} }
|
|
@@ -54,6 +59,8 @@ module Ea
|
|
|
54
59
|
map "targetPort", to: :target_port
|
|
55
60
|
map "connectorType", to: :connector_type
|
|
56
61
|
map "direction", to: :direction
|
|
62
|
+
map "sourceStereotype", to: :source_stereotype
|
|
63
|
+
map "targetStereotype", to: :target_stereotype
|
|
57
64
|
map "waypoints", to: :waypoints, render_empty: true
|
|
58
65
|
map "label", to: :label
|
|
59
66
|
map "style", to: :style
|
|
@@ -199,6 +199,8 @@ module Ea
|
|
|
199
199
|
relationship_ref: ref_for_raw_connector(conn),
|
|
200
200
|
connector_type: conn.connector_type,
|
|
201
201
|
direction: conn.direction,
|
|
202
|
+
source_stereotype: conn.sourcestereotype,
|
|
203
|
+
target_stereotype: conn.deststereotype,
|
|
202
204
|
waypoints: waypoints,
|
|
203
205
|
label_boxes: DEFAULT_LABEL_BOXES,
|
|
204
206
|
style: {},
|
|
@@ -240,6 +242,8 @@ module Ea
|
|
|
240
242
|
relationship_ref: ref_for_connector(link_row),
|
|
241
243
|
connector_type: connector&.connector_type,
|
|
242
244
|
direction: connector&.direction,
|
|
245
|
+
source_stereotype: connector&.sourcestereotype,
|
|
246
|
+
target_stereotype: connector&.deststereotype,
|
|
243
247
|
waypoints: waypoints_for_link(link_row, diagram_row),
|
|
244
248
|
source_port: connection_port(diagram_row, connector, :source, geom),
|
|
245
249
|
target_port: connection_port(diagram_row, connector, :target, geom),
|
|
@@ -8,20 +8,14 @@ module Ea
|
|
|
8
8
|
# stays a small value-object with just coordinate translation
|
|
9
9
|
# and formatting concerns.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# Each source is a method returning an Array<[x, y]> points.
|
|
20
|
-
# Composing new sources = adding a method, no modification of
|
|
21
|
-
# existing ones (OCP).
|
|
11
|
+
# Corpus-verified against 3,663 EA-published reference SVGs:
|
|
12
|
+
# EA's canvas covers the ELEMENT RECTS only — connector
|
|
13
|
+
# waypoints, arrow markers, and package-tab overhangs do NOT
|
|
14
|
+
# extend it. Width = element extent + 85 is exact on 72% of
|
|
15
|
+
# the corpus; height = element extent + 76 exact on 50% (the
|
|
16
|
+
# remainder carry invisible per-diagram reservations below
|
|
17
|
+
# the content).
|
|
22
18
|
class BoundsCalculator
|
|
23
|
-
MARKER_EXTENT = 15
|
|
24
|
-
PACKAGE_TAB_HEIGHT = 20
|
|
25
19
|
# EA's canvas includes non-uniform frame insets around the
|
|
26
20
|
# element content. Reverse-engineered from reference SVG
|
|
27
21
|
# byte-diff: maintenance diagram has element 169x80 at
|
|
@@ -30,11 +24,12 @@ module Ea
|
|
|
30
24
|
# canvas_width = element_width + INSET_LEFT + INSET_RIGHT
|
|
31
25
|
# canvas_height = element_height + INSET_TOP + INSET_BOTTOM
|
|
32
26
|
# The top inset accommodates the frame tab + label space
|
|
33
|
-
# above the element.
|
|
27
|
+
# above the element. The bottom inset 36 matches the
|
|
28
|
+
# dominant corpus mode (50% of 3,663 diagrams).
|
|
34
29
|
INSET_LEFT = 35
|
|
35
30
|
INSET_RIGHT = 50
|
|
36
31
|
INSET_TOP = 40
|
|
37
|
-
INSET_BOTTOM =
|
|
32
|
+
INSET_BOTTOM = 36
|
|
38
33
|
|
|
39
34
|
attr_reader :diagram, :model_index
|
|
40
35
|
|
|
@@ -44,11 +39,7 @@ module Ea
|
|
|
44
39
|
end
|
|
45
40
|
|
|
46
41
|
def compute
|
|
47
|
-
points =
|
|
48
|
-
points.concat(element_points)
|
|
49
|
-
points.concat(connector_points)
|
|
50
|
-
points.concat(marker_points)
|
|
51
|
-
points.concat(package_tab_points)
|
|
42
|
+
points = element_points
|
|
52
43
|
return [0, 0, 1, 1] if points.empty?
|
|
53
44
|
|
|
54
45
|
xs = points.map(&:first)
|
|
@@ -85,57 +76,6 @@ module Ea
|
|
|
85
76
|
end
|
|
86
77
|
pts
|
|
87
78
|
end
|
|
88
|
-
|
|
89
|
-
# Package elements render with a tab polygon ABOVE the
|
|
90
|
-
# element's logical top edge (height PACKAGE_TAB_HEIGHT).
|
|
91
|
-
# Include those points so the canvas reserves space.
|
|
92
|
-
def package_tab_points
|
|
93
|
-
return [] if model_index.nil?
|
|
94
|
-
|
|
95
|
-
pts = []
|
|
96
|
-
(diagram.elements || []).each do |e|
|
|
97
|
-
ref = e.model_element_ref
|
|
98
|
-
next unless ref
|
|
99
|
-
|
|
100
|
-
candidate = model_index[ref]
|
|
101
|
-
next unless candidate.is_a?(Ea::Model::Package)
|
|
102
|
-
|
|
103
|
-
primary = e.bounds || e.image_bounds
|
|
104
|
-
next unless primary
|
|
105
|
-
|
|
106
|
-
pts << [primary.x, primary.y - PACKAGE_TAB_HEIGHT]
|
|
107
|
-
pts << [primary.x + primary.width, primary.y]
|
|
108
|
-
end
|
|
109
|
-
pts
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def connector_points
|
|
113
|
-
pts = []
|
|
114
|
-
(diagram.connectors || []).each do |c|
|
|
115
|
-
(c.waypoints || []).each do |wp|
|
|
116
|
-
next unless wp.position
|
|
117
|
-
|
|
118
|
-
pts << [wp.position.x, wp.position.y]
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
pts
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def marker_points
|
|
125
|
-
pts = []
|
|
126
|
-
(diagram.connectors || []).each do |c|
|
|
127
|
-
waypoints = (c.waypoints || []).map(&:position).compact
|
|
128
|
-
next unless waypoints.size >= 2
|
|
129
|
-
|
|
130
|
-
source = waypoints.first
|
|
131
|
-
target = waypoints.last
|
|
132
|
-
pts << [source.x - MARKER_EXTENT, source.y - MARKER_EXTENT]
|
|
133
|
-
pts << [source.x + MARKER_EXTENT, source.y + MARKER_EXTENT]
|
|
134
|
-
pts << [target.x - MARKER_EXTENT, target.y - MARKER_EXTENT]
|
|
135
|
-
pts << [target.x + MARKER_EXTENT, target.y + MARKER_EXTENT]
|
|
136
|
-
end
|
|
137
|
-
pts
|
|
138
|
-
end
|
|
139
79
|
end
|
|
140
80
|
end
|
|
141
81
|
end
|
|
@@ -21,10 +21,16 @@ module Ea
|
|
|
21
21
|
%(<g style="#{group_style(context)}">\n#{text_blocks.join("\n")}\n</g>)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# EA wraps note text by MEASURED width (Carlito advances),
|
|
25
|
+
# not character count: lines break at the last word whose
|
|
26
|
+
# cumulative integer textLength fits bounds.width - 15.
|
|
27
|
+
# Verified against EA-published reference SVGs.
|
|
24
28
|
def wrapped_lines(context, body)
|
|
25
|
-
|
|
29
|
+
usable = context.bounds.width - 15
|
|
30
|
+
size = context.size
|
|
31
|
+
family = context.family
|
|
26
32
|
body.to_s.split(/\n/).flat_map do |para|
|
|
27
|
-
para
|
|
33
|
+
wrap_paragraph(para, usable, size, family)
|
|
28
34
|
end.each_with_index.map do |line, idx|
|
|
29
35
|
y = context.bounds.y + context.theme.note.text_y_offset +
|
|
30
36
|
(idx * context.theme.note.line_height)
|
|
@@ -40,6 +46,26 @@ module Ea
|
|
|
40
46
|
end
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
def wrap_paragraph(para, usable, size, family)
|
|
50
|
+
words = para.strip.split(/\s+/)
|
|
51
|
+
lines = []
|
|
52
|
+
current = ""
|
|
53
|
+
words.each do |word|
|
|
54
|
+
candidate = current.empty? ? word : "#{current} #{word}"
|
|
55
|
+
width = TextRenderer.estimate_width(candidate, size, nil,
|
|
56
|
+
family: family).round
|
|
57
|
+
if current.empty? || width <= usable
|
|
58
|
+
current = candidate
|
|
59
|
+
else
|
|
60
|
+
lines << current
|
|
61
|
+
current = word
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
lines << current unless current.empty?
|
|
65
|
+
lines
|
|
66
|
+
end
|
|
67
|
+
module_function :wrap_paragraph
|
|
68
|
+
|
|
43
69
|
def group_style(context)
|
|
44
70
|
"stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel; " \
|
|
45
71
|
"fill:#{context.theme.text_color};fill-opacity:1.00; " \
|
|
@@ -67,7 +67,11 @@ module Ea
|
|
|
67
67
|
lookup: lookup).to_s
|
|
68
68
|
end
|
|
69
69
|
else
|
|
70
|
-
|
|
70
|
+
# EA lists attribute rows alphabetically (case-
|
|
71
|
+
# insensitive) — corpus-verified: 991 boxes strictly
|
|
72
|
+
# alphabetical vs 24 strictly stored-Pos order.
|
|
73
|
+
props.sort_by { |p| [p.name.to_s.downcase, p.name.to_s] }
|
|
74
|
+
.flat_map do |prop|
|
|
71
75
|
lines = []
|
|
72
76
|
stereotype = property_stereotype(prop)
|
|
73
77
|
lines << "«#{stereotype}»" if stereotype
|
|
@@ -13,9 +13,13 @@ module Ea
|
|
|
13
13
|
# Rendered as a third compartment below the attribute
|
|
14
14
|
# compartment for Enumeration classifiers.
|
|
15
15
|
class EnumerationLiteralRenderer
|
|
16
|
+
# EA lists literals alphabetically (case-insensitive) and
|
|
17
|
+
# spaces rows at the same 13px pitch as attributes at 7pt
|
|
18
|
+
# (size + 6) — corpus-verified against EA-published SVGs.
|
|
16
19
|
def self.render(literals, bounds:, first_y:, family:, size:)
|
|
17
|
-
line_h = size +
|
|
18
|
-
|
|
20
|
+
line_h = size + 6
|
|
21
|
+
sorted = literals.sort_by { |l| [l.name.to_s.downcase, l.name.to_s] }
|
|
22
|
+
text_blocks = sorted.each_with_index.map do |literal, idx|
|
|
19
23
|
y = first_y + (idx * line_h)
|
|
20
24
|
build_text(bounds.x + 5, y, literal.name.to_s, family, size)
|
|
21
25
|
end
|
|
@@ -11,16 +11,17 @@ module Ea
|
|
|
11
11
|
#
|
|
12
12
|
# Context (Struct) carries everything a provider might need:
|
|
13
13
|
# classifier, diagram_package_id, visually_nested,
|
|
14
|
-
# umldi_keyword, bounds_width, font_size,
|
|
15
|
-
# off_canvas_parent_name
|
|
14
|
+
# umldi_keyword, bounds_width, font_size, family,
|
|
15
|
+
# off_canvas_parent_name, foreign_package_name
|
|
16
16
|
#
|
|
17
17
|
# Each provider implements:
|
|
18
18
|
# def self.call(context) → Array<[[text, style], ...]>
|
|
19
19
|
module HeaderLinePipeline
|
|
20
20
|
Context = Struct.new(:classifier, :diagram_package_id,
|
|
21
21
|
:visually_nested, :umldi_keyword,
|
|
22
|
-
:bounds_width, :font_size,
|
|
22
|
+
:bounds_width, :font_size, :family,
|
|
23
23
|
:off_canvas_parent_name,
|
|
24
|
+
:foreign_package_name,
|
|
24
25
|
keyword_init: true)
|
|
25
26
|
|
|
26
27
|
PROVIDERS = [
|
|
@@ -8,16 +8,19 @@ module Ea
|
|
|
8
8
|
# Emits the classifier's display name, with these behaviors:
|
|
9
9
|
#
|
|
10
10
|
# - Bold-italic for abstract Klass; bold otherwise.
|
|
11
|
-
# -
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# - Foreign elements (owning package ≠ the diagram's package,
|
|
12
|
+
# gated on t_diagram.ShowForeign) render the qualified
|
|
13
|
+
# name "OwningPackage::Name".
|
|
14
|
+
# - Qualified-name wrap: when the combined integer
|
|
15
|
+
# textLength exceeds the element bounds width, EA splits
|
|
16
|
+
# "pkg::Class" across two lines ("pkg::" then "Class"),
|
|
17
|
+
# each centered independently. Corpus-verified: every
|
|
18
|
+
# wrapped header overflows its box and every single-line
|
|
19
|
+
# header fits (tightest observed slack 6px).
|
|
14
20
|
#
|
|
15
21
|
# InstanceSpecification returns [] (InstanceSpec provider
|
|
16
22
|
# already emitted the full label).
|
|
17
23
|
class Name
|
|
18
|
-
QUALIFIED_WRAP_WIDTH_FACTOR = 0.55
|
|
19
|
-
QUALIFIED_WRAP_PADDING = 8
|
|
20
|
-
|
|
21
24
|
def self.call(context)
|
|
22
25
|
classifier = context.classifier
|
|
23
26
|
return [] if classifier.is_a?(Ea::Model::InstanceSpecification)
|
|
@@ -34,9 +37,9 @@ module Ea
|
|
|
34
37
|
|
|
35
38
|
def self.wrapped_name_lines(classifier, context, weight)
|
|
36
39
|
name = display_name(classifier)
|
|
40
|
+
name = "#{context.foreign_package_name}::#{name}" if context.foreign_package_name
|
|
37
41
|
unless context.bounds_width && name.include?("::") &&
|
|
38
|
-
name_exceeds_bounds?(name, context
|
|
39
|
-
context.font_size || 9)
|
|
42
|
+
name_exceeds_bounds?(name, context, weight)
|
|
40
43
|
return [[name, weight]]
|
|
41
44
|
end
|
|
42
45
|
|
|
@@ -44,15 +47,18 @@ module Ea
|
|
|
44
47
|
[["#{qualifier}::", weight], [base, weight]]
|
|
45
48
|
end
|
|
46
49
|
|
|
47
|
-
def self.name_exceeds_bounds?(name,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
def self.name_exceeds_bounds?(name, context, weight)
|
|
51
|
+
font_style = weight == :bold_italic ? "italic" : "normal"
|
|
52
|
+
len = TextRenderer.estimate_width(
|
|
53
|
+
name, context.font_size || 9, nil,
|
|
54
|
+
family: context.family, weight: "700", style: font_style
|
|
55
|
+
).round
|
|
56
|
+
len > context.bounds_width
|
|
51
57
|
end
|
|
52
58
|
|
|
53
|
-
# EA renders the plain classifier name in element headers
|
|
54
|
-
#
|
|
55
|
-
#
|
|
59
|
+
# EA renders the plain classifier name in element headers;
|
|
60
|
+
# qualification is layered on by the pipeline via
|
|
61
|
+
# context.foreign_package_name.
|
|
56
62
|
def self.display_name(classifier, *_)
|
|
57
63
|
classifier.name.to_s
|
|
58
64
|
end
|
|
@@ -51,20 +51,12 @@ module Ea
|
|
|
51
51
|
refs.map { |ref| "«#{canonical(ref)}»" }
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# EA
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# against EA-published reference SVGs.
|
|
59
|
-
DISPLAY_OVERRIDES = {
|
|
60
|
-
"property" => "Property",
|
|
61
|
-
"featuretype" => "FeatureType",
|
|
62
|
-
"objecttype" => "ObjectType",
|
|
63
|
-
"codelist" => "CodeList"
|
|
64
|
-
}.freeze
|
|
65
|
-
|
|
54
|
+
# EA renders the stereotype string exactly as stored in
|
|
55
|
+
# t_object.Stereotype — corpus-verified: 3,284 placements
|
|
56
|
+
# render verbatim ('CodeList', 'codeList', 'codelist',
|
|
57
|
+
# 'featureType' …), zero case-canonicalized.
|
|
66
58
|
def self.canonical(ref)
|
|
67
|
-
|
|
59
|
+
ref
|
|
68
60
|
end
|
|
69
61
|
|
|
70
62
|
def self.fallback_name(classifier)
|
|
@@ -69,7 +69,9 @@ module Ea
|
|
|
69
69
|
umldi_keyword: element.umldi_keyword,
|
|
70
70
|
bounds_width: raw_bounds&.width,
|
|
71
71
|
font_size: size,
|
|
72
|
-
|
|
72
|
+
family: family,
|
|
73
|
+
off_canvas_parent_name: parent_name,
|
|
74
|
+
foreign_package_name: foreign_package_name_for(classifier)) : []
|
|
73
75
|
is_classifier = classifier.is_a?(Ea::Model::Classifier)
|
|
74
76
|
attr_lines = if is_classifier && show_attributes?
|
|
75
77
|
Element::AttributeRenderer.lines_for(
|
|
@@ -214,6 +216,26 @@ module Ea
|
|
|
214
216
|
Ea::Model::Constraint.new(name: name, kind: "Marker")
|
|
215
217
|
end
|
|
216
218
|
|
|
219
|
+
# EA qualifies a placed classifier's header with its owning
|
|
220
|
+
# package name ("OwningPackage::Name") exactly when the
|
|
221
|
+
# diagram's t_diagram.ShowForeign flag is set AND the
|
|
222
|
+
# classifier lives in a different package than the diagram
|
|
223
|
+
# (descendant packages included). Corpus-verified (450
|
|
224
|
+
# diagrams): ShowForeign=0 renders plain everywhere.
|
|
225
|
+
def foreign_package_name_for(classifier)
|
|
226
|
+
return nil unless diagram.show_foreign
|
|
227
|
+
return nil unless classifier.is_a?(Ea::Model::Classifier)
|
|
228
|
+
|
|
229
|
+
pkg_id = classifier.package_id
|
|
230
|
+
return nil if pkg_id.nil? || pkg_id == diagram.package_id
|
|
231
|
+
|
|
232
|
+
pkg = model_index ? model_index[pkg_id] : nil
|
|
233
|
+
return nil unless pkg.is_a?(Ea::Model::Package)
|
|
234
|
+
|
|
235
|
+
name = pkg.name.to_s
|
|
236
|
+
name.empty? ? nil : name
|
|
237
|
+
end
|
|
238
|
+
|
|
217
239
|
# EA renders the off-canvas parent classifier's name as an
|
|
218
240
|
# italic line at the top of a placed element's header when
|
|
219
241
|
# ALL of these hold:
|
|
@@ -61,7 +61,7 @@ module Ea
|
|
|
61
61
|
text_pos, mult_pos = tree_positions(end_kind, anchor, role,
|
|
62
62
|
text_pos, mult_pos)
|
|
63
63
|
end
|
|
64
|
-
show_property = property_label?(connector)
|
|
64
|
+
show_property = property_label?(connector, end_kind)
|
|
65
65
|
|
|
66
66
|
build_texts(role:, mult:, standalone:,
|
|
67
67
|
text_pos:, mult_pos:, show_property:)
|
|
@@ -113,27 +113,18 @@ module Ea
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
# EA renders «
|
|
117
|
-
#
|
|
118
|
-
# t_connector.
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
# 137 diagrams).
|
|
129
|
-
UNDIRECTED_DIRECTIONS = %w[Unspecified Bi-Directional].freeze
|
|
130
|
-
|
|
131
|
-
def property_label?(connector)
|
|
132
|
-
direction = connector.direction.to_s
|
|
133
|
-
return false if direction.empty?
|
|
134
|
-
return false if UNDIRECTED_DIRECTIONS.include?(direction)
|
|
135
|
-
|
|
136
|
-
true
|
|
116
|
+
# EA renders «Property» between role name and multiplicity
|
|
117
|
+
# exactly on connector ends carrying
|
|
118
|
+
# t_connector.SourceStereotype / DestStereotype = "Property"
|
|
119
|
+
# (case-insensitive; stored variants include "property").
|
|
120
|
+
# Direction does not decide — Fig 11's directed aggregations
|
|
121
|
+
# carry no end stereotype and render none, while the CityGML
|
|
122
|
+
# profile diagrams mark ends explicitly.
|
|
123
|
+
PROPERTY_KEYWORD = "property"
|
|
124
|
+
|
|
125
|
+
def property_label?(connector, end_kind)
|
|
126
|
+
stereo = end_kind == :source ? connector.source_stereotype : connector.target_stereotype
|
|
127
|
+
stereo.to_s.downcase == PROPERTY_KEYWORD
|
|
137
128
|
end
|
|
138
129
|
|
|
139
130
|
def role_empty?(role)
|
|
@@ -47,6 +47,10 @@ module Ea
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def texts_for(connector)
|
|
50
|
+
# EA never labels NoteLink connectors — the note text lives
|
|
51
|
+
# in t_connector.Name but renders only inside the Note box.
|
|
52
|
+
return [] if connector.connector_type.to_s.casecmp("NoteLink").zero?
|
|
53
|
+
|
|
50
54
|
points = waypoint_pairs(connector)
|
|
51
55
|
return [] if points.size < 2
|
|
52
56
|
|
data/lib/ea/theme/definition.rb
CHANGED
|
@@ -62,7 +62,7 @@ module Ea
|
|
|
62
62
|
tab_label_padding: 10,
|
|
63
63
|
default_tab_width: 105).freeze
|
|
64
64
|
DEFAULT_NOTE = NoteSpec.new(fold_size: 12, text_x_offset: 5,
|
|
65
|
-
text_y_offset: 12, line_height:
|
|
65
|
+
text_y_offset: 12, line_height: 13).freeze
|
|
66
66
|
|
|
67
67
|
def initialize(id:, name: nil, font_family: nil, font_size: nil,
|
|
68
68
|
font_size_unit: "pt", text_weight_normal: 400,
|
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.18
|
|
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-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|