ea 0.6.30 → 0.6.32

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: d0b7f42f300df411de328df0ab9c076927ef919fbe723cf262310107f1c324ba
4
- data.tar.gz: 27d5559ae59009bb83a9fcf3d806354fe4300c52e7dc736820d798823fb578f4
3
+ metadata.gz: 6e23a708761ca82e0a23584e7c5ee557251532481defbd16712c676963ad7fbe
4
+ data.tar.gz: dfaeae3624f35848cf7d801e5fdf9c1bf990fa162514838353b98b282da76e51
5
5
  SHA512:
6
- metadata.gz: '09b19a0e6f50428e7840bd962f741753b8512cc9d41cd00954a86467cf6e4965e2016e903a9f496162d63bc98415ab7c3b74b9c8e81c371e027a4074c48c7222'
7
- data.tar.gz: eaa09bf8b99d624cb785581fa5c674c9cd2f37cbfac9a7554d4cfd21859f84edfe9643a6cc8ad1863b358b74c46bf656cf7ec4be5549796ed4f1f275c36d1f1d
6
+ metadata.gz: 8efc865e83d8c43843f276c3307158b90ea6674bdf252f39804c48f8cab7c98c765536f1b382773efe2a4a6fd629f97acb6e35d37fd545cdcd5a70f18b98f387
7
+ data.tar.gz: 9eee4896484462b96f7060de2700e893ab4cd2a76b6d4ee7217e1908d5003767914d4e80a9c06a9cea8723ca609f2bae965b903a86660000d2690d39ca442e92
@@ -165,8 +165,8 @@ module Ea
165
165
  target = diagram_object_placement(diagram_row.diagram_id, target_id)
166
166
  return nil unless source && target
167
167
 
168
- source_bounds = bounds_from_rect(source)
169
- target_bounds = bounds_from_rect(target)
168
+ source_bounds = drawn_bounds_for(source)
169
+ target_bounds = drawn_bounds_for(target)
170
170
  waypoints = if source_id == target_id
171
171
  self_loop_waypoints(source_bounds)
172
172
  else
@@ -224,8 +224,8 @@ module Ea
224
224
  target_placement = diagram_object_placement(diagram_row.diagram_id, target_id)
225
225
  return nil unless source_placement && target_placement
226
226
 
227
- source_bounds = bounds_from_rect(source_placement)
228
- target_bounds = bounds_from_rect(target_placement)
227
+ source_bounds = drawn_bounds_for(source_placement)
228
+ target_bounds = drawn_bounds_for(target_placement)
229
229
  waypoints = direct_waypoints(source_bounds, target_bounds)
230
230
  Ea::Model::DiagramConnector.new(
231
231
  id: IdNormalizer.synthetic("xc", diagram_row.diagram_id, object_id),
@@ -247,10 +247,15 @@ module Ea
247
247
  # renders as a straight line between the two element edges,
248
248
  # with SourceRole labels and appropriate markers.
249
249
  #
250
- # Only Association and Aggregation types produce phantom
251
- # connectors. Generalization, Dependency, and Package types
252
- # do NOT get phantom rendering.
253
- PHANTOM_CONNECTOR_TYPES = %w[Association Aggregation].freeze
250
+ # Dependency AND Generalization phantoms draw: Main/
251
+ # 9FCDB717 renders 12 of 13 model dependencies among the
252
+ # placed packages (the 13th suppressed by the diagram's
253
+ # single Hidden link row); All/F209CE4B renders all 11
254
+ # row-less generalizations as white triangles. A Hidden
255
+ # link row suppresses; absence of a row does not.
256
+ PHANTOM_CONNECTOR_TYPES = %w[Association Aggregation Dependency
257
+ Generalization].freeze
258
+ PHANTOM_DOUBLE_BACK_TYPES = %w[Dependency].freeze
254
259
 
255
260
  def phantom_connectors(diagram_row, explicit_connectors)
256
261
  placed_ids = placed_object_ids_on(diagram_row.diagram_id)
@@ -308,7 +313,7 @@ module Ea
308
313
  target_stereotype: conn.deststereotype,
309
314
  waypoints: waypoints,
310
315
  label_boxes: DEFAULT_LABEL_BOXES,
311
- style: {},
316
+ style: PHANTOM_DOUBLE_BACK_TYPES.include?(conn.connector_type) ? { direct: true } : {},
312
317
  hidden: false,
313
318
  has_geometry_offsets: false
314
319
  )
@@ -378,7 +383,33 @@ module Ea
378
383
  connector.end_object_id)
379
384
  return [] unless source && target
380
385
 
381
- direct_waypoints(bounds_from_rect(source), bounds_from_rect(target))
386
+ direct_waypoints(drawn_bounds_for(source),
387
+ drawn_bounds_for(target))
388
+ end
389
+
390
+ # The outline EA clips connector rays against: the DRAWN
391
+ # autosized box, not the stored logical rect. Packages draw
392
+ # narrower than their stored rect (body = autosized tab +
393
+ # 15), so their docks land on the drawn edges (E58034A3:
394
+ # supplier right-edge dock 155 drawn vs 121 stored). All
395
+ # other element kinds draw at the stored rect.
396
+ PACKAGE_BODY_EXTRA = 15
397
+
398
+ def drawn_bounds_for(placement)
399
+ logical = bounds_from_rect(placement)
400
+ obj = database.find_object(placement.ea_object_id.to_i)
401
+ return logical unless obj && obj.object_type == "Package"
402
+
403
+ tab = ::Ea::Svg::EaEmitter::Element::PackageShapeRenderer
404
+ .autosized_tab_width(label: obj.name.to_s,
405
+ stereotype: obj.stereotype.to_s,
406
+ size: 7)
407
+ Ea::Model::Bounds.new(
408
+ x: logical.x,
409
+ y: logical.y,
410
+ width: tab + PACKAGE_BODY_EXTRA,
411
+ height: logical.height
412
+ )
382
413
  end
383
414
 
384
415
  # Straight-line waypoints between the two box-outline
@@ -62,9 +62,8 @@ module Ea
62
62
  target_multiplicity_upper: parse_upper(row.destcard),
63
63
  source_multiplicity_raw: raw_card(row.sourcecard),
64
64
  target_multiplicity_raw: raw_card(row.destcard),
65
- source_aggregation: ConnectorRelationshipMap
66
- .source_aggregation_for(row.connector_type),
67
- target_aggregation: aggregation_from(row.destaccess),
65
+ source_aggregation: source_aggregation_for(row),
66
+ target_aggregation: aggregation_from(row.destisaggregate),
68
67
  source_navigable: navigable?(row.sourceaccess, row.direction,
69
68
  :source),
70
69
  target_navigable: navigable?(row.destaccess, row.direction, :target)
@@ -112,15 +111,27 @@ module Ea
112
111
  end
113
112
  end
114
113
 
115
- def aggregation_from(access_value)
116
- case access_value.to_s
117
- when "0" then "none"
118
- when "1" then "shared"
119
- when "2" then "composite"
114
+ # EA stores the aggregate end in t_connector.SourceIsAggregate/
115
+ # DestIsAggregate (0 none, 1 shared, 2 composite) — NOT in
116
+ # DestAccess, which carries the accessibility string
117
+ # ("Public"). The connector TYPE only implies a source-side
118
+ # aggregation when the flags are silent (legacy
119
+ # Connector_Type encoding).
120
+ def aggregation_from(flag_value)
121
+ case flag_value.to_i
122
+ when 1 then "shared"
123
+ when 2 then "composite"
120
124
  else "none"
121
125
  end
122
126
  end
123
127
 
128
+ def source_aggregation_for(row)
129
+ flagged = aggregation_from(row.sourceisaggregate)
130
+ return flagged if flagged != "none"
131
+
132
+ ConnectorRelationshipMap.source_aggregation_for(row.connector_type)
133
+ end
134
+
124
135
  def navigable?(_access, direction, end_label)
125
136
  # EA's navigability is messy. Treat unspecified direction
126
137
  # as bidirectional, "Source -> Destination" as target-only
@@ -58,17 +58,23 @@ module Ea
58
58
  pts = waypoints_for(connector)
59
59
  return nil if pts.size < 2
60
60
 
61
- d = pts.each_with_index.map do |p, idx|
62
- x, y = translate_point(p)
63
- "#{idx.zero? ? 'M' : 'L'} #{Canvas.coord(x)} #{Canvas.coord(y)}"
64
- end.join(" ")
65
- # EA writes a trailing zero-length "M <start>" repeat for
66
- # DIRECT (auto-routed straight) connectors — verified on
67
- # context diagrams where every line carries it (B6847577,
68
- # E58034A3, 8CF27BE8).
61
+ # DIRECT (auto-routed straight) connectors: EA writes the
62
+ # segment TARGET-FIRST with a trailing zero-length repeat —
63
+ # "M target L source M target" (B6847577, E58034A3,
64
+ # 8CF27BE8). Same pixels as source-first; only the byte
65
+ # order differs, so reversal lives here at the emitter and
66
+ # the marker anchoring (source = first waypoint) is
67
+ # untouched.
69
68
  if pts.size == 2 && direct?(connector)
70
- x, y = translate_point(pts.first)
71
- d = "#{d} M #{Canvas.coord(x)} #{Canvas.coord(y)}"
69
+ x1, y1 = translate_point(pts.first)
70
+ x2, y2 = translate_point(pts.last)
71
+ d = "M #{Canvas.coord(x2)} #{Canvas.coord(y2)} L #{Canvas.coord(x1)} #{Canvas.coord(y1)} " \
72
+ "M #{Canvas.coord(x2)} #{Canvas.coord(y2)}"
73
+ else
74
+ d = pts.each_with_index.map do |p, idx|
75
+ x, y = translate_point(p)
76
+ "#{idx.zero? ? 'M' : 'L'} #{Canvas.coord(x)} #{Canvas.coord(y)}"
77
+ end.join(" ")
72
78
  end
73
79
  %(<path d="#{d}" shape-rendering="auto"/>)
74
80
  end
@@ -16,6 +16,15 @@ module Ea
16
16
 
17
17
  def self.specs_for(connector, source, target, before_target,
18
18
  after_source, relationship: nil)
19
+ # EA draws the navigability arrow on directed
20
+ # associations AND aggregations (333EFCCF: 6 aggregations
21
+ # render diamond + path arrow); Direction
22
+ # "Unspecified"/empty draws a plain line (EC670ED5: the
23
+ # single Unspecified association is arrowless;
24
+ # C852CC42/TransferNode: 9 Unspecified all arrowless).
25
+ direction = connector.direction.to_s
26
+ return [] if direction.empty? || direction == "Unspecified"
27
+
19
28
  return bidirectional_specs(source, target, before_target,
20
29
  after_source) if bidirectional?(connector)
21
30
 
@@ -4,49 +4,59 @@ module Ea
4
4
  module Svg
5
5
  module EaEmitter
6
6
  module Marker
7
- # Diamond marker at the whole end of an aggregation or
8
- # composition, PLUS a navigability arrow at the part end
9
- # when the connector's direction is "Destination -> Source".
10
- # Reference SVGs confirm EA emits BOTH markers for
11
- # reverse-direction aggregations on the plateau: the diamond
12
- # distinguishes aggregation from association, the arrow shows
13
- # navigability direction. Forward-direction ("Source ->
14
- # Destination") aggregations get the diamond only — verified
15
- # against basic.qea's "Two Level Class Composition Hierarchy"
16
- # diagram, where the reference SVG has 17 diamonds and 0
17
- # arrows.
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).
18
25
  class Diamond < Kind
19
26
  def self.handles?(effective_type)
20
27
  effective_type == "Aggregation" || effective_type == "Composition"
21
28
  end
22
29
 
23
30
  def self.specs_for(connector, source, target, before_target, after_source, relationship: nil)
24
- whole_end_at_source = whole_end_at_source?(connector)
25
- whole_anchor = whole_end_at_source ? source : target
26
- whole_base = whole_end_at_source ? after_source : before_target
31
+ whole_at_target = whole_at_target?(connector, relationship)
32
+ whole_anchor = whole_at_target ? target : source
33
+ whole_base = whole_at_target ? before_target : after_source
27
34
  specs = [Registry::Spec.new(shape: :diamond,
28
35
  anchor: whole_anchor,
29
36
  base: whole_base)]
30
- return specs unless navigable_part_end?(connector)
37
+ return specs unless whole_at_target
31
38
 
32
- part_anchor = whole_end_at_source ? target : source
33
- part_base = whole_end_at_source ? before_target : after_source
34
39
  specs << Registry::Spec.new(shape: :arrow,
35
- anchor: part_anchor,
36
- base: part_base)
40
+ anchor: whole_anchor,
41
+ base: whole_base)
37
42
  specs
38
43
  end
39
44
 
40
- # EA renders the navigability arrow at the part end of an
41
- # aggregation only when the connector's direction is
42
- # "Destination -> Source" — the conventional EA encoding
43
- # for "part -> whole" with navigability back to the whole.
44
- # Forward-direction aggregations ("Source -> Destination")
45
- # omit the arrow: the diamond already communicates the
46
- # aggregation semantic and the destination is not
47
- # explicitly navigable.
48
- def self.navigable_part_end?(connector)
49
- connector.direction == "Destination -> Source"
45
+ def self.whole_at_target?(connector, relationship)
46
+ return true if dest_flagged?(relationship)
47
+ return false if source_flagged?(relationship)
48
+
49
+ !whole_end_at_source?(connector)
50
+ end
51
+
52
+ def self.dest_flagged?(relationship)
53
+ relationship &&
54
+ %w[shared composite].include?(relationship.target_aggregation.to_s)
55
+ end
56
+
57
+ def self.source_flagged?(relationship)
58
+ relationship &&
59
+ %w[shared composite].include?(relationship.source_aggregation.to_s)
50
60
  end
51
61
  end
52
62
  end
@@ -14,14 +14,19 @@ module Ea
14
14
  # the diagram's generalizations use polygons.
15
15
  class OpenTriangle < Kind
16
16
  def self.handles?(effective_type)
17
- %w[Generalization Realization Dependency].include?(effective_type)
17
+ %w[Generalization Realization Realisation Dependency
18
+ InformationFlow NoteLink].include?(effective_type)
18
19
  end
19
20
 
20
21
  def self.specs_for(connector, source, target, before_target, after_source, relationship: nil)
21
22
  whole_end_at_source = whole_end_at_source?(connector)
22
23
  anchor = whole_end_at_source ? target : source
23
24
  base = whole_end_at_source ? before_target : after_source
24
- shape = effective_type?(connector) == "Dependency" ? :dependency_arrow : :triangle
25
+ shape = if %w[Dependency InformationFlow].include?(effective_type?(connector))
26
+ :dependency_arrow
27
+ else
28
+ :triangle
29
+ end
25
30
  [Registry::Spec.new(shape: shape, anchor: anchor, base: base)]
26
31
  end
27
32
 
@@ -44,7 +44,19 @@ module Ea
44
44
  !attr_lines.empty? || !op_lines.empty? ||
45
45
  enum_literals.any? || tagged_values.any? ||
46
46
  (constraints&.any?) ||
47
- (!package_content_lines.nil? && !package_content_lines.empty?)
47
+ (!package_content_lines.nil? && !package_content_lines.empty?) ||
48
+ hidden_features_present?
49
+ end
50
+
51
+ # EA draws the header divider whenever the element HAS a
52
+ # feature compartment — even one whose rows are all hidden
53
+ # by visibility flags (10E3E08A: CodeList with 3 attributes
54
+ # toggled off still renders the divider over an empty body;
55
+ # only feature-less elements omit it).
56
+ def hidden_features_present?
57
+ return false unless classifier.respond_to?(:properties)
58
+
59
+ classifier.properties.any? || classifier.operations.any?
48
60
  end
49
61
  end
50
62
  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.30"
7
+ VERSION = "0.6.32"
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.30
4
+ version: 0.6.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.