railroad_diagrams 0.1.0
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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +21 -0
- data/MIT +21 -0
- data/README.md +30 -0
- data/Rakefile +4 -0
- data/exe/railroad_diagrams +7 -0
- data/lib/railroad_diagrams/alternating_sequence.rb +169 -0
- data/lib/railroad_diagrams/choice.rb +209 -0
- data/lib/railroad_diagrams/command.rb +84 -0
- data/lib/railroad_diagrams/comment.rb +48 -0
- data/lib/railroad_diagrams/diagram.rb +107 -0
- data/lib/railroad_diagrams/diagram_item.rb +77 -0
- data/lib/railroad_diagrams/diagram_multi_container.rb +23 -0
- data/lib/railroad_diagrams/end.rb +39 -0
- data/lib/railroad_diagrams/group.rb +75 -0
- data/lib/railroad_diagrams/horizontal_choice.rb +247 -0
- data/lib/railroad_diagrams/multiple_choice.rb +140 -0
- data/lib/railroad_diagrams/non_terminal.rb +67 -0
- data/lib/railroad_diagrams/one_or_more.rb +86 -0
- data/lib/railroad_diagrams/optional.rb +9 -0
- data/lib/railroad_diagrams/optional_sequence.rb +214 -0
- data/lib/railroad_diagrams/path.rb +117 -0
- data/lib/railroad_diagrams/sequence.rb +59 -0
- data/lib/railroad_diagrams/skip.rb +26 -0
- data/lib/railroad_diagrams/stack.rb +120 -0
- data/lib/railroad_diagrams/start.rb +62 -0
- data/lib/railroad_diagrams/style.rb +67 -0
- data/lib/railroad_diagrams/terminal.rb +63 -0
- data/lib/railroad_diagrams/text_diagram.rb +341 -0
- data/lib/railroad_diagrams/version.rb +5 -0
- data/lib/railroad_diagrams/zero_or_more.rb +9 -0
- data/lib/railroad_diagrams.rb +50 -0
- data/sample/sample.html +215 -0
- data/test.rb +570 -0
- metadata +81 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class Diagram < DiagramMultiContainer
|
5
|
+
def initialize(*items, **kwargs)
|
6
|
+
super('svg', items.to_a, { 'class' => DIAGRAM_CLASS })
|
7
|
+
@type = kwargs.fetch(:type, 'simple')
|
8
|
+
|
9
|
+
if @items.any?
|
10
|
+
@items.unshift(Start.new(@type)) unless @items.first.is_a?(Start)
|
11
|
+
@items.push(End.new(@type)) unless @items.last.is_a?(End)
|
12
|
+
end
|
13
|
+
|
14
|
+
@up = 0
|
15
|
+
@down = 0
|
16
|
+
@height = 0
|
17
|
+
@width = 0
|
18
|
+
|
19
|
+
@items.each do |item|
|
20
|
+
next if item.is_a?(Style)
|
21
|
+
|
22
|
+
@width += item.width + (item.needs_space ? 20 : 0)
|
23
|
+
@up = [@up, item.up - @height].max
|
24
|
+
@height += item.height
|
25
|
+
@down = [@down - item.height, item.down].max
|
26
|
+
end
|
27
|
+
|
28
|
+
@width -= 10 if @items[0].needs_space
|
29
|
+
@width -= 10 if @items[-1].needs_space
|
30
|
+
@formatted = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
items = items.map(&:to_s).join(', ')
|
35
|
+
pieces = items ? [items] : []
|
36
|
+
pieces.push("type=#{@type}") if @type != 'simple'
|
37
|
+
"Diagram(#{pieces.join(', ')})"
|
38
|
+
end
|
39
|
+
|
40
|
+
def format(padding_top = 20, padding_right = nil, padding_bottom = nil, padding_left = nil)
|
41
|
+
padding_right = padding_top if padding_right.nil?
|
42
|
+
padding_bottom = padding_top if padding_bottom.nil?
|
43
|
+
padding_left = padding_right if padding_left.nil?
|
44
|
+
|
45
|
+
x = padding_left
|
46
|
+
y = padding_top + @up
|
47
|
+
g = DiagramItem.new('g')
|
48
|
+
g.attrs['transform'] = 'translate(.5 .5)' if STROKE_ODD_PIXEL_LENGTH
|
49
|
+
|
50
|
+
@items.each do |item|
|
51
|
+
if item.needs_space
|
52
|
+
Path.new(x, y).h(10).add(g)
|
53
|
+
x += 10
|
54
|
+
end
|
55
|
+
item.format(x, y, item.width).add(g)
|
56
|
+
x += item.width
|
57
|
+
y += item.height
|
58
|
+
if item.needs_space
|
59
|
+
Path.new(x, y).h(10).add(g)
|
60
|
+
x += 10
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@attrs['width'] = (@width + padding_left + padding_right).to_s
|
65
|
+
@attrs['height'] = (@up + @height + @down + padding_top + padding_bottom).to_s
|
66
|
+
@attrs['viewBox'] = "0 0 #{@attrs['width']} #{@attrs['height']}"
|
67
|
+
g.add(self)
|
68
|
+
@formatted = true
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def text_diagram
|
73
|
+
separator, = TextDiagram.get_parts(['separator'])
|
74
|
+
diagram_td = items[0].text_diagram
|
75
|
+
items[1..].each do |item|
|
76
|
+
item_td = item.text_diagram
|
77
|
+
item_td.expand(1, 1, 0, 0) if item.needs_space
|
78
|
+
diagram_td = diagram_td.append_right(separator)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def write_svg(write)
|
83
|
+
format unless @formatted
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def write_text(_write)
|
89
|
+
output = text_diagram
|
90
|
+
output = "#{output.lines.join("\n")}\n"
|
91
|
+
output = output.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"') if ESCAPE_HTML
|
92
|
+
write(output)
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_standalone(write, css = nil)
|
96
|
+
format unless @formatted
|
97
|
+
css = Style.default_style if css
|
98
|
+
Style.new(css).add(self)
|
99
|
+
@attrs['xmlns'] = 'http://www.w3.org/2000/svg'
|
100
|
+
@attrs['xmlns:xlink'] = 'http://www.w3.org/1999/xlink'
|
101
|
+
DiagramItem.write_svg(write)
|
102
|
+
@children.pop
|
103
|
+
@attrs.delete('xmlns')
|
104
|
+
@attrs.delete('xmlns:xlink')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class DiagramItem
|
5
|
+
attr_reader :up, :down, :height, :width, :needs_space, :attrs, :children
|
6
|
+
|
7
|
+
def initialize(name, attrs: {}, text: nil)
|
8
|
+
@name = name
|
9
|
+
@up = 0
|
10
|
+
@height = 0
|
11
|
+
@down = 0
|
12
|
+
@width = 0
|
13
|
+
@needs_space = false
|
14
|
+
@attrs = attrs || {}
|
15
|
+
@children = text ? [text] : []
|
16
|
+
end
|
17
|
+
|
18
|
+
def format(x, y, width)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def text_diagram
|
23
|
+
raise NotImplementedError 'Virtual'
|
24
|
+
end
|
25
|
+
|
26
|
+
def add(parent)
|
27
|
+
parent.children.push self
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_svg(write)
|
32
|
+
write.call("<#{@name}")
|
33
|
+
@attrs.sort.each do |name, value|
|
34
|
+
write.call(" #{name}=\"#{RailroadDiagrams.escape_attr(value)}\"")
|
35
|
+
end
|
36
|
+
write.call('>')
|
37
|
+
write.call("\n") if @name in %w[g svg]
|
38
|
+
@children.each do |child|
|
39
|
+
if child.is_a?(DiagramItem) || child.is_a?(Path) || child.is_a?(Style)
|
40
|
+
child.write_svg(write)
|
41
|
+
else
|
42
|
+
write.call(RailroadDiagrams.escape_html(child))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
write.call("</#{@name}>")
|
46
|
+
end
|
47
|
+
|
48
|
+
def walk(_callback)
|
49
|
+
callback(self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_str
|
53
|
+
"DiagramItem(#{@name}, #{@attrs}, #{@children})"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def wrap_string(value)
|
59
|
+
if value.class <= DiagramItem
|
60
|
+
value
|
61
|
+
else
|
62
|
+
Terminal.new(value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def determine_gaps(outer, inner)
|
67
|
+
diff = outer - inner
|
68
|
+
if INTERNAL_ALIGNMENT == 'left'
|
69
|
+
[0, diff]
|
70
|
+
elsif INTERNAL_ALIGNMENT == 'right'
|
71
|
+
[diff, 0]
|
72
|
+
else
|
73
|
+
[diff / 2, diff / 2]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class DiagramMultiContainer < DiagramItem
|
5
|
+
def initialize(name, items, attrs = nil, text = nil)
|
6
|
+
super(name, attrs:, text:)
|
7
|
+
@items = items.map { |item| wrap_string(item) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(x, y, width)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def walk(callback)
|
15
|
+
callback(self)
|
16
|
+
@items.each { |item| item.walk(callback) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_str
|
20
|
+
"DiagramMultiContainer(#{@name}, #{@items}, #{@attrs}, #{@children})"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class End < DiagramItem
|
5
|
+
def initialize(type = 'simple')
|
6
|
+
super('path')
|
7
|
+
@width = 20
|
8
|
+
@up = 10
|
9
|
+
@down = 10
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"End(type=#{@type})"
|
15
|
+
end
|
16
|
+
|
17
|
+
def format(x, y, _width)
|
18
|
+
@attrs['d'] =
|
19
|
+
if @type == 'simple'
|
20
|
+
"M #{x} #{y} h 20 m -10 -10 v 20 m 10 -20 v 20"
|
21
|
+
else
|
22
|
+
"M #{x} #{y} h 20 m 0 -10 v 20"
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def text_diagram
|
28
|
+
cross, line, tee_left = TextDiagram.get_parts(%w[cross line tee_left])
|
29
|
+
end_node =
|
30
|
+
if @type == 'simple'
|
31
|
+
line + cross + tee_left
|
32
|
+
else
|
33
|
+
line + tee_left
|
34
|
+
end
|
35
|
+
|
36
|
+
TextDiagram.new(0, 0, [end_node])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class Group < DiagramItem
|
5
|
+
def initialize(item, label = nil)
|
6
|
+
super('g')
|
7
|
+
@item = wrap_string(item)
|
8
|
+
|
9
|
+
@label =
|
10
|
+
if label.is_a?(DiagramItem)
|
11
|
+
label
|
12
|
+
elsif label
|
13
|
+
Comment.new(label)
|
14
|
+
end
|
15
|
+
|
16
|
+
item_width = @item.width + (@item.needs_space ? 20 : 0)
|
17
|
+
label_width = @label ? @label.width : 0
|
18
|
+
@width = [item_width, label_width, AR * 2].max
|
19
|
+
|
20
|
+
@height = @item.height
|
21
|
+
|
22
|
+
@box_up = [@item.up + VS, AR].max
|
23
|
+
@up = @box_up
|
24
|
+
@up += @label.up + @label.height + @label.down if @label
|
25
|
+
|
26
|
+
@down = [@item.down + VS, AR].max
|
27
|
+
|
28
|
+
@needs_space = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
"Group(#{@item}, label=#{@label})"
|
33
|
+
end
|
34
|
+
|
35
|
+
def format(x, y, width)
|
36
|
+
left_gap, right_gap = determine_gaps(width, @width)
|
37
|
+
Path.new(x, y).h(left_gap).add(self)
|
38
|
+
Path.new(x + left_gap + @width, y + @height).h(right_gap).add(self)
|
39
|
+
x += left_gap
|
40
|
+
|
41
|
+
DiagramItem.new(
|
42
|
+
'rect',
|
43
|
+
attrs: {
|
44
|
+
'x' => x,
|
45
|
+
'y' => y - @box_up,
|
46
|
+
'width' => @width,
|
47
|
+
'height' => @height + @box_up + @down,
|
48
|
+
'rx' => AR,
|
49
|
+
'ry' => AR,
|
50
|
+
'class' => 'group-box'
|
51
|
+
}
|
52
|
+
).add(self)
|
53
|
+
|
54
|
+
@item.format(x, y, @width).add(self)
|
55
|
+
@label.format(x, y - (@box_up + @label.down + @label.height), @width).add(self) if @label
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def walk(callback)
|
61
|
+
callback.call(self)
|
62
|
+
item.walk(callback)
|
63
|
+
label&.walk(callback)
|
64
|
+
end
|
65
|
+
|
66
|
+
def text_diagram
|
67
|
+
diagram_td = TextDiagram.round_rect(@item.text_diagram, dashed: true)
|
68
|
+
if @label
|
69
|
+
label_td = @label.text_diagram
|
70
|
+
diagram_td = label_td.append_below(diagram_td, [], move_entry: true, move_exit: true).expand(0, 0, 1, 0)
|
71
|
+
end
|
72
|
+
diagram_td
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailroadDiagrams
|
4
|
+
class HorizontalChoice < DiagramMultiContainer
|
5
|
+
def self.new(*items)
|
6
|
+
return Sequence.new(*items) if items.size <= 1
|
7
|
+
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(*items)
|
12
|
+
super('g', items)
|
13
|
+
all_but_last = @items[0...-1]
|
14
|
+
middles = @items[1...-1]
|
15
|
+
first = @items.first
|
16
|
+
last = @items.last
|
17
|
+
@needs_space = false
|
18
|
+
|
19
|
+
@width =
|
20
|
+
AR + # starting track
|
21
|
+
(AR * 2 * (@items.size - 1)) + # inbetween tracks
|
22
|
+
@items.sum { |x| x.width + (x.needs_space ? 20 : 0) } + # items
|
23
|
+
(last.height > 0 ? AR : 0) + # needs space to curve up
|
24
|
+
AR # ending track
|
25
|
+
|
26
|
+
# Always exits at entrance height
|
27
|
+
@height = 0
|
28
|
+
|
29
|
+
# All but the last have a track running above them
|
30
|
+
@upper_track = [AR * 2, VS, all_but_last.map(&:up).max + VS].max
|
31
|
+
@up = [@upper_track, last.up].max
|
32
|
+
|
33
|
+
# All but the first have a track running below them
|
34
|
+
# Last either straight-lines or curves up, so has different calculation
|
35
|
+
@lower_track = [
|
36
|
+
VS,
|
37
|
+
middles.any? ? middles.map { |x| x.height + [x.down + VS, AR * 2].max }.max : 0,
|
38
|
+
last.height + last.down + VS
|
39
|
+
].max
|
40
|
+
if first.height < @lower_track
|
41
|
+
# Make sure there's at least 2*AR room between first exit and lower track
|
42
|
+
@lower_track = [@lower_track, first.height + (AR * 2)].max
|
43
|
+
end
|
44
|
+
@down = [@lower_track, first.height + first.down].max
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
items = @items.map(&:to_s).join(', ')
|
49
|
+
"HorizontalChoice(#{items})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def format(x, y, width)
|
53
|
+
# Hook up the two sides if self is narrower than its stated width.
|
54
|
+
left_gap, right_gap = determine_gaps(width, @width)
|
55
|
+
Path.new(x, y).h(left_gap).add(self)
|
56
|
+
Path.new(x + left_gap + @width, y + @height).h(right_gap).add(self)
|
57
|
+
x += left_gap
|
58
|
+
|
59
|
+
first = @items.first
|
60
|
+
last = @items.last
|
61
|
+
|
62
|
+
# upper track
|
63
|
+
upper_span =
|
64
|
+
@items[0...-1].sum { |item| item.width + (item.needs_space ? 20 : 0) } +
|
65
|
+
((@items.size - 2) * AR * 2) -
|
66
|
+
AR
|
67
|
+
|
68
|
+
Path.new(x, y)
|
69
|
+
.arc('se')
|
70
|
+
.up(@upper_track - (AR * 2))
|
71
|
+
.arc('wn')
|
72
|
+
.h(upper_span)
|
73
|
+
.add(self)
|
74
|
+
|
75
|
+
# lower track
|
76
|
+
lower_span =
|
77
|
+
@items[1..].sum { |item| item.width + (item.needs_space ? 20 : 0) } +
|
78
|
+
((@items.size - 2) * AR * 2) +
|
79
|
+
(last.height.positive? ? AR : 0) -
|
80
|
+
AR
|
81
|
+
|
82
|
+
lower_start = x + AR + first.width + (first.needs_space ? 20 : 0) + (AR * 2)
|
83
|
+
|
84
|
+
Path.new(lower_start, y + @lower_track)
|
85
|
+
.h(lower_span)
|
86
|
+
.arc('se')
|
87
|
+
.up(@lower_track - (AR * 2))
|
88
|
+
.arc('wn')
|
89
|
+
.add(self)
|
90
|
+
|
91
|
+
# Items
|
92
|
+
@items.each_with_index do |item, i|
|
93
|
+
# input track
|
94
|
+
if i.zero?
|
95
|
+
Path.new(x, y)
|
96
|
+
.h(AR)
|
97
|
+
.add(self)
|
98
|
+
x += AR
|
99
|
+
else
|
100
|
+
Path.new(x, y - @upper_track)
|
101
|
+
.arc('ne')
|
102
|
+
.v(@upper_track - (AR * 2))
|
103
|
+
.arc('ws')
|
104
|
+
.add(self)
|
105
|
+
x += AR * 2
|
106
|
+
end
|
107
|
+
|
108
|
+
# item
|
109
|
+
item_width = item.width + (item.needs_space ? 20 : 0)
|
110
|
+
item.format(x, y, item_width).add(self)
|
111
|
+
x += item_width
|
112
|
+
|
113
|
+
# output track
|
114
|
+
if i == @items.size - 1
|
115
|
+
if item.height.zero?
|
116
|
+
Path.new(x, y).h(AR).add(self)
|
117
|
+
else
|
118
|
+
Path.new(x, y + item.height).arc('se').add(self)
|
119
|
+
end
|
120
|
+
elsif i.zero? && item.height > @lower_track
|
121
|
+
# Needs to arc up to meet the lower track, not down.
|
122
|
+
if item.height - @lower_track >= AR * 2
|
123
|
+
Path.new(x, y + item.height)
|
124
|
+
.arc('se')
|
125
|
+
.v(@lower_track - item.height + (AR * 2))
|
126
|
+
.arc('wn')
|
127
|
+
.add(self)
|
128
|
+
else
|
129
|
+
# Not enough space to fit two arcs
|
130
|
+
# so just bail and draw a straight line for now.
|
131
|
+
Path.new(x, y + item.height)
|
132
|
+
.l(AR * 2, @lower_track - item.height)
|
133
|
+
.add(self)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
Path.new(x, y + item.height)
|
137
|
+
.arc('ne')
|
138
|
+
.v(@lower_track - item.height - (AR * 2))
|
139
|
+
.arc('ws')
|
140
|
+
.add(self)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def text_diagram
|
147
|
+
line, line_vertical, roundcorner_bot_left, roundcorner_bot_right,
|
148
|
+
roundcorner_top_left, roundcorner_top_right = TextDiagram.get_parts(
|
149
|
+
%w[line line_vertical roundcorner_bot_left roundcorner_bot_right roundcorner_top_left
|
150
|
+
roundcorner_top_right]
|
151
|
+
)
|
152
|
+
|
153
|
+
# Format all the child items, so we can know the maximum entry, exit, and height.
|
154
|
+
item_tds = @items.map(&:text_diagram)
|
155
|
+
|
156
|
+
# diagram_entry: distance from top to lowest entry, aka distance from top to diagram entry, aka final diagram entry and exit.
|
157
|
+
diagram_entry = item_tds.map(&:entry).max
|
158
|
+
# soil_to_baseline: distance from top to lowest entry before rightmost item, aka distance from skip-over-items line to rightmost entry, aka SOIL height.
|
159
|
+
soil_to_baseline = item_tds[0..-2].map(&:entry).max || 0
|
160
|
+
# top_to_soil: distance from top to skip-over-items line.
|
161
|
+
top_to_soil = diagram_entry - soil_to_baseline
|
162
|
+
# baseline_to_suil: distance from lowest entry or exit after leftmost item to bottom, aka distance from entry to skip-under-items line, aka SUIL height.
|
163
|
+
baseline_to_suil = item_tds[1..-1].map { |td| td.height - [td.entry, td.exit].min }.max.to_i - 1
|
164
|
+
|
165
|
+
# The diagram starts with a line from its entry up to skip-over-items line:
|
166
|
+
lines = Array.new(top_to_soil, ' ')
|
167
|
+
lines << (roundcorner_top_left + line)
|
168
|
+
lines += Array.new(soil_to_baseline, line_vertical + ' ')
|
169
|
+
lines << (roundcorner_bot_right + line)
|
170
|
+
|
171
|
+
diagram_td = TextDiagram.new(lines.size - 1, lines.size - 1, lines)
|
172
|
+
|
173
|
+
item_tds.each_with_index do |item_td, item_num|
|
174
|
+
if item_num > 0
|
175
|
+
# All items except the leftmost start with a line from the skip-over-items line down to their entry,
|
176
|
+
# with a joining-line across at the skip-under-items line:
|
177
|
+
lines = Array.new(top_to_soil, ' ')
|
178
|
+
# All such items except the rightmost also have a continuation of the skip-over-items line:
|
179
|
+
line_to_next_item = item_num == item_tds.size - 1 ? ' ' : line
|
180
|
+
lines << (roundcorner_top_right + line_to_next_item)
|
181
|
+
lines += Array.new(soil_to_baseline, line_vertical + ' ')
|
182
|
+
lines << (roundcorner_bot_left + line)
|
183
|
+
lines += Array.new(baseline_to_suil, ' ')
|
184
|
+
lines << (line * 2)
|
185
|
+
|
186
|
+
entry_td = TextDiagram.new(diagram_td.exit, diagram_td.exit, lines)
|
187
|
+
diagram_td = diagram_td.append_right(entry_td, '')
|
188
|
+
end
|
189
|
+
|
190
|
+
part_td = TextDiagram.new(0, 0, [])
|
191
|
+
|
192
|
+
if item_num < item_tds.size - 1
|
193
|
+
# All items except the rightmost start with a segment of the skip-over-items line at the top.
|
194
|
+
# followed by enough blank lines to push their entry down to the previous item's exit:
|
195
|
+
lines = []
|
196
|
+
lines << (line * item_td.width)
|
197
|
+
lines += Array.new(soil_to_baseline - item_td.entry, ' ' * item_td.width)
|
198
|
+
soil_segment = TextDiagram.new(0, 0, lines)
|
199
|
+
part_td = part_td.append_below(soil_segment, [])
|
200
|
+
end
|
201
|
+
|
202
|
+
part_td = part_td.append_below(item_td, [], move_entry: true, move_exit: true)
|
203
|
+
|
204
|
+
if item_num > 0
|
205
|
+
# All items except the leftmost end with enough blank lines to pad down to the skip-under-items
|
206
|
+
# line, followed by a segment of the skip-under-items line:
|
207
|
+
lines = Array.new(baseline_to_suil - (item_td.height - item_td.entry) + 1, ' ' * item_td.width)
|
208
|
+
lines << (line * item_td.width)
|
209
|
+
suil_segment = TextDiagram.new(0, 0, lines)
|
210
|
+
part_td = part_td.append_below(suil_segment, [])
|
211
|
+
end
|
212
|
+
|
213
|
+
diagram_td = diagram_td.append_right(part_td, '')
|
214
|
+
|
215
|
+
if item_num < item_tds.size - 1
|
216
|
+
# All items except the rightmost have a line from their exit down to the skip-under-items line,
|
217
|
+
# with a joining-line across at the skip-over-items line:
|
218
|
+
lines = Array.new(top_to_soil, ' ')
|
219
|
+
lines << (line * 2)
|
220
|
+
lines += Array.new(diagram_td.exit - top_to_soil - 1, ' ')
|
221
|
+
lines << (line + roundcorner_top_right)
|
222
|
+
lines += Array.new(baseline_to_suil - (diagram_td.exit - diagram_td.entry), ' ' + line_vertical)
|
223
|
+
line_from_prev_item = item_num > 0 ? line : ' '
|
224
|
+
lines << (line_from_prev_item + roundcorner_bot_left)
|
225
|
+
|
226
|
+
entry = diagram_entry + 1 + (diagram_td.exit - diagram_td.entry)
|
227
|
+
exit_td = TextDiagram.new(entry, diagram_entry + 1, lines)
|
228
|
+
diagram_td = diagram_td.append_right(exit_td, '')
|
229
|
+
else
|
230
|
+
# The rightmost item has a line from the skip-under-items line and from its exit up to the diagram exit:
|
231
|
+
lines = []
|
232
|
+
line_from_exit = diagram_td.exit == diagram_td.entry ? line : ' '
|
233
|
+
lines << (line_from_exit + roundcorner_top_left)
|
234
|
+
lines += Array.new(diagram_td.exit - diagram_td.entry - 1, ' ' + line_vertical)
|
235
|
+
lines << (line + roundcorner_bot_right) if diagram_td.exit != diagram_td.entry
|
236
|
+
lines += Array.new(baseline_to_suil - (diagram_td.exit - diagram_td.entry), ' ' + line_vertical)
|
237
|
+
lines << (line + roundcorner_bot_right)
|
238
|
+
|
239
|
+
exit_td = TextDiagram.new(diagram_td.exit - diagram_td.entry, 0, lines)
|
240
|
+
diagram_td = diagram_td.append_right(exit_td, '')
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
diagram_td
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|