shiny/text: implement ReadXxx.

Change-Id: Ide648c2ed487a318f293e8e73e287538ba3e40e3
Reviewed-on: https://go-review.googlesource.com/19113
Reviewed-by: Andrew Gerrand <adg@golang.org>
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2016-01-30 15:23:49 +11:00
Родитель 731fba202d
Коммит efa3f21b09
2 изменённых файлов: 225 добавлений и 1 удалений

Просмотреть файл

@ -8,7 +8,9 @@ package text
// now.
import (
"io"
"strings"
"unicode/utf8"
"golang.org/x/image/math/fixed"
)
@ -49,7 +51,7 @@ type Caret struct {
k int32
}
// TODO: many Caret methods: Seek, ReadXxx, WriteXxx, Delete, maybe others.
// TODO: many Caret methods: Seek, WriteXxx, Delete, maybe others.
// Close closes the Caret.
func (c *Caret) Close() error {
@ -68,6 +70,116 @@ func (c *Caret) Close() error {
return nil
}
// leanForwards moves the Caret from the right end of one Box to the left end
// of the next Box, crossing Lines and Paragraphs to find that next Box. It
// returns whether the Caret moved; false means that the Caret was located at
// the end of the Frame's text.
//
// Diagramatically, suppose we have two adjacent boxes (shown by square
// brackets below), with the Caret (an integer location called Caret.pos in the
// Frame's text) in the middle of the "foo2bar3" word:
// [foo0 foo1 foo2]^[bar3 bar4 bar5]
// leanForwards moves Caret.k from fooBox.j to barBox.i, also updating the
// Caret's p, l and b. Caret.pos remains unchanged.
func (c *Caret) leanForwards() bool {
// Assert that the Caret c is at the end of its Box.
if c.k != c.f.boxes[c.b].j {
panic("text: invalid state")
}
if nextB := c.f.boxes[c.b].next; nextB != 0 {
c.b = nextB
c.k = c.f.boxes[c.b].i
return true
}
if nextL := c.f.lines[c.l].next; nextL != 0 {
c.l = nextL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
return true
}
if nextP := c.f.paragraphs[c.p].next; nextP != 0 {
c.p = nextP
c.l = c.f.paragraphs[c.p].firstL
c.b = c.f.lines[c.l].firstB
c.k = c.f.boxes[c.b].i
return true
}
return false
}
// Read satisfies the io.Reader interface by copying those bytes after the
// Caret and incrementing the Caret.
func (c *Caret) Read(buf []byte) (n int, err error) {
for len(buf) > 0 {
if j := c.f.boxes[c.b].j; c.k < j {
nn := copy(buf, c.f.text[c.k:j])
n += nn
c.pos += int32(nn)
c.k += int32(nn)
if c.k == j && buf[nn-1] == '\n' {
// A Caret can't be placed at the end of a Paragraph, unless it
// is the final Paragraph.
c.leanForwards()
}
buf = buf[nn:]
continue
}
if !c.leanForwards() {
break
}
}
if int(c.pos) == c.f.len {
err = io.EOF
}
return n, err
}
// ReadByte returns the next byte after the Caret and increments the Caret.
func (c *Caret) ReadByte() (x byte, err error) {
for {
if j := c.f.boxes[c.b].j; c.k < j {
x = c.f.text[c.k]
c.pos++
c.k++
if x == '\n' {
// A Caret can't be placed at the end of a Paragraph, unless it
// is the final Paragraph.
c.leanForwards()
}
return x, nil
}
if !c.leanForwards() {
return 0, io.EOF
}
}
}
// ReadRune returns the next rune after the Caret and increments the Caret.
func (c *Caret) ReadRune() (r rune, size int, err error) {
for {
if j := c.f.boxes[c.b].j; c.k < j {
r, size = utf8.DecodeRune(c.f.text[c.k:j])
if r >= utf8.RuneSelf && size == 1 {
// We decoded invalid UTF-8, possibly because a valid UTF-8 rune
// straddled this box and the next one.
panic("TODO: invalid UTF-8")
}
c.pos += int32(size)
c.k += int32(size)
if r == '\n' {
// A Caret can't be placed at the end of a Paragraph, unless it
// is the final Paragraph.
c.leanForwards()
}
return r, size, nil
}
if !c.leanForwards() {
return 0, 0, io.EOF
}
}
}
// WriteString inserts s into the Frame's text at the Caret.
//
// The error returned is always nil.

112
shiny/text/text_test.go Normal file
Просмотреть файл

@ -0,0 +1,112 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package text
import (
"image"
"io"
"io/ioutil"
"reflect"
"testing"
"golang.org/x/image/math/fixed"
)
// toyFace implements the font.Face interface by measuring every rune's width
// as 1 pixel.
type toyFace struct{}
func (toyFace) Close() error {
return nil
}
func (toyFace) Glyph(dot fixed.Point26_6, r rune) (image.Rectangle, image.Image, image.Point, fixed.Int26_6, bool) {
panic("unimplemented")
}
func (toyFace) GlyphBounds(r rune) (fixed.Rectangle26_6, fixed.Int26_6, bool) {
panic("unimplemented")
}
func (toyFace) GlyphAdvance(r rune) (fixed.Int26_6, bool) {
return fixed.I(1), true
}
func (toyFace) Kern(r0, r1 rune) fixed.Int26_6 {
return 0
}
// iRobot is some text that contains both ASCII and non-ASCII runes.
const iRobot = "\"I, Robot\" in Russian is \"Я, робот\".\nIt's about robots.\n"
func iRobotFrame() *Frame {
f := new(Frame)
f.SetFace(toyFace{})
f.SetMaxWidth(fixed.I(10))
c := f.NewCaret()
c.WriteString(iRobot)
c.Close()
return f
}
func TestRead(t *testing.T) {
f := iRobotFrame()
c := f.NewCaret()
defer c.Close()
asBytes, err := ioutil.ReadAll(c)
if err != nil {
t.Fatalf("ReadAll: %v", err)
}
got, want := string(asBytes), iRobot
if got != want {
t.Fatalf("\ngot: %q\nwant: %q", got, want)
}
}
func TestReadByte(t *testing.T) {
f := iRobotFrame()
c := f.NewCaret()
defer c.Close()
got, want := []byte(nil), []byte(iRobot)
for {
x, err := c.ReadByte()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("ReadByte: %v", err)
}
got = append(got, x)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("\ngot: %v\nwant: %v", got, want)
}
}
func TestReadRune(t *testing.T) {
f := iRobotFrame()
c := f.NewCaret()
defer c.Close()
got, want := []rune(nil), []rune(iRobot)
for {
r, _, err := c.ReadRune()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("ReadRune: %v", err)
}
got = append(got, r)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("\ngot: %v\nwant: %v", got, want)
}
}
// TODO: fuzz-test that all the invariants remain true when modifying a Frame's
// text.
//
// TODO: enumerate all of the invariants, e.g. that the Boxes' i:j ranges do
// not overlap.