ea 0.6.104 → 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 +4 -4
- data/lib/ea/sources/qea/diagram_builder/auto_routing.rb +48 -1
- data/lib/ea/sources/qea/diagram_builder/stored_square_ports.rb +54 -1
- data/lib/ea/sources/qea/diagram_builder.rb +1 -0
- data/lib/ea/svg/ea_emitter/connector_redock.rb +53 -6
- data/lib/ea/svg/ea_emitter/orthogonal_ray.rb +111 -0
- data/lib/ea/svg/ea_emitter.rb +3 -0
- data/lib/ea/version.rb +1 -1
- metadata +3 -2
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
|
|
@@ -4,7 +4,7 @@ module Ea
|
|
|
4
4
|
module Sources
|
|
5
5
|
module Qea
|
|
6
6
|
class DiagramBuilder
|
|
7
|
-
#
|
|
7
|
+
# Stored mathematical docking for auto-routed associations.
|
|
8
8
|
module AutoRouting
|
|
9
9
|
private
|
|
10
10
|
|
|
@@ -22,6 +22,53 @@ module Ea
|
|
|
22
22
|
model
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# When the shared row misses the target, retain the physical ports
|
|
26
|
+
# and let the emitter clip their ray before inserting the elbow.
|
|
27
|
+
def auto_port_elbow(model, link_row, diagram_row, geom, context)
|
|
28
|
+
return model if context || !auto_port_elbow_candidate?(model, link_row, geom)
|
|
29
|
+
|
|
30
|
+
source, target = stored_ray_boxes(link_row, diagram_row)
|
|
31
|
+
origins = [stored_ray_origin(source, geom[:sx], geom[:sy]),
|
|
32
|
+
stored_ray_origin(target, geom[:ex], geom[:ey])]
|
|
33
|
+
return model unless auto_port_elbow_origins?(source, target, origins, geom)
|
|
34
|
+
|
|
35
|
+
apply_auto_port_elbow(model, origins)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def apply_auto_port_elbow(model, origins)
|
|
39
|
+
model.source_anchor, model.target_anchor = origins
|
|
40
|
+
model.waypoints = origins.map { |point| Ea::Model::Waypoint.new(position: point) }
|
|
41
|
+
model.style = model.style.to_h.merge(association_ray_outline: true, auto_port_elbow: true)
|
|
42
|
+
model
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def auto_port_elbow_candidate?(model, link_row, geom)
|
|
46
|
+
auto_port_elbow_model?(model) && link_row.path.to_s.empty? &&
|
|
47
|
+
source_row_style?(model.style.to_h) && uniform_overlap_offsets?(geom) && geom[:edge] == 4
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def auto_port_elbow_model?(model)
|
|
51
|
+
model.connector_type == "Association" && model.has_geometry_offsets &&
|
|
52
|
+
model.source_anchor && model.target_anchor && model.waypoints.size == 2
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def auto_port_elbow_origins?(source, target, origins, geom)
|
|
56
|
+
return false if overlap_origins(source, target, geom)
|
|
57
|
+
return false unless source_row_faces_target?(source, target, geom[:edge])
|
|
58
|
+
return false unless origins.first.x == source.x - 1
|
|
59
|
+
return false if within_height?(origins.first.y, target)
|
|
60
|
+
return false unless crosses_target_horizontal_edge?(origins, target)
|
|
61
|
+
|
|
62
|
+
[source, target].zip(origins).all? { |box, origin| inside_box?(origin, grown_bounds(box, 1)) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def crosses_target_horizontal_edge?(origins, target)
|
|
66
|
+
outline = grown_bounds(target, 1)
|
|
67
|
+
edge = Ea::Model::Bounds.new(x: outline.x, width: outline.width, height: 0,
|
|
68
|
+
y: origins.first.y < outline.y ? outline.y : outline.y + outline.height)
|
|
69
|
+
segment_crosses_box?(*origins, edge)
|
|
70
|
+
end
|
|
71
|
+
|
|
25
72
|
def source_row_origins(source, target, geom)
|
|
26
73
|
return nil unless source_row_faces_target?(source, target, geom[:edge])
|
|
27
74
|
|
|
@@ -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? &&
|
|
@@ -443,6 +443,7 @@ module Ea
|
|
|
443
443
|
model = correct_stored_ray_origins(model, link_row, diagram_row, geom, context)
|
|
444
444
|
model = source_row_association_ray(model, link_row, diagram_row, geom, context)
|
|
445
445
|
model = overlap_ray_origins(model, link_row, diagram_row, geom, context)
|
|
446
|
+
model = auto_port_elbow(model, link_row, diagram_row, geom, context)
|
|
446
447
|
model = mark_default_square_ray(model, link_row, geom, context)
|
|
447
448
|
restore_stored_square_ports(model, link_row, diagram_row, geom, context)
|
|
448
449
|
end
|
|
@@ -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,13 +59,45 @@ 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
|
|
62
66
|
pairs = default_square_pairs(connector, points, source_bounds, target, pairs)
|
|
63
|
-
|
|
67
|
+
source = expanded ? outline.first : source_bounds
|
|
68
|
+
pairs = stored_top_pairs(connector, source, pairs)
|
|
69
|
+
auto_port_elbow_pairs(connector, source, target, pairs)
|
|
64
70
|
end
|
|
65
71
|
|
|
72
|
+
# Auto routing turns at the clipped target column when the source
|
|
73
|
+
# leaves a side edge and the target enters through its top or bottom.
|
|
74
|
+
def auto_port_elbow_pairs(connector, source, target, pairs)
|
|
75
|
+
return pairs unless connector.style.to_h[:auto_port_elbow] == true
|
|
76
|
+
|
|
77
|
+
first, last = pairs
|
|
78
|
+
return pairs unless on_side_edge?(first, source) &&
|
|
79
|
+
[target.y, target.y + target.height].include?(last[1]) &&
|
|
80
|
+
last[0].between?(target.x, target.x + target.width)
|
|
81
|
+
return pairs if first[0] == last[0] || first[1] == last[1]
|
|
82
|
+
|
|
83
|
+
[first, [last[0], first[1]], last]
|
|
84
|
+
end
|
|
85
|
+
private_class_method :auto_port_elbow_pairs
|
|
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
|
+
|
|
66
101
|
# EDGE=1 explicitly seats an external source ray on the top edge;
|
|
67
102
|
# its first contact can instead be the bottom when its origin is below.
|
|
68
103
|
def stored_top_pairs(connector, source, pairs)
|
|
@@ -273,10 +308,9 @@ module Ea
|
|
|
273
308
|
end
|
|
274
309
|
private_class_method :forward_route?
|
|
275
310
|
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
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)
|
|
280
314
|
ax, ay = anchor.x.to_f, anchor.y.to_f
|
|
281
315
|
ox, oy = other.x.to_f, other.y.to_f
|
|
282
316
|
dx, dy = ox - ax, oy - ay
|
|
@@ -297,12 +331,25 @@ module Ea
|
|
|
297
331
|
x >= bounds.x - 0.01 && x <= bounds.x + bounds.width + 0.01 &&
|
|
298
332
|
y >= bounds.y - 0.01 && y <= bounds.y + bounds.height + 0.01
|
|
299
333
|
end
|
|
300
|
-
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
|
|
301
339
|
# EA floors the dock coordinates (F851A657: 297.75 -> 297,
|
|
302
340
|
# 433.7 -> 433) - a rounded-up dock is a visible miss.
|
|
303
341
|
best ? [best[1].floor, best[2].floor] : [ax.floor, ay.floor]
|
|
304
342
|
end
|
|
305
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
|
+
|
|
306
353
|
# EA never docks a horizontal connector line at the header
|
|
307
354
|
# divider: endpoints landing on (box side edge, divider_y)
|
|
308
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
|