1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-27 13:48:49 +08:00

Skeleton for function that draws lines.

- tests.
- testdraw wrapper.
- basic functionality that just draws lines, not crossings yet.
This commit is contained in:
Jakub Sobon 2018-06-24 21:49:30 -04:00
parent 31f3862524
commit 1bc6a7ccd4
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
4 changed files with 880 additions and 1 deletions

179
draw/hv_line.go Normal file
View File

@ -0,0 +1,179 @@
package draw
// hv_line.go contains code that draws horizontal and vertical lines.
import (
"fmt"
"image"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/cell"
)
// HVLineOption is used to provide options to HVLine().
type HVLineOption interface {
// set sets the provided option.
set(*hVLineOptions)
}
// hVLineOptions stores the provided options.
type hVLineOptions struct {
cellOpts []cell.Option
lineStyle LineStyle
}
// hVLineOption implements HVLineOption.
type hVLineOption func(*hVLineOptions)
// set implements HVLineOption.set.
func (o hVLineOption) set(opts *hVLineOptions) {
o(opts)
}
// DefaultHVLineStyle is the default value for the HVLineStyle option.
const DefaultHVLineStyle = LineStyleLight
// HVLineStyle sets the style of the line.
// Defaults to DefaultHVLineStyle.
func HVLineStyle(ls LineStyle) HVLineOption {
return hVLineOption(func(opts *hVLineOptions) {
opts.lineStyle = ls
})
}
// HVLineCellOpts sets options on the cells that contain the line.
// Where two lines cross, the cell representing the crossing point inherits
// options set on the line that was drawn last.
func HVLineCellOpts(cOpts ...cell.Option) HVLineOption {
return hVLineOption(func(opts *hVLineOptions) {
opts.cellOpts = cOpts
})
}
// HVLine represents one horizontal or vertical line.
type HVLine struct {
start image.Point
end image.Point
}
// HVLines draws horizontal or vertical lines. Handles drawing of the correct
// characters for locations where any two lines cross (e.g. a corner, a T shape
// a cross). Each line must be at least one cell long. Both start and end
// must be on the same horizontal (same X coordinate) or same vertical (same Y
// coordinate) line.
func HVLines(c *canvas.Canvas, lines []HVLine, opts ...HVLineOption) error {
for _, l := range lines {
line, err := newHVLine(c, l.start, l.end, opts...)
if err != nil {
return err
}
switch {
case line.horizontal():
for curX := line.start.X; ; curX++ {
cur := image.Point{curX, line.start.Y}
if _, err := c.SetCell(cur, line.mainPart, line.opts.cellOpts...); err != nil {
return err
}
if curX == line.end.X {
break
}
}
case line.vertical():
for curY := line.start.Y; ; curY++ {
cur := image.Point{line.start.X, curY}
if _, err := c.SetCell(cur, line.mainPart, line.opts.cellOpts...); err != nil {
return err
}
if curY == line.end.Y {
break
}
}
}
}
return nil
}
// hVLine represents a line that will be drawn on the canvas.
type hVLine struct {
// start is the starting point of the line.
start image.Point
// end is the ending point of the line.
end image.Point
// parts are characters that represent parts of the line of the style
// chosen in the options.
parts map[linePart]rune
// mainPart is either parts[vLine] or parts[hLine] depending on whether
// this is horizontal or vertical line.
mainPart rune
// opts are the options provided in a call to HVLine().
opts *hVLineOptions
}
// newHVLine creates a new hVLine instance.
// Swaps start and end iof necessary, so that horizontal drawing is always left
// to right and vertical is always top down.
func newHVLine(c *canvas.Canvas, start, end image.Point, opts ...HVLineOption) (*hVLine, error) {
if ar := c.Area(); !start.In(ar) || !end.In(ar) {
return nil, fmt.Errorf("both the start%v and the end%v must be in the canvas area: %v", start, end, ar)
}
opt := &hVLineOptions{
lineStyle: DefaultHVLineStyle,
}
for _, o := range opts {
o.set(opt)
}
parts, err := lineParts(opt.lineStyle)
if err != nil {
return nil, err
}
var mainPart rune
switch {
case start.X != end.X && start.Y != end.Y:
return nil, fmt.Errorf("can only draw horizontal (same X coordinates) or vertical (same Y coordinates), got start:%v end:%v", start, end)
case start.X == end.X && start.Y == end.Y:
return nil, fmt.Errorf("the line must at least one cell long, got start%v, end%v", start, end)
case start.X == end.X:
mainPart = parts[vLine]
if start.Y > end.Y {
start, end = end, start
}
case start.Y == end.Y:
mainPart = parts[hLine]
if start.X > end.X {
start, end = end, start
}
}
return &hVLine{
start: start,
end: end,
parts: parts,
mainPart: mainPart,
opts: opt,
}, nil
}
// horizontal determines if this is a horizontal line.
func (hvl *hVLine) horizontal() bool {
return hvl.mainPart == hvl.parts[hLine]
}
// vertical determines if this is a vertical line.
func (hvl *hVLine) vertical() bool {
return hvl.mainPart == hvl.parts[vLine]
}

659
draw/hv_line_test.go Normal file
View File

@ -0,0 +1,659 @@
package draw
import (
"image"
"testing"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/testcanvas"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/terminal/faketerm"
)
func TestHVLines(t *testing.T) {
tests := []struct {
desc string
canvas image.Rectangle // Size of the canvas for the test.
lines []HVLine
opts []HVLineOption
want func(size image.Point) *faketerm.Terminal
wantErr bool
}{
{
desc: "fails when line isn't horizontal or vertical",
canvas: image.Rect(0, 0, 1, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{1, 1},
},
},
want: func(size image.Point) *faketerm.Terminal {
return faketerm.MustNew(size)
},
wantErr: true,
},
{
desc: "fails when start isn't in the canvas",
canvas: image.Rect(0, 0, 1, 1),
lines: []HVLine{
{
start: image.Point{2, 0},
end: image.Point{0, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
return faketerm.MustNew(size)
},
wantErr: true,
},
{
desc: "fails when end isn't in the canvas",
canvas: image.Rect(0, 0, 1, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
return faketerm.MustNew(size)
},
wantErr: true,
},
{
desc: "fails when the line has zero length",
canvas: image.Rect(0, 0, 1, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
return faketerm.MustNew(size)
},
wantErr: true,
},
{
desc: "draws single horizontal line",
canvas: image.Rect(0, 0, 3, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "respects line style set explicitly",
canvas: image.Rect(0, 0, 3, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
},
opts: []HVLineOption{
HVLineStyle(LineStyleLight),
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "respects cell options",
canvas: image.Rect(0, 0, 3, 1),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
},
opts: []HVLineOption{
HVLineCellOpts(
cell.FgColor(cell.ColorYellow),
cell.BgColor(cell.ColorBlue),
),
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine],
cell.FgColor(cell.ColorYellow),
cell.BgColor(cell.ColorBlue),
)
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine],
cell.FgColor(cell.ColorYellow),
cell.BgColor(cell.ColorBlue),
)
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine],
cell.FgColor(cell.ColorYellow),
cell.BgColor(cell.ColorBlue),
)
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws single horizontal line, supplied in reverse direction",
canvas: image.Rect(0, 0, 3, 1),
lines: []HVLine{
{
start: image.Point{1, 0},
end: image.Point{0, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws single vertical line",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws single vertical line, supplied in reverse direction",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{1, 1},
end: image.Point{1, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "parallel horizontal lines don't affect each other",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
{
start: image.Point{0, 1},
end: image.Point{2, 1},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "parallel vertical lines don't affect each other",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "perpendicular lines that don't cross don't affect each other",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
{
start: image.Point{1, 1},
end: image.Point{2, 1},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws top left corner",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[topLeftCorner])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws top right corner",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{2, 0},
end: image.Point{2, 2},
},
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[topRightCorner])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws bottom left corner",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
{
start: image.Point{0, 2},
end: image.Point{2, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[bottomLeftCorner])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws bottom right corner",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{2, 0},
end: image.Point{2, 2},
},
{
start: image.Point{0, 2},
end: image.Point{2, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[bottomRightCorner])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws T horizontal and up",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 2},
end: image.Point{2, 2},
},
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hAndUp])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws T horizontal and down",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hAndDown])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws T vertical and left",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 1},
end: image.Point{2, 1},
},
{
start: image.Point{2, 0},
end: image.Point{2, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vAndLeft])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws T vertical and right",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 1},
end: image.Point{2, 1},
},
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vAndRight])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws a cross",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
{
start: image.Point{0, 1},
end: image.Point{2, 1},
},
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vAndH])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[hLine])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[vLine])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[vLine])
testcanvas.MustApply(c, ft)
return ft
},
},
{
desc: "draws multiple crossings",
canvas: image.Rect(0, 0, 3, 3),
lines: []HVLine{
// Three horizontal lines.
{
start: image.Point{0, 0},
end: image.Point{2, 0},
},
{
start: image.Point{0, 1},
end: image.Point{2, 1},
},
{
start: image.Point{0, 2},
end: image.Point{2, 2},
},
// Three vertical lines.
{
start: image.Point{0, 0},
end: image.Point{0, 2},
},
{
start: image.Point{1, 0},
end: image.Point{1, 2},
},
{
start: image.Point{2, 0},
end: image.Point{2, 2},
},
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
c := testcanvas.MustNew(ft.Area())
parts := lineStyleChars[LineStyleLight]
testcanvas.MustSetCell(c, image.Point{0, 0}, parts[topLeftCorner])
testcanvas.MustSetCell(c, image.Point{1, 0}, parts[hAndDown])
testcanvas.MustSetCell(c, image.Point{2, 0}, parts[topRightCorner])
testcanvas.MustSetCell(c, image.Point{0, 1}, parts[vAndRight])
testcanvas.MustSetCell(c, image.Point{1, 1}, parts[vAndH])
testcanvas.MustSetCell(c, image.Point{2, 1}, parts[vAndLeft])
testcanvas.MustSetCell(c, image.Point{0, 2}, parts[bottomLeftCorner])
testcanvas.MustSetCell(c, image.Point{1, 2}, parts[hAndUp])
testcanvas.MustSetCell(c, image.Point{2, 2}, parts[bottomRightCorner])
testcanvas.MustApply(c, ft)
return ft
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
c, err := canvas.New(tc.canvas)
if err != nil {
t.Fatalf("canvas.New => unexpected error: %v", err)
}
err = HVLines(c, tc.lines, tc.opts...)
if (err != nil) != tc.wantErr {
t.Errorf("HVLines => unexpected error: %v, wantErr: %v", err, tc.wantErr)
}
if err != nil {
return
}
got, err := faketerm.New(c.Size())
if err != nil {
t.Fatalf("faketerm.New => unexpected error: %v", err)
}
if err := c.Apply(got); err != nil {
t.Fatalf("Apply => unexpected error: %v", err)
}
if diff := faketerm.Diff(tc.want(c.Size()), got); diff != "" {
t.Errorf("HVLines => %v", diff)
}
})
}
}

View File

@ -14,12 +14,17 @@
package draw
import "fmt"
import (
"fmt"
runewidth "github.com/mattn/go-runewidth"
)
// line_style.go contains the Unicode characters used for drawing lines of
// different styles.
// lineStyleChars maps the line styles to the corresponding component characters.
// Source: http://en.wikipedia.org/wiki/Box-drawing_character.
var lineStyleChars = map[LineStyle]map[linePart]rune{
LineStyleLight: {
hLine: '─',
@ -28,9 +33,28 @@ var lineStyleChars = map[LineStyle]map[linePart]rune{
topRightCorner: '┐',
bottomLeftCorner: '└',
bottomRightCorner: '┘',
hAndUp: '┴',
hAndDown: '┬',
vAndLeft: '┤',
vAndRight: '├',
vAndH: '┼',
},
}
// init verifies that all line parts are half-width runes (occupy only one
// cell).
func init() {
for ls, parts := range lineStyleChars {
for part, r := range parts {
if got := runewidth.RuneWidth(r); got > 1 {
panic(fmt.Errorf("line style %v line part %v is a rune %c with width %v, all parts must be half-width runes (width of one)", ls, part, r, got))
}
}
}
}
// TODO(mum4k): Check inside init() that all of these are half-width runes.
// lineParts returns the line component characters for the provided line style.
func lineParts(ls LineStyle) (map[linePart]rune, error) {
parts, ok := lineStyleChars[ls]
@ -80,6 +104,11 @@ var linePartNames = map[linePart]string{
topRightCorner: "linePartTopRightCorner",
bottomLeftCorner: "linePartBottomLeftCorner",
bottomRightCorner: "linePartBottomRightCorner",
hAndUp: "linePartHAndUp",
hAndDown: "linePartHAndDown",
vAndLeft: "linePartVAndLeft",
vAndRight: "linePartVAndRight",
vAndH: "linePartVAndH",
}
const (
@ -89,4 +118,9 @@ const (
topRightCorner
bottomLeftCorner
bottomRightCorner
hAndUp
hAndDown
vAndLeft
vAndRight
vAndH
)

View File

@ -43,3 +43,10 @@ func MustRectangle(c *canvas.Canvas, r image.Rectangle, opts ...draw.RectangleOp
panic(fmt.Sprintf("draw.Rectangle => unexpected error: %v", err))
}
}
// MustHVLines draws the vertical / horizontal lines or panics.
func MustHVLines(c *canvas.Canvas, lines []draw.HVLine, opts ...draw.HVLineOption) {
if err := draw.HVLines(c, lines, opts...); err != nil {
panic(fmt.Sprintf("draw.HVLines => unexpected error: %v", err))
}
}