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:
Родитель
731fba202d
Коммит
efa3f21b09
|
@ -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.
|
||||
|
|
|
@ -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.
|
Загрузка…
Ссылка в новой задаче