ea 0.6.116 → 0.6.118

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: 21ccebe3cc90560d4079f23a067cb41d94cbdeb87bc07873406edc97dd414c71
4
- data.tar.gz: f9c39d517836ec0c8a6f737761e9f7fbc47ca49f4cfe2256aeccccfa38df4659
3
+ metadata.gz: e42aa88829a389d252b8d4e186b8e68cde1b91e75d378b7e1174fddaa52956c5
4
+ data.tar.gz: fa575e58c5e079d1dabae96073f3a4927f82073c277027b574a77f51bea59454
5
5
  SHA512:
6
- metadata.gz: 60a5c35a8607a8169f6e7be4866c07972b37e75f790851e36191ea4ec93a9ac8c793589fedccdbcacef456d44d96dd791687a6deb54ac7943d7d547060685072
7
- data.tar.gz: b6cef3d3516f3c290ec2c1a05f138213bd3a7f2b18f0f063113f22865c89835804977c733b22f60819d7004c998dd9b98b7b244da8c70505bfb0873d1f184c7b
6
+ metadata.gz: cdd8a961460f9c1e2cc3e51c34778d3fbaa33337c7af0460d499003147e14f827d796317a4ac3005076fdc219b3e1e6bfcef4a53f622f1f4f985dbb1eab65222
7
+ data.tar.gz: 72596c91f90ceba1757587bbbc3e29ec1d938e0c8c0a9ed2421f3f3ccc2a66ec162faadcc5cffc54aa670a04d2dd4f9e5769775aa9fab7655e75a2ea6bb0f9f1
@@ -73,7 +73,7 @@ module Ea
73
73
  x: bounds.x + (context.theme.attribute_spec.content_x_offset || 26),
74
74
  y: y_pos,
75
75
  family: context.family, size: context.size,
76
- size_unit: context.size_unit, fill: fill
76
+ size_unit: context.size_unit, logical_height: context.logical_height, fill: fill
77
77
  ).to_svg
78
78
  end
79
79
  module_function :line_text
@@ -86,7 +86,7 @@ module Ea
86
86
  x: x_pos,
87
87
  y: y_pos,
88
88
  family: context.family, size: context.size,
89
- size_unit: context.size_unit, fill: fill,
89
+ size_unit: context.size_unit, logical_height: context.logical_height, fill: fill,
90
90
  style: "italic"
91
91
  ).to_svg
92
92
  end
@@ -14,7 +14,9 @@ module Ea
14
14
  Element::LegendRenderer.render(
15
15
  context.bounds,
16
16
  legend: context.model_element.legend,
17
- family: context.family
17
+ family: context.family,
18
+ item_font: { size: context.element&.font_size&.nonzero?, unit: context.size_unit,
19
+ logical_height: context.logical_height }
18
20
  )
19
21
  elsif context.model_element.is_a?(Ea::Model::Note)
20
22
  Element::NoteShapeRenderer.render(
@@ -20,6 +20,7 @@ module Ea
20
20
  bounds: context.bounds,
21
21
  first_y: context.geometry.tagged_value_first_y,
22
22
  family: context.family, size: context.size,
23
+ size_unit: context.size_unit, logical_height: context.logical_height,
23
24
  fill: DEFAULT_TEXT_COLOR
24
25
  )
25
26
  end
@@ -12,10 +12,10 @@ module Ea
12
12
 
13
13
  attr_reader :diagram, :model_index, :frame, :document
14
14
 
15
- def initialize(diagram, model_index:, frame: true, document: nil, **_options)
15
+ def initialize(diagram, model_index:, frame: nil, document: nil, **_options)
16
16
  @diagram = diagram
17
17
  @model_index = model_index
18
- @frame = frame
18
+ @frame = frame.nil? ? !empty_diagram?(diagram) : frame
19
19
  @document = document
20
20
  end
21
21
 
@@ -86,6 +86,11 @@ module Ea
86
86
  %(<g>#{svg}</g>)
87
87
  end
88
88
  private :render_image
89
+
90
+ def empty_diagram?(source_diagram)
91
+ source_diagram.elements.empty? && source_diagram.connectors.empty?
92
+ end
93
+ private :empty_diagram?
89
94
  end
90
95
  end
91
96
  end
@@ -196,7 +196,7 @@ module Ea
196
196
  def tagged_value_first_y
197
197
  return nil unless tagged_values_count.to_i.positive?
198
198
 
199
- (content_bottom_y || raw_attr_first_y) + size + 5
199
+ content_bottom_y ? content_bottom_y + size + 5 : raw_attr_first_y
200
200
  end
201
201
 
202
202
  # The lowest content edge among attributes, enum literals,
@@ -15,6 +15,15 @@ module Ea
15
15
  HeaderLinePipeline.for(classifier, **opts)
16
16
  end
17
17
 
18
+ # A parent followed directly by the name has a 3px gap beyond
19
+ # the row pitch. A class stereotype needs 6px; interfaces use 3px.
20
+ def parent_ghost_gap(lines)
21
+ style = lines.dig(0, 1)
22
+ return 0 unless %i[italic italic_tight].include?(style)
23
+
24
+ style == :italic_tight || %i[bold bold_italic].include?(lines.dig(1, 1)) ? 3 : 6
25
+ end
26
+
18
27
  # Delegate to the Name provider so historical callers and
19
28
  # specs that test display_name keep working.
20
29
  def display_name(classifier, *_)
@@ -20,20 +20,13 @@ module Ea
20
20
  line_h = Ea::Fonts::Metrics.compartment_line_height(
21
21
  size, family: family, unit: size_unit, offset: line_offset, logical_height: logical_height
22
22
  )
23
+ ghost_shift = HeaderLines.parent_ghost_gap(lines)
23
24
  text_blocks = lines.each_with_index.map do |(text, style), idx|
24
25
  weight = case style
25
26
  when :bold, :bold_italic then weight_bold
26
27
  else weight_normal
27
28
  end
28
29
  font_style = %i[italic italic_tight bold_italic].include?(style) ? "italic" : "normal"
29
- # EA spaces the line AFTER a right-aligned ghost 3px
30
- # wider than the pitch. Interfaces use a tighter +3
31
- # (936AA434/Surface: ghost 100, «interface» 116).
32
- ghost_shift = case lines.first.last
33
- when :italic then 6
34
- when :italic_tight then 3
35
- else 0
36
- end
37
30
  y = first_y + (idx * line_h) +
38
31
  (idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
39
32
  x = if %i[italic italic_tight].include?(style)
@@ -32,15 +32,16 @@ module Ea
32
32
  LABEL_BASELINE_FROM_ICON_TOP = 12
33
33
  DEFAULT_ITEM_FONT_SIZE = 9
34
34
 
35
- def self.render(bounds, legend:, family:, canvas: nil)
36
- new(bounds, legend, family, canvas).to_svg
35
+ def self.render(bounds, legend:, family:, canvas: nil, item_font: {})
36
+ new(bounds, legend, family, canvas, item_font).to_svg
37
37
  end
38
38
 
39
- def initialize(bounds, legend, family, canvas)
39
+ def initialize(bounds, legend, family, canvas, item_font = {})
40
40
  @bounds = bounds
41
41
  @legend = legend
42
42
  @family = family
43
43
  @canvas = canvas
44
+ @item_font = item_font[:size]&.nonzero? ? item_font : {}
44
45
  end
45
46
 
46
47
  def to_svg
@@ -49,7 +50,7 @@ module Ea
49
50
 
50
51
  private
51
52
 
52
- attr_reader :bounds, :legend, :family, :canvas
53
+ attr_reader :bounds, :legend, :family, :canvas, :item_font
53
54
 
54
55
  def container_layer
55
56
  body = format(
@@ -151,8 +152,9 @@ module Ea
151
152
 
152
153
  # Real font metrics when available; legacy per-char
153
154
  # estimate otherwise (families without metric tables).
154
- def estimated_width(text, size, weight: nil)
155
- measured = Ea::Fonts::Metrics.text_length(text, size, family: family, weight: weight)
155
+ def estimated_width(text, size, weight: nil, logical_height: nil)
156
+ measured = Ea::Fonts::Metrics.text_length(text, size, family: family, weight: weight,
157
+ logical_height: logical_height)
156
158
  return measured if measured
157
159
 
158
160
  text.to_s.each_char.sum do |ch|
@@ -161,7 +163,10 @@ module Ea
161
163
  end
162
164
 
163
165
  def widest_item_label_width
164
- legend.items.map { |i| estimated_width(i.name, item_font_size) }.max || 0.0
166
+ logical_height = item_font[:logical_height] if (item_font[:unit] || "pt") == "pt"
167
+ legend.items.map do |item|
168
+ estimated_width(item.name, item_font_size, logical_height: logical_height)
169
+ end.max || 0.0
165
170
  end
166
171
 
167
172
  def item_top_y(index)
@@ -177,7 +182,7 @@ module Ea
177
182
  end
178
183
 
179
184
  def item_font_size
180
- DEFAULT_ITEM_FONT_SIZE
185
+ item_font[:size] || DEFAULT_ITEM_FONT_SIZE
181
186
  end
182
187
 
183
188
  def title_text_width
@@ -186,10 +191,11 @@ module Ea
186
191
 
187
192
  def build_text(content, x:, y:, size:, weight:)
188
193
  tx, ty = translate(x, y)
194
+ size_unit = weight.zero? ? (item_font[:unit] || "pt") : "pt"
189
195
  TextRenderer.new(
190
196
  content: content.to_s, x: tx, y: ty,
191
- family: family, size: size, size_unit: "pt",
192
- weight: weight, fill: label_fill
197
+ family: family, size: size, size_unit: size_unit,
198
+ weight: weight, fill: label_fill, logical_height: weight.zero? ? item_font[:logical_height] : nil
193
199
  ).to_svg
194
200
  end
195
201
 
@@ -185,25 +185,33 @@ module Ea
185
185
  end
186
186
 
187
187
  def body_width
188
- [bounds.width, self.class.autosized_tab_width(label: label, stereotype: stereotype, size: size) + TAB_BODY_EXTRA].max
188
+ [bounds.width, autosized_tab_width + TAB_BODY_EXTRA].max
189
189
  end
190
190
 
191
191
  def tab_width
192
- [body_width,
193
- self.class.autosized_tab_width(label: label, stereotype: stereotype, size: size)].min
192
+ [body_width, autosized_tab_width].min
193
+ end
194
+
195
+ def autosized_tab_width
196
+ self.class.autosized_tab_width(
197
+ label: label, stereotype: stereotype, size: size, family: family
198
+ )
194
199
  end
195
200
 
196
201
  # Tab width fits the widest text line (stereotype or name)
197
202
  # plus padding, floored at EA's minimum tab width. The body
198
203
  # extends 15px past the tab on the right.
199
- def self.autosized_tab_width(label:, stereotype:, size:)
200
- lines = [label, stereotype && !stereotype.empty? ? "«#{stereotype}»" : nil].compact
201
- widest = lines.map { |l| TextRenderer.estimate_width(l, size) }.max || 0
204
+ def self.autosized_tab_width(label:, stereotype:, size:, family: nil)
205
+ lines = [[label, 700]]
206
+ lines << ["«#{stereotype}»", 0] if stereotype && !stereotype.empty?
207
+ widest = lines.map do |text, weight|
208
+ TextRenderer.estimate_width(text, size, family: family, weight: weight)
209
+ end.max || 0
202
210
  [widest + TAB_NAME_EXTRA, DEFAULT_TAB_WIDTH].max.round
203
211
  end
204
212
 
205
- def self.body_width_for(label:, stereotype:, size:)
206
- autosized_tab_width(label: label, stereotype: stereotype, size: size) + TAB_BODY_EXTRA
213
+ def self.body_width_for(label:, stereotype:, size:, family: nil)
214
+ autosized_tab_width(label: label, stereotype: stereotype, size: size, family: family) + TAB_BODY_EXTRA
207
215
  end
208
216
 
209
217
  end
@@ -16,26 +16,37 @@ module Ea
16
16
  HEADER_X_OFFSET = 5
17
17
  LINE_X_OFFSET = 5
18
18
 
19
- def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000")
20
- line_h = size + 4
21
- text_blocks = []
22
- text_blocks << build_text(bounds.x + centered_x_offset(bounds, size), first_y,
23
- HEADER_TEXT, family, size, fill, style: "italic")
19
+ def self.render(tagged_values, bounds:, first_y:, family:, size:, fill: "#000000",
20
+ size_unit: "px", logical_height: nil)
21
+ line_h = line_height(size, family, size_unit, logical_height)
22
+ text_blocks = [build_text(bounds.x + centered_x_offset(bounds, size), first_y,
23
+ HEADER_TEXT, family, size, fill,
24
+ style: "italic", size_unit: size_unit, logical_height: logical_height)]
24
25
  tagged_values.each_with_index do |tv, idx|
25
- y = first_y + ((idx + 1) * line_h)
26
- text_blocks << build_text(bounds.x + LINE_X_OFFSET, y,
27
- "#{tv.key} = #{tv.value}", family, size, fill)
26
+ text_blocks << build_text(bounds.x + LINE_X_OFFSET, first_y + ((idx + 1) * line_h),
27
+ "#{tv.key} = #{tv.value}", family, size, fill,
28
+ size_unit: size_unit, logical_height: logical_height)
28
29
  end
29
30
  %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
30
31
  end
31
32
 
32
- def self.build_text(x, y, content, family, size, fill, style: "normal")
33
+ def self.build_text(x, y, content, family, size, fill, style: "normal",
34
+ size_unit: "px", logical_height: nil)
33
35
  TextRenderer.new(content: content, x: x, y: y,
34
- family: family, size: size, fill: fill,
35
- style: style).to_svg
36
+ family: family, size: size, fill: fill,
37
+ size_unit: size_unit, logical_height: logical_height,
38
+ style: style).to_svg
36
39
  end
37
40
  private_class_method :build_text
38
41
 
42
+ def self.line_height(size, family, size_unit, logical_height)
43
+ Ea::Fonts::Metrics.compartment_line_height(
44
+ size, family: family, unit: size_unit, logical_height: logical_height,
45
+ offset: size_unit == "pt" ? 6 : 4
46
+ )
47
+ end
48
+ private_class_method :line_height
49
+
39
50
  # Center the "tags" header text within the bounds width.
40
51
  def self.centered_x_offset(bounds, size)
41
52
  text_width = HEADER_TEXT.length * (size / 2)
@@ -74,7 +74,7 @@ module Ea
74
74
  end
75
75
 
76
76
  def package_stereotype(context)
77
- context.classifier.stereotype_refs.first.to_s
77
+ context.model_element.stereotype_refs.first.to_s
78
78
  end
79
79
 
80
80
  def record_drawn_bounds(element, context)
@@ -84,11 +84,11 @@ module Ea
84
84
  return unless raw
85
85
 
86
86
  width = context.bounds.width
87
- if classifier_package?(context.classifier)
87
+ if classifier_package?(context.model_element)
88
88
  tab = Element::PackageShapeRenderer.autosized_tab_width(
89
- label: context.classifier.name.to_s,
89
+ label: context.model_element.name.to_s,
90
90
  stereotype: package_stereotype(context),
91
- size: context.size
91
+ size: context.size, family: context.family
92
92
  )
93
93
  width = [raw.width, tab + Element::PackageShapeRenderer::TAB_BODY_EXTRA].max
94
94
  end
@@ -316,8 +316,7 @@ module Ea
316
316
  size_unit: size_unit, logical_height: logical_height,
317
317
  header_ghost: header_lines.any? &&
318
318
  %i[italic italic_tight].include?(header_lines.first.last),
319
- header_ghost_tight: header_lines.any? &&
320
- header_lines.first.last == :italic_tight,
319
+ header_ghost_tight: Element::HeaderLines.parent_ghost_gap(header_lines) == 3,
321
320
  header_lines_count: header_lines.size,
322
321
  attr_lines_count: attr_lines.size,
323
322
  op_lines_count: op_lines.size,
@@ -705,7 +704,7 @@ module Ea
705
704
  end
706
705
 
707
706
  def font_resolver
708
- @font_resolver ||= FontResolver.new(diagram, theme: theme)
707
+ @font_resolver ||= FontResolver.new(diagram, theme: theme, model_index: model_index)
709
708
  end
710
709
 
711
710
  def element_filter
@@ -23,10 +23,11 @@ module Ea
23
23
  # a use-default marker (see QEA DiagramBuilder.font_size_from).
24
24
  DEFAULT_ELEMENT_FONT_SIZE = 7
25
25
 
26
- attr_reader :diagram, :theme
26
+ attr_reader :diagram, :theme, :model_index
27
27
 
28
- def initialize(diagram, theme: nil)
28
+ def initialize(diagram, theme: nil, model_index: {})
29
29
  @diagram = diagram
30
+ @model_index = model_index
30
31
  @theme = theme || Ea::Theme::Registry.default
31
32
  end
32
33
 
@@ -84,7 +85,8 @@ module Ea
84
85
  end
85
86
 
86
87
  # Unthemed diagrams without a saved family use Cambria.
87
- # Saved families still supply the diagram default.
88
+ # Saved classifier families still supply the diagram default.
89
+ # A note's explicit font belongs to its body, not sibling classifiers.
88
90
  def diagram_default_family
89
91
  families = element_families
90
92
  return FALLBACK_DEFAULT_FAMILY if families.empty?
@@ -102,7 +104,10 @@ module Ea
102
104
  end
103
105
 
104
106
  def element_families
105
- (diagram.elements || []).map(&:font_family).compact.reject do |family|
107
+ placements = (diagram.elements || []).reject do |element|
108
+ model_index[element.model_element_ref].is_a?(Ea::Model::Note)
109
+ end
110
+ placements.map(&:font_family).compact.reject do |family|
106
111
  family.empty? || DEFAULT_MARKER_FAMILIES.include?(family)
107
112
  end
108
113
  end
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.116"
7
+ VERSION = "0.6.118"
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.116
4
+ version: 0.6.118
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.