diff options
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go')
-rw-r--r-- | examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go new file mode 100644 index 000000000..5c21dd0ba --- /dev/null +++ b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go @@ -0,0 +1,188 @@ +// 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 buffer implements a 2-D buffer of cells. +package buffer + +import ( + "fmt" + "image" + + "github.com/mum4k/termdash/cell" + "github.com/mum4k/termdash/private/area" + "github.com/mum4k/termdash/private/runewidth" +) + +// NewCells breaks the provided text into cells and applies the options. +func NewCells(text string, opts ...cell.Option) []*Cell { + var res []*Cell + for _, r := range text { + res = append(res, NewCell(r, opts...)) + } + return res +} + +// Cell represents a single cell on the terminal. +type Cell struct { + // Rune is the rune stored in the cell. + Rune rune + + // Opts are the cell options. + Opts *cell.Options +} + +// String implements fmt.Stringer. +func (c *Cell) String() string { + return fmt.Sprintf("{%q}", c.Rune) +} + +// NewCell returns a new cell. +func NewCell(r rune, opts ...cell.Option) *Cell { + return &Cell{ + Rune: r, + Opts: cell.NewOptions(opts...), + } +} + +// Copy returns a copy the cell. +func (c *Cell) Copy() *Cell { + return &Cell{ + Rune: c.Rune, + Opts: cell.NewOptions(c.Opts), + } +} + +// Apply applies the provided options to the cell. +func (c *Cell) Apply(opts ...cell.Option) { + for _, opt := range opts { + opt.Set(c.Opts) + } +} + +// Buffer is a 2-D buffer of cells. +// The axes increase right and down. +// Uninitialized buffer is invalid, use New to create an instance. +// Don't set cells directly, use the SetCell method instead which safely +// handles limits and wide unicode characters. +type Buffer [][]*Cell + +// New returns a new Buffer of the provided size. +func New(size image.Point) (Buffer, error) { + if size.X <= 0 { + return nil, fmt.Errorf("invalid buffer width (size.X): %d, must be a positive number", size.X) + } + if size.Y <= 0 { + return nil, fmt.Errorf("invalid buffer height (size.Y): %d, must be a positive number", size.Y) + } + + b := make([][]*Cell, size.X) + for col := range b { + b[col] = make([]*Cell, size.Y) + for row := range b[col] { + b[col][row] = NewCell(0) + } + } + return b, nil +} + +// SetCell sets the rune of the specified cell in the buffer. Returns the +// number of cells the rune occupies, wide runes can occupy multiple cells when +// printed on the terminal. See http://www.unicode.org/reports/tr11/. +// Use the options to specify which attributes to modify, if an attribute +// option isn't specified, the attribute retains its previous value. +func (b Buffer) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) { + partial, err := b.IsPartial(p) + if err != nil { + return -1, err + } + if partial { + return -1, fmt.Errorf("cannot set rune %q at point %v, it is a partial cell occupied by a wide rune in the previous cell", r, p) + } + + remW, err := b.RemWidth(p) + if err != nil { + return -1, err + } + rw := runewidth.RuneWidth(r) + if rw == 0 { + // Even if the rune is invisible, like the zero-value rune, it still + // occupies at least the target cell. + rw = 1 + } + if rw > remW { + return -1, fmt.Errorf("cannot set rune %q of width %d at point %v, only have %d remaining cells at this line", r, rw, p, remW) + } + + c := b[p.X][p.Y] + c.Rune = r + c.Apply(opts...) + return rw, nil +} + +// IsPartial returns true if the cell at the specified point holds a part of a +// full width rune from a previous cell. See +// http://www.unicode.org/reports/tr11/. +func (b Buffer) IsPartial(p image.Point) (bool, error) { + size := b.Size() + ar, err := area.FromSize(size) + if err != nil { + return false, err + } + + if !p.In(ar) { + return false, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar) + } + + if p.X == 0 && p.Y == 0 { + return false, nil + } + + prevP := image.Point{p.X - 1, p.Y} + if prevP.X < 0 { + prevP = image.Point{size.X - 1, p.Y - 1} + } + + prevR := b[prevP.X][prevP.Y].Rune + switch rw := runewidth.RuneWidth(prevR); rw { + case 0, 1: + return false, nil + case 2: + return true, nil + default: + return false, fmt.Errorf("buffer cell %v contains rune %q which has an unsupported rune with %d", prevP, prevR, rw) + } +} + +// RemWidth returns the remaining width (horizontal row of cells) available +// from and inclusive of the specified point. +func (b Buffer) RemWidth(p image.Point) (int, error) { + size := b.Size() + ar, err := area.FromSize(size) + if err != nil { + return -1, err + } + + if !p.In(ar) { + return -1, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar) + } + return size.X - p.X, nil +} + +// Size returns the size of the buffer. +func (b Buffer) Size() image.Point { + return image.Point{ + len(b), + len(b[0]), + } +} |