ea 0.6.122 → 0.6.123

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: 134a5c15aeb52f545cffe73a1541d36c404bc73daef03794c3cc0289744c0364
4
- data.tar.gz: 231fad8c18f4f7ab8b72bc7e3f4b23652f2b44dfd5b9e6b54d8377cc3e4625d9
3
+ metadata.gz: d897b1e2165c9993420958c081ee3e2eedebbfbb8ac4db1ea8f160fd45913a3d
4
+ data.tar.gz: 667e5cce39c543c97f96fd7b64c994d72808daf3bf6a875ae112e0efe4b2623b
5
5
  SHA512:
6
- metadata.gz: 210b2520ec8a7d00782cc06b65a27ff3b391c611a4994b97aabc3281528db9e1356a96cd1b07a8734533f50c8c1b9a9e82e74576cf2a25f533c442e163ea09e4
7
- data.tar.gz: d46dd157918d5fa7a1d9884d6cdba517a4d3fc911894c6f800d2cece0fdb62e2b5a09dfa030bd80cc604fe5ab2a91a310d86b5c9893c54b81cad2536200563ac
6
+ metadata.gz: e8789c4187a4c72534f2e4d17f1caf650e7da1748aa08bed49764fa6fde0dafdfc1bf8c6811ab7a33e31b6da1ffe0a71397b6ca60b1a8c2e7ee0e65a4798368d
7
+ data.tar.gz: 37bf77d8c14e921888081ee961506f835b5cd0de1bb28de790d5ba848003915922e83c83e2b29d6db0f58141f25dc1d643059f642950ee6cd2028da87d42b42f
@@ -5,12 +5,10 @@ module Ea
5
5
  module EaEmitter
6
6
  module Compartment
7
7
  # Instance-name underline for object diagrams. EA draws a
8
- # short horizontal line just below each instance name to
9
- # denote that it is an instance (UML convention). Rendered
10
- # only when the diagram's `diagram_type` is "Object".
8
+ # short horizontal line below each instance name to denote
9
+ # that it is an instance (UML convention).
11
10
  module InstanceUnderline
12
- Y_OFFSET = 2
13
- WIDTH_FACTOR = 0.65
11
+ Y_OFFSET = 1
14
12
 
15
13
  module_function
16
14
 
@@ -19,11 +17,22 @@ module Ea
19
17
  return nil unless object_diagram?(context)
20
18
  return nil if context.header_lines.empty?
21
19
 
22
- name_line = context.header_lines.last
23
- text = name_line.first.to_s
24
- return nil if text.empty?
20
+ header = Element::HeaderRenderer.line_layout(
21
+ context.header_lines,
22
+ bounds: context.bounds,
23
+ first_y: context.geometry.header_first_y,
24
+ family: context.family,
25
+ size: context.size,
26
+ size_unit: context.size_unit,
27
+ logical_height: context.logical_height,
28
+ weight_normal: context.theme.text_weight_normal,
29
+ weight_bold: context.theme.text_weight_bold,
30
+ width_factor: context.theme.text_width_factor,
31
+ line_offset: context.theme.compartments.header_line_offset
32
+ ).last
33
+ return nil unless header && !header.text.to_s.empty?
25
34
 
26
- underline_path(context, text)
35
+ underline_path(header)
27
36
  end
28
37
 
29
38
  def object_diagram?(context)
@@ -31,18 +40,13 @@ module Ea
31
40
  end
32
41
  module_function :object_diagram?
33
42
 
34
- def underline_path(context, text)
35
- bounds = context.bounds
36
- text_width = TextRenderer.estimate_width(text, context.size,
37
- WIDTH_FACTOR)
38
- x_start = bounds.x + (bounds.width - text_width) / 2.0
39
- y_pos = context.geometry.header_first_y + Y_OFFSET
40
- build_path_tag(x_start, y_pos, text_width)
43
+ def underline_path(header)
44
+ build_path_tag(header.x, header.y + Y_OFFSET, header.text_length)
41
45
  end
42
46
  module_function :underline_path
43
47
 
44
- def build_path_tag(x_start, y_pos, text_width)
45
- x_end = x_start + text_width
48
+ def build_path_tag(x_start, y_pos, text_length)
49
+ x_end = x_start + text_length
46
50
  coords = format_coords(x_start, y_pos, x_end)
47
51
  d = "M #{coords[:x1]} #{coords[:y1]} L #{coords[:x2]} #{coords[:y1]}"
48
52
  style = "stroke-width:1;stroke-linecap:square;" \
@@ -14,6 +14,7 @@ module Ea
14
14
  ROW_HEIGHT = 16
15
15
  ICON_X_OFFSET = 5
16
16
  TEXT_X_OFFSET = 23
17
+ FIRST_BASELINE_OFFSET = 15
17
18
 
18
19
  module_function
19
20
 
@@ -22,7 +23,7 @@ module Ea
22
23
  return nil if rows.nil? || rows.empty?
23
24
 
24
25
  bounds = context.bounds
25
- first_y = context.geometry.attr_first_y
26
+ first_y = first_baseline(context)
26
27
  blocks = rows.each_with_index.flat_map do |row, idx|
27
28
  y_top = first_y - 11 + (idx * ROW_HEIGHT)
28
29
  y_text = first_y + (idx * ROW_HEIGHT)
@@ -34,6 +35,14 @@ module Ea
34
35
  wrap_group(blocks.join("\n"))
35
36
  end
36
37
 
38
+ def first_baseline(context)
39
+ context.bounds.y + Element::PackageShapeRenderer.tab_height_for(
40
+ stereotype: Shape.package_stereotype(context.model_element),
41
+ family: context.family, size: context.size, size_unit: context.size_unit
42
+ ) + FIRST_BASELINE_OFFSET
43
+ end
44
+ private_class_method :first_baseline
45
+
37
46
  def wrap_group(body)
38
47
  %(<g style="#{group_style}">\n#{body}\n</g>)
39
48
  end
@@ -11,55 +11,70 @@ module Ea
11
11
  # the left edge from text-width approximation since SVG text
12
12
  # x= is the start position, not the center).
13
13
  class HeaderRenderer
14
+ HeaderLine = Struct.new(:text, :style, :x, :y, :text_length,
15
+ :weight, :font_style, keyword_init: true)
16
+
14
17
  def self.render(lines, bounds:, first_y:, family:,
15
18
  size:, size_unit: "pt", logical_height: nil,
16
19
  fill: "#000000", weight_normal: 400, weight_bold: 700,
17
20
  stroke_in_text: "#000000", width_factor: 0.612,
18
21
  line_offset: 6)
19
22
  logical_height = nil unless size_unit == "pt"
23
+ layout = line_layout(
24
+ lines, bounds: bounds, first_y: first_y, family: family,
25
+ size: size, size_unit: size_unit, logical_height: logical_height,
26
+ weight_normal: weight_normal, weight_bold: weight_bold,
27
+ width_factor: width_factor, line_offset: line_offset
28
+ )
29
+ text_blocks = layout.map do |line|
30
+ TextRenderer.new(
31
+ content: line.text,
32
+ x: line.x, y: line.y,
33
+ family: family, size: size, size_unit: size_unit, logical_height: logical_height,
34
+ weight: line.weight, style: line.font_style, fill: fill,
35
+ stroke_in_text: stroke_in_text, width_factor: width_factor,
36
+ text_length: line.text_length
37
+ ).to_svg
38
+ end
39
+ %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
40
+ end
41
+
42
+ # Returns the exact integer placement and width used for each
43
+ # emitted header line so dependent compartments can align to it.
44
+ def self.line_layout(lines, bounds:, first_y:, family:, size:,
45
+ size_unit: "pt", logical_height: nil,
46
+ weight_normal: 400, weight_bold: 700,
47
+ width_factor: 0.612, line_offset: 6)
48
+ logical_height = nil unless size_unit == "pt"
20
49
  line_h = Ea::Fonts::Metrics.compartment_line_height(
21
- size, family: family, unit: size_unit, offset: line_offset, logical_height: logical_height
50
+ size, family: family, unit: size_unit, offset: line_offset,
51
+ logical_height: logical_height
22
52
  )
23
53
  ghost_shift = HeaderLines.parent_ghost_gap(lines)
24
- text_blocks = lines.each_with_index.map do |(text, style), idx|
25
- weight = case style
26
- when :bold, :bold_italic then weight_bold
27
- else weight_normal
28
- end
54
+ lines.each_with_index.map do |(text, style), idx|
55
+ weight = %i[bold bold_italic].include?(style) ? weight_bold : weight_normal
29
56
  font_style = %i[italic italic_tight bold_italic].include?(style) ? "italic" : "normal"
57
+ text_length = TextRenderer.estimate_width(
58
+ text, size, width_factor, family: family, weight: weight,
59
+ style: font_style, logical_height: logical_height
60
+ ).round
30
61
  y = first_y + (idx * line_h) +
31
62
  (idx >= 1 && ghost_shift.positive? ? ghost_shift : 0)
32
63
  x = if %i[italic italic_tight].include?(style)
33
- # Parent ghosts end 3px inside the box's right edge.
34
- bounds.x + bounds.width - 3 -
35
- Ea::Svg::EaEmitter::TextRenderer.estimate_width(
36
- text, size, width_factor,
37
- family: family, weight: weight, style: font_style, logical_height: logical_height
38
- ).round
64
+ bounds.x + bounds.width - 3 - text_length
39
65
  else
40
- center_x_for(text, bounds, size, width_factor,
41
- family: family, weight: weight,
42
- style: font_style, logical_height: logical_height)
66
+ center_x_for(text_length, bounds)
43
67
  end
44
- TextRenderer.new(
45
- content: text,
46
- x: x, y: y,
47
- family: family, size: size, size_unit: size_unit, logical_height: logical_height,
48
- weight: weight, style: font_style, fill: fill,
49
- stroke_in_text: stroke_in_text, width_factor: width_factor
50
- ).to_svg
68
+ HeaderLine.new(text: text, style: style, x: x, y: y,
69
+ text_length: text_length, weight: weight,
70
+ font_style: font_style)
51
71
  end
52
- %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
53
72
  end
54
73
 
55
74
  # EA centers text by its INTEGER textLength and floors the
56
75
  # resulting x (verified exact against EA-published SVGs).
57
- def self.center_x_for(text, bounds, size, width_factor,
58
- family: nil, weight: nil, style: nil, logical_height: nil)
59
- len = Ea::Svg::EaEmitter::TextRenderer.estimate_width(
60
- text, size, width_factor, family: family, weight: weight, style: style, logical_height: logical_height
61
- ).round
62
- (bounds.x + (bounds.width - len) / 2.0).floor
76
+ def self.center_x_for(text_length, bounds)
77
+ (bounds.x + (bounds.width - text_length) / 2.0).floor
63
78
  end
64
79
  private_class_method :center_x_for
65
80
  end
@@ -26,9 +26,10 @@ module Ea
26
26
  text_blocks = []
27
27
  has_receptions = operations.any?(&:is_reception)
28
28
  if has_receptions
29
+ heading_width = reception_heading_width(family, size, size_unit, logical_height)
29
30
  text_blocks << TextRenderer.new(
30
31
  content: "receptions",
31
- x: bounds.x + (bounds.width / 2.0) - (8 * size * 0.65) / 2,
32
+ x: bounds.x + (bounds.width / 2.0) - heading_width / 2.0,
32
33
  y: first_y,
33
34
  family: family, size: size, size_unit: size_unit, logical_height: logical_height,
34
35
  style: "italic", fill: "#000000"
@@ -36,12 +37,14 @@ module Ea
36
37
  end
37
38
  ops = has_receptions ? operations : operations
38
39
  offset = has_receptions ? 1 : 0
40
+ content_x_offset = CONTENT_X_OFFSET + 2 * (size - 7)
41
+ content_x_offset -= 1 if Ea::Fonts::Metrics.compact_compartment_font?(size, family: family, unit: size_unit)
39
42
  ops.each_with_index do |op, idx|
40
43
  y = first_y + ((idx + offset) * line_h)
41
44
  text_blocks << build_text(bounds.x + VISIBILITY_X_OFFSET, y,
42
45
  visibility_prefix(op), family, size, size_unit, logical_height: logical_height)
43
46
  text = operation_text(op, parameter_detail: parameter_detail)
44
- text_blocks << build_text(bounds.x + AttributeRenderer.content_x_offset(size), y,
47
+ text_blocks << build_text(bounds.x + content_x_offset, y,
45
48
  text, family, size, size_unit, logical_height: logical_height)
46
49
  end
47
50
  %(<g style="#{Style::TEXT_GROUP}">\n#{text_blocks.join("\n")}\n</g>)
@@ -102,6 +105,15 @@ module Ea
102
105
  end
103
106
  private_class_method :reception_params
104
107
 
108
+ def self.reception_heading_width(family, size, size_unit, logical_height)
109
+ TextRenderer.estimate_width(
110
+ "receptions", size,
111
+ family: family, weight: 400, style: "italic",
112
+ logical_height: size_unit == "pt" ? logical_height : nil
113
+ ).round
114
+ end
115
+ private_class_method :reception_heading_width
116
+
105
117
  def self.build_text(x, y, content, family, size, size_unit, logical_height: nil)
106
118
  TextRenderer.new(content: content, x: x, y: y,
107
119
  family: family, size: size, size_unit: size_unit,
@@ -45,6 +45,21 @@ module Ea
45
45
  family, size, size_unit, text_fill).to_svg
46
46
  end
47
47
 
48
+ def self.tab_height_for(stereotype:, family:, size:, size_unit: "pt")
49
+ rows = stereotype && !stereotype.empty? ? 2 : 1
50
+ nominal_height = rows == 2 ? TAB_HEIGHT_DOUBLE : TAB_HEIGHT_SINGLE
51
+ line_height = if size_unit == "pt"
52
+ Ea::Fonts::Metrics.compartment_line_height(
53
+ size, family: family, unit: size_unit, offset: TAB_TEXT_PADDING
54
+ )
55
+ else
56
+ TAB_LABEL_Y_OFFSET
57
+ end
58
+ # Preserve the nominal padding while each row follows the same
59
+ # font line height used to place the package header text.
60
+ nominal_height + rows * (line_height - TAB_LABEL_Y_OFFSET)
61
+ end
62
+
48
63
  def initialize(bounds, fill, stroke, stroke_width, label,
49
64
  stereotype, family, size, size_unit, text_fill)
50
65
  @bounds = bounds
@@ -147,11 +162,8 @@ module Ea
147
162
  end
148
163
 
149
164
  def tab_height
150
- nominal_height = has_stereotype? ? TAB_HEIGHT_DOUBLE : TAB_HEIGHT_SINGLE
151
- rows = has_stereotype? ? 2 : 1
152
- # Preserve the nominal padding while each row follows the same
153
- # font line height used to place the package header text.
154
- nominal_height + rows * (tab_line_height - TAB_LABEL_Y_OFFSET)
165
+ self.class.tab_height_for(stereotype: stereotype, family: family,
166
+ size: size, size_unit: size_unit)
155
167
  end
156
168
 
157
169
  def body_points
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.122"
7
+ VERSION = "0.6.123"
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.122
4
+ version: 0.6.123
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.