SandpointsGitHook/vendor/github.com/bep/goat/canvas.go

1160 lines
24 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package goat
import (
"bufio"
"bytes"
"io"
"sort"
)
// Characters where more than one line segment can come together.
var jointRunes = []rune{'.', '\'', '+', '*', 'o'}
var reservedRunes = map[rune]bool{
'-': true,
'_': true,
'|': true,
'v': true,
'^': true,
'>': true,
'<': true,
'o': true,
'*': true,
'+': true,
'.': true,
'\'': true,
'/': true,
'\\': true,
')': true,
'(': true,
' ': true,
}
func contains(in []rune, r rune) bool {
for _, v := range in {
if r == v {
return true
}
}
return false
}
func isJoint(r rune) bool {
return contains(jointRunes, r)
}
func isDot(r rune) bool {
return r == 'o' || r == '*'
}
func isTriangle(r rune) bool {
return r == '^' || r == 'v' || r == '<' || r == '>'
}
// Canvas represents a 2D ASCII rectangle.
type Canvas struct {
Width int
Height int
data map[Index]rune
text map[Index]rune
}
func (c *Canvas) String() string {
var buffer bytes.Buffer
for h := 0; h < c.Height; h++ {
for w := 0; w < c.Width; w++ {
idx := Index{w, h}
// Grab from our text buffer and if nothing's there try the data
// buffer.
r := c.text[idx]
if r == 0 {
r = c.runeAt(idx)
}
_, err := buffer.WriteRune(r)
if err != nil {
continue
}
}
err := buffer.WriteByte('\n')
if err != nil {
continue
}
}
return buffer.String()
}
func (c *Canvas) heightScreen() int {
return c.Height*16 + 8 + 1
}
func (c *Canvas) widthScreen() int {
return (c.Width + 1) * 8
}
func (c *Canvas) runeAt(i Index) rune {
if val, ok := c.data[i]; ok {
return val
}
return ' '
}
// NewCanvas creates a new canvas with contents read from the given io.Reader.
// Content should be newline delimited.
func NewCanvas(in io.Reader) Canvas {
width := 0
height := 0
scanner := bufio.NewScanner(in)
data := make(map[Index]rune)
for scanner.Scan() {
line := scanner.Text()
w := 0
// Can't use index here because it corresponds to unicode offsets
// instead of logical characters.
for _, c := range line {
idx := Index{x: w, y: height}
data[idx] = rune(c)
w++
}
if w > width {
width = w
}
height++
}
text := make(map[Index]rune)
c := Canvas{
Width: width,
Height: height,
data: data,
text: text,
}
// Extract everything we detect as text to make diagram parsing easier.
for idx := range leftRight(width, height) {
if c.isText(idx) {
c.text[idx] = c.runeAt(idx)
}
}
for idx := range c.text {
delete(c.data, idx)
}
return c
}
// Drawable represents anything that can Draw itself.
type Drawable interface {
Draw(out io.Writer)
}
// Line represents a straight segment between two points.
type Line struct {
start Index
stop Index
// dashed bool
needsNudgingDown bool
needsNudgingLeft bool
needsNudgingRight bool
needsTinyNudgingLeft bool
needsTinyNudgingRight bool
// This is a line segment all by itself. This centers the segment around
// the midline.
lonely bool
// N or S. Only useful for half steps - chops of this half of the line.
chop Orientation
orientation Orientation
state lineState
}
type lineState int
const (
_Unstarted lineState = iota
_Started
)
func (l *Line) started() bool {
return l.state == _Started
}
func (l *Line) setStart(i Index) {
if l.state == _Unstarted {
l.start = i
l.stop = i
l.state = _Started
}
}
func (l *Line) setStop(i Index) {
if l.state == _Started {
l.stop = i
}
}
func (l *Line) goesSomewhere() bool {
return l.start != l.stop
}
func (l *Line) horizontal() bool {
return l.orientation == E || l.orientation == W
}
func (l *Line) vertical() bool {
return l.orientation == N || l.orientation == S
}
func (l *Line) diagonal() bool {
return l.orientation == NE || l.orientation == SE || l.orientation == SW || l.orientation == NW
}
// Triangle corresponds to "^", "v", "<" and ">" runes in the absence of
// surrounding alphanumerics.
type Triangle struct {
start Index
orientation Orientation
needsNudging bool
}
// Circle corresponds to "o" or "*" runes in the absence of surrounding
// alphanumerics.
type Circle struct {
start Index
bold bool
}
// RoundedCorner corresponds to combinations of "-." or "-'".
type RoundedCorner struct {
start Index
orientation Orientation
}
// Text corresponds to any runes not reserved for diagrams, or reserved runes
// surrounded by alphanumerics.
type Text struct {
start Index
contents string
}
// Bridge correspondes to combinations of "-)-" or "-(-" and is displayed as
// the vertical line "hopping over" the horizontal.
type Bridge struct {
start Index
orientation Orientation
}
// Orientation represents the primary direction that a Drawable is facing.
type Orientation int
const (
NONE Orientation = iota // No orientation; no structure present.
N // North
NE // Northeast
NW // Northwest
S // South
SE // Southeast
SW // Southwest
E // East
W // West
)
func (c *Canvas) WriteSVGBody(dst io.Writer) {
writeBytes(dst, "<g transform='translate(8,16)'>\n")
for _, l := range c.Lines() {
l.Draw(dst)
}
for _, t := range c.Triangles() {
t.Draw(dst)
}
for _, c := range c.RoundedCorners() {
c.Draw(dst)
}
for _, c := range c.Circles() {
c.Draw(dst)
}
for _, b := range c.Bridges() {
b.Draw(dst)
}
for _, t := range c.Text() {
t.Draw(dst)
}
writeBytes(dst, "</g>\n")
}
// Lines returns a slice of all Line drawables that we can detect -- in all
// possible orientations.
func (c *Canvas) Lines() []Line {
horizontalMidlines := c.getLinesForSegment('-')
diagUpLines := c.getLinesForSegment('/')
for i, l := range diagUpLines {
// /_
if c.runeAt(l.start.east()) == '_' {
diagUpLines[i].needsTinyNudgingLeft = true
}
// _
// /
if c.runeAt(l.stop.north()) == '_' {
diagUpLines[i].needsTinyNudgingRight = true
}
// _
// /
if !l.lonely && c.runeAt(l.stop.nEast()) == '_' {
diagUpLines[i].needsTinyNudgingRight = true
}
// _/
if !l.lonely && c.runeAt(l.start.west()) == '_' {
diagUpLines[i].needsTinyNudgingLeft = true
}
// \
// /
if !l.lonely && c.runeAt(l.stop.north()) == '\\' {
diagUpLines[i].needsTinyNudgingRight = true
}
// /
// \
if !l.lonely && c.runeAt(l.start.south()) == '\\' {
diagUpLines[i].needsTinyNudgingLeft = true
}
}
diagDownLines := c.getLinesForSegment('\\')
for i, l := range diagDownLines {
// _\
if c.runeAt(l.stop.west()) == '_' {
diagDownLines[i].needsTinyNudgingRight = true
}
// _
// \
if c.runeAt(l.start.north()) == '_' {
diagDownLines[i].needsTinyNudgingLeft = true
}
// _
// \
if !l.lonely && c.runeAt(l.start.nWest()) == '_' {
diagDownLines[i].needsTinyNudgingLeft = true
}
// \_
if !l.lonely && c.runeAt(l.stop.east()) == '_' {
diagDownLines[i].needsTinyNudgingRight = true
}
// \
// /
if !l.lonely && c.runeAt(l.stop.south()) == '/' {
diagDownLines[i].needsTinyNudgingRight = true
}
// /
// \
if !l.lonely && c.runeAt(l.start.north()) == '/' {
diagDownLines[i].needsTinyNudgingLeft = true
}
}
horizontalBaselines := c.getLinesForSegment('_')
for i, l := range horizontalBaselines {
// TODO: make this nudge an orientation
horizontalBaselines[i].needsNudgingDown = true
// _
// _| |
if c.runeAt(l.stop.sEast()) == '|' || c.runeAt(l.stop.nEast()) == '|' {
horizontalBaselines[i].needsNudgingRight = true
}
// _
// | _|
if c.runeAt(l.start.sWest()) == '|' || c.runeAt(l.start.nWest()) == '|' {
horizontalBaselines[i].needsNudgingLeft = true
}
// _
// _/ \
if c.runeAt(l.stop.east()) == '/' || c.runeAt(l.stop.sEast()) == '\\' {
horizontalBaselines[i].needsTinyNudgingRight = true
}
// _
// \_ /
if c.runeAt(l.start.west()) == '\\' || c.runeAt(l.start.sWest()) == '/' {
horizontalBaselines[i].needsTinyNudgingLeft = true
}
// _\
if c.runeAt(l.stop.east()) == '\\' {
horizontalBaselines[i].needsNudgingRight = true
horizontalBaselines[i].needsTinyNudgingRight = true
}
//
// /_
if c.runeAt(l.start.west()) == '/' {
horizontalBaselines[i].needsNudgingLeft = true
horizontalBaselines[i].needsTinyNudgingLeft = true
}
// _
// /
if c.runeAt(l.stop.south()) == '/' {
horizontalBaselines[i].needsTinyNudgingRight = true
}
// _
// \
if c.runeAt(l.start.south()) == '\\' {
horizontalBaselines[i].needsTinyNudgingLeft = true
}
// _
// '
if c.runeAt(l.start.sWest()) == '\'' {
horizontalBaselines[i].needsNudgingLeft = true
}
// _
// '
if c.runeAt(l.stop.sEast()) == '\'' {
horizontalBaselines[i].needsNudgingRight = true
}
}
verticalLines := c.getLinesForSegment('|')
var lines []Line
lines = append(lines, horizontalMidlines...)
lines = append(lines, horizontalBaselines...)
lines = append(lines, verticalLines...)
lines = append(lines, diagUpLines...)
lines = append(lines, diagDownLines...)
lines = append(lines, c.HalfSteps()...)
return lines
}
func newHalfStep(i Index, chop Orientation) Line {
return Line{
start: i,
stop: i.south(),
lonely: true,
chop: chop,
orientation: S,
}
}
func (c *Canvas) HalfSteps() []Line {
var lines []Line
for idx := range upDown(c.Width, c.Height) {
if o := c.partOfHalfStep(idx); o != NONE {
lines = append(
lines,
newHalfStep(idx, o),
)
}
}
return lines
}
func (c *Canvas) getLinesForSegment(segment rune) []Line {
var iter canvasIterator
var orientation Orientation
var passThroughs []rune
switch segment {
case '-':
iter = leftRight
orientation = E
passThroughs = append(jointRunes, '<', '>', '(', ')')
case '_':
iter = leftRight
orientation = E
passThroughs = append(jointRunes, '|')
case '|':
iter = upDown
orientation = S
passThroughs = append(jointRunes, '^', 'v')
case '/':
iter = diagUp
orientation = NE
passThroughs = append(jointRunes, 'o', '*', '<', '>', '^', 'v', '|')
case '\\':
iter = diagDown
orientation = SE
passThroughs = append(jointRunes, 'o', '*', '<', '>', '^', 'v', '|')
default:
return nil
}
return c.getLines(iter, segment, passThroughs, orientation)
}
// ci: the order that we traverse locations on the canvas.
// segment: the primary character we're tracking for this line.
// passThroughs: characters the line segment is allowed to be drawn underneath
// (without terminating the line).
// orientation: the orientation for this line.
func (c *Canvas) getLines(
ci canvasIterator,
segment rune,
passThroughs []rune,
o Orientation,
) []Line {
var lines []Line
// Helper to throw the current line we're tracking on to the slice and
// start a new one.
snip := func(l Line) Line {
// Only collect lines that actually go somewhere or are isolated
// segments.
if l.goesSomewhere() {
lines = append(lines, l)
}
return Line{orientation: o}
}
currentLine := Line{orientation: o}
lastSeenRune := ' '
for idx := range ci(c.Width+1, c.Height+1) {
r := c.runeAt(idx)
isSegment := r == segment
isPassThrough := contains(passThroughs, r)
isRoundedCorner := c.isRoundedCorner(idx)
isDot := isDot(r)
isTriangle := isTriangle(r)
justPassedThrough := contains(passThroughs, lastSeenRune)
shouldKeep := (isSegment || isPassThrough) && isRoundedCorner == NONE
// This is an edge case where we have a rounded corner... that's also a
// joint... attached to orthogonal line, e.g.:
//
// '+--
// |
//
// TODO: This also depends on the orientation of the corner and our
// line.
// NW / NE line can't go with EW/NS lines, vertical is OK though.
if isRoundedCorner != NONE && o != E && (c.partOfVerticalLine(idx) || c.partOfDiagonalLine(idx)) {
shouldKeep = true
}
// Don't connect | to > for diagonal lines or )) for horizontal lines.
if isPassThrough && justPassedThrough && o != S {
currentLine = snip(currentLine)
}
// Don't connect o to o, + to o, etc. This character is a new pass-through
// so we still want to respect shouldKeep; we just don't want to draw
// the existing line through this cell.
if justPassedThrough && (isDot || isTriangle) {
currentLine = snip(currentLine)
}
switch currentLine.state {
case _Unstarted:
if shouldKeep {
currentLine.setStart(idx)
}
case _Started:
if !shouldKeep {
// Snip the existing line, don't add the current cell to it
// *unless* its a line segment all by itself. If it is, keep a
// record that it's an individual segment because we need to
// adjust later in the / and \ cases.
if !currentLine.goesSomewhere() && lastSeenRune == segment {
if !c.partOfRoundedCorner(currentLine.start) {
currentLine.setStop(idx)
currentLine.lonely = true
}
}
currentLine = snip(currentLine)
} else if isPassThrough {
// Snip the existing line but include the current pass-through
// character because we may be continuing the line.
currentLine.setStop(idx)
currentLine = snip(currentLine)
currentLine.setStart(idx)
} else if shouldKeep {
// Keep the line going and extend it by this character.
currentLine.setStop(idx)
}
}
lastSeenRune = r
}
return lines
}
// Triangles returns a slice of all detectable Triangles.
func (c *Canvas) Triangles() []Drawable {
var triangles []Drawable
o := NONE
for idx := range upDown(c.Width, c.Height) {
needsNudging := false
start := idx
r := c.runeAt(idx)
if !isTriangle(r) {
continue
}
// Identify our orientation and nudge the triangle to touch any
// adjacent walls.
switch r {
case '^':
o = N
// ^ and ^
// / \
if c.runeAt(start.sWest()) == '/' {
o = NE
} else if c.runeAt(start.sEast()) == '\\' {
o = NW
}
case 'v':
o = S
// / and \
// v v
if c.runeAt(start.nEast()) == '/' {
o = SW
} else if c.runeAt(start.nWest()) == '\\' {
o = SE
}
case '<':
o = W
case '>':
o = E
}
// Determine if we need to snap the triangle to something and, if so,
// draw a tail if we need to.
switch o {
case N:
r := c.runeAt(start.north())
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(triangles, newHalfStep(start, N))
}
case NW:
r := c.runeAt(start.nWest())
// Need to draw a tail.
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(
triangles,
Line{
start: start.nWest(),
stop: start,
orientation: SE,
},
)
}
case NE:
r := c.runeAt(start.nEast())
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(
triangles,
Line{
start: start,
stop: start.nEast(),
orientation: NE,
},
)
}
case S:
r := c.runeAt(start.south())
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(triangles, newHalfStep(start, S))
}
case SE:
r := c.runeAt(start.sEast())
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(
triangles,
Line{
start: start,
stop: start.sEast(),
orientation: SE,
},
)
}
case SW:
r := c.runeAt(start.sWest())
if r == '-' || isJoint(r) && !isDot(r) {
needsNudging = true
triangles = append(
triangles,
Line{
start: start.sWest(),
stop: start,
orientation: NE,
},
)
}
case W:
r := c.runeAt(start.west())
if isDot(r) {
needsNudging = true
}
case E:
r := c.runeAt(start.east())
if isDot(r) {
needsNudging = true
}
}
triangles = append(
triangles,
Triangle{
start: start,
orientation: o,
needsNudging: needsNudging,
},
)
}
return triangles
}
// Circles returns a slice of all 'o' and '*' characters not considered text.
func (c *Canvas) Circles() []Circle {
var circles []Circle
for idx := range upDown(c.Width, c.Height) {
// TODO INCOMING
if c.runeAt(idx) == 'o' {
circles = append(circles, Circle{start: idx})
} else if c.runeAt(idx) == '*' {
circles = append(circles, Circle{start: idx, bold: true})
}
}
return circles
}
// RoundedCorners returns a slice of all curvy corners in the diagram.
func (c *Canvas) RoundedCorners() []RoundedCorner {
var corners []RoundedCorner
for idx := range leftRight(c.Width, c.Height) {
if o := c.isRoundedCorner(idx); o != NONE {
corners = append(
corners,
RoundedCorner{start: idx, orientation: o},
)
}
}
return corners
}
// For . and ' characters this will return a non-NONE orientation if the
// character falls on a rounded corner.
func (c *Canvas) isRoundedCorner(i Index) Orientation {
r := c.runeAt(i)
if !isJoint(r) {
return NONE
}
left := i.west()
right := i.east()
lowerLeft := i.sWest()
lowerRight := i.sEast()
upperLeft := i.nWest()
upperRight := i.nEast()
opensUp := r == '\'' || r == '+'
opensDown := r == '.' || r == '+'
dashRight := c.runeAt(right) == '-' || c.runeAt(right) == '+' || c.runeAt(right) == '_' || c.runeAt(upperRight) == '_'
dashLeft := c.runeAt(left) == '-' || c.runeAt(left) == '+' || c.runeAt(left) == '_' || c.runeAt(upperLeft) == '_'
isVerticalSegment := func(i Index) bool {
r := c.runeAt(i)
return r == '|' || r == '+' || r == ')' || r == '(' || isDot(r)
}
// .- or .-
// | +
if opensDown && dashRight && isVerticalSegment(lowerLeft) {
return NW
}
// -. or -. or -. or _. or -.
// | + ) ) o
if opensDown && dashLeft && isVerticalSegment(lowerRight) {
return NE
}
// | or + or | or + or + or_ )
// -' -' +' +' ++ '
if opensUp && dashLeft && isVerticalSegment(upperRight) {
return SE
}
// | or +
// '- '-
if opensUp && dashRight && isVerticalSegment(upperLeft) {
return SW
}
return NONE
}
// A wrapper to enable sorting.
type indexRuneDrawable struct {
i Index
r rune
Drawable
}
// Text returns a slice of all text characters not belonging to part of the diagram.
// How these characters are identified is rather complicated.
func (c *Canvas) Text() []Drawable {
newLine := func(i Index, r rune, o Orientation) Drawable {
stop := i
switch o {
case NE:
stop = i.nEast()
case SE:
stop = i.sEast()
}
l := Line{
start: i,
stop: stop,
lonely: true,
orientation: o,
}
return indexRuneDrawable{
Drawable: l,
i: i,
r: r,
}
}
text := make([]Drawable, len(c.text))
var j int
for i, r := range c.text {
switch r {
// Weird unicode edge cases that markdeep handles. These get
// substituted with lines.
case '':
text[j] = newLine(i, r, NE)
case '╲':
text[j] = newLine(i, r, SE)
case '':
text[j] = newLine(i, r, NE)
default:
text[j] = indexRuneDrawable{Drawable: Text{start: i, contents: string(r)}, i: i, r: r}
}
j++
}
sort.Slice(text, func(i, j int) bool {
ti, tj := text[i].(indexRuneDrawable), text[j].(indexRuneDrawable)
if ti.i.x == tj.i.x {
return ti.i.y < tj.i.y || (ti.i.y == tj.i.y && ti.r < tj.r)
}
return ti.i.x < tj.i.x
})
return text
}
// Bridges returns a slice of all bridges, "-)-" or "-(-".
func (c *Canvas) Bridges() []Drawable {
var bridges []Drawable
for idx := range leftRight(c.Width, c.Height) {
if o := c.isBridge(idx); o != NONE {
bridges = append(
bridges,
newHalfStep(idx.north(), S),
newHalfStep(idx.south(), N),
Bridge{
start: idx,
orientation: o,
},
)
}
}
return bridges
}
// -)- or -(- or
func (c *Canvas) isBridge(i Index) Orientation {
r := c.runeAt(i)
left := c.runeAt(i.west())
right := c.runeAt(i.east())
if left != '-' || right != '-' {
return NONE
}
if r == '(' {
return W
}
if r == ')' {
return E
}
return NONE
}
func (c *Canvas) isText(i Index) bool {
// Short circuit, we already saw this index and called it text.
if _, isText := c.text[i]; isText {
return true
}
if c.runeAt(i) == ' ' {
return false
}
if !c.isReserved(i) {
return true
}
// This is a reserved character with an incoming line (e.g., "|") above it,
// so call it non-text.
if c.hasLineAboveOrBelow(i) {
return false
}
// Reserved characters like "o" or "*" with letters sitting next to them
// are probably text.
// TODO: Fix this to count contiguous blocks of text. If we had a bunch of
// reserved characters previously that were counted as text then this
// should be as well, e.g., "A----B".
// We're reserved but surrounded by text and probably part of an existing
// word. Use a hash lookup on the left to preserve chains of
// reserved-but-text characters like "foo----bar".
if _, textLeft := c.text[i.west()]; textLeft || !c.isReserved(i.east()) {
return true
}
w := i.west()
e := i.east()
if !(c.runeAt(w) == ' ' && c.runeAt(e) == ' ') {
return false
}
// Circles surrounded by whitespace shouldn't be shown as text.
if c.runeAt(i) == 'o' || c.runeAt(i) == '*' {
return false
}
// We're surrounded by whitespace + text on either side.
if !c.isReserved(w.west()) || !c.isReserved(e.east()) {
return true
}
return false
}
// Returns true if the character at this index is not reserved for diagrams.
// Characters like "o" need more context (e.g., are other text characters
// nearby) to determine whether they're part of a diagram.
func (c *Canvas) isReserved(i Index) bool {
r := c.runeAt(i)
_, isReserved := reservedRunes[r]
return isReserved
}
// Returns true if it looks like this character belongs to anything besides a
// horizontal line. This is the context we use to determine if a reserved
// character is text or not.
func (c *Canvas) hasLineAboveOrBelow(i Index) bool {
r := c.runeAt(i)
switch r {
case '*', 'o', '+', 'v', '^':
return c.partOfDiagonalLine(i) || c.partOfVerticalLine(i)
case '|':
return c.partOfVerticalLine(i) || c.partOfRoundedCorner(i)
case '/', '\\':
return c.partOfDiagonalLine(i)
case '-':
return c.partOfRoundedCorner(i)
case '(', ')':
return c.partOfVerticalLine(i)
}
return false
}
// Returns true if a "|" segment passes through this index.
func (c *Canvas) partOfVerticalLine(i Index) bool {
this := c.runeAt(i)
north := c.runeAt(i.north())
south := c.runeAt(i.south())
jointAboveMe := this == '|' && isJoint(north)
if north == '|' || jointAboveMe {
return true
}
jointBelowMe := this == '|' && isJoint(south)
if south == '|' || jointBelowMe {
return true
}
return false
}
// Return true if a "--" segment passes through this index.
func (c *Canvas) partOfHorizontalLine(i Index) bool {
return c.runeAt(i.east()) == '-' || c.runeAt(i.west()) == '-'
}
func (c *Canvas) partOfDiagonalLine(i Index) bool {
r := c.runeAt(i)
n := c.runeAt(i.north())
s := c.runeAt(i.south())
nw := c.runeAt(i.nWest())
se := c.runeAt(i.sEast())
ne := c.runeAt(i.nEast())
sw := c.runeAt(i.sWest())
switch r {
// Diagonal segments can be connected to joint or other segments.
case '/':
return ne == r || sw == r || isJoint(ne) || isJoint(sw) || n == '\\' || s == '\\'
case '\\':
return nw == r || se == r || isJoint(nw) || isJoint(se) || n == '/' || s == '/'
// For everything else just check if we have segments next to us.
default:
return nw == '\\' || ne == '/' || sw == '/' || se == '\\'
}
}
// For "-" and "|" characters returns true if they could be part of a rounded
// corner.
func (c *Canvas) partOfRoundedCorner(i Index) bool {
r := c.runeAt(i)
switch r {
case '-':
dotNext := c.runeAt(i.west()) == '.' || c.runeAt(i.east()) == '.'
hyphenNext := c.runeAt(i.west()) == '\'' || c.runeAt(i.east()) == '\''
return dotNext || hyphenNext
case '|':
dotAbove := c.runeAt(i.nWest()) == '.' || c.runeAt(i.nEast()) == '.'
hyphenBelow := c.runeAt(i.sWest()) == '\'' || c.runeAt(i.sEast()) == '\''
return dotAbove || hyphenBelow
}
return false
}
// TODO: Have this take care of all the vertical line nudging.
func (c *Canvas) partOfHalfStep(i Index) Orientation {
r := c.runeAt(i)
if r != '\'' && r != '.' && r != '|' {
return NONE
}
if c.isRoundedCorner(i) != NONE {
return NONE
}
w := c.runeAt(i.west())
e := c.runeAt(i.east())
n := c.runeAt(i.north())
s := c.runeAt(i.south())
nw := c.runeAt(i.nWest())
ne := c.runeAt(i.nEast())
switch r {
case '\'':
// _ _
// '- -'
if (nw == '_' && e == '-') || (w == '-' && ne == '_') {
return N
}
case '.':
// _.- -._
if (w == '-' && e == '_') || (w == '_' && e == '-') {
return S
}
case '|':
//// _ _
//// | |
if n != '|' && (ne == '_' || nw == '_') {
return N
}
if n == '-' {
return N
}
//// _| |_
if s != '|' && (w == '_' || e == '_') {
return S
}
if s == '-' {
return S
}
}
return NONE
}