mirror of
https://github.com/mum4k/termdash.git
synced 2025-04-28 13:48:51 +08:00
Merge pull request #56 from mum4k/draw-line
Function that draws horizontal and vertical lines.
This commit is contained in:
commit
6cb9f54dda
206
draw/hv_line.go
Normal file
206
draw/hv_line.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright 2018 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHVLineOptions returns a new hVLineOptions instance.
|
||||||
|
func newHVLineOptions() *hVLineOptions {
|
||||||
|
return &hVLineOptions{
|
||||||
|
lineStyle: DefaultHVLineStyle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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 is the cell where the line starts.
|
||||||
|
start image.Point
|
||||||
|
// end is the cell where the line ends.
|
||||||
|
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
|
||||||
|
// or a cross). Each line must be at least two cells 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 {
|
||||||
|
opt := newHVLineOptions()
|
||||||
|
for _, o := range opts {
|
||||||
|
o.set(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := newHVLineGraph()
|
||||||
|
for _, l := range lines {
|
||||||
|
line, err := newHVLine(c, l.start, l.end, opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.addLine(line)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case line.horizontal():
|
||||||
|
for curX := line.start.X; ; curX++ {
|
||||||
|
cur := image.Point{curX, line.start.Y}
|
||||||
|
if _, err := c.SetCell(cur, line.mainPart, opt.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, opt.cellOpts...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if curY == line.end.Y {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range g.multiEdgeNodes() {
|
||||||
|
r, err := n.rune(opt.lineStyle)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.SetCell(n.p, r, opt.cellOpts...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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 if 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 *hVLineOptions) (*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)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts, err := lineParts(opts.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,
|
||||||
|
mainPart: mainPart,
|
||||||
|
opts: opts,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// horizontal determines if this is a horizontal line.
|
||||||
|
func (hvl *hVLine) horizontal() bool {
|
||||||
|
return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][hLine]
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertical determines if this is a vertical line.
|
||||||
|
func (hvl *hVLine) vertical() bool {
|
||||||
|
return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][vLine]
|
||||||
|
}
|
204
draw/hv_line_graph.go
Normal file
204
draw/hv_line_graph.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
// Copyright 2018 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package draw
|
||||||
|
|
||||||
|
// hv_line_graph.go helps to keep track of locations where lines cross.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hVLineEdge is an edge between two points on the graph.
|
||||||
|
type hVLineEdge struct {
|
||||||
|
// from is the starting node of this edge.
|
||||||
|
// From is guaranteed to be less than to.
|
||||||
|
from image.Point
|
||||||
|
|
||||||
|
// to is the ending point of this edge.
|
||||||
|
to image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHVLineEdge returns a new edge between the two points.
|
||||||
|
func newHVLineEdge(from, to image.Point) hVLineEdge {
|
||||||
|
return hVLineEdge{
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hVLineNode represents one node in the graph.
|
||||||
|
// I.e. one cell.
|
||||||
|
type hVLineNode struct {
|
||||||
|
// p is the point where this node is.
|
||||||
|
p image.Point
|
||||||
|
|
||||||
|
// edges are the edges between this node and the surrounding nodes.
|
||||||
|
// The code only supports horizontal and vertical lines so there can only
|
||||||
|
// ever be edges to nodes on these planes.
|
||||||
|
edges map[hVLineEdge]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHVLineNode creates a new newHVLineNode.
|
||||||
|
func newHVLineNode(p image.Point) *hVLineNode {
|
||||||
|
return &hVLineNode{
|
||||||
|
p: p,
|
||||||
|
edges: map[hVLineEdge]bool{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasDown determines if this node has an edge to the one below it.
|
||||||
|
func (n *hVLineNode) hasDown() bool {
|
||||||
|
target := newHVLineEdge(n.p, image.Point{n.p.X, n.p.Y + 1})
|
||||||
|
_, ok := n.edges[target]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasUp determines if this node has an edge to the one above it.
|
||||||
|
func (n *hVLineNode) hasUp() bool {
|
||||||
|
target := newHVLineEdge(image.Point{n.p.X, n.p.Y - 1}, n.p)
|
||||||
|
_, ok := n.edges[target]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasLeft determines if this node has an edge to the next node on the left.
|
||||||
|
func (n *hVLineNode) hasLeft() bool {
|
||||||
|
target := newHVLineEdge(image.Point{n.p.X - 1, n.p.Y}, n.p)
|
||||||
|
_, ok := n.edges[target]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasRight determines if this node has an edge to the next node on the right.
|
||||||
|
func (n *hVLineNode) hasRight() bool {
|
||||||
|
target := newHVLineEdge(n.p, image.Point{n.p.X + 1, n.p.Y})
|
||||||
|
_, ok := n.edges[target]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// rune, given the selected line style returns the correct line character to
|
||||||
|
// represent this node.
|
||||||
|
// Only handles nodes with two or more edges, as returned by multiEdgeNodes().
|
||||||
|
func (n *hVLineNode) rune(ls LineStyle) (rune, error) {
|
||||||
|
parts, err := lineParts(ls)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(n.edges) {
|
||||||
|
case 2:
|
||||||
|
switch {
|
||||||
|
case n.hasLeft() && n.hasRight():
|
||||||
|
return parts[hLine], nil
|
||||||
|
case n.hasUp() && n.hasDown():
|
||||||
|
return parts[vLine], nil
|
||||||
|
case n.hasDown() && n.hasRight():
|
||||||
|
return parts[topLeftCorner], nil
|
||||||
|
case n.hasDown() && n.hasLeft():
|
||||||
|
return parts[topRightCorner], nil
|
||||||
|
case n.hasUp() && n.hasRight():
|
||||||
|
return parts[bottomLeftCorner], nil
|
||||||
|
case n.hasUp() && n.hasLeft():
|
||||||
|
return parts[bottomRightCorner], nil
|
||||||
|
default:
|
||||||
|
return -1, fmt.Errorf("unexpected two edges in node representing point %v: %v", n.p, n.edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
switch {
|
||||||
|
case n.hasUp() && n.hasLeft() && n.hasRight():
|
||||||
|
return parts[hAndUp], nil
|
||||||
|
case n.hasDown() && n.hasLeft() && n.hasRight():
|
||||||
|
return parts[hAndDown], nil
|
||||||
|
case n.hasUp() && n.hasDown() && n.hasRight():
|
||||||
|
return parts[vAndRight], nil
|
||||||
|
case n.hasUp() && n.hasDown() && n.hasLeft():
|
||||||
|
return parts[vAndLeft], nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -1, fmt.Errorf("unexpected three edges in node representing point %v: %v", n.p, n.edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
return parts[vAndH], nil
|
||||||
|
default:
|
||||||
|
return -1, fmt.Errorf("unexpected number of edges(%d) in node representing point %v", len(n.edges), n.p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hVLineGraph represents lines on the canvas as a bidirectional graph of
|
||||||
|
// nodes. Helps to determine the characters that should be used where multiple
|
||||||
|
// lines cross.
|
||||||
|
type hVLineGraph struct {
|
||||||
|
nodes map[image.Point]*hVLineNode
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHVLineGraph creates a new hVLineGraph.
|
||||||
|
func newHVLineGraph() *hVLineGraph {
|
||||||
|
return &hVLineGraph{
|
||||||
|
nodes: make(map[image.Point]*hVLineNode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreateNode gets an existing or creates a new node for the point.
|
||||||
|
func (g *hVLineGraph) getOrCreateNode(p image.Point) *hVLineNode {
|
||||||
|
if n, ok := g.nodes[p]; ok {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
n := newHVLineNode(p)
|
||||||
|
g.nodes[p] = n
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// addLine adds a line to the graph.
|
||||||
|
// This adds edges between all the points on the line.
|
||||||
|
func (g *hVLineGraph) addLine(line *hVLine) {
|
||||||
|
switch {
|
||||||
|
case line.horizontal():
|
||||||
|
for curX := line.start.X; curX < line.end.X; curX++ {
|
||||||
|
from := image.Point{curX, line.start.Y}
|
||||||
|
to := image.Point{curX + 1, line.start.Y}
|
||||||
|
n1 := g.getOrCreateNode(from)
|
||||||
|
n2 := g.getOrCreateNode(to)
|
||||||
|
edge := newHVLineEdge(from, to)
|
||||||
|
n1.edges[edge] = true
|
||||||
|
n2.edges[edge] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case line.vertical():
|
||||||
|
for curY := line.start.Y; curY < line.end.Y; curY++ {
|
||||||
|
from := image.Point{line.start.X, curY}
|
||||||
|
to := image.Point{line.start.X, curY + 1}
|
||||||
|
n1 := g.getOrCreateNode(from)
|
||||||
|
n2 := g.getOrCreateNode(to)
|
||||||
|
edge := newHVLineEdge(from, to)
|
||||||
|
n1.edges[edge] = true
|
||||||
|
n2.edges[edge] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiEdgeNodes returns all nodes that have more than one edge. These are
|
||||||
|
// the nodes where we might need to use different line characters to represent
|
||||||
|
// the crossing of multiple lines.
|
||||||
|
func (g *hVLineGraph) multiEdgeNodes() []*hVLineNode {
|
||||||
|
var nodes []*hVLineNode
|
||||||
|
for _, n := range g.nodes {
|
||||||
|
if len(n.edges) <= 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nodes = append(nodes, n)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
375
draw/hv_line_graph_test.go
Normal file
375
draw/hv_line_graph_test.go
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
// Copyright 2018 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package draw
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
"github.com/mum4k/termdash/canvas"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMultiEdgeNodes(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
lines []HVLine
|
||||||
|
want []*hVLineNode
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no lines added",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "single-edge nodes only",
|
||||||
|
lines: []HVLine{
|
||||||
|
{
|
||||||
|
start: image.Point{0, 0},
|
||||||
|
end: image.Point{0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: image.Point{1, 0},
|
||||||
|
end: image.Point{1, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "lines don't cross",
|
||||||
|
lines: []HVLine{
|
||||||
|
{
|
||||||
|
start: image.Point{0, 0},
|
||||||
|
end: image.Point{0, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: image.Point{1, 0},
|
||||||
|
end: image.Point{1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []*hVLineNode{
|
||||||
|
{
|
||||||
|
p: image.Point{0, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{0, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
p: image.Point{1, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{1, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "lines cross, node has two edges",
|
||||||
|
lines: []HVLine{
|
||||||
|
{
|
||||||
|
start: image.Point{0, 0},
|
||||||
|
end: image.Point{0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: image.Point{0, 0},
|
||||||
|
end: image.Point{1, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []*hVLineNode{
|
||||||
|
{
|
||||||
|
p: image.Point{0, 0},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{1, 0}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "lines cross, node has three edges",
|
||||||
|
lines: []HVLine{
|
||||||
|
{
|
||||||
|
start: image.Point{0, 0},
|
||||||
|
end: image.Point{0, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: image.Point{0, 1},
|
||||||
|
end: image.Point{1, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []*hVLineNode{
|
||||||
|
{
|
||||||
|
p: image.Point{0, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{0, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "lines cross, node has four edges",
|
||||||
|
lines: []HVLine{
|
||||||
|
{
|
||||||
|
start: image.Point{1, 0},
|
||||||
|
end: image.Point{1, 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: image.Point{0, 1},
|
||||||
|
end: image.Point{2, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []*hVLineNode{
|
||||||
|
{
|
||||||
|
p: image.Point{1, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{1, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
c, err := canvas.New(image.Rect(0, 0, 3, 3))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("canvas.New => unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := newHVLineGraph()
|
||||||
|
for i, l := range tc.lines {
|
||||||
|
line, err := newHVLine(c, l.start, l.end, newHVLineOptions())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("newHVLine[%d] => unexpected error: %v", i, err)
|
||||||
|
}
|
||||||
|
g.addLine(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := g.multiEdgeNodes()
|
||||||
|
|
||||||
|
lessFn := func(i, j int) bool {
|
||||||
|
return got[i].p.X < got[j].p.X || got[i].p.Y < got[j].p.Y
|
||||||
|
}
|
||||||
|
sort.Slice(got, lessFn)
|
||||||
|
sort.Slice(tc.want, lessFn)
|
||||||
|
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||||
|
t.Errorf("multiEdgeNodes => unexpected diff (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeRune(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
node *hVLineNode
|
||||||
|
ls LineStyle
|
||||||
|
want rune
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "fails on node with no edges",
|
||||||
|
node: &hVLineNode{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "fails on unsupported two edge combination",
|
||||||
|
node: &hVLineNode{
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "fails on unsupported three edge combination",
|
||||||
|
node: &hVLineNode{
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "fails on unsupported line style",
|
||||||
|
node: &hVLineNode{},
|
||||||
|
ls: LineStyle(-1),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "horizontal line",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{1, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 1}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][hLine],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "vertical line",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{1, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{1, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][vLine],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "top left corner",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{0, 0},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{1, 0}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][topLeftCorner],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "top right corner",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{2, 0},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{2, 0}): true,
|
||||||
|
newHVLineEdge(image.Point{2, 0}, image.Point{2, 1}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][topRightCorner],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bottom left corner",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{0, 2},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{0, 2}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 2}, image.Point{1, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][bottomLeftCorner],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "bottom right corner",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{2, 2},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 2}, image.Point{2, 2}): true,
|
||||||
|
newHVLineEdge(image.Point{2, 1}, image.Point{2, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][bottomRightCorner],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "T horizontal and up",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{1, 2},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{1, 2}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 2}, image.Point{1, 2}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 2}, image.Point{2, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][hAndUp],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "T horizontal and down",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{1, 0},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{1, 0}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{2, 0}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{1, 1}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][hAndDown],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "T vertical and right",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{0, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{0, 0}, image.Point{0, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{0, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][vAndRight],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "T vertical and left",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{2, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{2, 0}, image.Point{2, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{2, 1}, image.Point{2, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][vAndLeft],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "cross",
|
||||||
|
node: &hVLineNode{
|
||||||
|
p: image.Point{1, 1},
|
||||||
|
edges: map[hVLineEdge]bool{
|
||||||
|
newHVLineEdge(image.Point{1, 0}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{0, 1}, image.Point{1, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{2, 1}): true,
|
||||||
|
newHVLineEdge(image.Point{1, 1}, image.Point{1, 2}): true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ls: LineStyleLight,
|
||||||
|
want: lineStyleChars[LineStyleLight][vAndH],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got, err := tc.node.rune(tc.ls)
|
||||||
|
if (err != nil) != tc.wantErr {
|
||||||
|
t.Errorf("rune => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("rune => got %c, want %c", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
673
draw/hv_line_test.go
Normal file
673
draw/hv_line_test.go
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
// Copyright 2018 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
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, 2, 2),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -14,12 +14,17 @@
|
|||||||
|
|
||||||
package draw
|
package draw
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
|
)
|
||||||
|
|
||||||
// line_style.go contains the Unicode characters used for drawing lines of
|
// line_style.go contains the Unicode characters used for drawing lines of
|
||||||
// different styles.
|
// different styles.
|
||||||
|
|
||||||
// lineStyleChars maps the line styles to the corresponding component characters.
|
// 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{
|
var lineStyleChars = map[LineStyle]map[linePart]rune{
|
||||||
LineStyleLight: {
|
LineStyleLight: {
|
||||||
hLine: '─',
|
hLine: '─',
|
||||||
@ -28,14 +33,31 @@ var lineStyleChars = map[LineStyle]map[linePart]rune{
|
|||||||
topRightCorner: '┐',
|
topRightCorner: '┐',
|
||||||
bottomLeftCorner: '└',
|
bottomLeftCorner: '└',
|
||||||
bottomRightCorner: '┘',
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// lineParts returns the line component characters for the provided line style.
|
// lineParts returns the line component characters for the provided line style.
|
||||||
func lineParts(ls LineStyle) (map[linePart]rune, error) {
|
func lineParts(ls LineStyle) (map[linePart]rune, error) {
|
||||||
parts, ok := lineStyleChars[ls]
|
parts, ok := lineStyleChars[ls]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("unsupported line style %v", ls)
|
return nil, fmt.Errorf("unsupported line style %d", ls)
|
||||||
}
|
}
|
||||||
return parts, nil
|
return parts, nil
|
||||||
}
|
}
|
||||||
@ -80,6 +102,11 @@ var linePartNames = map[linePart]string{
|
|||||||
topRightCorner: "linePartTopRightCorner",
|
topRightCorner: "linePartTopRightCorner",
|
||||||
bottomLeftCorner: "linePartBottomLeftCorner",
|
bottomLeftCorner: "linePartBottomLeftCorner",
|
||||||
bottomRightCorner: "linePartBottomRightCorner",
|
bottomRightCorner: "linePartBottomRightCorner",
|
||||||
|
hAndUp: "linePartHAndUp",
|
||||||
|
hAndDown: "linePartHAndDown",
|
||||||
|
vAndLeft: "linePartVAndLeft",
|
||||||
|
vAndRight: "linePartVAndRight",
|
||||||
|
vAndH: "linePartVAndH",
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -89,4 +116,9 @@ const (
|
|||||||
topRightCorner
|
topRightCorner
|
||||||
bottomLeftCorner
|
bottomLeftCorner
|
||||||
bottomRightCorner
|
bottomRightCorner
|
||||||
|
hAndUp
|
||||||
|
hAndDown
|
||||||
|
vAndLeft
|
||||||
|
vAndRight
|
||||||
|
vAndH
|
||||||
)
|
)
|
||||||
|
@ -43,3 +43,10 @@ func MustRectangle(c *canvas.Canvas, r image.Rectangle, opts ...draw.RectangleOp
|
|||||||
panic(fmt.Sprintf("draw.Rectangle => unexpected error: %v", err))
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user