ea 0.6.106 → 0.6.108

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/config/themes/119.yml +3 -2
  3. data/lib/ea/fonts/cambria_7_styled_widths.json +293 -0
  4. data/lib/ea/fonts/metrics.rb +50 -7
  5. data/lib/ea/model/diagram_element.rb +4 -0
  6. data/lib/ea/sources/qea/diagram_builder/auto_routing.rb +124 -11
  7. data/lib/ea/sources/qea/diagram_builder/font_settings.rb +50 -0
  8. data/lib/ea/sources/qea/diagram_builder/routing.rb +82 -82
  9. data/lib/ea/sources/qea/diagram_builder.rb +9 -30
  10. data/lib/ea/sources/qea/instance_builder.rb +1 -0
  11. data/lib/ea/sources/qea/property_builder.rb +11 -1
  12. data/lib/ea/svg/ea_emitter/color_resolver.rb +1 -2
  13. data/lib/ea/svg/ea_emitter/compartment/attributes.rb +2 -1
  14. data/lib/ea/svg/ea_emitter/compartment/constraints.rb +5 -14
  15. data/lib/ea/svg/ea_emitter/compartment/enum_literals.rb +11 -5
  16. data/lib/ea/svg/ea_emitter/compartment/header.rb +2 -1
  17. data/lib/ea/svg/ea_emitter/compartment/note_body.rb +6 -4
  18. data/lib/ea/svg/ea_emitter/compartment/note_html.rb +66 -0
  19. data/lib/ea/svg/ea_emitter/compartment/operations.rb +2 -1
  20. data/lib/ea/svg/ea_emitter/compartment.rb +1 -0
  21. data/lib/ea/svg/ea_emitter/connector_redock.rb +30 -14
  22. data/lib/ea/svg/ea_emitter/element/attribute_line_builder.rb +11 -9
  23. data/lib/ea/svg/ea_emitter/element/attribute_renderer.rb +42 -17
  24. data/lib/ea/svg/ea_emitter/element/compartment_geometry.rb +46 -15
  25. data/lib/ea/svg/ea_emitter/element/constraint_renderer.rb +34 -2
  26. data/lib/ea/svg/ea_emitter/element/enumeration_literal_renderer.rb +8 -5
  27. data/lib/ea/svg/ea_emitter/element/header_line_pipeline.rb +3 -2
  28. data/lib/ea/svg/ea_emitter/element/header_line_provider/instance_spec.rb +10 -5
  29. data/lib/ea/svg/ea_emitter/element/header_line_provider/name.rb +2 -1
  30. data/lib/ea/svg/ea_emitter/element/header_renderer.rb +14 -12
  31. data/lib/ea/svg/ea_emitter/element/operation_renderer.rb +10 -7
  32. data/lib/ea/svg/ea_emitter/elements.rb +41 -33
  33. data/lib/ea/svg/ea_emitter/font_resolver.rb +14 -4
  34. data/lib/ea/svg/ea_emitter/ghost_labels.rb +3 -0
  35. data/lib/ea/svg/ea_emitter/label/end_label.rb +37 -4
  36. data/lib/ea/svg/ea_emitter/labels.rb +12 -4
  37. data/lib/ea/svg/ea_emitter/marker/diamond.rb +17 -23
  38. data/lib/ea/svg/ea_emitter/render_context.rb +1 -1
  39. data/lib/ea/svg/ea_emitter/text_renderer.rb +6 -5
  40. data/lib/ea/theme/definition.rb +5 -2
  41. data/lib/ea/theme/loader.rb +3 -2
  42. data/lib/ea/version.rb +1 -1
  43. data/memory/deviations.md +3 -0
  44. metadata +5 -1
@@ -116,6 +116,7 @@ module Ea
116
116
  size = font_resolver.size_for(element)
117
117
  family = font_resolver.family_for(element)
118
118
  size_unit = font_resolver.size_unit_for(element)
119
+ logical_height = font_resolver.logical_height_for(element)
119
120
  parent_name = off_canvas_parent_name_for(classifier)
120
121
 
121
122
 
@@ -135,7 +136,8 @@ module Ea
135
136
  classifier.properties.to_a.any? { |pr| !pr.association_id }))
136
137
  Element::AttributeRenderer.lines_for(
137
138
  classifier, lookup: attribute_lookup,
138
- exclude_association_ids: drawn_association_ids
139
+ exclude_association_ids: drawn_association_ids,
140
+ stored_order: element.style&.fetch(:stored_attribute_order, false)
139
141
  )
140
142
  else
141
143
  []
@@ -151,8 +153,8 @@ module Ea
151
153
  # 22 + 3 (CharacterString 197+27=224, Integer 92+25=117).
152
154
  # Titles never drive growth - they wrap instead.
153
155
  if attr_lines.any? || op_lines.any?
154
- row_w = [row_content_width(attr_lines, 5, size, family),
155
- row_content_width(op_lines, 3, size, family)].compact.max
156
+ row_w = [row_content_width(attr_lines, 5, size, family, logical_height),
157
+ row_content_width(op_lines, 3, size, family, logical_height)].compact.max
156
158
  if row_w && row_w > bounds.width
157
159
  bounds = Ea::Model::Bounds.new(
158
160
  x: bounds.x, y: bounds.y,
@@ -166,18 +168,19 @@ module Ea
166
168
  visually_nested: visually_nested?(element),
167
169
  umldi_keyword: element.umldi_keyword,
168
170
  bounds_width: bounds.width,
169
- font_size: size,
171
+ font_size: size, logical_height: logical_height,
170
172
  family: family,
171
173
  off_canvas_parent_name: parent_name,
172
174
  foreign_package_name: foreign_package_name_for(classifier),
173
175
  suppress_stereotypes: suppress_stereotypes?) : []
174
176
  geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
175
- tagged_values_for(classifier).size,
176
- header_lines, classifier: classifier,
177
- constraints_count: constraints_for(classifier).size,
178
- marker_count: marker_lines_for(classifier, element).size,
179
- enum_literals_count: enum_row_count_for(classifier),
180
- attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
177
+ tagged_values_for(classifier).size,
178
+ header_lines, classifier: classifier, family: family,
179
+ size_unit: size_unit, logical_height: logical_height,
180
+ constraints_count: constraints_for(classifier).size,
181
+ marker_count: marker_lines_for(classifier, element).size,
182
+ enum_literals_count: enum_row_count_for(classifier),
183
+ attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
181
184
  # EA suppresses the whole attribute compartment when its
182
185
  # rows do not fit the stored box height (never truncates
183
186
  # rows) - the AOS box on CityFurniture hides its
@@ -195,12 +198,13 @@ module Ea
195
198
  geometry.attr_bottom_y.to_i > bounds.y + bounds.height
196
199
  attr_lines = []
197
200
  geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
198
- tagged_values_for(classifier).size,
199
- header_lines, classifier: classifier,
200
- constraints_count: constraints_for(classifier).size,
201
- marker_count: marker_lines_for(classifier, element).size,
202
- enum_literals_count: enum_row_count_for(classifier),
203
- attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
201
+ tagged_values_for(classifier).size,
202
+ header_lines, classifier: classifier, family: family,
203
+ size_unit: size_unit, logical_height: logical_height,
204
+ constraints_count: constraints_for(classifier).size,
205
+ marker_count: marker_lines_for(classifier, element).size,
206
+ enum_literals_count: enum_row_count_for(classifier),
207
+ attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
204
208
  end
205
209
  # EA suppresses the operations compartment when its rows
206
210
  # overflow the stored box height (same rule as attributes)
@@ -215,22 +219,24 @@ module Ea
215
219
  height: geometry.op_bottom_y.to_i + 8 - bounds.y
216
220
  )
217
221
  geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
218
- tagged_values_for(classifier).size,
219
- header_lines, classifier: classifier,
220
- constraints_count: constraints_for(classifier).size,
221
- marker_count: marker_lines_for(classifier, element).size,
222
- enum_literals_count: enum_row_count_for(classifier),
223
- attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
222
+ tagged_values_for(classifier).size,
223
+ header_lines, classifier: classifier, family: family,
224
+ size_unit: size_unit, logical_height: logical_height,
225
+ constraints_count: constraints_for(classifier).size,
226
+ marker_count: marker_lines_for(classifier, element).size,
227
+ enum_literals_count: enum_row_count_for(classifier),
228
+ attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
224
229
  elsif op_lines.any? && geometry.op_first_y &&
225
230
  geometry.op_bottom_y.to_i > bounds.y + bounds.height
226
231
  op_lines = []
227
232
  geometry = compartment_geometry(bounds, size, attr_lines, op_lines,
228
- tagged_values_for(classifier).size,
229
- header_lines, classifier: classifier,
230
- constraints_count: constraints_for(classifier).size,
231
- marker_count: marker_lines_for(classifier, element).size,
232
- enum_literals_count: enum_row_count_for(classifier),
233
- attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
233
+ tagged_values_for(classifier).size,
234
+ header_lines, classifier: classifier, family: family,
235
+ size_unit: size_unit, logical_height: logical_height,
236
+ constraints_count: constraints_for(classifier).size,
237
+ marker_count: marker_lines_for(classifier, element).size,
238
+ enum_literals_count: enum_row_count_for(classifier),
239
+ attr_group_lead: Element::AttributeRenderer.group_lead_total(attr_lines))
234
240
  end
235
241
  # Real attributes grow the DRAWN box past the stored
236
242
  # height instead of being dropped (0CABA7D7: stored h44
@@ -264,7 +270,7 @@ module Ea
264
270
  stroke: resolve_stroke,
265
271
  stroke_width: resolve_stroke_width,
266
272
  text_fill: theme.text_color,
267
- family: family, size: size, size_unit: size_unit,
273
+ family: family, size: size, size_unit: size_unit, logical_height: logical_height,
268
274
  header_lines: header_lines,
269
275
  attr_lines: attr_lines,
270
276
  op_lines: op_lines,
@@ -282,14 +288,14 @@ module Ea
282
288
  )
283
289
  end
284
290
 
285
- def row_content_width(lines, right_pad, size, family)
291
+ def row_content_width(lines, right_pad, size, family, logical_height)
286
292
  return nil if lines.to_a.empty?
287
293
 
288
294
  tl = lines.filter_map do |line|
289
295
  text = line.sub(/\A\S /, "")
290
296
  Ea::Fonts::Metrics.text_length(text, size,
291
297
  family: family,
292
- weight: "400")&.round
298
+ weight: "400", logical_height: logical_height)&.round
293
299
  end.max
294
300
  return nil unless tl
295
301
 
@@ -300,8 +306,9 @@ module Ea
300
306
  tagged_count, header_lines, classifier: nil,
301
307
  constraints_count: 0, marker_count: 0,
302
308
  enum_literals_count: 0,
303
- attr_group_lead: 0)
304
- CompartmentGeometry.new(bounds: bounds, size: size,
309
+ attr_group_lead: 0, family: nil, size_unit: "pt", logical_height: nil)
310
+ CompartmentGeometry.new(bounds: bounds, size: size, family: family,
311
+ size_unit: size_unit, logical_height: logical_height,
305
312
  header_ghost: header_lines.any? &&
306
313
  %i[italic italic_tight].include?(header_lines.first.last),
307
314
  header_ghost_tight: header_lines.any? &&
@@ -495,6 +502,7 @@ module Ea
495
502
 
496
503
  def foreign_package_name_for(classifier)
497
504
  return nil unless diagram.show_foreign
505
+ return nil if diagram.style_ex.to_s.split(";").include?("NoFullScope=1")
498
506
  return nil unless classifier.is_a?(Ea::Model::Classifier)
499
507
 
500
508
  # Nested classifiers (t_object.ParentID -> a Class) qualify
@@ -41,9 +41,8 @@ module Ea
41
41
  DEFAULT_MARKER_FAMILIES.include?(explicit)
42
42
  return explicit
43
43
  end
44
- return theme.font_family if theme.themed? && theme.font_family
45
44
 
46
- diagram_default_family || DEFAULT_FONT_FAMILY
45
+ theme_family_for(explicit) || diagram_default_family || DEFAULT_FONT_FAMILY
47
46
  end
48
47
 
49
48
  def size_for(element)
@@ -58,8 +57,12 @@ module Ea
58
57
  DEFAULT_ELEMENT_FONT_SIZE
59
58
  end
60
59
 
61
- def size_unit_for(_element)
62
- theme.font_size_unit || "pt"
60
+ def size_unit_for(element)
61
+ element&.font_size_unit || theme.font_size_unit || "pt"
62
+ end
63
+
64
+ def logical_height_for(element)
65
+ element&.font_logical_height if size_unit_for(element) == "pt"
63
66
  end
64
67
 
65
68
  def weight_for(element)
@@ -73,6 +76,13 @@ module Ea
73
76
 
74
77
  private
75
78
 
79
+ def theme_family_for(explicit)
80
+ return unless theme.themed?
81
+ return theme.font_family if DEFAULT_MARKER_FAMILIES.include?(explicit)
82
+
83
+ theme.element_font_family || theme.font_family
84
+ end
85
+
76
86
  # EA's app-default font depends on the system locale:
77
87
  # Japanese Windows → "Yu Gothic UI" 13, English Windows →
78
88
  # "Calibri" 10. We can't query EA's runtime, so we infer
@@ -42,8 +42,11 @@ module Ea
42
42
 
43
43
  private
44
44
 
45
+ # EA does not draw a connector whose other end is missing from
46
+ # the diagram, so it has no waypoints and no ghost label either.
45
47
  def visible_connectors
46
48
  (diagram.connectors || []).select(&:renderable?)
49
+ .reject { |connector| connector.waypoints.empty? }
47
50
  end
48
51
 
49
52
  def text_for(ghost)
@@ -78,6 +78,7 @@ module Ea
78
78
  if tree && zero_offset?(text_box, mult_box)
79
79
  text_pos, mult_pos = tree_positions(end_kind, anchor, role,
80
80
  text_pos, mult_pos)
81
+ mult_pos = tree_left_multiplicity(end_kind, heading, anchor, mult_box, mult) || mult_pos
81
82
  elsif mult_box && stored_offsets_zero?(mult_box)
82
83
  # Start at the legacy default; measured directional seats
83
84
  # below replace it for default Cambria labels.
@@ -94,12 +95,14 @@ module Ea
94
95
  source_property_seat(connector, direction, anchor, text_box, role) ||
95
96
  plain_upward_seat(connector, direction, anchor, text_box, role) ||
96
97
  role_seat(direction, anchor, text_box, role) || text_pos
97
- mult_pos = directional_multiplicity(connector, direction, anchor, mult_box, mult) ||
98
+ mult_pos = directional_multiplicity(direction, anchor, mult_box, mult) ||
99
+ horizontal_tree_multiplicity(connector, direction, anchor, mult_box, mult) ||
98
100
  mult_seat(direction, anchor, mult_box) || mult_pos
99
101
  elsif end_kind == :target && !(tree && zero_offset?(text_box, mult_box))
100
102
  text_pos = target_vertical_role(connector, heading, anchor, text_box, role) ||
101
103
  target_rightward_role(heading, anchor, text_box, role) || text_pos
102
- mult_pos = target_multiplicity_seat(heading, anchor, mult_box) || mult_pos
104
+ mult_pos = target_multiplicity_seat(heading, anchor, mult_box) ||
105
+ target_horizontal_multiplicity(heading, anchor, mult_box, mult) || mult_pos
103
106
  end
104
107
 
105
108
  build_texts(role:, mult:, standalone:,
@@ -306,17 +309,47 @@ module Ea
306
309
  %w[1 3].include?(style[:mode]) && style[:lwidth] == "2" && !style[:tree]
307
310
  end
308
311
 
309
- def directional_multiplicity(connector, direction, anchor, box, mult)
312
+ def directional_multiplicity(direction, anchor, box, mult)
310
313
  return nil unless cambria_box?(box)
311
314
 
312
315
  x, y = seat_corner(anchor, box)
313
316
  return [x + 8, y + 15] if direction == :down
314
- return nil unless direction == :left && source_property_font?(connector, direction)
317
+ return nil unless direction == :left
315
318
 
316
319
  width = Ea::Fonts::Metrics.text_length(mult, font_size, family: font_family)
317
320
  [x - width - 3, y + 15] if width
318
321
  end
319
322
 
323
+ # On an "H" tree route a property end leaving right seats its
324
+ # multiplicity one text width further right than the plain
325
+ # rightward seat.
326
+ def horizontal_tree_multiplicity(connector, direction, anchor, box, mult)
327
+ return nil unless direction == :right && property_label?(connector, :source)
328
+ return nil unless connector.style.to_h[:tree] == "H" && cambria_box?(box) && !mult_empty?(mult)
329
+
330
+ width = Ea::Fonts::Metrics.text_length(mult, font_size, family: font_family)
331
+ x, y = seat_corner(anchor, box)
332
+ [x + SEAT_X_INSET + width, y + 15] if width
333
+ end
334
+
335
+ # A tree-route source leaving left seats its multiplicity like any
336
+ # other leftward source rather than at the rightward tree default.
337
+ def tree_left_multiplicity(end_kind, heading, anchor, box, mult)
338
+ return nil if end_kind != :source || mult_empty?(mult) || leg_direction(end_kind, heading) != :left
339
+
340
+ directional_multiplicity(:left, anchor, box, mult)
341
+ end
342
+
343
+ # A target whose first leg runs sideways seats its multiplicity at
344
+ # the stored corner like a source leaving the same way.
345
+ def target_horizontal_multiplicity(heading, anchor, box, mult)
346
+ return nil unless seat_font? && heading && heading[1].zero? && !heading[0].zero?
347
+ return nil if mult_empty?(mult)
348
+ return directional_multiplicity(:left, anchor, box, mult) if heading[0].negative?
349
+
350
+ mult_seat(:right, anchor, box)
351
+ end
352
+
320
353
  def target_multiplicity_seat(heading, anchor, box)
321
354
  return nil unless seat_font?
322
355
  return nil unless heading && heading[0].zero? && heading[1].negative?
@@ -84,24 +84,32 @@ module Ea
84
84
  stored_src = end_boxes(boxes, :llt, :llb)
85
85
  stored_tgt = end_boxes(boxes, :lrt, :lrb)
86
86
  stored_labels = !(stored_src.nil? && stored_tgt.nil?)
87
- src_tb, src_mb = stored_src || default_label_boxes(points, :source)
88
- tgt_tb, tgt_mb = stored_tgt || default_label_boxes(points, :target)
87
+ src_tb, src_mb = stored_src || unstored_label_boxes(points, :source, stored_labels)
88
+ tgt_tb, tgt_mb = stored_tgt || unstored_label_boxes(points, :target, stored_labels)
89
89
 
90
90
  texts = []
91
91
  end_renderer.texts(text_box: src_tb, mult_box: src_mb,
92
92
  anchor: source_pt, connector: connector,
93
93
  end_kind: :source, tree: tree && tree[:source],
94
- heading: stored_src && source_heading(connector, points),
94
+ heading: (stored_src || stored_labels) && source_heading(connector, points),
95
95
  stored_labels: stored_labels).each { |t| texts << t }
96
96
  end_renderer.texts(text_box: tgt_tb,
97
97
  mult_box: tgt_mb,
98
98
  anchor: target_pt, connector: connector,
99
99
  end_kind: :target, tree: tree && tree[:target],
100
- heading: stored_tgt && target_heading(connector, points),
100
+ heading: (stored_tgt || stored_labels) && target_heading(connector, points),
101
101
  stored_labels: stored_labels).each { |t| texts << t }
102
102
  texts
103
103
  end
104
104
 
105
+ # A connector that stores label boxes for only one end seats the
106
+ # other end's labels at its anchor, like a stored zero offset.
107
+ def unstored_label_boxes(points, end_kind, stored_labels)
108
+ return default_label_boxes(points, end_kind) unless stored_labels
109
+
110
+ [{ "ox" => 0, "oy" => 0, "alignment" => 0 }, { "ox" => 0, "oy" => 0, "alignment" => 0 }]
111
+ end
112
+
105
113
  # EA tree routes are a perfect L: source-exit horizontal into
106
114
  # the corner, vertical trunk into the target dock. Returns
107
115
  # per-end flags when the connector matches, else nil.
@@ -4,27 +4,11 @@ module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
6
  module Marker
7
- # Diamond marker at the whole end of an aggregation, plus a
8
- # navigability arrow apexing at the diamond's line-side tip.
9
- #
10
- # WHOLE-END SELECTION: EA flags the whole end either through
11
- # t_connector.DestAccess (1 shared / 2 composite -> whole at
12
- # the TARGET; corpus-verified 333EFCCF: PersonalRight_E is
13
- # the DestAccess=2 end and every diamond sits there) or
14
- # through the Connector_Type itself ("Aggregation"/
15
- # "Composition" -> whole at the SOURCE; basic.qea's
16
- # composition diagram). DestAccess wins when both appear.
17
- # Without relationship data the direction heuristic applies
18
- # ("Destination -> Source" -> whole at target).
19
- #
20
- # ARROW: dest-flagged aggregations draw a base-12 height-15
21
- # open arrow stacked on the diamond tip pointing into the
22
- # whole (333EFCCF: 6 diamonds + 6 path arrows). Type-inferred
23
- # source-side aggregations draw the diamond only (basic.qea:
24
- # 17 diamonds, 0 arrows).
7
+ # EA endpoint fields select the diamond end; direction selects
8
+ # the arrow end for target-owned aggregations.
25
9
  class Diamond < Kind
26
10
  def self.handles?(effective_type)
27
- effective_type == "Aggregation" || effective_type == "Composition"
11
+ %w[Aggregation Composition].include?(effective_type)
28
12
  end
29
13
 
30
14
  def self.specs_for(connector, source, target, before_target, after_source, relationship: nil)
@@ -36,10 +20,8 @@ module Ea
36
20
  base: whole_base)]
37
21
  return specs unless whole_at_target
38
22
 
39
- specs << Registry::Spec.new(shape: :arrow,
40
- anchor: whole_anchor,
41
- base: whole_base)
42
- specs
23
+ specs.concat(direction_specs(connector, source, target,
24
+ before_target, after_source))
43
25
  end
44
26
 
45
27
  def self.whole_at_target?(connector, relationship)
@@ -58,6 +40,18 @@ module Ea
58
40
  relationship &&
59
41
  %w[shared composite].include?(relationship.source_aggregation.to_s)
60
42
  end
43
+
44
+ def self.direction_specs(connector, source, target, before_target,
45
+ after_source)
46
+ if connector.direction == "Destination -> Source"
47
+ return [Registry::Spec.new(shape: :arrow, anchor: source,
48
+ base: after_source)]
49
+ end
50
+
51
+ [Registry::Spec.new(shape: :arrow, anchor: target,
52
+ base: before_target)]
53
+ end
54
+ private_class_method :direction_specs
61
55
  end
62
56
  end
63
57
  end
@@ -14,7 +14,7 @@ module Ea
14
14
  class RenderContext < Struct.new(
15
15
  :element, :bounds, :model_element, :classifier,
16
16
  :fill, :stroke, :stroke_width, :text_fill,
17
- :family, :size, :size_unit,
17
+ :family, :size, :size_unit, :logical_height,
18
18
  :header_lines, :attr_lines, :op_lines,
19
19
  :enum_literals, :tagged_values, :constraints, :marker_lines,
20
20
  :package_content_lines,
@@ -24,12 +24,12 @@ module Ea
24
24
 
25
25
  attr_reader :content, :x, :y, :family, :size, :weight, :style,
26
26
  :fill, :text_length, :rotation, :size_unit,
27
- :stroke_in_text, :width_factor
27
+ :stroke_in_text, :width_factor, :logical_height
28
28
 
29
29
  def initialize(content:, x:, y:, family:, size:, weight: 400,
30
30
  style: "normal", fill: DEFAULT_FILL,
31
31
  text_length: nil, rotation: DEFAULT_ROTATION,
32
- size_unit: "px",
32
+ size_unit: "px", logical_height: nil,
33
33
  stroke_in_text: DEFAULT_STROKE_IN_TEXT,
34
34
  width_factor: DEFAULT_WIDTH_FACTOR)
35
35
  @content = content.to_s
@@ -43,6 +43,7 @@ module Ea
43
43
  @text_length = text_length
44
44
  @rotation = rotation
45
45
  @size_unit = size_unit
46
+ @logical_height = logical_height if size_unit == "pt"
46
47
  @stroke_in_text = stroke_in_text
47
48
  @width_factor = width_factor
48
49
  end
@@ -154,10 +155,10 @@ module Ea
154
155
  GLYPH_FALLBACK = 0.61
155
156
 
156
157
  def self.estimate_width(text, size, _width_factor = nil, family: nil,
157
- weight: nil, style: nil)
158
+ weight: nil, style: nil, logical_height: nil)
158
159
  real = Ea::Fonts::Metrics.text_length(text, size, family: family,
159
160
  weight: weight,
160
- style: style)
161
+ style: style, logical_height: logical_height)
161
162
  return real if real
162
163
 
163
164
  text.to_s.each_char.sum do |ch|
@@ -170,7 +171,7 @@ module Ea
170
171
 
171
172
  width = self.class.estimate_width(content, size, width_factor,
172
173
  family: family, weight: weight,
173
- style: style)
174
+ style: style, logical_height: logical_height)
174
175
  width.round.to_s
175
176
  end
176
177
 
@@ -29,7 +29,7 @@ module Ea
29
29
  attr_reader :id, :name,
30
30
  :font_family, :font_size, :font_size_unit,
31
31
  :text_weight_normal, :text_weight_bold,
32
- :element_font_size, :element_font_unit,
32
+ :element_font_family, :element_font_size, :element_font_unit,
33
33
  :text_color, :attribute_text_color, :method_text_color,
34
34
  :border_color, :fill_color, :background_color,
35
35
  :stroke_in_text_color,
@@ -69,7 +69,7 @@ module Ea
69
69
  label_font_size_unit: nil,
70
70
  font_size_unit: "pt", text_weight_normal: 400,
71
71
  text_weight_bold: 700,
72
- element_font_size: 9, element_font_unit: "pt",
72
+ element_font_family: nil, element_font_size: 9, element_font_unit: "pt",
73
73
  text_color: "#000000", attribute_text_color: nil,
74
74
  method_text_color: nil, border_color: "#000000",
75
75
  fill_color: "#FFFFFF", background_color: "#FFFFFF",
@@ -90,6 +90,7 @@ module Ea
90
90
  @font_size_unit = font_size_unit
91
91
  @text_weight_normal = text_weight_normal
92
92
  @text_weight_bold = text_weight_bold
93
+ @element_font_family = element_font_family
93
94
  @element_font_size = element_font_size
94
95
  @element_font_unit = element_font_unit
95
96
  @text_color = text_color
@@ -138,6 +139,7 @@ module Ea
138
139
 
139
140
  def with(**overrides)
140
141
  clear_label_override(overrides, :font_family, :label_font_family)
142
+ clear_label_override(overrides, :font_family, :element_font_family)
141
143
  clear_label_override(overrides, :font_size, :label_font_size)
142
144
  clear_label_override(overrides, :font_size_unit, :label_font_size_unit)
143
145
  Definition.new(**to_h.merge(overrides))
@@ -176,6 +178,7 @@ module Ea
176
178
  label_font_size_unit: @label_font_size_unit,
177
179
  text_weight_normal: text_weight_normal,
178
180
  text_weight_bold: text_weight_bold,
181
+ element_font_family: element_font_family,
179
182
  element_font_size: element_font_size,
180
183
  element_font_unit: element_font_unit,
181
184
  text_color: text_color,
@@ -11,7 +11,7 @@ module Ea
11
11
  # font: { family: Carlito, size: 7, unit: pt, weight_normal: 400, ... }
12
12
  # element_font: { size: 9, unit: pt }
13
13
  # label_font: { family: Cambria, size: 7, unit: pt }
14
- # colors: { text: "#000000", attribute_text: "#66413F", ... }
14
+ # colors: { text: "#000000", attribute_text: "#336699", ... }
15
15
  # strokes: { element_border: 2, connector_line: 2, ... }
16
16
  # text_metrics: { width_factor: 0.65 }
17
17
  # canvas: { inset_left: 35, inset_right: 50, ... }
@@ -62,10 +62,11 @@ module Ea
62
62
  font_size_unit: font["unit"] || "pt",
63
63
  text_weight_normal: font["weight_normal"] || 400,
64
64
  text_weight_bold: font["weight_bold"] || 700,
65
+ element_font_family: elem_font["family"],
65
66
  element_font_size: elem_font["size"] || 9,
66
67
  element_font_unit: elem_font["unit"] || "pt",
67
68
  text_color: colors["text"] || "#000000",
68
- attribute_text_color: colors["attribute_text"],
69
+ attribute_text_color: colors["attribute_text"] || colors["text"] || "#000000",
69
70
  method_text_color: colors["method_text"],
70
71
  border_color: colors["border"] || "#000000",
71
72
  fill_color: colors["fill"] || "#FFFFFF",
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.106"
7
+ VERSION = "0.6.108"
8
8
  end
@@ -0,0 +1,3 @@
1
+ # Focused Ruby deviations
2
+
3
+ - Issue 135 attribute ordering: RuboCop Metrics/MethodLength flags the 14-line real-QEA fixture builder, and Metrics/BlockLength flags its six-example public pipeline regression group. Keep the fixture together and the directly related controls in one group; splitting them would obscure the input collections and ordering contract.
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.106
4
+ version: 0.6.108
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -617,6 +617,7 @@ files:
617
617
  - lib/ea/export/xsd/namespace_registry.rb
618
618
  - lib/ea/fonts.rb
619
619
  - lib/ea/fonts/cambria_7_regular_widths.json
620
+ - lib/ea/fonts/cambria_7_styled_widths.json
620
621
  - lib/ea/fonts/carlito_metrics.json
621
622
  - lib/ea/fonts/metrics.rb
622
623
  - lib/ea/guid_format.rb
@@ -818,6 +819,7 @@ files:
818
819
  - lib/ea/sources/qea/context_diagram_support.rb
819
820
  - lib/ea/sources/qea/diagram_builder.rb
820
821
  - lib/ea/sources/qea/diagram_builder/auto_routing.rb
822
+ - lib/ea/sources/qea/diagram_builder/font_settings.rb
821
823
  - lib/ea/sources/qea/diagram_builder/routing.rb
822
824
  - lib/ea/sources/qea/diagram_builder/stored_square_ports.rb
823
825
  - lib/ea/sources/qea/diagram_style_parser.rb
@@ -892,6 +894,7 @@ files:
892
894
  - lib/ea/svg/ea_emitter/compartment/instance_underline.rb
893
895
  - lib/ea/svg/ea_emitter/compartment/marker.rb
894
896
  - lib/ea/svg/ea_emitter/compartment/note_body.rb
897
+ - lib/ea/svg/ea_emitter/compartment/note_html.rb
895
898
  - lib/ea/svg/ea_emitter/compartment/note_text.rb
896
899
  - lib/ea/svg/ea_emitter/compartment/operations.rb
897
900
  - lib/ea/svg/ea_emitter/compartment/package_contents.rb
@@ -1012,6 +1015,7 @@ files:
1012
1015
  - lib/ea/xml_escape.rb
1013
1016
  - lib/tasks/fonts.rake
1014
1017
  - lib/tasks/svg_bench.rake
1018
+ - memory/deviations.md
1015
1019
  - reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes-list.png
1016
1020
  - reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-1-themes.png
1017
1021
  - reference-docs/enterprise-architect-appearance-dialogs/appearance-1-diagram-2-gradients.png