2018-06-25 11:50:52 +08:00
|
|
|
// 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 {
|
2018-06-25 12:07:33 +08:00
|
|
|
// from is the starting node of this edge.
|
|
|
|
// From is guaranteed to be less than to.
|
2018-06-25 11:50:52 +08:00
|
|
|
from image.Point
|
2018-06-25 12:07:33 +08:00
|
|
|
|
|
|
|
// to is the ending point of this edge.
|
|
|
|
to image.Point
|
2018-06-25 11:50:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2018-06-25 12:07:33 +08:00
|
|
|
// 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.
|
2018-06-25 11:50:52 +08:00
|
|
|
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.
|
2018-06-25 12:07:33 +08:00
|
|
|
// Only handles nodes with two or more edges, as returned by multiEdgeNodes().
|
2018-06-25 11:50:52 +08:00
|
|
|
func (n *hVLineNode) rune(ls LineStyle) (rune, error) {
|
2018-06-25 12:07:33 +08:00
|
|
|
parts, err := lineParts(ls)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
|
2018-06-25 11:50:52 +08:00
|
|
|
switch len(n.edges) {
|
|
|
|
case 2:
|
|
|
|
switch {
|
|
|
|
case n.hasLeft() && n.hasRight():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[hLine], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasUp() && n.hasDown():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[vLine], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasDown() && n.hasRight():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[topLeftCorner], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasDown() && n.hasLeft():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[topRightCorner], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasUp() && n.hasRight():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[bottomLeftCorner], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasUp() && n.hasLeft():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[bottomRightCorner], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
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():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[hAndUp], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasDown() && n.hasLeft() && n.hasRight():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[hAndDown], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasUp() && n.hasDown() && n.hasRight():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[vAndRight], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
case n.hasUp() && n.hasDown() && n.hasLeft():
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[vAndLeft], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
|
|
|
|
default:
|
|
|
|
return -1, fmt.Errorf("unexpected three edges in node representing point %v: %v", n.p, n.edges)
|
|
|
|
}
|
|
|
|
|
|
|
|
case 4:
|
2018-06-25 12:07:33 +08:00
|
|
|
return parts[vAndH], nil
|
2018-06-25 11:50:52 +08:00
|
|
|
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
|
|
|
|
}
|