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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 673f03cddf9ae4a7cb297beaa9b9fab0b3e272e26771d976c90c521726465af5
4
- data.tar.gz: 1147cce73671336e59d68dc04caeee0cb66276ce946e5af3a8b07d378f21ac7b
3
+ metadata.gz: c4e23583eed4c1a582614c26b2c77ada31ab41af83b00b474fcdc199d807b599
4
+ data.tar.gz: 7c02275d511da08c73e261f53815f8d55092420a7bff501151c5b713162068d6
5
5
  SHA512:
6
- metadata.gz: 44fcd7c27d72d01fef2fdf70498bee178904e54af09484506ff61ff2372281cbc03b5c7d23d4750f4ad7f1c2a474ad3fb3c79cfbbcc61e520f151d11a670a2be
7
- data.tar.gz: 5a5ba8607a019d59be72fa1416cfbd66056e751420318bc02042a0ba02bd82dcc66452a97d25c26b2a0f408f132a2891a37db3e19f14f1c4ad98dfe1f4319c19
6
+ metadata.gz: 378bb61902c1103887fcc9807e675fd2ebfc2dfe50c7b5266c20f2570b02a42ed0c8b120fc81ca626a6b4d9a421f45d2fd151c21b189714282a50f668ce07b79
7
+ data.tar.gz: 3e4edfe09c4c9235c39e47b6cd202f64bed7904d2b99567cabbff418ecbb7298a1e38b7a0c805fe2e12d760b7e2c11274cf67ee36811925dad804c9c24e61670
@@ -34,7 +34,7 @@ colors:
34
34
 
35
35
  # Stroke widths.
36
36
  strokes:
37
- element_border: 2
37
+ element_border: 1
38
38
  connector_line: 2
39
39
  divider: 2
40
40
  marker: 2
@@ -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; EA strokes it at the 2px box outline.
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
- # Explicit zero-width borders keep that end on the geometric edge;
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
- return box if border_width?(diagram, ref, duid, 0)
250
-
251
- Ea::Model::Bounds.new(x: box.x - 1, y: box.y - 1,
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)
@@ -23,6 +23,7 @@ module Ea
23
23
  :off_canvas_parent_name,
24
24
  :foreign_package_name,
25
25
  :suppress_stereotypes,
26
+ :stereotype_fqname,
26
27
  keyword_init: true)
27
28
 
28
29
  PROVIDERS = [
@@ -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 renders the stereotype string exactly as stored in
59
- # t_object.Stereotype — corpus-verified: 3,284 placements
60
- # render verbatim ('CodeList', 'codeList', 'codelist',
61
- # 'featureType' …), zero case-canonicalized.
62
- def self.canonical(ref)
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
- legend.heading_size || Ea::Model::Legend::DEFAULT_HEADING_SIZE
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. locale fallback (Calibri 10)
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
- # EA's app-default font depends on the system locale:
87
- # Japanese Windows → "Yu Gothic UI" 13, English Windows →
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 = "Calibri"
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
- style[:stored_association_ray] == true && style.values_at(:mode, :lwidth, :tree) == ["3", "2", nil]
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
- font_family.to_s.casecmp("Cambria").zero? &&
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
@@ -4,5 +4,5 @@ module Ea
4
4
  # Gem release version. Extracted to its own file so the
5
5
  # `gem-release` tool's `gem bump` can find and update it via the
6
6
  # standard `lib/<gem>/version.rb` convention.
7
- VERSION = "0.6.113"
7
+ VERSION = "0.6.115"
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ea
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.113
4
+ version: 0.6.115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.