-
Notifications
You must be signed in to change notification settings - Fork 3
/
line.go
138 lines (133 loc) · 3.15 KB
/
line.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package asciiart
import (
"strings"
)
// The Line element.
type Line struct {
X1 float64 // x-axis coordinate of the start of the line
Y1 float64 // y-axis coordinate of the start of the line
X2 float64 // x-axis coordinate of the end of the line
Y2 float64 // y-axis coordinate of the end of the line
ArrowStart bool // arrow at the start of the line
ArrowEnd bool // arrow at the end of the line
Dotted bool // line is dotted
}
const (
lineChars = `-|/\:=<>^v`
leftArrows = `<^`
rightArrows = `>v`
dottedChars = `:=`
)
// parseLine tries to parse a line starting at position (starX, startY).
// Since the parsing runs from top to bottom and from left to right at this
// stage we only have to consider 4 possible directions (starting from x):
//
// x-
// /|\
//
// Possible start situations:
//
// -- <- -+ | : ^ ^ | : / ^ / \ ^ \
// | : | : + + / / + \ \ +
// == <= =+
//
func (p *Parser) parseLine(
parent elem,
lines [][]byte,
startX, startY int,
) error {
var l Line
l.X1 = float64(startX)
l.Y1 = float64(startY)
// process start
cell := lines[startY][startX]
switch {
case strings.ContainsAny(string(cell), leftArrows):
l.ArrowStart = true
case strings.ContainsAny(string(cell), rightArrows):
return &ParseError{X: startX, Y: startY, Err: ErrRightArrow}
case strings.ContainsAny(string(cell), dottedChars):
l.Dotted = true
}
// follow line
x := startX
y := startY
cell = lines[y][x]
// for arrows we need a head start
switch cell {
case '<':
x++
case '^':
y++
if y < len(lines) {
if x < len(lines[y]) && (lines[y][x] == '|' || lines[y][x] == ':') {
break
} else if x+1 < len(lines[y]) && lines[y][x+1] == '\\' {
lines[y-1][x] = ' ' // nom nom nom
x++
break
} else if x > 0 && x-1 < len(lines[y]) && lines[y][x-1] == '/' {
lines[y-1][x] = ' ' // nom nom nom
x--
break
}
}
}
if y >= len(lines) || x >= len(lines[y]) ||
!strings.ContainsAny(string(lines[y][x]), lineChars) {
// line starting with arrow is too short, ignore it and parse it as text
return p.parseTextline(parent, lines, startX, startY)
}
forLoop:
for x >= 0 && y < len(lines) && x < len(lines[y]) {
cell := lines[y][x]
lines[y][x] = ' ' // nom nom nom
// save last position
l.X2 = float64(x)
l.Y2 = float64(y)
// move
switch cell {
case '-':
x++
case '=':
l.Dotted = true
x++
case '|':
y++
case ':':
l.Dotted = true
y++
case '/':
x--
y++
case '\\':
x++
y++
case '>', 'v':
l.ArrowEnd = true
break forLoop
case '+':
// switch to polyline parsing
return p.parsePolyline(parent, lines, &l)
case '<', '^':
return &ParseError{X: x, Y: y, Err: ErrLineLeftArrow}
default:
break forLoop
}
}
// check minimum length
if l.X1 == l.X2 && l.Y1 == l.Y2 {
return &ParseError{X: startX, Y: startY, Err: ErrLineTooShort}
}
// scale
l.scale(p)
// add line to parent
parent.addElem(&l)
return nil
}
func (l *Line) scale(p *Parser) {
l.X1 = l.X1*p.xScale + p.xScale/2
l.Y1 = l.Y1*p.yScale + p.yScale/2
l.X2 = l.X2*p.xScale + p.xScale/2
l.Y2 = l.Y2*p.yScale + p.yScale/2
}