ea 0.6.108 → 0.6.110

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: e5457390e5e190fab6345e9bb50a0bb773b1223e7e0617de9b8121efb6eb0cf7
4
- data.tar.gz: b56bf3ce3bb1b5b8f1339f509b1ba99080f72eb480c0d85db5826c7251f91caa
3
+ metadata.gz: 55380e3923a2afe6babb0b3163079cc273e7f054cc8d1549db7985ace1f45757
4
+ data.tar.gz: dd92a38559eb5c1688771391995247055d633530fdcb4712a62aa087a31bfbf4
5
5
  SHA512:
6
- metadata.gz: 769abf22c7038cab232e7ca4965dd44d6e11c1940d1de7cbc9f5821478109d5113d430805af6278caa34fa827167f92834400631992305f402ea38483d2e98d9
7
- data.tar.gz: 19a7d924227c5783ac2e348ab6b682943746112bc3809fd32d2c1edef3c92fabdf2e74ac1de1f910c996b355825ab083cb25d20d7910ac68851801d4d5c5b401
6
+ metadata.gz: 9c98de5218163ad487eb0453bcf8fca9ea844a8cecbe3d36856ce2a44ab5728a691792d9a112c9efdf4f1e4084360a655052494af40764e339f0086692c8f6d8
7
+ data.tar.gz: 83fe0979cd7213d32a2615efd5d8e4f54f22c4c94761f5ec8bc15da01adf5070878fe937475a7424a8e8afc144cff1b0bc3620137bd491584d59cbb4733a75cf
@@ -101,6 +101,11 @@ module Ea
101
101
  style_flags["OpParams"] != "0"
102
102
  end
103
103
 
104
+ # Type-only parameter detail is stored as OpParams=1.
105
+ def operation_parameter_detail
106
+ :type_only if style_flags["OpParams"] == "1"
107
+ end
108
+
104
109
  # Element Alias instead of Name in the header. Defaults to false
105
110
  # — most diagrams show the Name.
106
111
  def use_alias?
@@ -6,13 +6,11 @@ module Ea
6
6
  module Compartment
7
7
  # Enumeration literal compartment: divider line + "literals"
8
8
  # italic header + each literal name. Skipped when the
9
- # classifier has no literals or the geometry cannot fit them.
9
+ # classifier has no literals or no content geometry.
10
10
  module EnumLiterals
11
11
  module_function
12
12
 
13
- # EA: literal rows sit directly under the header divider,
14
- # 13px apart at 7pt (attr rows use 14), first row at
15
- # divider + 14. No compartment divider, no header label.
13
+ # Caption and literal rows share the same baseline spacing.
16
14
  LITERAL_LINE_OFFSET = 6
17
15
 
18
16
  def render(context)
@@ -29,16 +27,8 @@ module Ea
29
27
  )
30
28
  text_blocks = []
31
29
  row_y = context.geometry.enum_literal_first_y
32
- # EA renders a centered italic "literals" caption above
33
- # the rows when the literals carry stored code values
34
- # (t_attribute.Default); bare literal lists render the
35
- # rows directly. Corpus-verified (PT_TypeOfSA:
36
- # "literals" caption + "ServingParcel = SP" rows;
37
- # CV_SequenceType: bare rows, no caption).
38
- if caption?(context)
39
- text_blocks << caption_text(context, row_y)
40
- row_y += line_h
41
- end
30
+ text_blocks << caption_text(context, row_y)
31
+ row_y += line_h
42
32
  context.enum_literals.each_with_index do |literal, idx|
43
33
  y = row_y + (idx * line_h)
44
34
  text_blocks << visibility_placeholder(context, y)
@@ -47,28 +37,6 @@ module Ea
47
37
  wrap_group(text_blocks, context.theme.text_color)
48
38
  end
49
39
 
50
- def caption?(context)
51
- values_caption?(context) || grown?(context)
52
- end
53
-
54
- # EA draws the 'literals' caption on every enum box it
55
- # re-lays (grown height) - corpus 8/8 grown=caption. Boxes
56
- # keeping stored size keep their stored caption state,
57
- # captured by the stored literal values.
58
- def grown?(context)
59
- stored = context.element&.bounds
60
- return false unless stored
61
-
62
- context.bounds.height > stored.height + 4
63
- end
64
-
65
- def values_caption?(context)
66
- context.enum_literals.any? do |l|
67
- value = l.value.to_s
68
- !value.empty? && value != l.name.to_s
69
- end
70
- end
71
-
72
40
  def caption_text(context, y)
73
41
  content = "literals"
74
42
  len = TextRenderer.estimate_width(content, context.size, nil,
@@ -101,9 +69,16 @@ module Ea
101
69
 
102
70
  def literal_name_text(context, literal, y)
103
71
  content = literal_display(literal)
72
+ # EA uses the attribute row point-size increment for literal names.
73
+ offset = context.theme.attribute_spec.content_x_offset || 26
74
+ offset += 2 * (context.size - 7) if context.size_unit == "pt"
75
+ if Ea::Fonts::Metrics.compact_compartment_font?(context.size,
76
+ family: context.family, unit: context.size_unit)
77
+ offset -= 1
78
+ end
104
79
  TextRenderer.new(
105
80
  content: content,
106
- x: context.bounds.x + (context.theme.attribute_spec.content_x_offset || 26),
81
+ x: context.bounds.x + offset,
107
82
  y: y,
108
83
  family: context.family, size: context.size,
109
84
  size_unit: context.size_unit, logical_height: context.logical_height,
@@ -28,7 +28,8 @@ module Ea
28
28
  bounds: context.bounds,
29
29
  first_y: context.geometry.op_first_y,
30
30
  family: context.family, size: context.size,
31
- size_unit: context.size_unit, logical_height: context.logical_height
31
+ size_unit: context.size_unit, logical_height: context.logical_height,
32
+ parameter_detail: context.diagram&.display_config&.operation_parameter_detail
32
33
  )
33
34
  parts.join("\n")
34
35
  end
@@ -14,8 +14,7 @@ module Ea
14
14
  Element::LegendRenderer.render(
15
15
  context.bounds,
16
16
  legend: context.model_element.legend,
17
- family: context.family,
18
- canvas: context.canvas
17
+ family: context.family
19
18
  )
20
19
  elsif context.model_element.is_a?(Ea::Model::Package) ||
21
20
  context.model_element.is_a?(Ea::Model::Note)
@@ -72,8 +72,19 @@ module Ea
72
72
  end
73
73
  private :line_height
74
74
 
75
+ # A source font can export at the same point size with a smaller
76
+ # logical cell. Seat its first baseline using the same height delta
77
+ # as its row pitch, without changing compact-font spacing.
78
+ def source_baseline_adjustment
79
+ nominal_height = Ea::Fonts::Metrics.compartment_line_height(
80
+ size, family: @family, unit: @size_unit, offset: 0
81
+ )
82
+ line_height(0) - nominal_height
83
+ end
84
+ private :source_baseline_adjustment
85
+
75
86
  def header_first_y
76
- bounds.y + size + (header_top_padding || 12)
87
+ bounds.y + size + (header_top_padding || 12) + source_baseline_adjustment
77
88
  end
78
89
 
79
90
  # Y of the divider line between header and the rest.
@@ -113,7 +124,7 @@ module Ea
113
124
  end
114
125
 
115
126
  def raw_attr_first_y
116
- return bounds.y + size + (header_top_padding || 12) + 12 unless divider_y
127
+ return header_first_y + 12 unless divider_y
117
128
 
118
129
  divider_y + line_height((attr_first_offset || 7) - 1) + 1
119
130
  end
@@ -152,9 +163,8 @@ module Ea
152
163
  end
153
164
 
154
165
  # EA draws a single divider under the header and seats
155
- # enumeration literals directly after it (+14 at 7pt),
156
- # exactly like attribute rows - no second divider and no
157
- # "literals" header. Verified against EA-published SVGs.
166
+ # the enumeration caption directly after it (+14 at 7pt),
167
+ # followed by the literal rows without a second divider.
158
168
  def enum_literal_first_y
159
169
  return nil unless divider_y
160
170
 
@@ -11,12 +11,9 @@ module Ea
11
11
  # - Foreign elements (owning package ≠ the diagram's package,
12
12
  # gated on t_diagram.ShowForeign) render the qualified
13
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
+ # - Qualified names wrap after their colon separator when
15
+ # the measured name reaches the box width minus 6px.
16
+ # Each resulting line retains its separator and is centered.
20
17
  #
21
18
  # InstanceSpecification returns [] (InstanceSpec provider
22
19
  # already emitted the full label).
@@ -42,9 +39,8 @@ module Ea
42
39
  return [[name, weight]]
43
40
  end
44
41
 
45
- if name.include?("::")
46
- qualifier, base = name.split("::", 2)
47
- [["#{qualifier}::", weight], [base, weight]]
42
+ if (qualified = name.match(/\A(.+?:{1,2})([^:\s].*)\z/))
43
+ [[qualified[1], weight], [qualified[2], weight]]
48
44
  else
49
45
  HeaderLinePipeline.wrap_words(name, context, weight).map do |line|
50
46
  [line, weight]
@@ -59,11 +55,10 @@ module Ea
59
55
  family: context.family, weight: "700", style: font_style,
60
56
  logical_height: context.logical_height
61
57
  ).round
62
- # Same usable width as wrap_words: box width - 6.
63
- # EA wraps "Content information::MD_FeatureCatalogue-
64
- # Description" (parts 86 + 137 + space 4 = 227) inside
65
- # a 230px box (F851A657).
66
- len > context.bounds_width.to_i - 6
58
+ # Qualified classifier names also wrap at the exact boundary;
59
+ # ordinary word wrapping keeps the shared pipeline threshold.
60
+ usable = context.bounds_width.to_i - 6
61
+ name.match?(/:{1,2}[^:\s]/) ? len >= usable : len > usable
67
62
  end
68
63
 
69
64
  # EA renders the plain classifier name in element headers;
@@ -9,7 +9,7 @@ module Ea
9
9
  # (verified against plateau reference SVGs):
10
10
  #
11
11
  # ┌──────────────────────────────┐
12
- # │ 凡例 │ ← title baseline 19px below top
12
+ # │ 凡例 │ ← title baseline 20px below top
13
13
  # │ ┌──┐ │
14
14
  # │ └──┘ GMLに定義されたクラス │ ← item 0 (icon top + 30px)
15
15
  # │ ┌──┐ │
@@ -24,8 +24,8 @@ module Ea
24
24
  class LegendRenderer
25
25
  CONTAINER_RX = 3
26
26
  ICON_SIZE = 17
27
- TITLE_Y_OFFSET = 22
28
- FIRST_ITEM_Y_OFFSET = 33
27
+ TITLE_Y_OFFSET = 20
28
+ FIRST_ITEM_Y_OFFSET = 30
29
29
  ITEM_SPACING = 19
30
30
  ICON_X_OFFSET = 10
31
31
  ICON_LABEL_GAP = 22
@@ -16,7 +16,8 @@ module Ea
16
16
  VISIBILITY_X_OFFSET = 5
17
17
  CONTENT_X_OFFSET = 22
18
18
 
19
- def self.render(operations, bounds:, first_y:, family:, size:, size_unit: "px", logical_height: nil)
19
+ def self.render(operations, bounds:, first_y:, family:, size:, size_unit: "px",
20
+ logical_height: nil, parameter_detail: nil)
20
21
  # EA's op rows pitch 13 (size + 6), same as attribute
21
22
  # rows (6D4C665D: ops 541..658 step 13).
22
23
  line_h = Ea::Fonts::Metrics.compartment_line_height(
@@ -39,14 +40,15 @@ module Ea
39
40
  y = first_y + ((idx + offset) * line_h)
40
41
  text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
41
42
  visibility_prefix(op), family, size, size_unit, logical_height: logical_height)
43
+ text = operation_text(op, parameter_detail: parameter_detail)
42
44
  text_blocks << build_text(bounds.x + AttributeRenderer.content_x_offset(size), y,
43
- operation_text(op), family, size, size_unit, logical_height: logical_height)
45
+ text, family, size, size_unit, logical_height: logical_height)
44
46
  end
45
47
  %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
46
48
  end
47
49
 
48
50
  # Builds display lines for a classifier's operations.
49
- def self.lines_for(classifier)
51
+ def self.lines_for(classifier, parameter_detail: nil)
50
52
  ops = classifier.operations
51
53
  return [] unless ops&.any?
52
54
 
@@ -54,7 +56,7 @@ module Ea
54
56
  visibility = visibility_prefix(op).strip
55
57
  params = (op.parameters || []).map do |p|
56
58
  type = namespace_double_colon(p.type_name)
57
- p.direction == "inout" ? "#{type}*" : "#{p.name}: #{type}"
59
+ parameter_text(p, type, parameter_detail)
58
60
  end.join(", ")
59
61
  return_type = namespace_double_colon(op.return_type_name)
60
62
  "#{visibility} #{op.name}(#{params}): #{return_type}".strip
@@ -66,9 +68,9 @@ module Ea
66
68
  end
67
69
  private_class_method :visibility_prefix
68
70
 
69
- def self.operation_text(operation)
71
+ def self.operation_text(operation, parameter_detail: nil)
70
72
  prefix = operation.is_reception ? "«signal» " : ""
71
- text = "#{prefix}#{operation.name}(#{reception_params(operation)})"
73
+ text = "#{prefix}#{operation.name}(#{reception_params(operation, parameter_detail)})"
72
74
  return text unless operation.return_type_name && !operation.is_reception
73
75
 
74
76
  "#{text}: #{operation.return_type_name}"
@@ -82,11 +84,19 @@ module Ea
82
84
  end
83
85
  private_class_method :namespace_double_colon
84
86
 
85
- def self.reception_params(operation)
87
+ def self.parameter_text(param, type, parameter_detail)
88
+ return "#{type}*" if param.direction == "inout"
89
+ return type if parameter_detail == :type_only
90
+
91
+ "#{param.name}: #{type}"
92
+ end
93
+ private_class_method :parameter_text
94
+
95
+ def self.reception_params(operation, parameter_detail)
86
96
  params = (operation.parameters || []).filter_map { |p|
87
97
  next nil if p.type_name.to_s.empty?
88
98
 
89
- p.direction == "inout" ? "#{p.type_name}*" : "#{p.name}: #{p.type_name}"
99
+ parameter_text(p, p.type_name, parameter_detail)
90
100
  }.join(", ")
91
101
  params
92
102
  end
@@ -144,7 +144,10 @@ module Ea
144
144
  end
145
145
  op_lines = if is_classifier && show_operations? &&
146
146
  (operations_visible?(element) || regenerated_context?)
147
- Element::OperationRenderer.lines_for(classifier)
147
+ Element::OperationRenderer.lines_for(
148
+ classifier,
149
+ parameter_detail: display_config.operation_parameter_detail
150
+ )
148
151
  else
149
152
  []
150
153
  end
@@ -440,12 +443,10 @@ module Ea
440
443
  classifier.literals || []
441
444
  end
442
445
 
443
- # The "literals" caption occupies a row slot when any
444
- # literal carries a stored code value.
446
+ # A populated literal compartment includes its caption row.
445
447
  def enum_row_count_for(classifier)
446
448
  literals = enum_literals_for(classifier)
447
- caption = literals.any? { |l| !l.value.to_s.empty? && l.value.to_s != l.name.to_s }
448
- literals.size + (caption ? 1 : 0)
449
+ literals.empty? ? 0 : literals.size + 1
449
450
  end
450
451
 
451
452
  def tagged_values_for(classifier)
@@ -99,9 +99,10 @@ module Ea
99
99
  horizontal_tree_multiplicity(connector, direction, anchor, mult_box, mult) ||
100
100
  mult_seat(direction, anchor, mult_box) || mult_pos
101
101
  elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
102
- text_pos = target_vertical_role(connector, heading, anchor, text_box, role) ||
102
+ text_pos = target_property_role(connector, heading, anchor, text_box, role) ||
103
+ target_vertical_role(connector, heading, anchor, text_box, role) ||
103
104
  target_rightward_role(heading, anchor, text_box, role) || text_pos
104
- mult_pos = target_multiplicity_seat(heading, anchor, mult_box) ||
105
+ mult_pos = target_multiplicity_seat(connector, heading, anchor, mult_box) ||
105
106
  target_horizontal_multiplicity(heading, anchor, mult_box, mult) || mult_pos
106
107
  end
107
108
 
@@ -213,6 +214,25 @@ module Ea
213
214
  [x - SEAT_X_INSET - width, y - 6]
214
215
  end
215
216
 
217
+ # An upward target uses the downward property's right edge and
218
+ # the upward target baseline, both relative to its stored corner.
219
+ def target_property_role(connector, heading, anchor, box, role)
220
+ return nil unless upward_target_property?(connector, heading)
221
+ return nil unless plain_vertical_box?(box) && box["alignment"] == 1
222
+
223
+ width = Ea::Fonts::Metrics.text_length(role, font_size, family: font_family)
224
+ return nil unless width
225
+
226
+ x, y = seat_corner(anchor, box)
227
+ inset, = property_role_offsets(:down)
228
+ [x - inset - width, y - 7]
229
+ end
230
+
231
+ def upward_target_property?(connector, heading)
232
+ heading && heading[0].zero? && heading[1].negative? &&
233
+ property_label?(connector, :target) && downward_property_route?(connector.style.to_h)
234
+ end
235
+
216
236
  def target_vertical_role(connector, heading, anchor, box, role)
217
237
  return nil unless heading && heading[0].zero? && !heading[1].zero?
218
238
 
@@ -350,12 +370,19 @@ module Ea
350
370
  mult_seat(:right, anchor, box)
351
371
  end
352
372
 
353
- def target_multiplicity_seat(heading, anchor, box)
373
+ def target_multiplicity_seat(connector, heading, anchor, box)
354
374
  return nil unless seat_font?
355
375
  return nil unless heading && heading[0].zero? && heading[1].negative?
356
- return nil unless cambria_box?(box) && stored_offsets_zero?(box)
376
+ return nil unless target_multiplicity_box?(connector, box)
377
+
378
+ x, y = seat_corner(anchor, box)
379
+ [x + 8, y - 7]
380
+ end
357
381
 
358
- [anchor[0] + 8, anchor[1] - 7]
382
+ def target_multiplicity_box?(connector, box)
383
+ # Property targets seat at the stored corner; plain moved labels
384
+ # retain their explicit offsets.
385
+ cambria_box?(box) && (stored_offsets_zero?(box) || property_label?(connector, :target))
359
386
  end
360
387
 
361
388
  def cambria_box?(box)
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.108"
7
+ VERSION = "0.6.110"
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.108
4
+ version: 0.6.110
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.