ea 0.6.100 → 0.6.101
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 +4 -4
- data/lib/ea/sources/qea/diagram_builder/routing.rb +186 -15
- data/lib/ea/sources/qea/diagram_builder.rb +14 -2
- data/lib/ea/svg/ea_emitter/connector_redock.rb +80 -2
- data/lib/ea/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c722ef7c5b7c7968f5beb08538b31b8090a3ab1533b0418a9600752c2724ef3
|
|
4
|
+
data.tar.gz: 818694e4771383a00ae453f76a7b3f98e79b6b5a839d9459d415ec72129e4226
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b00b8fddbd7701f724dc20bd6640dfc53b7a99903d7a075581be94c9f3674254a114fa976a2a895d5ba231ca3b0c93f7b8ce241c42ef4d5c9e106f82c7b660a6
|
|
7
|
+
data.tar.gz: c41701279192e06adba33b39063dac26a9355dedf1a46fd687079013c341c880f8f3d83278876593cf27733c527f41e22032140b901246b0b6e335cf29dcb308
|
|
@@ -113,21 +113,38 @@ module Ea
|
|
|
113
113
|
return model unless association_ray_candidate?(model, link_row)
|
|
114
114
|
|
|
115
115
|
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
116
|
-
origins = association_ray_origins(source, target, geom)
|
|
116
|
+
origins = association_ray_origins(source, target, geom, model.style.to_h[:lwidth])
|
|
117
117
|
return model unless origins
|
|
118
118
|
|
|
119
|
-
model.waypoints =
|
|
120
|
-
Ea::Model::Waypoint.new(position: point)
|
|
121
|
-
end
|
|
119
|
+
model.waypoints = port_waypoints(model)
|
|
122
120
|
model.source_anchor, model.target_anchor = origins
|
|
121
|
+
model.style = model.style.to_h.merge(stored_association_ray: true, association_ray_outline: true)
|
|
123
122
|
model
|
|
124
123
|
end
|
|
125
124
|
|
|
126
|
-
def
|
|
125
|
+
def port_waypoints(model)
|
|
126
|
+
[model.source_port, model.target_port].map do |point|
|
|
127
|
+
Ea::Model::Waypoint.new(position: point)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Width-2 boxes also own their 1px outer stroke, so a stored
|
|
132
|
+
# origin on that outline is still inside its box.
|
|
133
|
+
def association_ray_origins(source, target, geom, width)
|
|
134
|
+
outline = width == "2" ? 1 : 0
|
|
127
135
|
origins = [stored_ray_origin(source, geom[:sx], geom[:sy]),
|
|
128
136
|
stored_ray_origin(target, geom[:ex], geom[:ey])]
|
|
129
|
-
|
|
130
|
-
origins
|
|
137
|
+
boxes = [source, target].map { |box| grown_bounds(box, outline) }
|
|
138
|
+
inside = origins.zip(boxes).all? { |o, b| inside_box?(o, b) }
|
|
139
|
+
return unless inside && origins.first != origins.last
|
|
140
|
+
|
|
141
|
+
origins if width == "2" || sub_pixel_safe_origins?(origins, [source, target] + boxes)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def grown_bounds(bounds, amount)
|
|
145
|
+
Ea::Model::Bounds.new(x: bounds.x - amount, y: bounds.y - amount,
|
|
146
|
+
width: bounds.width + 2 * amount,
|
|
147
|
+
height: bounds.height + 2 * amount)
|
|
131
148
|
end
|
|
132
149
|
|
|
133
150
|
def association_ray_candidate?(model, link_row)
|
|
@@ -141,15 +158,13 @@ module Ea
|
|
|
141
158
|
%i[direct regenerated].none? { |key| style.key?(key) }
|
|
142
159
|
end
|
|
143
160
|
|
|
144
|
-
#
|
|
145
|
-
# half pixel. An origin on its border then clips differently
|
|
161
|
+
# Width-0 origins are whole pixels while EA's box centers may sit
|
|
162
|
+
# on a half pixel. An origin on its border then clips differently
|
|
146
163
|
# unless the line is axis-aligned, which no rounding changes.
|
|
147
164
|
def sub_pixel_safe_origins?(origins, boxes)
|
|
148
165
|
first, last = origins
|
|
149
|
-
return false if first == last
|
|
150
|
-
|
|
151
166
|
axis_aligned = first.x == last.x || first.y == last.y
|
|
152
|
-
axis_aligned || origins.
|
|
167
|
+
axis_aligned || origins.none? { |point| boxes.any? { |box| on_border?(point, box) } }
|
|
153
168
|
end
|
|
154
169
|
|
|
155
170
|
def on_border?(point, bounds)
|
|
@@ -352,6 +367,14 @@ module Ea
|
|
|
352
367
|
route
|
|
353
368
|
end
|
|
354
369
|
|
|
370
|
+
# One stored corner that is level with both boxes already is the
|
|
371
|
+
# route (vertical leg out of the source, horizontal leg into the
|
|
372
|
+
# target); flattening it to the facing edges would lose it.
|
|
373
|
+
def level_corner_route?(points, link_row, connector, src, tgt)
|
|
374
|
+
points.size == 3 && level_entry_style?(link_row, connector) &&
|
|
375
|
+
!level_entry_point(points[1], src).nil? && !level_entry_point(points[1], tgt).nil?
|
|
376
|
+
end
|
|
377
|
+
|
|
355
378
|
def level_entry_style?(link_row, connector)
|
|
356
379
|
style = DiagramStyleParser.parse(link_row.style)
|
|
357
380
|
%w[Generalization Aggregation].include?(connector.connector_type) &&
|
|
@@ -419,6 +442,62 @@ module Ea
|
|
|
419
442
|
[bounds.y, bounds.y + bounds.height].include?(point[1])
|
|
420
443
|
end
|
|
421
444
|
|
|
445
|
+
# A width-2 Mode 3 Aggregation without a tree style keeps an authored
|
|
446
|
+
# orthogonal stair-step of three or more corners. Each end docks on
|
|
447
|
+
# the 1px outline its adjacent leg points at, from the corner's own
|
|
448
|
+
# coordinate; any end that cannot do so leaves the route unchanged.
|
|
449
|
+
def stored_stair_route(link_row, connector, source, target)
|
|
450
|
+
return nil unless stair_style?(link_row, connector)
|
|
451
|
+
|
|
452
|
+
corners = stored_square_corners(link_row)
|
|
453
|
+
return nil unless corners && corners.size >= 3 && alternating_legs?(corners)
|
|
454
|
+
|
|
455
|
+
first = outline_dock(corners[0], corners[1], source)
|
|
456
|
+
last = outline_dock(corners[-1], corners[-2], target)
|
|
457
|
+
[first, *corners, last] if first && last
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def stair_style?(link_row, connector)
|
|
461
|
+
style = DiagramStyleParser.parse(link_row.style)
|
|
462
|
+
connector.connector_type == "Aggregation" && style[:mode] == "3" &&
|
|
463
|
+
style[:lwidth] == "2" && !style.key?(:tree)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Every leg is non-zero and runs along the opposite axis of the last.
|
|
467
|
+
def alternating_legs?(corners)
|
|
468
|
+
axes = corners.each_cons(2).map { |from, to| leg_axis(from, to) }
|
|
469
|
+
axes.none?(&:nil?) && axes.each_cons(2).all? { |a, b| a != b }
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def leg_axis(from, to)
|
|
473
|
+
return :vertical if from[0] == to[0] && from[1] != to[1]
|
|
474
|
+
|
|
475
|
+
:horizontal if from[1] == to[1] && from[0] != to[0]
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# The dock runs perpendicular to the corner's own leg.
|
|
479
|
+
def outline_dock(corner, neighbour, box)
|
|
480
|
+
if corner[1] == neighbour[1]
|
|
481
|
+
vertical_outline_dock(corner, box)
|
|
482
|
+
else
|
|
483
|
+
horizontal_outline_dock(corner, box)
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def vertical_outline_dock(corner, box)
|
|
488
|
+
x, y = corner
|
|
489
|
+
return nil unless x.between?(box.x, box.x + box.width) && !within_height?(y, box)
|
|
490
|
+
|
|
491
|
+
[x, y < box.y ? box.y - 1 : box.y + box.height + 1]
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def horizontal_outline_dock(corner, box)
|
|
495
|
+
x, y = corner
|
|
496
|
+
return nil unless within_height?(y, box) && !x.between?(box.x, box.x + box.width)
|
|
497
|
+
|
|
498
|
+
[x < box.x ? box.x - 1 : box.x + box.width + 1, y]
|
|
499
|
+
end
|
|
500
|
+
|
|
422
501
|
# A valid lateral corner joins one vertical leg and one horizontal
|
|
423
502
|
# leg. Keep it even when the boxes also admit a shorter direct route.
|
|
424
503
|
# ported: both ends carry explicit SX/SY and EX/EY offsets. An
|
|
@@ -433,6 +512,66 @@ module Ea
|
|
|
433
512
|
corner && lateral_corner_route(style, corner, source, target)
|
|
434
513
|
end
|
|
435
514
|
|
|
515
|
+
# A horizontal lateral tree with no usable stored corner leaves
|
|
516
|
+
# the source's top or bottom edge at its center x, turns at the
|
|
517
|
+
# target's center y and enters the target's facing side edge.
|
|
518
|
+
# When that y lies inside the source's height the route is one
|
|
519
|
+
# horizontal line between the facing side edges. Every dock is
|
|
520
|
+
# flush on its box edge.
|
|
521
|
+
def derived_lateral_route(link_row, connector, source, target)
|
|
522
|
+
style = DiagramStyleParser.parse(link_row.style)
|
|
523
|
+
return nil unless style[:mode] == "3" && style[:tree] == "LH" &&
|
|
524
|
+
derived_lateral_path?(connector.connector_type, link_row.path.to_s)
|
|
525
|
+
|
|
526
|
+
x = source.x + source.width / 2
|
|
527
|
+
y = target.y + target.height / 2
|
|
528
|
+
edge = lateral_target_edge(x, target)
|
|
529
|
+
return nil unless edge
|
|
530
|
+
|
|
531
|
+
if within_height?(y, source)
|
|
532
|
+
side = target.x > x ? source.x + source.width : source.x
|
|
533
|
+
[[side, y], [edge, y]] if ((edge - side) * (edge - x)).positive?
|
|
534
|
+
else
|
|
535
|
+
[[x, y < source.y ? source.y : source.y + source.height], [x, y], [edge, y]]
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# An Association may carry one stored corner that no valid lateral
|
|
540
|
+
# route accepted; a Generalization derives only from an empty Path.
|
|
541
|
+
def derived_lateral_path?(connector_type, path)
|
|
542
|
+
case connector_type
|
|
543
|
+
when "Association" then path.match?(/\A(?:-?\d+:-?\d+;?)?\z/)
|
|
544
|
+
when "Generalization" then path.empty?
|
|
545
|
+
else false
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# A derived level line docks flush at its own two ends; anchoring
|
|
550
|
+
# the ray there keeps the emitter's clip from moving them.
|
|
551
|
+
def pin_derived_lateral_line(model, link_row, diagram_row, context)
|
|
552
|
+
style = model.style.to_h
|
|
553
|
+
return model if context || model.waypoints.size != 2 ||
|
|
554
|
+
style[:mode] != "3" || style[:tree] != "LH"
|
|
555
|
+
|
|
556
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
557
|
+
connector = database.find_connector(link_row.connectorid)
|
|
558
|
+
line = connector && derived_lateral_route(link_row, connector, source, target)
|
|
559
|
+
return model unless line && line.size == 2 &&
|
|
560
|
+
line == model.waypoints.map { |wp| [wp.position.x, wp.position.y] }
|
|
561
|
+
|
|
562
|
+
model.source_anchor, model.target_anchor = line.map { |x, y| Ea::Model::Point.new(x: x, y: y) }
|
|
563
|
+
model.style = style.merge(derived_lateral_line: true)
|
|
564
|
+
model
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# The target side edge a vertical leg at x faces, or nil when x
|
|
568
|
+
# lies within the target's span.
|
|
569
|
+
def lateral_target_edge(x_position, target)
|
|
570
|
+
return target.x if x_position < target.x
|
|
571
|
+
|
|
572
|
+
target.x + target.width if x_position > target.x + target.width
|
|
573
|
+
end
|
|
574
|
+
|
|
436
575
|
# An untyped Mode 3 Association corner takes whichever leg
|
|
437
576
|
# orientation its position fits; width-2 docks sit on the 1px
|
|
438
577
|
# outer outline.
|
|
@@ -613,12 +752,44 @@ module Ea
|
|
|
613
752
|
model
|
|
614
753
|
end
|
|
615
754
|
|
|
755
|
+
# A two-waypoint Mode 3 Aggregation with only a target port draws the
|
|
756
|
+
# line from the source center to the target's stored ray origin
|
|
757
|
+
# (center + EX, center - EY), clipped at the outlines; the boundary
|
|
758
|
+
# port is only the derived crossing of that origin's ray.
|
|
759
|
+
def correct_partial_port_origins(model, link_row, diagram_row, geom, context)
|
|
760
|
+
return model if context || !partial_port_origin_candidate?(model, link_row)
|
|
761
|
+
|
|
762
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
763
|
+
return model unless horizontal_ray_gap?(source, target)
|
|
764
|
+
|
|
765
|
+
model.source_anchor = stored_ray_origin(source, 0, 0)
|
|
766
|
+
model.target_anchor = stored_ray_origin(target, geom[:ex], geom[:ey])
|
|
767
|
+
model
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
def partial_port_origin_candidate?(model, link_row)
|
|
771
|
+
style = model.style.to_h
|
|
772
|
+
model.connector_type == "Aggregation" && model.waypoints.size == 2 &&
|
|
773
|
+
link_row.path.to_s.empty? && style[:mode] == "3" && style[:lwidth] == "2" &&
|
|
774
|
+
!style.key?(:tree) &&
|
|
775
|
+
model.source_port.nil? && model.target_port
|
|
776
|
+
end
|
|
777
|
+
|
|
616
778
|
def partial_port_jog_candidate?(model, link_row)
|
|
617
779
|
style = model.style.to_h
|
|
618
780
|
model.connector_type == "Aggregation" &&
|
|
619
|
-
link_row.path.to_s.empty? && style[:mode] == "3" &&
|
|
620
|
-
!style.key?(:tree) && model.
|
|
621
|
-
model
|
|
781
|
+
link_row.path.to_s.empty? && style[:mode] == "3" &&
|
|
782
|
+
!style.key?(:tree) && model.waypoints.size == 2 &&
|
|
783
|
+
explicit_port_side?(model, style[:lwidth])
|
|
784
|
+
end
|
|
785
|
+
|
|
786
|
+
# Width 2 keeps the evidenced target-only port; width 0 accepts a
|
|
787
|
+
# single explicit port at either end.
|
|
788
|
+
def explicit_port_side?(model, width)
|
|
789
|
+
ports = [model.source_port, model.target_port]
|
|
790
|
+
return model.source_port.nil? && !model.target_port.nil? if width == "2"
|
|
791
|
+
|
|
792
|
+
width == "0" && ports.compact.size == 1
|
|
622
793
|
end
|
|
623
794
|
|
|
624
795
|
def computed_jog_candidate?(model, link_row, geom)
|
|
@@ -362,12 +362,20 @@ module Ea
|
|
|
362
362
|
target_stereotype: conn.deststereotype,
|
|
363
363
|
waypoints: waypoints,
|
|
364
364
|
label_boxes: DEFAULT_LABEL_BOXES,
|
|
365
|
-
style:
|
|
365
|
+
style: phantom_style(conn.connector_type),
|
|
366
366
|
hidden: false,
|
|
367
367
|
has_geometry_offsets: false
|
|
368
368
|
)
|
|
369
369
|
end
|
|
370
370
|
|
|
371
|
+
# Internal producer flag, like :direct. A model relationship with no
|
|
372
|
+
# link row is stroked at the 2px box outline, so its ends dock there.
|
|
373
|
+
def phantom_style(connector_type)
|
|
374
|
+
return { direct: true } if PHANTOM_DOUBLE_BACK_TYPES.include?(connector_type)
|
|
375
|
+
|
|
376
|
+
%w[Aggregation Association].include?(connector_type) ? { phantom_outline: true } : {}
|
|
377
|
+
end
|
|
378
|
+
|
|
371
379
|
# Computes the edge attachment point on source bounds that
|
|
372
380
|
# faces the target bounds. Uses the center-to-center direction
|
|
373
381
|
# to pick the nearest edge intersection.
|
|
@@ -422,7 +430,9 @@ module Ea
|
|
|
422
430
|
is_substitutable: substitutable_connector?(connector)
|
|
423
431
|
), link_row, diagram_row, geom)
|
|
424
432
|
model = collapse_partial_port_jog(model, link_row, diagram_row)
|
|
433
|
+
model = correct_partial_port_origins(model, link_row, diagram_row, geom, context)
|
|
425
434
|
model = direct_empty_path_ray(model, link_row, diagram_row, geom, context)
|
|
435
|
+
model = pin_derived_lateral_line(model, link_row, diagram_row, context)
|
|
426
436
|
model = mode2_overlap_ray(model, link_row, diagram_row, geom, context)
|
|
427
437
|
model = association_stored_ray(model, link_row, diagram_row, geom, context)
|
|
428
438
|
model = correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
@@ -666,15 +676,17 @@ module Ea
|
|
|
666
676
|
src = bounds_from_rect(source_placement)
|
|
667
677
|
tgt = bounds_from_rect(target_placement)
|
|
668
678
|
tree_route = stored_square_route(link_row, connector, src, tgt) ||
|
|
679
|
+
stored_stair_route(link_row, connector, src, tgt) ||
|
|
669
680
|
stored_lateral_route(link_row, connector, src, tgt,
|
|
670
681
|
ported: source_port && target_port) ||
|
|
682
|
+
derived_lateral_route(link_row, connector, src, tgt) ||
|
|
671
683
|
horizontal_tree_route(points, link_row, connector, src, tgt)
|
|
672
684
|
if tree_route
|
|
673
685
|
points = tree_route
|
|
674
686
|
elsif %w[Association Aggregation].include?(connector.connector_type) &&
|
|
675
687
|
edge_out != 2
|
|
676
688
|
stored_tree = preserve_stored_tree?(points, link_row, connector, src, tgt)
|
|
677
|
-
unless stored_tree
|
|
689
|
+
unless stored_tree || level_corner_route?(points, link_row, connector, src, tgt)
|
|
678
690
|
points = facing_edge_route(points, src, tgt)
|
|
679
691
|
end
|
|
680
692
|
# A width-2 Aggregation that keeps its stored bends is
|
|
@@ -39,10 +39,15 @@ module Ea
|
|
|
39
39
|
return target && [points[0], points[1], target]
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
return nil if flush_lateral_line?(connector, points, source_bounds, target_bounds)
|
|
43
|
+
|
|
42
44
|
anchors = [connector.source_anchor, connector.target_anchor]
|
|
43
45
|
bounds = [source_bounds, target_bounds]
|
|
44
46
|
original = clipped_pairs(bounds, anchors)
|
|
45
|
-
|
|
47
|
+
unless stroke_outline?(connector, bounds) || overlap_ray?(connector) ||
|
|
48
|
+
bordered_outline?(connector, diagram, [source_ref, target_ref], bounds)
|
|
49
|
+
return original
|
|
50
|
+
end
|
|
46
51
|
|
|
47
52
|
# EA docks these markers on the outer edge of the 2px box stroke.
|
|
48
53
|
outline = bounds.map do |box|
|
|
@@ -53,6 +58,29 @@ module Ea
|
|
|
53
58
|
forward_route?(original, candidate) ? candidate : original
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
# A horizontal lateral tree the QEA builder derived as one level
|
|
62
|
+
# line (internal style flag) keeps it between the facing side edges.
|
|
63
|
+
def flush_lateral_line?(connector, points, source, target)
|
|
64
|
+
style = connector.style.to_h
|
|
65
|
+
return false unless style[:derived_lateral_line] && style[:mode] == "3" &&
|
|
66
|
+
style[:tree] == "LH" && points.size == 2 &&
|
|
67
|
+
%w[Association Generalization].include?(connector.connector_type)
|
|
68
|
+
return false unless connector.source_anchor && connector.target_anchor
|
|
69
|
+
|
|
70
|
+
first, last = points
|
|
71
|
+
pinned = [[connector.source_anchor.x, connector.source_anchor.y],
|
|
72
|
+
[connector.target_anchor.x, connector.target_anchor.y]]
|
|
73
|
+
pinned == [first, last] && first[1] == last[1] &&
|
|
74
|
+
on_side_edge?(first, source) && on_side_edge?(last, target)
|
|
75
|
+
end
|
|
76
|
+
private_class_method :flush_lateral_line?
|
|
77
|
+
|
|
78
|
+
def on_side_edge?(point, bounds)
|
|
79
|
+
[bounds.x, bounds.x + bounds.width].include?(point[0]) &&
|
|
80
|
+
point[1].between?(bounds.y, bounds.y + bounds.height)
|
|
81
|
+
end
|
|
82
|
+
private_class_method :on_side_edge?
|
|
83
|
+
|
|
56
84
|
def lateral_tree?(connector, points)
|
|
57
85
|
style = connector.style.to_h
|
|
58
86
|
points.size == 3 && style[:mode] == "3" && style[:lwidth] == "2" &&
|
|
@@ -97,7 +125,7 @@ module Ea
|
|
|
97
125
|
private_class_method :facing_coordinate
|
|
98
126
|
|
|
99
127
|
def stroke_outline?(connector, bounds)
|
|
100
|
-
connector.style.to_h[:lwidth] == "2" &&
|
|
128
|
+
(connector.style.to_h[:lwidth] == "2" || default_model_connector?(connector)) &&
|
|
101
129
|
bounds.all? { |box| box.width.positive? && box.height.positive? } &&
|
|
102
130
|
outline_connector?(connector, bounds)
|
|
103
131
|
end
|
|
@@ -111,9 +139,42 @@ module Ea
|
|
|
111
139
|
end
|
|
112
140
|
private_class_method :overlap_ray?
|
|
113
141
|
|
|
142
|
+
# The QEA builder flags a model relationship drawn without any
|
|
143
|
+
# diagram link row; EA strokes it at the 2px box outline.
|
|
144
|
+
def default_model_connector?(connector)
|
|
145
|
+
connector.style.to_h[:phantom_outline] == true && !connector.has_geometry_offsets &&
|
|
146
|
+
%w[Aggregation Association].include?(connector.connector_type)
|
|
147
|
+
end
|
|
148
|
+
private_class_method :default_model_connector?
|
|
149
|
+
|
|
150
|
+
# A thin straight Generalization/Aggregation/Association between two elements
|
|
151
|
+
# drawn with a 2px border stops on the outer edge of those borders.
|
|
152
|
+
def bordered_outline?(connector, diagram, refs, bounds)
|
|
153
|
+
style = connector.style.to_h
|
|
154
|
+
return false unless style[:lwidth] == "0" && !style[:tree]
|
|
155
|
+
return false unless %w[Generalization Aggregation Association].include?(connector.connector_type)
|
|
156
|
+
return false unless bounds.all? { |box| box.width.positive? && box.height.positive? }
|
|
157
|
+
|
|
158
|
+
ends = [[refs.first, style[:soid]], [refs.last, style[:eoid]]]
|
|
159
|
+
ends.all? { |ref, duid| border_two?(diagram, ref, duid) }
|
|
160
|
+
end
|
|
161
|
+
private_class_method :bordered_outline?
|
|
162
|
+
|
|
163
|
+
# Every placement of the element (the DUID-matched one when the
|
|
164
|
+
# connector names it) must declare a border width of 2.
|
|
165
|
+
def border_two?(diagram, ref, duid)
|
|
166
|
+
placed = Array(diagram&.elements).select { |el| el.model_element_ref == ref }
|
|
167
|
+
named = placed.select { |el| duid && el.duid.to_s.casecmp?(duid.to_s) }
|
|
168
|
+
placed = named unless named.empty?
|
|
169
|
+
placed.any? && placed.all? { |el| el.line_width == 2 || el.style.to_h[:lwth] == "2" }
|
|
170
|
+
end
|
|
171
|
+
private_class_method :border_two?
|
|
172
|
+
|
|
114
173
|
def outline_connector?(connector, bounds)
|
|
174
|
+
return true if default_model_connector?(connector)
|
|
115
175
|
return true if %w[Generalization Aggregation].include?(connector.connector_type)
|
|
116
176
|
|
|
177
|
+
return true if association_ray_outline?(connector)
|
|
117
178
|
return false unless stored_association?(connector) || offset_free_association?(connector)
|
|
118
179
|
|
|
119
180
|
# Diagonal Association rounding and tree docks keep their existing rules.
|
|
@@ -122,6 +183,15 @@ module Ea
|
|
|
122
183
|
end
|
|
123
184
|
private_class_method :outline_connector?
|
|
124
185
|
|
|
186
|
+
# The QEA builder marks the stored Association rays it produced;
|
|
187
|
+
# their diagonal docks sit on the outer stroke edge like the
|
|
188
|
+
# axis-aligned ones. Unmarked diagonals keep the legacy docking.
|
|
189
|
+
def association_ray_outline?(connector)
|
|
190
|
+
connector.connector_type == "Association" &&
|
|
191
|
+
connector.style.to_h[:association_ray_outline] == true
|
|
192
|
+
end
|
|
193
|
+
private_class_method :association_ray_outline?
|
|
194
|
+
|
|
125
195
|
def stored_association?(connector)
|
|
126
196
|
style = connector.style.to_h
|
|
127
197
|
connector.connector_type == "Association" && connector.has_geometry_offsets &&
|
|
@@ -244,12 +314,20 @@ module Ea
|
|
|
244
314
|
# anchor row is one explicit horizontal line: EA seats neither end
|
|
245
315
|
# in a compartment row, unlike Generalization/Aggregation targets.
|
|
246
316
|
def explicit_association_ray?(pairs, connector)
|
|
317
|
+
produced = connector.style.to_h[:stored_association_ray] == true
|
|
318
|
+
return true if produced && pairs.first[1] == pairs.last[1]
|
|
319
|
+
|
|
320
|
+
certified_association_ray?(pairs, connector)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def certified_association_ray?(pairs, connector)
|
|
247
324
|
return false unless explicit_horizontal_ray?(connector, %w[Association])
|
|
248
325
|
|
|
249
326
|
connector.source_anchor.y == connector.target_anchor.y &&
|
|
250
327
|
connector.source_port.y == connector.source_anchor.y &&
|
|
251
328
|
pairs.first[1] == pairs.last[1]
|
|
252
329
|
end
|
|
330
|
+
private_class_method :certified_association_ray?
|
|
253
331
|
private_class_method :explicit_association_ray?
|
|
254
332
|
|
|
255
333
|
def explicit_horizontal_ray?(connector, types = %w[Generalization Aggregation])
|
data/lib/ea/version.rb
CHANGED