ea 0.6.105 → 0.6.106
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 944b29164fec6d17c5e89224547ae993a7ef4dfbee74ac1c15208eba6f88f0d5
|
|
4
|
+
data.tar.gz: e0d40f4203d9ef1bf8a1d6f0cf72349b7e3e052ed2c3c920f96f1ca655ff2741
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4087c56867c690c67f9c99011a3e8bd4c043fdcb4bb25880a6d7020d7bab5c6e9bed6adc5b25475e37d9dfe64034d6ce7a52fea44d163fb7a41df37998ce1186
|
|
7
|
+
data.tar.gz: 4ae9f0ff3fc328d2f4ceeec2a9e8b0acb51fc72326c9806b00471449dfc656d3b60029f002b4bd5e59269c32807144ed1a660dafb29bc9b4916731e5ba57e761
|
|
@@ -10,7 +10,16 @@ module Ea
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
12
|
def restore_stored_square_ports(model, link_row, diagram_row, geom, context)
|
|
13
|
-
return model if context
|
|
13
|
+
return model if context
|
|
14
|
+
|
|
15
|
+
model = mark_orthogonal_origin_ray(model, link_row, diagram_row, geom)
|
|
16
|
+
return model if model.style.to_h[:orthogonal_origin_ray] == true
|
|
17
|
+
|
|
18
|
+
restore_global_square_ports(model, link_row, diagram_row, geom)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def restore_global_square_ports(model, link_row, diagram_row, geom)
|
|
22
|
+
return model unless stored_square_port_style?(model, link_row, geom)
|
|
14
23
|
|
|
15
24
|
connector = database.find_connector(link_row.connectorid)
|
|
16
25
|
points = stored_square_port_coordinates(connector)
|
|
@@ -25,6 +34,50 @@ module Ea
|
|
|
25
34
|
model
|
|
26
35
|
end
|
|
27
36
|
|
|
37
|
+
# A square route with no stored bends uses the original mathematical
|
|
38
|
+
# ray to choose its orthogonal dock axis at render time.
|
|
39
|
+
def mark_orthogonal_origin_ray(model, link_row, diagram_row, geom)
|
|
40
|
+
return model unless orthogonal_origin_style?(model, link_row, geom)
|
|
41
|
+
|
|
42
|
+
origins = orthogonal_math_origins(link_row, diagram_row, geom)
|
|
43
|
+
return model unless origins
|
|
44
|
+
|
|
45
|
+
model.source_anchor, model.target_anchor = origins
|
|
46
|
+
model.waypoints = origins.map { |point| Ea::Model::Waypoint.new(position: point) }
|
|
47
|
+
model.style = model.style.to_h.merge(orthogonal_origin_ray: true)
|
|
48
|
+
model
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def orthogonal_math_origins(link_row, diagram_row, geom)
|
|
52
|
+
connector = database.find_connector(link_row.connectorid)
|
|
53
|
+
boxes = stored_square_port_boxes(connector, diagram_row)
|
|
54
|
+
return unless boxes && orthogonal_box_gap?(*boxes)
|
|
55
|
+
|
|
56
|
+
offsets = [[geom[:sx], geom[:sy]], [geom[:ex], geom[:ey]]]
|
|
57
|
+
origins = boxes.zip(offsets).map { |box, offset| stored_ray_origin(box, *offset) }
|
|
58
|
+
origins if orthogonal_origins_inside?(origins, boxes)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def orthogonal_origins_inside?(origins, boxes)
|
|
62
|
+
origins.zip(boxes).all? { |point, box| origin_inside_outline?(point, box) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def orthogonal_origin_style?(model, link_row, geom)
|
|
66
|
+
%w[Association Generalization].include?(model.connector_type) && link_row.path.to_s.empty? &&
|
|
67
|
+
model.style.to_h.values_at(:mode, :tree, :lwidth) == %w[3 OS 2] &&
|
|
68
|
+
%i[sx sy ex ey].all? { |key| !geom[key].nil? }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def orthogonal_box_gap?(source, target)
|
|
72
|
+
[source, target].all? { |box| box.width.positive? && box.height.positive? } &&
|
|
73
|
+
(horizontal_ray_gap?(source, target) ^ vertical_ray_gap?(source, target))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def origin_inside_outline?(point, box)
|
|
77
|
+
point.x.between?(box.x - 1, box.x + box.width + 1) &&
|
|
78
|
+
point.y.between?(box.y - 1, box.y + box.height + 1)
|
|
79
|
+
end
|
|
80
|
+
|
|
28
81
|
def stored_square_port_style?(model, link_row, geom)
|
|
29
82
|
style = model.style.to_h
|
|
30
83
|
model.connector_type == "Association" && link_row.path.to_s.empty? &&
|
|
@@ -34,6 +34,9 @@ module Ea
|
|
|
34
34
|
return nil unless source_bounds && target_bounds
|
|
35
35
|
return nil if source_ref == target_ref
|
|
36
36
|
|
|
37
|
+
square = OrthogonalRay.pairs_for(connector, source_bounds, target_bounds)
|
|
38
|
+
return square if square
|
|
39
|
+
|
|
37
40
|
if lateral
|
|
38
41
|
target = lateral_target(points[1], target_bounds, connector.style[:tree])
|
|
39
42
|
return target && [points[0], points[1], target]
|
|
@@ -56,6 +59,7 @@ module Ea
|
|
|
56
59
|
width: box.width + 2, height: box.height + 2)
|
|
57
60
|
end
|
|
58
61
|
candidate = clipped_pairs(outline, anchors)
|
|
62
|
+
candidate[0] = external_source_point(connector, outline.first, anchors, candidate.first)
|
|
59
63
|
expanded = forward_route?(original, candidate)
|
|
60
64
|
pairs = expanded ? candidate : original
|
|
61
65
|
target = expanded ? outline.last : target_bounds
|
|
@@ -80,6 +84,20 @@ module Ea
|
|
|
80
84
|
end
|
|
81
85
|
private_class_method :auto_port_elbow_pairs
|
|
82
86
|
|
|
87
|
+
# An external saved ray can cross two source edges; retain its
|
|
88
|
+
# clockwise perimeter choice instead of the nearest crossing.
|
|
89
|
+
def external_source_point(connector, source, anchors, fallback)
|
|
90
|
+
style = connector.style.to_h
|
|
91
|
+
return fallback unless style[:stored_association_ray] == true && style[:lwidth] == "2"
|
|
92
|
+
|
|
93
|
+
first, last = anchors
|
|
94
|
+
return fallback if first.x.between?(source.x, source.x + source.width) &&
|
|
95
|
+
first.y.between?(source.y, source.y + source.height)
|
|
96
|
+
|
|
97
|
+
anchor_exit(first, source, last, perimeter: true)
|
|
98
|
+
end
|
|
99
|
+
private_class_method :external_source_point
|
|
100
|
+
|
|
83
101
|
# EDGE=1 explicitly seats an external source ray on the top edge;
|
|
84
102
|
# its first contact can instead be the bottom when its origin is below.
|
|
85
103
|
def stored_top_pairs(connector, source, pairs)
|
|
@@ -290,10 +308,9 @@ module Ea
|
|
|
290
308
|
end
|
|
291
309
|
private_class_method :forward_route?
|
|
292
310
|
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
|
|
296
|
-
def anchor_exit(anchor, bounds, other)
|
|
311
|
+
# Interior rays use the nearest forward exit. External saved sources
|
|
312
|
+
# can request the first intersecting edge in clockwise perimeter order.
|
|
313
|
+
def anchor_exit(anchor, bounds, other, perimeter: false)
|
|
297
314
|
ax, ay = anchor.x.to_f, anchor.y.to_f
|
|
298
315
|
ox, oy = other.x.to_f, other.y.to_f
|
|
299
316
|
dx, dy = ox - ax, oy - ay
|
|
@@ -314,12 +331,25 @@ module Ea
|
|
|
314
331
|
x >= bounds.x - 0.01 && x <= bounds.x + bounds.width + 0.01 &&
|
|
315
332
|
y >= bounds.y - 0.01 && y <= bounds.y + bounds.height + 0.01
|
|
316
333
|
end
|
|
317
|
-
best =
|
|
334
|
+
best = if perimeter
|
|
335
|
+
on_perimeter.min_by { |_t, x, y| perimeter_order(x, y, bounds) }
|
|
336
|
+
else
|
|
337
|
+
on_perimeter.min_by(&:first)
|
|
338
|
+
end
|
|
318
339
|
# EA floors the dock coordinates (F851A657: 297.75 -> 297,
|
|
319
340
|
# 433.7 -> 433) - a rounded-up dock is a visible miss.
|
|
320
341
|
best ? [best[1].floor, best[2].floor] : [ax.floor, ay.floor]
|
|
321
342
|
end
|
|
322
343
|
|
|
344
|
+
def perimeter_order(point_x, point_y, bounds)
|
|
345
|
+
return 0 if point_y == bounds.y
|
|
346
|
+
return 1 if point_x == bounds.x + bounds.width
|
|
347
|
+
return 2 if point_y == bounds.y + bounds.height
|
|
348
|
+
|
|
349
|
+
3
|
|
350
|
+
end
|
|
351
|
+
private_class_method :perimeter_order
|
|
352
|
+
|
|
323
353
|
# EA never docks a horizontal connector line at the header
|
|
324
354
|
# divider: endpoints landing on (box side edge, divider_y)
|
|
325
355
|
# move down one row slot to divider_y + 13 - the first
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ea
|
|
4
|
+
module Svg
|
|
5
|
+
module EaEmitter
|
|
6
|
+
# Orthogonal docking from the nearer physical intersection of a stored ray.
|
|
7
|
+
module OrthogonalRay
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def pairs_for(connector, source, target)
|
|
11
|
+
return unless eligible?(connector, source, target)
|
|
12
|
+
|
|
13
|
+
origins = [connector.source_anchor, connector.target_anchor]
|
|
14
|
+
|
|
15
|
+
boxes = [source, target]
|
|
16
|
+
physical = boxes.map { |box| outline(box) }
|
|
17
|
+
clipped = physical_intersections(origins, physical)
|
|
18
|
+
selected = nearer_origin(clipped, origins)
|
|
19
|
+
orthogonal_pairs(clipped, selected, boxes, physical, origins) unless selected.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eligible?(connector, source, target)
|
|
23
|
+
connector.style.to_h[:orthogonal_origin_ray] == true && source && target &&
|
|
24
|
+
connector.source_anchor && connector.target_anchor
|
|
25
|
+
end
|
|
26
|
+
private_class_method :eligible?
|
|
27
|
+
|
|
28
|
+
def physical_intersections(origins, physical)
|
|
29
|
+
origins.each_with_index.map do |origin, index|
|
|
30
|
+
ConnectorRedock.anchor_exit(origin, physical[index], origins[1 - index])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
private_class_method :physical_intersections
|
|
34
|
+
|
|
35
|
+
def outline(box)
|
|
36
|
+
Ea::Model::Bounds.new(x: box.x - 1, y: box.y - 1,
|
|
37
|
+
width: box.width + 2, height: box.height + 2)
|
|
38
|
+
end
|
|
39
|
+
private_class_method :outline
|
|
40
|
+
|
|
41
|
+
def nearer_origin(clipped, origins)
|
|
42
|
+
distances = clipped.zip(origins).map do |(x, y), origin|
|
|
43
|
+
(x - origin.x)**2 + (y - origin.y)**2
|
|
44
|
+
end
|
|
45
|
+
distances.each_with_index.min.last unless distances.first == distances.last
|
|
46
|
+
end
|
|
47
|
+
private_class_method :nearer_origin
|
|
48
|
+
|
|
49
|
+
def orthogonal_pairs(clipped, selected, boxes, physical, origins)
|
|
50
|
+
point = clipped[selected]
|
|
51
|
+
projected = project_opposite(point, physical[selected], boxes[1 - selected])
|
|
52
|
+
return unless projected && forward_projection?(point, projected, origins, selected)
|
|
53
|
+
|
|
54
|
+
selected.zero? ? [point, projected] : [projected, point]
|
|
55
|
+
end
|
|
56
|
+
private_class_method :orthogonal_pairs
|
|
57
|
+
|
|
58
|
+
def project_opposite(point, own, other)
|
|
59
|
+
case physical_edge_axis(point, own)
|
|
60
|
+
when :horizontal then horizontal_projection(point, other)
|
|
61
|
+
when :vertical then vertical_projection(point, other)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
private_class_method :project_opposite
|
|
65
|
+
|
|
66
|
+
def physical_edge_axis(point, own)
|
|
67
|
+
horizontal = [own.y, own.y + own.height].include?(point[1])
|
|
68
|
+
vertical = [own.x, own.x + own.width].include?(point[0])
|
|
69
|
+
return unless horizontal ^ vertical
|
|
70
|
+
|
|
71
|
+
horizontal ? :horizontal : :vertical
|
|
72
|
+
end
|
|
73
|
+
private_class_method :physical_edge_axis
|
|
74
|
+
|
|
75
|
+
def horizontal_projection(point, other)
|
|
76
|
+
x, y = point
|
|
77
|
+
return unless x.between?(other.x, other.x + other.width)
|
|
78
|
+
|
|
79
|
+
edge = facing_edge(y, other.y, other.height)
|
|
80
|
+
[x, edge] if edge
|
|
81
|
+
end
|
|
82
|
+
private_class_method :horizontal_projection
|
|
83
|
+
|
|
84
|
+
def vertical_projection(point, other)
|
|
85
|
+
x, y = point
|
|
86
|
+
return unless y.between?(other.y, other.y + other.height)
|
|
87
|
+
|
|
88
|
+
edge = facing_edge(x, other.x, other.width)
|
|
89
|
+
[edge, y] if edge
|
|
90
|
+
end
|
|
91
|
+
private_class_method :vertical_projection
|
|
92
|
+
|
|
93
|
+
def facing_edge(coordinate, start, extent)
|
|
94
|
+
return start if coordinate < start
|
|
95
|
+
return start + extent if coordinate > start + extent
|
|
96
|
+
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
private_class_method :facing_edge
|
|
100
|
+
|
|
101
|
+
def forward_projection?(point, projected, origins, selected)
|
|
102
|
+
first, last = origins.values_at(selected, 1 - selected)
|
|
103
|
+
x, y = point
|
|
104
|
+
target_x, target_y = projected
|
|
105
|
+
((last.x - first.x) * (target_x - x) + (last.y - first.y) * (target_y - y)).positive?
|
|
106
|
+
end
|
|
107
|
+
private_class_method :forward_projection?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/ea/svg/ea_emitter.rb
CHANGED
|
@@ -45,6 +45,9 @@ module Ea
|
|
|
45
45
|
autoload :Document, "ea/svg/ea_emitter/document"
|
|
46
46
|
autoload :Label, "ea/svg/ea_emitter/label"
|
|
47
47
|
autoload :Compartment, "ea/svg/ea_emitter/compartment"
|
|
48
|
+
autoload :OrthogonalRay, "ea/svg/ea_emitter/orthogonal_ray"
|
|
49
|
+
private_constant :OrthogonalRay
|
|
50
|
+
|
|
48
51
|
autoload :ConnectorRedock, "ea/svg/ea_emitter/connector_redock"
|
|
49
52
|
autoload :RenderContext, "ea/svg/ea_emitter/render_context"
|
|
50
53
|
autoload :VisibilitySymbol, "ea/svg/ea_emitter/visibility_symbol"
|
data/lib/ea/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ea
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.106
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -950,6 +950,7 @@ files:
|
|
|
950
950
|
- lib/ea/svg/ea_emitter/marker/plus.rb
|
|
951
951
|
- lib/ea/svg/ea_emitter/marker/registry.rb
|
|
952
952
|
- lib/ea/svg/ea_emitter/markers.rb
|
|
953
|
+
- lib/ea/svg/ea_emitter/orthogonal_ray.rb
|
|
953
954
|
- lib/ea/svg/ea_emitter/render_context.rb
|
|
954
955
|
- lib/ea/svg/ea_emitter/style.rb
|
|
955
956
|
- lib/ea/svg/ea_emitter/text_renderer.rb
|