├── LICENSE ├── README.md ├── box ├── b.go ├── box.go ├── boxscan_test.go ├── elastic.go ├── elastic_test.go ├── ruler.go ├── run.go ├── run_test.go └── scan.go ├── boxscan.go ├── boxscan_test.go ├── chop.go ├── clean.go ├── color.go ├── config.go ├── delete.go ├── delete_test.go ├── doc.go ├── draw.go ├── drawer.go ├── elastic.png ├── etch_test.go ├── example ├── basic │ └── basic.go ├── elastic │ └── elastic.go └── utf8 │ └── utf8.go ├── frame.go ├── framefix ├── doc.go ├── fix.go ├── frame.go ├── frame_test.go ├── import_test.go ├── main.go ├── main_test.go └── typecheck.go ├── fuzz_test.go ├── indexof.go ├── indexof_test.go ├── insert.go ├── insert_test.go ├── pointof.go ├── pointof_test.go ├── rgba.go ├── scroll └── scroll.go ├── select.go ├── select_test.go ├── testdata ├── TestInsert1000.expected.png ├── TestInsert10Chars.expected.png ├── TestInsert22Chars2Lines.expected.png ├── TestInsertOneChar.expected.png ├── TestInsertRegion0.expected.png ├── TestInsertRegion1.expected.png ├── TestInsertRegion2.expected.png ├── TestInsertRegion3.expected.png ├── TestInsertRegion4.expected.png ├── TestInsertRegion5.expected.png ├── TestInsertTabSpaceNewline.expected.png ├── TestMidToEnd.expected.png ├── TestMidToEndThenStartToMid.expected.png ├── TestSelect.expected.png ├── TestSelect0to0.expected.png ├── TestSelect0to1.expected.png ├── TestSelectAll.expected.png ├── TestSelectAllSub1.expected.png ├── TestSelectAllSubAll.expected.png ├── TestSelectEndLineAndDec.expected.png ├── TestSelectLine.expected.png ├── TestSelectLinePlus.expected.png ├── TestSelectLinePlus1.expected.png ├── TestSelectNone.expected.png ├── TestSelectNoneUntick.expected.png ├── TestSelectTabSpaceNewline.expected.png └── TestSelectTabSpaceNewlineSub1.expected.png ├── tick.go ├── vendor ├── github.com │ ├── as │ │ ├── etch │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── etch.go │ │ ├── font │ │ │ ├── README.md │ │ │ ├── cache.go │ │ │ ├── cliche.go │ │ │ ├── draw.go │ │ │ ├── font.go │ │ │ ├── gofont.go │ │ │ ├── gomedium │ │ │ │ └── gomedium.go │ │ │ ├── gomono │ │ │ │ └── gomono.go │ │ │ ├── goregular │ │ │ │ └── goregular.go │ │ │ ├── hex.go │ │ │ ├── replacer.go │ │ │ ├── resizer.go │ │ │ └── rune.go │ │ └── io │ │ │ └── spaz │ │ │ └── spaz.go │ └── golang │ │ └── freetype │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── raster │ │ ├── geom.go │ │ ├── paint.go │ │ ├── raster.go │ │ └── stroke.go │ │ └── truetype │ │ ├── face.go │ │ ├── glyph.go │ │ ├── hint.go │ │ ├── opcodes.go │ │ └── truetype.go ├── golang.org │ └── x │ │ └── image │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── font │ │ ├── font.go │ │ └── gofont │ │ │ ├── gomedium │ │ │ └── data.go │ │ │ ├── gomono │ │ │ └── data.go │ │ │ └── goregular │ │ │ └── data.go │ │ └── math │ │ └── fixed │ │ └── fixed.go └── modules.txt ├── wrap.go └── write.go /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright as (c) 2017, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Frame 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/as/frame)](https://goreportcard.com/report/github.com/as/frame) 3 | 4 | ## Synopsis 5 | 6 | Package frame implements graphical, editable text widgets compatible with Plan 9's libframe(3). Unlike libframe, the text is 7 | byte-addressed and preserves NUL terminated strings. The related `github.com/as/font` package provides a superset of the 8 | `golang.org/x/font.Face` interface and implements additional functionality for working with Go fonts and frames. 9 | 10 | ![paint](elastic.png) 11 | 12 | ## Installation 13 | 14 | ``` 15 | go get -d -u github.com/as/frame/... 16 | ``` 17 | 18 | ## Updates 19 | 20 | NOTE: The API has changed since this README.md was updated. 21 | 22 | - The font functionality is no longer embedded in the frame package 23 | - The frame.New constructor package function has been modified to take a Config struct 24 | 25 | Run the provided gofix program under to programatically update your packages: 26 | 27 | ``` 28 | go get -u github.com/as/frame/... 29 | go install github.com/as/frame/framefix 30 | framefix github.com/as/ui 31 | ``` 32 | 33 | ## Description 34 | 35 | A Frame is a graphical text container. It draws text on a bitmap, using previously-drawn text as a cache. 36 | 37 | Create one using New: 38 | 39 | ``` 40 | dst := image.NewRGBA( image.Rect(0,0,100,100), 41 | f := frame.New(dst, dst.Bounds(), &Config struct { 42 | Color: frame.Mono, 43 | Font : font.NewFace(12), 44 | } 45 | ``` 46 | 47 | ## Rendering 48 | 49 | The most frequent operations are Insert and Delete. Insert renders text at a given offset without overwriting existing 50 | text. Delete deletes a range of text and moves existing text after it into its range. Ranges are defined by two integers 51 | and behave equivalently to Go slice indices. 52 | 53 | Insert and delete are inverses. 54 | 55 | ``` 56 | f.Insert([]byte("hello world."), 0) 57 | f.Delete(0, 11) 58 | ``` 59 | 60 | ## Projection 61 | 62 | `PointOf` projects the index of a character to a 2D image.Point on the image. `IndexOf` does the opposite, projecting 63 | an index to a point. 64 | 65 | They are also inverse operations. 66 | 67 | ``` 68 | f.InsertString("hello") 69 | f.IndexOf(f.PointOf(4)) // returns: 4 70 | f.PointOf(f.IndexOf(image.Pt(25,25))) // returns: (25, 25) 71 | ``` 72 | 73 | There is no method for extracting the values of characters in the frame. The data structures are designed to be fast write-only containers. 74 | 75 | 76 | ## Selection 77 | 78 | Frames select a continuous range of text with `Select`. The currently-selected range is queried with `Dot`. 79 | 80 | ``` 81 | f.InsertString("hello") 82 | f.Select(0,2) 83 | f.Dot() // returns (0,2) 84 | ``` 85 | 86 | 87 | ## Drawing 88 | 89 | Because the bitmap is an arbitrary image and also a living cache of glyphs, drawing 90 | on the bitmap between rendering operations persists on the underlying glyphs. There 91 | are a few ways to re-render the bitmap or a region of it. 92 | 93 | 94 | ``` 95 | Recolor(pt image.Point, p0, p1 int64, cols Palette) 96 | Recolor colors the range p0:p1 by redrawing the foreground, background, and font glyphs 97 | 98 | Redraw(pt image.Point, p0, p1 int64, issel bool) 99 | Redraw redraws the characters between p0:p1. It accesses the cache of drawn glyph widths 100 | to avoid remeasuring strings 101 | 102 | RedrawAt(pt image.Point, text, back image.Image) 103 | RedrawAt refreshes the entire image to the right of the given pt. Everything below is redrawn. 104 | 105 | Refresh() 106 | Refresh recomputes the state of the frame from scratch. This is an expensive operation compared 107 | to redraw 108 | 109 | Paint(pt0, pt1 image.Point, col image.Image) 110 | Paint paints the color col on the frame at points pt0-pt1. The result is a Z shaped fill 111 | consisting of at-most 3 rectangles. No text is redrawn. 112 | ``` 113 | 114 | ## Examples 115 | 116 | - Basic 117 | https://github.com/as/frame/blob/master/example/basic/basic.go 118 | 119 | - UTF-8 120 | https://github.com/as/frame/blob/master/example/utf8/utf8.go 121 | 122 | - Elastic 123 | https://github.com/as/frame/blob/master/example/elastic/elastic.go 124 | 125 | 126 | ## Feature Set 127 | 128 | - UTF8 129 | - ASCII 130 | - Elastic tabstops 131 | - Semantic replacement characters 132 | 133 | # Note 134 | 135 | A frame's text is not addressable. Once the characters are written to the frame, there is no 136 | mechanism to retrieve value from within the frame. Use a buffer to store text for reading 137 | and the range addresses of the frame to access bytes from that buffer. 138 | 139 | See `github.com/as/ui/win` for an example. 140 | 141 | ## See Also 142 | 143 | http://doc.cat-v.org/plan_9/4th_edition/papers/sam/ 144 | 145 | Specifically, the section `Data structures in the terminal` served as a guide 146 | -------------------------------------------------------------------------------- /box/b.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | type B interface { 4 | Seek(bn int, whence int) int 5 | Next() bool 6 | Prev() bool 7 | Box() *Box 8 | } 9 | type boxes struct { 10 | bn int 11 | b []Box 12 | } 13 | 14 | func clamp(v, l, h int) int { 15 | if v < l { 16 | return l 17 | } 18 | if v > h { 19 | return h 20 | } 21 | return v 22 | } 23 | func (b *boxes) Prev() bool { 24 | if b.bn == 0 { 25 | return false 26 | } 27 | b.bn-- 28 | return true 29 | } 30 | func (b *boxes) Next() bool { 31 | if b.bn+1 == len(b.b) { 32 | return false 33 | } 34 | b.bn++ 35 | return true 36 | } 37 | func (b *boxes) Box() *Box { 38 | return &b.b[b.bn] 39 | } 40 | 41 | func (b *boxes) Seek(bn int, whence int) int { 42 | oldbn := b.bn 43 | switch whence { 44 | case 0: 45 | b.bn = clamp(bn, 0, len(b.b)) 46 | case 1: 47 | b.bn = clamp(b.bn+bn, 0, len(b.b)) 48 | case 2: 49 | b.bn = clamp(b.bn+bn, 0, len(b.b)) 50 | } 51 | return oldbn 52 | } 53 | 54 | func PrevLine(bx B) bool { 55 | if bx.Seek(0, 1) == 0 { 56 | return false 57 | } 58 | for bx.Box().Break() != '\n' && bx.Prev() { 59 | } 60 | if bx.Seek(0, 1) == 0 && bx.Box().Break() == '\n' { 61 | return true 62 | } 63 | for bx.Prev() && bx.Box().Break() != '\n' { 64 | } 65 | if bx.Box().Break() == '\n' { 66 | return bx.Next() 67 | } 68 | return true 69 | } 70 | -------------------------------------------------------------------------------- /box/box.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | const SLOP = 25 4 | 5 | type Box struct { 6 | Nrune int 7 | Ptr []byte 8 | Width int 9 | Minwidth int 10 | } 11 | 12 | func (b *Box) Break() byte { 13 | n := b.Len() 14 | if n == 0 { 15 | return 0 16 | } 17 | return b.Ptr[0] 18 | } 19 | 20 | func (b *Box) Len() int { 21 | if b.Nrune < 0 { 22 | return 1 23 | } 24 | return b.Nrune 25 | } 26 | 27 | func (b *Box) Bytes() []byte { 28 | n := b.Len() 29 | if n <= 0 { 30 | return nil 31 | } 32 | return b.Ptr[:n] 33 | } 34 | -------------------------------------------------------------------------------- /box/boxscan_test.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/as/font" 8 | ) 9 | 10 | var fsize = 11 11 | 12 | func genBench(b *testing.B, in []byte, min, max int, fn func(int) Face, ftsize int, bxceil int) { 13 | b.Helper() 14 | b.SetBytes(int64(len(in))) 15 | r := NewRun(min, max, NewCache(fn(ftsize))) 16 | b.ResetTimer() 17 | for i := 0; i < b.N; i++ { 18 | r.Boxscan(in, bxceil) 19 | } 20 | } 21 | 22 | func roll(size int) []byte { 23 | b := new(bytes.Buffer) 24 | for i := 0; i < size; i++ { 25 | b.WriteByte(byte(i)) 26 | } 27 | return b.Bytes() 28 | } 29 | 30 | func BenchmarkScanByte(b *testing.B) { genBench(b, []byte("a"), 5, 5000, NewGoMono, 11, 1) } 31 | func BenchmarkScan16Bytes(b *testing.B) { 32 | genBench(b, []byte("The quick brown "), 5, 5000, NewGoMono, 11, 1) 33 | } 34 | func BenchmarkScanHelloWorld(b *testing.B) { 35 | genBench(b, []byte(`package main\nimport "fmt"\n\nfunc main(){\n\tfmt.Println("hello world")\n}\n\n`), 5, 5000, NewGoMono, 11, 1) 36 | } 37 | func Benchmark100000Lines(b *testing.B) { 38 | genBench(b, bytes.Repeat([]byte{'\n'}, 100000), 5, 5000, NewGoMono, 8, 100000) 39 | } 40 | func Benchmark100000Lines2Byte(b *testing.B) { 41 | genBench(b, bytes.Repeat([]byte{'a', '\n'}, 100000), 5, 5000, NewGoMono, 16, 100000) 42 | } 43 | func Benchmark100000Lines4Byte(b *testing.B) { 44 | genBench(b, bytes.Repeat([]byte{'a', 'a', 'a', '\n'}, 100000), 5, 5000, NewGoMono, 16, 100000) 45 | } 46 | func Benchmark100000Lines16Byte(b *testing.B) { 47 | genBench(b, bytes.Repeat([]byte{'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', '\n'}, 100000), 5, 5000, NewGoMono, 16, 100000) 48 | } 49 | func BenchmarkScanBinary100(b *testing.B) { genBench(b, roll(100), 5, 5000, NewGoMono, 11, 100) } 50 | func BenchmarkScanBinary1000(b *testing.B) { genBench(b, roll(1000), 5, 5000, NewGoMono, 11, 1000) } 51 | func BenchmarkScanBinary5000(b *testing.B) { genBench(b, roll(5000), 5, 5000, NewGoMono, 11, 5000) } 52 | func BenchmarkScanBinary10000(b *testing.B) { 53 | genBench(b, roll(10000), 5, 5000, NewGoMono, 11, 10000) 54 | } 55 | func BenchmarkScanBinary100000(b *testing.B) { 56 | genBench(b, roll(100000), 5, 5000, NewGoMono, 11, 100000) 57 | } 58 | func BenchmarkLongLine100000(b *testing.B) { 59 | genBench(b, bytes.Repeat([]byte{'a'}, 100000), 5, 5000, NewGoMono, 8, 100000) 60 | } 61 | -------------------------------------------------------------------------------- /box/elastic.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | func (f *Run) Stretch2(nb int, bx []Box) (pb int) { 4 | panic("unimplemented") 5 | } 6 | 7 | // Elastic tabstop experiment section. Not ready for general use by any means 8 | // the text/tabwriter package implements elastic tabstops, but that package 9 | // assumes that all chars are the same width and that text needs to be rescanned. 10 | // 11 | // The frame already distinguishes between tabs, newlines, and plain text characters 12 | // by encapsulating them in measured boxes. A direct copy of the tabwriter code would 13 | // ignore the datastructures in the frame and their sublinear runtime cost. 14 | // 15 | // The current elastic algorithm has suboptimal runtime performance 16 | // 17 | func (f *Run) Stretch(nb int) (pb int) { 18 | if nb <= 0 { 19 | return 0 20 | } 21 | // fmt.Println() 22 | // fmt.Printf("\n\ntrace bn=%d\n", nb) 23 | nc := 0 24 | nl := 0 25 | dx := 0 26 | 27 | cmax := make(map[int]int) 28 | cbox := make(map[int][]int) 29 | 30 | nb = f.StartCell(nb) 31 | pb = nb - 1 32 | if nb == f.Nbox { 33 | return 0 34 | } 35 | Loop: 36 | for ; nb < f.Nbox; nb++ { 37 | b := &f.Box[nb] 38 | // fmt.Printf("switch box: %#v\n", b) 39 | switch b.Break() { 40 | case '\t': 41 | dx += b.Width 42 | cbox[nc] = append(cbox[nc], nb) 43 | max := cmax[nc] 44 | if dx > max { 45 | cmax[nc] = dx 46 | } 47 | nc++ 48 | // fmt.Printf(" tab: dx=%d ncol=%d\n", dx, nc) 49 | dx = 0 50 | case '\n': 51 | nl++ 52 | dx = 0 53 | if nc == 0 { 54 | // A line with no tabs; end of cell 55 | //fmt.Printf(" nl (no cols): dx=%d nl=%d\n", dx, nl-1) 56 | break Loop 57 | } 58 | //fmt.Printf(" nl : dx=%d nl=%d nc=%d\n", dx, nl-1, nc) 59 | nc = 0 60 | default: 61 | dx += b.Width 62 | //fmt.Printf(" plain : dx=%d wid=%d nc=%d\n", dx, b.Width, nc) 63 | } 64 | } 65 | for c, bns := range cbox { 66 | max := cmax[c] 67 | for _, bn := range bns { 68 | b := &f.Box[bn] 69 | b.Width = max 70 | if bn == 0 { 71 | 72 | } else { 73 | pb := f.Box[bn-1] 74 | if pb.Break() != '\n' && pb.Break() != '\t' { 75 | b.Width -= f.Box[bn-1].Width 76 | } 77 | } 78 | if b.Width < b.Minwidth { 79 | b.Width = b.Minwidth 80 | } 81 | } 82 | } 83 | return pb 84 | } 85 | 86 | func (f *Run) Findcol(bn int, coln int) (cbn int, xmax int) { 87 | c := 0 88 | for ; bn < f.Nbox; bn++ { 89 | b := &f.Box[bn] 90 | if b.Break() == '\t' { 91 | c++ 92 | } 93 | if b.Break() != '\n' { 94 | xmax += b.Width 95 | } 96 | if c == coln { 97 | break 98 | } 99 | bn++ 100 | } 101 | if c != coln { 102 | return -1, 0 103 | } 104 | return bn, xmax 105 | 106 | } 107 | 108 | func (f *Run) Colof(bn int) (coln, xmax int) { 109 | if bn == 0 { 110 | return 0, 0 111 | } 112 | bs := f.StartLine(bn) 113 | for { 114 | b := &f.Box[bs] 115 | if b.Break() == '\t' { 116 | coln++ 117 | } 118 | if b.Break() != '\n' { 119 | xmax += b.Width 120 | } 121 | if bn == bs { 122 | break 123 | } 124 | bs++ 125 | } 126 | if xmax != 0 { 127 | coln++ 128 | } 129 | return coln, xmax 130 | } 131 | 132 | // EndCell returns the first box beyond the end of the 133 | // current cell under bn 134 | func (f *Run) EndCell(bn int) int { 135 | oldbn := bn 136 | bn = f.StartLine(bn) 137 | ltb := 0 138 | ncol := 0 139 | Loop: 140 | for ; bn != f.Nbox; bn++ { 141 | b := &f.Box[bn] 142 | switch b.Break() { 143 | case '\n': 144 | if ncol == 0 { 145 | bn = ltb 146 | break Loop 147 | } 148 | ncol = 0 149 | case '\t': 150 | ncol++ 151 | ltb = bn 152 | } 153 | } 154 | if oldbn > f.Nbox { 155 | return oldbn 156 | } 157 | if bn >= f.Nbox { 158 | return f.Nbox 159 | } 160 | if bn <= oldbn { 161 | return oldbn 162 | } 163 | return bn + 1 164 | } 165 | 166 | // StartCell returns the first box in the cell 167 | func (f *Run) StartCell(bn int) int { 168 | // println(bn) 169 | if bn == 0 { 170 | return 0 171 | } 172 | ncols := 0 173 | nrows := 0 174 | // oldbn := bn 175 | bn = f.EndLine(bn) 176 | lsb := bn 177 | if bn == f.Nbox { 178 | //nrows++ 179 | } 180 | var b *Box 181 | Loop: 182 | for bn-1 != 0 { 183 | b = &f.Box[bn-1] 184 | switch b.Break() { 185 | case '\n': 186 | if ncols == 0 { 187 | if nrows == 0 { 188 | return 0 189 | } 190 | break Loop 191 | } 192 | lsb = bn 193 | nrows++ 194 | ncols = 0 195 | case '\t': 196 | ncols++ 197 | default: 198 | } 199 | bn-- 200 | } 201 | if ncols == 0 { 202 | return lsb 203 | } 204 | if bn-1 == 0 { 205 | if f.Box[bn-1].Break() == '\n' { 206 | return bn 207 | } 208 | return bn - 1 209 | } 210 | return lsb 211 | // println("bn-1", bn-1) 212 | // f.DumpBoxes() 213 | if bn-1 == 0 && f.Box[bn-1].Break() != '\n' { 214 | //return 0 215 | } 216 | if f.Box[bn].Break() == '\t' && f.Box[bn-1].Break() != '\n' { 217 | return bn 218 | } 219 | // println("return", bn) 220 | return bn 221 | } 222 | 223 | // NextCell is like EndCell, except it doesn't assume bn 224 | // is part of a cell. It skips past the current cell under 225 | // bn and any non-cellular boxes afterward, returning the 226 | // starting box of the next cell or f.Nbox 227 | func (f *Run) NextCell(bn int) int { 228 | bn = f.EndCell(bn) 229 | oldbn := bn 230 | for ; bn != f.Nbox; bn++ { 231 | b := &f.Box[bn] 232 | if b.Break() == '\t' { 233 | bn = f.StartCell(bn) 234 | break 235 | } 236 | } 237 | if bn <= oldbn { 238 | return oldbn 239 | } 240 | return bn 241 | } 242 | 243 | func (f *Run) StartLine(bn int) int { 244 | for ; bn-1 >= 0; bn-- { 245 | b := &f.Box[bn-1] 246 | if b.Break() == '\n' { 247 | break 248 | } 249 | } 250 | return bn 251 | } 252 | 253 | func (f *Run) EndLine(bn int) int { 254 | for bn < f.Nbox { 255 | b := &f.Box[bn] 256 | if b.Break() == '\n' { 257 | break 258 | } 259 | bn++ 260 | } 261 | return bn 262 | } 263 | 264 | func (f *Run) NextLine(bn int) int { 265 | bn = f.EndLine(bn) 266 | if bn < f.Nbox { 267 | return bn + 1 268 | } 269 | return bn 270 | } 271 | 272 | func (f *Run) PrevLine(bn int) int { 273 | for ; bn >= 0; bn-- { 274 | b := &f.Box[bn] 275 | if b.Break() == '\n' { 276 | break 277 | } 278 | } 279 | if bn == -1 && f.Box[0].Break() == '\n' { 280 | return 0 281 | } 282 | for bn-1 >= 0 { 283 | b := &f.Box[bn-1] 284 | if b.Break() == '\n' { 285 | break 286 | } 287 | bn-- 288 | } 289 | return bn 290 | } 291 | -------------------------------------------------------------------------------- /box/elastic_test.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/as/font" 7 | ) 8 | 9 | func runwith(s string) *Run { 10 | r := NewRun(5, 5000, NewGoMono(fsize)) 11 | r.Boxscan([]byte(s), 1024) 12 | return &r 13 | } 14 | func checkbox(t *testing.T, testname string, havebx, wantbx int) { 15 | if wantbx != havebx { 16 | t.Logf("%s: have %d, want = %d\n", testname, havebx, wantbx) 17 | t.Fail() 18 | } 19 | } 20 | 21 | func TestStartLine1(t *testing.T) { 22 | r := runwith("the quick brown fox jumps over the lazy dog") 23 | for i := 0; i < r.Nbox; i++ { 24 | checkbox(t, "TestStartLine1", r.StartLine(i), 0) 25 | } 26 | } 27 | func TestStartLine2(t *testing.T) { 28 | r := runwith("the quick brown fox\njumps over the lazy dog") 29 | checkbox(t, "TestStartLine2", r.StartLine(0), 0) 30 | checkbox(t, "TestStartLine2", r.StartLine(1), 0) 31 | checkbox(t, "TestStartLine2", r.StartLine(2), 2) 32 | } 33 | func TestStartLine2TrailingNL(t *testing.T) { 34 | r := runwith("the quick brown fox\njumps over the lazy dog\n") 35 | checkbox(t, "TestStartLine2TrailingNL", r.StartLine(2), 2) 36 | checkbox(t, "TestStartLine2TrailingNL", r.StartLine(3), 2) 37 | } 38 | 39 | func TestStartLineCol(t *testing.T) { 40 | r := runwith("box0\tbox2\tboxx4\tboxxx6\nbx8\tbxA\tthe\tlazy\tdog\n") 41 | for i := 0; i <= 7; i++ { 42 | checkbox(t, "TestStartLineCol", r.StartLine(i), 0) 43 | } 44 | for i := 8; i < 16; i++ { 45 | checkbox(t, "TestStartLineCol", r.StartLine(i), 8) 46 | } 47 | checkbox(t, "TestStartLineCol", r.StartLine(18), 18) 48 | } 49 | 50 | func TestStartMultiLineCol(t *testing.T) { 51 | r := runwith("box0\tbox2\tboxx4\tboxxx6\nbx8\tbxA\tthe\tlazy\tdog\nbox18\tbox20\tboxx22\tboxxx24\nbx26\tb28\tthe\tlazy\tdog\n") 52 | for i := 0; i <= 7; i++ { 53 | checkbox(t, "TestStartLineCol", r.StartLine(i), 0) 54 | } 55 | for i := 8; i < 16; i++ { 56 | checkbox(t, "TestStartLineCol", r.StartLine(i), 8) 57 | } 58 | checkbox(t, "TestStartLineCol", r.StartLine(18), 18) 59 | for i := 18 + 0; i <= 18+7; i++ { 60 | checkbox(t, "TestStartLineCol", r.StartLine(i), 18+0) 61 | } 62 | for i := 18 + 8; i < 18+16; i++ { 63 | checkbox(t, "TestStartLineCol", r.StartLine(i), 18+8) 64 | } 65 | } 66 | 67 | /* 68 | func TestStartCell(t *testing.T) { 69 | r := runwith("\n\n\n\n\n\n\n\n\n\n10\t12\t14\t15\t\nabcdefg\n\nzzzzzzzzzzzzzzzzz") 70 | for i := 0; i < 10; i++ { 71 | checkbox(t, "TestStartCell", r.StartCell(i), i) 72 | } 73 | checkbox(t, "TestStartCell", r.StartCell(10), 10) 74 | checkbox(t, "TestStartCell", r.StartCell(11), 10) 75 | 76 | checkbox(t, "TestStartCell", r.StartCell(12), 10) 77 | checkbox(t, "TestStartCell", r.StartCell(13), 10) 78 | 79 | checkbox(t, "TestStartCell", r.StartCell(16), 10) 80 | checkbox(t, "TestStartCell", r.StartCell(23), 23) 81 | checkbox(t, "TestStartCell", r.StartCell(27), 27) 82 | } 83 | */ 84 | func TestStartCell2(t *testing.T) { 85 | r := runwith("\nAAA\tBBB\tCCC") 86 | //r.DumpBoxes() 87 | checkbox(t, "10", r.EndCell(3), 6) 88 | checkbox(t, "20", r.EndLine(3), 6) 89 | checkbox(t, `\nAAA\tBBB\tCCC`, r.StartCell(3), 1) 90 | r = runwith("AAA\tBBB\tCCC") 91 | //r.DumpBoxes() 92 | checkbox(t, "10", r.EndCell(3), 5) 93 | checkbox(t, "20", r.EndLine(3), 5) 94 | checkbox(t, `AAA\tBBB\tCCC`, r.StartCell(2), 0) 95 | } 96 | 97 | func TestEndCell(t *testing.T) { 98 | r := runwith("\n\n\n\n\n\n\n\n\n\n10\t12\t14\t15\t\nabcdefg\n\nzzzzzzzzzzzzzzzzz") 99 | 100 | for i := 0; i < 10; i++ { 101 | checkbox(t, "TestEndCell", r.EndCell(i), i) 102 | } 103 | checkbox(t, "TestEndCell", r.EndCell(10), 18) 104 | checkbox(t, "TestEndCell", r.EndCell(11), 18) 105 | 106 | checkbox(t, "TestEndCell", r.EndCell(12), 18) 107 | checkbox(t, "TestEndCell", r.EndCell(13), 18) 108 | 109 | checkbox(t, "TestEndCell", r.EndCell(16), 18) 110 | checkbox(t, "TestEndCell", r.EndCell(23), 23) 111 | checkbox(t, "TestEndCell", r.EndCell(27), 27) 112 | } 113 | 114 | func TestNextCell(t *testing.T) { 115 | r := runwith("\n\n\n\n\n\n\n\n\n\n10\t12\t14\t15\t\nabcdefg\n\nzzzzzzzzzzzzzzzzz") 116 | 117 | for i := 0; i < 10; i++ { 118 | checkbox(t, "TestNextCell", r.NextCell(i), 10) 119 | } 120 | checkbox(t, "TestNextCell", r.NextCell(10), 23) 121 | checkbox(t, "TestNextCell", r.NextCell(11), 23) 122 | 123 | checkbox(t, "TestNextCell", r.NextCell(12), 23) 124 | checkbox(t, "TestNextCell", r.NextCell(13), 23) 125 | 126 | checkbox(t, "TestNextCell", r.NextCell(16), 23) 127 | checkbox(t, "TestNextCell", r.NextCell(23), 23) 128 | } 129 | 130 | func TestNextCell2(t *testing.T) { 131 | r := runwith("\tfmt.Println(\"hello world\")\n}\none\ttwo\three") 132 | checkbox(t, "10", r.EndCell(1), 1) 133 | checkbox(t, "20", r.StartCell(6), 5) 134 | checkbox(t, "30", r.EndLine(6), 10) 135 | checkbox(t, "40", r.EndCell(6), 10) 136 | checkbox(t, "50", r.StartCell(6), 5) 137 | checkbox(t, "60", r.NextCell(1), 5) 138 | } 139 | 140 | /* 141 | func TestStretch1(t *testing.T) { 142 | r := runwith("AAA\tBBB\tCCC") 143 | r.Stretch(4) 144 | t.Fail() 145 | } 146 | 147 | func TestStretch2(t *testing.T) { 148 | r := runwith("AAA\tBBB\tCCC\n") 149 | r.Stretch(4) 150 | t.Fail() 151 | } 152 | 153 | func TestStretch3(t *testing.T) { 154 | r := runwith("AAA\tBBB\tCCC\nDDD\tEEE\tFFF") 155 | r.Stretch(4) 156 | t.Fail() 157 | } 158 | */ 159 | -------------------------------------------------------------------------------- /box/ruler.go: -------------------------------------------------------------------------------- 1 | package box 2 | -------------------------------------------------------------------------------- /box/run.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/as/font" 7 | ) 8 | 9 | // MaxBytes is the largest capacity of bytes in a box 10 | var MaxBytes = 256 + 3 11 | 12 | func NewRun(minDx, maxDx int, ft font.Face) Run { 13 | r := Run{ 14 | delta: 32, 15 | minDx: minDx, 16 | maxDx: maxDx, 17 | Face: ft, 18 | } 19 | r.ensure(r.delta) 20 | return r 21 | } 22 | 23 | // Run is a one-dimensional field of boxes. It can scan arbitrary text 24 | // into boxes with Bxscan(). 25 | type Run struct { 26 | Box []Box 27 | Nalloc int 28 | Nbox int 29 | Face font.Face 30 | Nchars int64 31 | Nlines int 32 | 33 | minDx, maxDx int 34 | delta int 35 | } 36 | 37 | func (f *Run) Combine(g *Run, n int) { 38 | b := g.Box[:g.Nbox] 39 | for i := range b { 40 | b := &b[i] 41 | b.Ptr = append([]byte{}, b.Ptr...) 42 | } 43 | f.Add(n, len(b)) 44 | copy(f.Box[n:], b) 45 | } 46 | 47 | // Count recomputes and returns the number of bytes 48 | // stored between box nb and the last box 49 | func (f *Run) Count(nb int) int64 { 50 | n := int64(0) 51 | for ; nb < f.Nbox; nb++ { 52 | n += int64((f.Box[nb]).Len()) 53 | } 54 | return n 55 | } 56 | 57 | // Reset resets all boxes in the run without deallocating 58 | // their data on the heap. If widthfn is not nill, it 59 | // becomes the new measuring function for the run. Boxes 60 | // in the run are not remeasured upon reset. 61 | func (f *Run) Reset(ft font.Face) { 62 | f.Nbox = 0 63 | f.Nchars = 0 64 | if ft != nil { 65 | f.Face = ft 66 | } 67 | } 68 | 69 | //Find finds the box containing q starting from box bn index 70 | // p and puts q at the start of the next box 71 | func (f *Run) Find(bn int, p, q int64) int { 72 | // fmt.Printf("find %d.%d -> %d\n",bn,p,q) 73 | for ; bn < f.Nbox; bn++ { 74 | b := &f.Box[bn] 75 | if p+int64(b.Len()) > q { 76 | break 77 | } 78 | p += int64(b.Len()) 79 | } 80 | if p != q { 81 | f.Split(bn, int(q-p)) 82 | bn++ 83 | } 84 | // fmt.Printf("find %d.%d -> %d = box %d\n",bn,p,q, bn) 85 | return bn 86 | } 87 | 88 | func dumpBoxes(bx []Box) { 89 | for i, b := range bx { 90 | fmt.Printf("[%d] (%p) (nrune=%d l=%d w=%d mw=%d bc=%x): %q\n", 91 | i, &bx[i], b.Nrune, (&b).Len(), b.Width, b.Minwidth, b.Break(), b.Ptr) 92 | } 93 | } 94 | 95 | func (f *Run) DumpBoxes() { 96 | fmt.Println("dumping boxes") 97 | fmt.Printf("nboxes: %d\n", f.Nbox) 98 | fmt.Printf("nalloc: %d\n", f.Nalloc) 99 | dumpBoxes(f.Box) 100 | } 101 | 102 | // Merge merges box bn and bn+1 103 | func (f *Run) Merge(bn int) { 104 | b0 := &f.Box[bn] 105 | b1 := &f.Box[bn+1] 106 | b0.Ptr = append(b0.Ptr, b1.Ptr...) 107 | b0.Width += b1.Width 108 | b0.Nrune += b1.Nrune 109 | f.Delete(bn+1, bn+1) 110 | } 111 | 112 | // Split splits box bn into two boxes; bn and bn+1, at index n 113 | func (f *Run) Split(bn, n int) { 114 | f.Dup(bn) 115 | b := &f.Box[bn] 116 | b.Ptr = append([]byte{}, b.Ptr...) 117 | f.Truncate(b, b.Nrune-n) 118 | f.Chop(&f.Box[bn+1], n) 119 | } 120 | 121 | // Chop drops the first n chars in box b 122 | func (f *Run) Chop(b *Box, n int) { 123 | if b.Nrune < 0 || b.Nrune < n { 124 | panic("Chop") 125 | } 126 | copy(b.Ptr, b.Ptr[n:]) 127 | b.Nrune -= n 128 | b.Ptr = b.Ptr[:b.Nrune] 129 | b.Width = f.Face.Dx(b.Ptr) 130 | } 131 | 132 | func (f *Run) Truncate(b *Box, n int) { 133 | if b.Nrune < 0 || b.Nrune < n { 134 | panic("Truncate") 135 | } 136 | b.Nrune -= n 137 | b.Ptr = b.Ptr[:b.Nrune] 138 | b.Width = f.Face.Dx(b.Ptr) 139 | } 140 | 141 | // Add adds n boxes after box bn, the rest are shifted up 142 | func (f *Run) Add(bn, n int) { 143 | if bn > f.Nbox { 144 | panic("Frame.Add") 145 | } 146 | if f.Nbox+n > f.Nalloc { 147 | f.Grow(n + SLOP) 148 | } 149 | copy(f.Box[bn+n:], f.Box[bn:f.Nbox]) 150 | f.Nbox += n 151 | } 152 | 153 | // Delete closes and deallocates n0-n1 inclusively 154 | func (f *Run) Delete(n0, n1 int) { 155 | if n0 >= f.Nbox || n1 >= f.Nbox || n1 < n0 { 156 | panic("Delete") 157 | } 158 | f.Free(n0, n1) 159 | f.Close(n0, n1) 160 | } 161 | 162 | // Free deallocates memory for boxes n0-n1 inclusively 163 | func (f *Run) Free(n0, n1 int) { 164 | if n1 < n0 { 165 | return 166 | } 167 | if n0 >= f.Nbox || n1 >= f.Nbox { 168 | panic("Free") 169 | } 170 | for i := n0; i < n1; i++ { 171 | if f.Box[i].Nrune >= 0 { 172 | f.Box[i].Ptr = nil 173 | //f.Box[i].Ptr = make([]byte, 0, MaxBytes) 174 | } 175 | } 176 | } 177 | 178 | // Grow allocates memory for delta more boxes 179 | func (f *Run) Grow(delta int) { 180 | f.Nalloc += delta 181 | f.Box = append(f.Box, make([]Box, delta)...) 182 | } 183 | 184 | // Dup copies the contents of box bn to box bn+1 185 | func (f *Run) Dup(bn int) { 186 | if f.Box[bn].Nrune < 0 { 187 | panic("Frame.Dup") 188 | } 189 | f.Add(bn, 1) 190 | // if f.Box[bn].Nrune >= 0 { 191 | f.Box[bn+1].Ptr = append([]byte{}, f.Box[bn].Ptr...) 192 | // } 193 | } 194 | 195 | // Close closess box n0-n1 inclusively. The rest are shifted down 196 | func (f *Run) Close(n0, n1 int) { 197 | if n0 >= f.Nbox || n1 >= f.Nbox || n1 < n0 { 198 | panic("Frame.Close") 199 | } 200 | n1++ 201 | for i := n1; i < f.Nbox; i++ { 202 | f.Box[i-(n1-n0)] = f.Box[i] 203 | } 204 | f.Nbox -= n1 - n0 205 | } 206 | 207 | func (b Run) String() string { 208 | s := "" 209 | bn, Nbox := 0, b.Nbox 210 | for ; bn < Nbox; bn++ { 211 | b := &b.Box[bn] 212 | s += string(b.Ptr) 213 | } 214 | return s 215 | } 216 | -------------------------------------------------------------------------------- /box/run_test.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/as/font" 7 | ) 8 | 9 | // helpful is an interface that allows this code to use Go1.9's t.Helper() method 10 | // without breaking out of data continuous integration components (CircleCI) which 11 | // run older Go versions not supporting t.Helper(). 12 | // 13 | // 14 | type help interface { 15 | Helper() 16 | } 17 | 18 | // genrun generates a run with a pre-fixed font and dimensions 19 | func genrun(s ...string) (r Run) { 20 | return Run{ 21 | delta: 32, 22 | minDx: 5, 23 | maxDx: 5000, 24 | Face: font.NewGoMono(11), 25 | Nalloc: len(s), 26 | Nbox: len(s), 27 | Box: genbox(5, 5000, 10, s...), 28 | } 29 | } 30 | 31 | func genbox(min, max, fontdx int, s ...string) (bx []Box) { 32 | if min == 0 { 33 | min = 5 34 | } 35 | if max == 0 { 36 | max = 5000 37 | } 38 | if fontdx == 0 { 39 | fontdx = 10 40 | } 41 | for _, s := range s { 42 | b := Box{ 43 | Nrune: len(s), 44 | Ptr: append(make([]byte, 0, MaxBytes), []byte(s)...), 45 | } 46 | if s == "\t" { 47 | b.Width = min 48 | b.Minwidth = min 49 | } else if s == "\n" { 50 | b.Nrune = -1 51 | b.Width = 5000 52 | } else { 53 | b.Width = fontdx * b.Nrune 54 | } 55 | bx = append(bx, b) 56 | } 57 | return bx 58 | } 59 | 60 | func runCk(t *testing.T, have Run) { 61 | if t, ok := interface{}(t).(help); ok { 62 | t.Helper() 63 | } 64 | boxCk(t, have.Box) 65 | } 66 | 67 | func boxCk(t *testing.T, have []Box) { 68 | 69 | if t, ok := interface{}(t).(help); ok { 70 | t.Helper() 71 | } 72 | for bn, h := range have { 73 | if h.Nrune < -1 { 74 | t.Logf("box %d: should never have Nrune < -1", bn) 75 | t.Fail() 76 | } 77 | if h.Nrune == -1 && len(h.Ptr) > 1 { 78 | t.Logf("box %d: Nrune < -1 but len(Ptr) > 1", bn) 79 | t.Fail() 80 | } 81 | if h.Nrune > MaxBytes { 82 | t.Logf("box %d: h.Nrune [%d] > MaxBytes [%d]", bn, h.Nrune, MaxBytes) 83 | t.Fail() 84 | } 85 | } 86 | } 87 | 88 | func runCompare(t *testing.T, strict bool, have, want Run) { 89 | 90 | if t, ok := interface{}(t).(help); ok { 91 | t.Helper() 92 | } 93 | runCk(t, have) 94 | runCk(t, want) 95 | h, w := have.Box, want.Box 96 | if !strict { 97 | h = h[:have.Nbox] 98 | w = w[:want.Nbox] 99 | } 100 | boxCompare(t, h, w) 101 | } 102 | 103 | func boxCompare(t *testing.T, have, want []Box) { 104 | if t, ok := interface{}(t).(help); ok { 105 | t.Helper() 106 | } 107 | 108 | failed := false 109 | fail := func() { 110 | t.Fail() 111 | failed = true 112 | } 113 | defer func() { 114 | if failed { 115 | dumpBoxes(have) 116 | dumpBoxes(want) 117 | } 118 | }() 119 | if len(want) != len(have) { 120 | t.Logf("box counts differ: have %d, want %d\n", len(have), len(want)) 121 | fail() 122 | } 123 | boxCk(t, have) 124 | boxCk(t, want) 125 | for bn := 0; bn < len(have); bn++ { 126 | h := have[bn] 127 | w := want[bn] 128 | if h.Nrune != w.Nrune { 129 | t.Logf("box reported sizes differ: have %d, want %d\n", h.Nrune, w.Nrune) 130 | fail() 131 | } 132 | sh, wh := string(h.Bytes()), string(w.Bytes()) 133 | if sh != wh { 134 | t.Logf("box contents differ: have: \n\t%q, want:\n\t%q\n", h.Nrune, w.Nrune) 135 | fail() 136 | } 137 | } 138 | 139 | } 140 | func TestCombine(t *testing.T) { 141 | r0 := genrun("hello") 142 | r1 := genrun("world") 143 | rWant := genrun("hello", "world") 144 | r0.Combine(&r1, 1) 145 | runCompare(t, false, r0, rWant) 146 | } 147 | -------------------------------------------------------------------------------- /box/scan.go: -------------------------------------------------------------------------------- 1 | package box 2 | 3 | import "unicode/utf8" 4 | 5 | //import "log" 6 | 7 | func (r *Run) ensure(nb int) { 8 | if nb == r.Nalloc { 9 | r.Grow(r.delta) 10 | if r.delta < 32768 { 11 | r.delta *= 2 12 | } 13 | } 14 | } 15 | func min(a, b int) int { 16 | if a < b { 17 | return a 18 | } 19 | return b 20 | } 21 | 22 | func (r *Run) zRunescan(s []byte, ymax int) { 23 | r.Nbox = 0 24 | r.Nchars = 0 25 | r.Nchars += int64(len(s)) 26 | i := 0 27 | nb := 0 28 | 29 | adv := 0 30 | for nl := 0; nl <= ymax; nb++ { 31 | if nb == r.Nalloc { 32 | r.Grow(r.delta) 33 | if r.delta < 32768 { 34 | r.delta *= 2 35 | } 36 | } 37 | i += adv 38 | if i == len(s) { 39 | break 40 | } 41 | c := s[i] 42 | switch c { 43 | default: 44 | for _, c := range string(s[i:min(len(s), MaxBytes)]) { 45 | if c == '\t' || c == '\n' { 46 | break 47 | } 48 | adv = utf8.RuneLen(c) 49 | } 50 | r.Box[nb] = Box{ 51 | Nrune: i, 52 | Ptr: s[i : i+adv], 53 | Width: r.Face.Dx(s[i : i+adv]), 54 | } 55 | case '\t': 56 | adv = 1 57 | r.Box[nb] = Box{ 58 | Nrune: -1, 59 | Ptr: s[i : i+adv], 60 | Width: r.minDx, 61 | Minwidth: r.minDx, 62 | } 63 | case '\n': 64 | adv = 1 65 | r.Box[nb] = Box{ 66 | Nrune: -1, 67 | Ptr: s[i : i+adv], 68 | Width: r.maxDx, 69 | } 70 | nl++ 71 | } 72 | } 73 | r.Nchars -= int64(len(s)) 74 | r.Nbox += nb 75 | } 76 | 77 | func (r *Run) Runescan(s []byte, ymax int) { 78 | r.Boxscan(s, ymax) 79 | } 80 | func (r *Run) Boxscan(s []byte, ymax int) { 81 | r.Nbox = 0 82 | r.Nchars = 0 83 | r.Nchars += int64(len(s)) 84 | i := 0 85 | nb := 0 86 | 87 | for nl := 0; nl <= ymax; nb++ { 88 | if nb == r.Nalloc { 89 | r.Grow(r.delta) 90 | if r.delta < 32768 { 91 | r.delta *= 2 92 | } 93 | } 94 | if i == len(s) { 95 | break 96 | } 97 | i++ 98 | c := s[i-1] 99 | switch c { 100 | default: 101 | for _, c = range s[i:min(len(s), MaxBytes)] { 102 | if special(c) { 103 | break 104 | } 105 | i++ 106 | } 107 | r.Box[nb] = Box{ 108 | Nrune: i, 109 | Ptr: s[:i], 110 | Width: r.Face.Dx(s[:i]), 111 | } 112 | case '\t': 113 | r.Box[nb] = Box{ 114 | Nrune: -1, 115 | Ptr: s[:i], 116 | Width: r.minDx, 117 | Minwidth: r.minDx, 118 | } 119 | case '\n': 120 | r.Box[nb] = Box{ 121 | Nrune: -1, 122 | Ptr: s[:i], 123 | Width: r.maxDx, 124 | } 125 | nl++ 126 | } 127 | s = s[i:] 128 | i = 0 129 | } 130 | r.Nchars -= int64(len(s)) 131 | r.Nbox += nb 132 | } 133 | 134 | func special(c byte) bool { 135 | return c == '\t' || c == '\n' 136 | } 137 | -------------------------------------------------------------------------------- /boxscan.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/as/font" 7 | "github.com/as/frame/box" 8 | ) 9 | 10 | const ( 11 | // strict enables panic on the condition that the frame is too small to fix 12 | // any characters 13 | strict = false 14 | ) 15 | 16 | // bxscan resets the measuring function and calls Bxscan in the embedded run 17 | func (f *Frame) boxscan(s []byte, pt image.Point) (image.Point, image.Point) { 18 | switch f.Face.(type) { 19 | case font.Rune: 20 | f.ir.Runescan(s, f.maxlines) 21 | case interface{}: 22 | f.ir.Boxscan(s, f.maxlines) 23 | } 24 | if f.elastic() { 25 | // TODO(as): remove this after adding tests since its redundant 26 | // 27 | // Just to see if the algorithm works not ideal to sift through all of 28 | // the boxes per insertion, although surprisingly faster than expected 29 | // to the point of where its almost unnoticable without the print 30 | // statements 31 | bn := f.ir.Nbox 32 | for bn > 0 { 33 | bn = f.ir.Stretch(bn) 34 | } 35 | f.ir.Stretch(bn) 36 | } 37 | pt = f.wrapMin(pt, &f.ir.Box[0]) 38 | return pt, f.boxscan2D(f.ir, pt) 39 | } 40 | 41 | func (f *Frame) boxscan2D(r *box.Run, pt image.Point) image.Point { 42 | n := 0 43 | for nb := 0; nb < r.Nbox; nb++ { 44 | b := &r.Box[nb] 45 | pt = f.wrapMin(pt, b) 46 | if pt.Y == f.r.Max.Y { 47 | r.Nchars -= r.Count(nb) 48 | r.Delete(nb, r.Nbox-1) 49 | break 50 | } 51 | if b.Nrune > 0 { 52 | if n = f.fits(pt, b); n == 0 { 53 | if strict { 54 | panic("boxscan2D: fits 0") 55 | } 56 | return pt 57 | } 58 | if n != b.Nrune { 59 | r.Split(nb, n) 60 | b = &r.Box[nb] 61 | } 62 | pt.X += b.Width 63 | } else { 64 | if b.Break() == '\n' { 65 | pt = f.wrap(pt) 66 | } else { 67 | pt.X += f.plot(pt, b) 68 | } 69 | } 70 | } 71 | return pt 72 | } 73 | -------------------------------------------------------------------------------- /boxscan_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import "testing" 4 | 5 | func BenchmarkScanASCII(b *testing.B) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /chop.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | // trim destroys boxes that went off frame 8 | func (f *Frame) trim(pt image.Point, p int64, bn int) { 9 | for ; bn < f.Nbox; bn++ { 10 | b := &f.Box[bn] 11 | if pt = f.wrapMax(pt, b); pt.Y >= f.r.Max.Y { 12 | break 13 | } 14 | p += int64(b.Len()) 15 | pt = f.advance(pt, b) 16 | } 17 | f.Nchars = p 18 | f.Nlines = f.maxlines 19 | if bn < f.Nbox { 20 | f.Run.Delete(bn, f.Nbox-1) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clean.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | func (f *Frame) clean(pt image.Point, n0, n1 int) { 8 | c := f.r.Max.X 9 | for ; n0 < n1-1; n0++ { 10 | b0 := &f.Box[n0] 11 | b1 := &f.Box[n0+1] 12 | pt = f.wrapMax(pt, b0) 13 | for b0.Nrune >= 0 && n0 < n1-1 && b1.Nrune >= 0 && pt.X+b0.Width+b1.Width < c { 14 | f.Merge(n0) 15 | n1-- 16 | } 17 | 18 | pt = f.advance(pt, &f.Box[n0]) // dont simplify this 19 | } 20 | 21 | for ; n0 < f.Nbox; n0++ { 22 | b0 := &f.Box[n0] 23 | pt = f.wrapMax(pt, b0) 24 | pt = f.advance(pt, b0) 25 | } 26 | 27 | f.full = 0 28 | if pt.Y >= f.r.Max.Y { 29 | f.full = 1 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | var ( 8 | // Common uniform colors found in Acme 9 | Black = image.Black 10 | White = image.White 11 | Yellow = uniform(0xfffffdff) 12 | Red = uniform(0xffe8efff) 13 | Green = uniform(0xefffe8ff) 14 | Blue = uniform(0xe8efffff) 15 | 16 | // Other colors 17 | Gray = uniform(0x1c1f26ff) 18 | Peach = uniform(0xfff8e8ff) 19 | Mauve = uniform(0x9090C0ff) 20 | ) 21 | 22 | var ( 23 | // Acme is the color scheme found in the Acme text editor 24 | Acme = Theme(Gray, Yellow, White, Blue) 25 | Mono = Theme(Black, White, White, Black) 26 | A = Theme(Gray, Peach, White, Mauve) 27 | ) 28 | 29 | // Color is constructed from a Palette pair. The Hi Palette describes 30 | // the appearance of highlighted text. 31 | type Color struct { 32 | Palette 33 | Hi Palette 34 | } 35 | 36 | // Pallete contains two images used to paint text and backgrounds 37 | // on the frame. 38 | type Palette struct { 39 | Text, Back image.Image 40 | } 41 | 42 | // Theme returns a Color for the given foreground and background 43 | // images. Two extra colors may be provided to set the highlighted 44 | // foreground and background image palette. 45 | func Theme(fg, bg image.Image, hi ...image.Image) Color { 46 | c := Color{Palette: Palette{Text: fg, Back: bg}} 47 | if len(hi) > 0 { 48 | c.Hi.Text = hi[0] 49 | } 50 | if len(hi) > 1 { 51 | c.Hi.Back = hi[1] 52 | } 53 | return c 54 | } 55 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "github.com/as/font" 5 | ) 6 | 7 | type Config struct { 8 | Flag int 9 | Scroll func(int) 10 | Color Color 11 | Face font.Face 12 | Drawer Drawer 13 | } 14 | 15 | func (c *Config) check() *Config { 16 | if c.Color == zc { 17 | c.Color = A 18 | } 19 | if c.Face == nil { 20 | c.Face = font.NewFace(11) 21 | } 22 | if c.Drawer == nil { 23 | c.Drawer = &defaultDrawer{} 24 | } 25 | return c 26 | } 27 | 28 | func getflag(flag ...int) (fl int) { 29 | if len(flag) != 0 { 30 | fl = flag[0] 31 | } 32 | if ForceElastic { 33 | fl |= FrElastic 34 | } 35 | if ForceUTF8 { 36 | fl |= FrUTF8 37 | } 38 | return fl 39 | } 40 | -------------------------------------------------------------------------------- /delete.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | 6 | _ "github.com/as/etch" 7 | ) 8 | 9 | // Delete deletes the range [p0:p1) and 10 | // returns the number of characters deleted 11 | func (f *Frame) Delete(p0, p1 int64) int { 12 | 13 | if p0 >= f.Nchars || p0 == p1 || f.b == nil { 14 | return 0 15 | } 16 | 17 | if p1 > f.Nchars { 18 | p1 = f.Nchars 19 | } 20 | if f.p0 == f.p1 { 21 | f.tickat(f.PointOf(int64(f.p0)), false) 22 | } 23 | n0 := f.Find(0, 0, p0) 24 | // eb := f.StartCell(n0) 25 | // eb = eb 26 | nn0 := n0 27 | n1 := f.Find(n0, p0, p1) 28 | pt0 := f.pointOfBox(p0, n0) 29 | ppt0 := pt0 30 | pt1 := f.PointOf(p1) 31 | f.Free(n0, n1-1) 32 | f.modified = true 33 | 34 | // Advance forward, copying the first un-deleted box 35 | // on the right all the way to the left, splitting them 36 | // when necessary to fit on a wrapped line. A bit of draw 37 | // computation is saved by keeping track of the selection 38 | // and interpolating its drawing routine into the same 39 | // loop. 40 | // 41 | // Might have to rethink this when adding support for 42 | // multiple selections. 43 | // 44 | // pt0/pt1: deletion start/stop 45 | // n0/n1: deleted box/first surviving box 46 | // int64(p1): char index of the surviving box 47 | 48 | pt0, pt1, n0, n1 = f.delete(pt0, pt1, n0, n1, int64(p1)) 49 | 50 | if n1 == f.Nbox && pt0.X != pt1.X { 51 | f.Paint(pt0, pt1, f.Color.Back) 52 | } 53 | 54 | // Delete more than a line. All the boxes have been shifted 55 | // but the bitmap might still have a copy of them down below 56 | if pt1.Y != pt0.Y { 57 | pt0, pt1, n1 = f.fixTrailer(pt0, pt1, n1) 58 | } 59 | 60 | f.Run.Close(n0, n1-1) 61 | if nn0 > 0 && f.Box[nn0-1].Nrune >= 0 && ppt0.X-f.Box[nn0-1].Width >= f.r.Min.X { 62 | nn0-- 63 | ppt0.X -= f.Box[nn0].Width 64 | } 65 | 66 | if n0 < f.Nbox-1 { 67 | n0++ 68 | } 69 | f.clean(ppt0, nn0, n0) 70 | 71 | if f.p1 > p1 { 72 | f.p1 -= p1 - p0 73 | } else if f.p1 > p0 { 74 | f.p1 = p0 75 | } 76 | if f.p0 > p1 { 77 | f.p0 -= p1 - p0 78 | } else if f.p0 > p0 { 79 | f.p0 = p0 80 | } 81 | 82 | f.Nchars -= p1 - p0 83 | if f.p0 == f.p1 { 84 | f.tickat(f.PointOf(f.p0), true) 85 | } 86 | pt0 = f.PointOf(f.Nchars) 87 | extra := 0 88 | if pt0.X > f.r.Min.X { 89 | extra = 1 90 | } 91 | h := f.Face.Dy() 92 | f.Nlines = (pt0.Y-f.r.Min.Y)/h + extra 93 | f.badElasticAlg() 94 | return int(p1 - p0) //n - f.Nlines 95 | } 96 | func (f *Frame) delete(pt0, pt1 image.Point, n0, n1 int, cn1 int64) (image.Point, image.Point, int, int) { 97 | h := f.Face.Dy() 98 | for pt1.X != pt0.X && n1 < f.Nbox { 99 | b := &f.Box[n1] 100 | pt0 = f.wrapMin(pt0, b) 101 | pt1 = f.wrapMax(pt1, b) 102 | r := image.Rectangle{pt0, pt0} 103 | r.Max.Y += h 104 | 105 | if b.Nrune > 0 { // non-newline 106 | n := f.fits(pt0, b) 107 | if n != b.Nrune { 108 | f.Split(n1, n) 109 | b = &f.Box[n1] 110 | } 111 | r.Max.X += b.Width 112 | f.Draw(f.b, r, f.b, pt1, f.op) 113 | //drawBorder(f.b, r.Inset(-4), Green, image.ZP, 8) 114 | cn1 += int64(b.Nrune) 115 | } else { 116 | r.Max.X = min(r.Max.X+f.project(pt0, b), f.r.Max.X) 117 | _, col := f.pick(cn1, f.p0, f.p1) 118 | f.Draw(f.b, r, col, pt0, f.op) 119 | cn1++ 120 | } 121 | pt1 = f.advance(pt1, b) 122 | pt0.X += f.plot(pt0, b) 123 | f.Box[n0] = f.Box[n1] 124 | n0++ 125 | n1++ 126 | } 127 | return pt0, pt1, n0, n1 128 | } 129 | func (f *Frame) fixTrailer(pt0, pt1 image.Point, n1 int) (image.Point, image.Point, int) { 130 | if n1 == f.Nbox && pt0.X != pt1.X { 131 | f.Paint(pt0, pt1, f.Color.Back) 132 | } 133 | h := f.Face.Dy() 134 | pt2 := f.pointOf(65536, pt1, n1) 135 | if pt2.Y > f.r.Max.Y { 136 | pt2.Y = f.r.Max.Y - h 137 | } 138 | if n1 < f.Nbox { 139 | q0 := pt0.Y + h 140 | q1 := pt1.Y + h 141 | q2 := pt2.Y + h 142 | if q2 > f.r.Max.Y { 143 | q2 = f.r.Max.Y 144 | } 145 | f.Draw(f.b, image.Rect(pt0.X, pt0.Y, pt0.X+(f.r.Max.X-pt1.X), q0), f.b, pt1, f.op) 146 | f.Draw(f.b, image.Rect(f.r.Min.X, q0, f.r.Max.X, q0+(q2-q1)), f.b, image.Pt(f.r.Min.X, q1), f.op) 147 | f.Paint(image.Pt(pt2.X, pt2.Y-(pt1.Y-pt0.Y)), pt2, f.Color.Back) 148 | } else { 149 | f.Paint(pt0, pt2, f.Color.Back) 150 | } 151 | return pt0, pt1, n1 152 | } 153 | 154 | func min(a, b int) int { 155 | if a < b { 156 | return a 157 | } 158 | return b 159 | } 160 | -------------------------------------------------------------------------------- /delete_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | "testing" 7 | 8 | "github.com/as/etch" 9 | "github.com/as/font" 10 | ) 11 | 12 | var ( 13 | R = image.Rect(0, 0, 232, 232) 14 | fsize = 11 15 | ) 16 | 17 | func tconf() *Config { 18 | return &Config{ 19 | Face: font.NewGoMono(fsize), 20 | Color: A, 21 | } 22 | } 23 | 24 | func abtest(r image.Rectangle) (fr0, fr1 *Frame, a, b *image.RGBA) { 25 | a = image.NewRGBA(r) 26 | b = image.NewRGBA(r) 27 | fr0 = New(a, a.Bounds(), tconf()) 28 | fr1 = New(b, b.Bounds(), tconf()) 29 | return fr0, fr1, a, b 30 | } 31 | 32 | func abtestbg(r image.Rectangle) (fa, fb *Frame, a, b *image.RGBA) { 33 | fa, fb, a, b = abtest(r) 34 | draw.Draw(a, a.Bounds(), fa.Color.Back, image.ZP, draw.Src) 35 | draw.Draw(b, b.Bounds(), fb.Color.Back, image.ZP, draw.Src) 36 | return fa, fb, a, b 37 | } 38 | 39 | func TestDeleteOneChar(t *testing.T) { 40 | h, w, have, want := abtestbg(R) 41 | h.Insert([]byte("1"), 0) 42 | h.Delete(0, h.Len()) 43 | h.Untick() 44 | w.Untick() 45 | etch.Assert(t, have, want, "TestDelete.png") 46 | } 47 | 48 | func TestDeleteLastLineNoNL(t *testing.T) { 49 | w, h, want, have := abtestbg(R) 50 | draw.Draw(want, want.Bounds(), w.Color.Back, image.ZP, draw.Src) 51 | draw.Draw(have, have.Bounds(), h.Color.Back, image.ZP, draw.Src) 52 | w.Insert([]byte("1234\ncccc\ndddd\n"), 0) 53 | h.Insert([]byte("1234\ncccc\ndddd"), 0) 54 | h.Delete(5, 10) 55 | w.Delete(5, 10) 56 | // We can untick because have has an extra newline 57 | h.Untick() 58 | w.Untick() 59 | etch.Assert(t, have, want, "TestDeleteLastLineNoNL.png") 60 | } 61 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package frame provides plan9-like editable text images on a raster display. This implementation 2 | // preserves NUL bytes, and uses a set of replacement characters for unrenderable text glyphs generated 3 | // with a smaller sized font (hexadecimal or ascii representation). 4 | // 5 | // A frame's text is not addressable. Once the characters are written to the frame, there is no 6 | // mechanism to retrieve their position from within the frame. Use a buffer to store text for reading 7 | // and the range addresses of the frame to access bytes from that buffer. 8 | // 9 | // See github.com/as/ui/win for an example. 10 | // 11 | // A frame is created using the New function 12 | // 13 | // img := image.NewRGBA(image.Rect(0,0,100,100)) 14 | // fr := frame.New(img, img.Bounds(), frame.NewGoMono(), frame.Mono) 15 | // 16 | // A frame supports these common operations 17 | // Insert: Insert text 18 | // Delete: Delete text 19 | // IndexOf: Index for point 20 | // PointOf: Point for index 21 | // Select: Select range 22 | // Dot: Return selected range 23 | // 24 | // Insert and Delete 25 | // 26 | // Frames supports two operations for rendering text: Insert and Delete. Insert inserts text at the 27 | // given index and moves existing characters after the index to the right. Delete deletes text in the 28 | // given range (a range is a pair of indices) and moves existing character after the index to the 29 | // left. 30 | // 31 | // The two operations are inverses of each other. 32 | // 33 | // fr.Insert([]byte("hello world."), 0) 34 | // fr.Delete(0, 11) 35 | // 36 | // Insert and delete return the number of characters inserted or deleted. 37 | // 38 | // To delete the last insertion: 39 | // p0 := 0 40 | // n := fr.Insert([]byte("123"), p0) 41 | // fr.Delete(p0, p0+n) 42 | // 43 | // To execute a traditional "write" operation: 44 | // s := []byte("hello") 45 | // fr.Delete(0, int64(len(s))) 46 | // fr.Insert(s, 0) 47 | // 48 | // Projection 49 | // 50 | // Frames can translate between coordinates of the mouse and character offsets in the frame itself using 51 | // IndexOf and PointOf. 52 | // 53 | // p0 := fr.IndexOf(image.Pt(0, 0)) // Returns the index under the 2D point (0,0) 54 | // pt0 := fr.PointOf(5) // Returns the 2D point over the index 55 | // 56 | // Selection 57 | // 58 | // Frames support selecting ranges of text along with returning those selected ranges. 59 | // 60 | // fr.Select(p0, p1) 61 | // fr.Dot() 62 | // 63 | // A more complicated facility exists for making a live selection. See example/basic for an example of 64 | // how to use it. 65 | // 66 | // fr.Sweep(...) 67 | // 68 | // Drawing 69 | // 70 | // No special operations are needed after a call to Insert, Delete, or Select. The frame's bitmap 71 | // is updated. However, there are four functions that will redraw the frame on the bitmap if 72 | // this is necessary. 73 | // 74 | // Recolor(pt image.Point, p0, p1 int64, cols Palette) 75 | // Recolor colors the range p0:p1 by redrawing the foreground, background, and font glyphs 76 | // 77 | // Redraw(pt image.Point, p0, p1 int64, issel bool) 78 | // Redraw redraws the characters between p0:p1. It accesses the cache of drawn glyph widths 79 | // to avoid remeasuring strings 80 | // 81 | // RedrawAt(pt image.Point, text, back image.Image) 82 | // RedrawAt refreshes the entire image to the right of the given pt. Everything below is redrawn. 83 | // 84 | // Refresh() 85 | // Refresh recomputes the state of the frame from scratch. This is an expensive operation compared 86 | // to redraw 87 | // 88 | // Display Sync 89 | // 90 | // After any operation that alters the frame, one can be sure that the changes can be written to 91 | // the frame's bitmap. However, the same can not be said for the exp/shiny window. There currently 92 | // exists an optimization (see github.com/as/drawcache) that caches rectangles that need to be 93 | // redrawn to the screen. This is because shiny (or the native drivers for it) are too slow to 94 | // refresh the entire window is that window's resolution is very high. 95 | // 96 | // 97 | // This rendering pipeline is bottlenecked, so an optimization is located between the |*| 98 | // 99 | // insert | frame | shiny buffer |*| shiny window 100 | // 101 | // 102 | package frame 103 | -------------------------------------------------------------------------------- /draw.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/as/frame/box" 7 | ) 8 | 9 | // Refresh renders the entire frame, including the underlying 10 | // bitmap. Refresh should not be called after insertion and deletion 11 | // unless the frame's RGBA bitmap was painted over by another 12 | // draw operation. 13 | func (f *Frame) Refresh() { 14 | cols := f.Color 15 | if f.p0 == f.p1 { 16 | ticked := f.Ticked 17 | if ticked { 18 | f.tickat(f.PointOf(f.p0), false) 19 | } 20 | f.drawsel(f.PointOf(0), 0, f.Nchars, cols.Back, cols.Text) 21 | if ticked { 22 | f.tickat(f.PointOf(f.p0), true) 23 | } 24 | return 25 | } 26 | pt := f.PointOf(0) 27 | pt = f.drawsel(pt, 0, f.p0, cols.Back, cols.Text) 28 | pt = f.drawsel(pt, f.p0, f.p1, cols.Hi.Back, cols.Hi.Text) 29 | f.drawsel(pt, f.p1, f.Nchars, cols.Back, cols.Text) 30 | } 31 | 32 | // RedrawAt renders the frame's bitmap starting at pt and working downwards. 33 | func (f *Frame) RedrawAt(pt image.Point, text, back image.Image) { 34 | f.redrawRun0(&(f.Run), pt, text, back) 35 | } 36 | 37 | // Redraw draws the range [p0:p1] at the given pt. 38 | func (f *Frame) Redraw(pt image.Point, p0, p1 int64, issel bool) { 39 | if f.Ticked { 40 | f.tickat(f.PointOf(f.p0), false) 41 | } 42 | 43 | if p0 == p1 { 44 | f.tickat(pt, issel) 45 | return 46 | } 47 | 48 | pal := f.Color.Palette 49 | if issel { 50 | pal = f.Color.Hi 51 | } 52 | f.drawsel(pt, p0, p1, pal.Back, pal.Text) 53 | } 54 | 55 | // Recolor redraws the range p0:p1 with the given palette 56 | func (f *Frame) Recolor(pt image.Point, p0, p1 int64, cols Palette) { 57 | f.drawsel(pt, p0, p1, cols.Back, cols.Text) 58 | f.modified = true 59 | } 60 | 61 | func (f *Frame) redrawRun0(r *box.Run, pt image.Point, text, back image.Image) image.Point { 62 | nb := 0 63 | for ; nb < r.Nbox; nb++ { 64 | b := &r.Box[nb] 65 | pt = f.wrapMax(pt, b) 66 | //if !f.noredraw && b.nrune >= 0 { 67 | if b.Nrune >= 0 { 68 | f.StringBG(f.b, pt, text, image.ZP, f.Face, b.Ptr, back, image.ZP) 69 | } 70 | pt.X += b.Width 71 | } 72 | return pt 73 | } 74 | 75 | // widthBox returns the width of box n. If the length of 76 | // alt is different than the box, alt is measured and 77 | // returned instead. 78 | func (f *Frame) widthBox(b *box.Box, alt []byte) int { 79 | if b.Nrune < 0 || len(alt) == b.Len() { 80 | return b.Width 81 | } 82 | return f.Face.Dx(alt) 83 | } 84 | 85 | func (f *Frame) drawsel(pt image.Point, p0, p1 int64, back, text image.Image) image.Point { 86 | { 87 | // doubled 88 | p0, p1 := int(p0), int(p1) 89 | q0 := 0 90 | trim := false 91 | 92 | var ( 93 | nb int 94 | b *box.Box 95 | ) 96 | 97 | for nb = 0; nb < f.Nbox; nb++ { 98 | b = &f.Box[nb] 99 | l := q0 + b.Len() 100 | if l > p0 { 101 | break 102 | } 103 | q0 = l 104 | } 105 | 106 | // Step into box, start coloring it 107 | // How much does this lambda slow things down? 108 | stepFill := func() { 109 | qt := pt 110 | pt = f.wrapMax(pt, b) 111 | if pt.Y > qt.Y { 112 | r := image.Rect(qt.X, qt.Y, f.r.Max.X, pt.Y) 113 | f.Draw(f.b, r, back, qt, f.op) 114 | } 115 | } 116 | for ; nb < f.Nbox && q0 < p1; nb++ { 117 | b = &f.Box[nb] 118 | if q0 >= p0 { // region 0 or 1 or 2 119 | stepFill() 120 | } 121 | ptr := b.Ptr[:b.Len()] 122 | if q0 < p0 { 123 | // region -1: shift p right inside the selection 124 | ptr = ptr[p0-q0:] 125 | q0 = p0 126 | } 127 | 128 | trim = false 129 | if q1 := q0 + len(ptr); q1 > p1 { 130 | // region 1: would draw too much, retract the selection 131 | lim := len(ptr) - (q1 - p1) 132 | ptr = ptr[:lim] 133 | trim = true 134 | } 135 | w := f.widthBox(b, ptr) 136 | if b.Nrune > 0 { 137 | f.Draw(f.b, image.Rect(pt.X, pt.Y, min(pt.X+w, f.r.Max.X), pt.Y+f.Face.Dy()), back, pt, f.op) 138 | f.StringBG(f.b, pt, text, image.ZP, f.Face, ptr, back, image.ZP) 139 | } else { 140 | f.Draw(f.b, image.Rect(pt.X, pt.Y, min(pt.X+w, f.r.Max.X), pt.Y+f.Face.Dy()), back, pt, f.op) 141 | } 142 | pt.X += w 143 | 144 | if q0 += len(ptr); q0 > p1 { 145 | break 146 | } 147 | } 148 | if p1 > p0 && nb != 0 && nb < f.Nbox && f.Box[nb-1].Len() > 0 && !trim { 149 | b = &f.Box[nb] 150 | stepFill() 151 | } 152 | return pt 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /drawer.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | 7 | . "github.com/as/font" 8 | "golang.org/x/image/font" 9 | ) 10 | 11 | // Drawer implements the set of methods a frame needs to draw on a draw.Image. The frame's default behavior is to use 12 | // the native image/draw package and x/exp/font packages to satisfy this interface. 13 | type Drawer interface { 14 | Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point, op draw.Op) 15 | //DrawMask(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op draw.Op) 16 | 17 | // StringBG draws a string to dst at point p 18 | StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft font.Face, s []byte, bg image.Image, bgp image.Point) int 19 | 20 | // Flush requests that prior calls to the draw and string methods are flushed from an underlying soft-screen. The list of rectangles provide 21 | // optional residency information. Implementations may refresh a superset of r, or ignore it entirely, as long as the entire region is 22 | // refreshed 23 | Flush(r ...image.Rectangle) error 24 | } 25 | 26 | func NewDefaultDrawer() Drawer { 27 | return &defaultDrawer{} 28 | } 29 | 30 | type defaultDrawer struct{} 31 | 32 | func (d *defaultDrawer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point, op draw.Op) { 33 | draw.Draw(dst, r, src, sp, op) 34 | } 35 | 36 | func (d *defaultDrawer) StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft font.Face, s []byte, bg image.Image, bgp image.Point) int { 37 | return StringBG(dst, p, src, sp, ft, s, bg, bgp) 38 | } 39 | 40 | func (d *defaultDrawer) Flush(r ...image.Rectangle) error { 41 | return nil 42 | } 43 | 44 | func negotiateFace(f font.Face, flags int) Face { 45 | if flags&FrUTF8 != 0 { 46 | return NewCache(NewRune(f)) 47 | } 48 | switch f := f.(type) { 49 | case Face: 50 | return f 51 | case font.Face: 52 | return Open(f) 53 | } 54 | return Open(f) 55 | } 56 | -------------------------------------------------------------------------------- /elastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/elastic.png -------------------------------------------------------------------------------- /etch_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "os" 7 | "testing" 8 | 9 | "github.com/as/etch" 10 | ) 11 | 12 | // If adding new graphical tests, change to modeSaveResult 13 | const testMode = modeCheckResult 14 | 15 | const ( 16 | modeSaveResult = iota 17 | modeCheckResult 18 | ) 19 | 20 | func TestMain(m *testing.M) { 21 | v := m.Run() 22 | if testMode == modeSaveResult { 23 | v = 1 24 | fmt.Println("*** DANGER ***") 25 | fmt.Println("*** testMode == modeSaveResult ") 26 | fmt.Println("*** change to testMode = modeCheckResult in etch_test.go") 27 | fmt.Println() 28 | } 29 | os.Exit(v) 30 | } 31 | 32 | func check(t *testing.T, have image.Image, name string, mode int) { 33 | wantfile := fmt.Sprintf("testdata/%s.expected.png", name) 34 | if mode == modeSaveResult { 35 | etch.WriteFile(t, wantfile, have) 36 | } 37 | etch.AssertFile(t, have, wantfile, fmt.Sprintf("%s.png", name)) 38 | } 39 | -------------------------------------------------------------------------------- /example/basic/basic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | "sync" 7 | 8 | "github.com/as/frame" 9 | "golang.org/x/exp/shiny/driver" 10 | "golang.org/x/exp/shiny/screen" 11 | "golang.org/x/mobile/event/key" 12 | "golang.org/x/mobile/event/lifecycle" 13 | "golang.org/x/mobile/event/mouse" 14 | "golang.org/x/mobile/event/paint" 15 | "golang.org/x/mobile/event/size" 16 | ) 17 | 18 | var wg sync.WaitGroup 19 | var winSize = image.Pt(1024, 768) 20 | 21 | func main() { 22 | driver.Main(func(src screen.Screen) { 23 | var dirty = true 24 | wind, _ := src.NewWindow(&screen.NewWindowOptions{winSize.X, winSize.Y, "basic"}) 25 | b, _ := src.NewBuffer(winSize) 26 | draw.Draw(b.RGBA(), b.Bounds(), frame.A.Back, image.ZP, draw.Src) 27 | fr := frame.New(b.RGBA(), b.Bounds(), nil) 28 | fr.Refresh() 29 | wind.Send(paint.Event{}) 30 | ck := func() { 31 | if dirty || fr.Dirty() { 32 | wind.Send(paint.Event{}) 33 | } 34 | dirty = false 35 | } 36 | 37 | flush := func() { 38 | wind.Upload(fr.Bounds().Min, b, fr.Bounds()) 39 | wind.Publish() 40 | } 41 | for { 42 | switch e := wind.NextEvent().(type) { 43 | case mouse.Event: 44 | if e.Button == 1 && e.Direction == 1 { 45 | p0 := fr.IndexOf(pt(e)) 46 | fr.Select(p0, p0) 47 | flush() 48 | frameSweep(fr, wind, flush) 49 | wind.Send(paint.Event{}) 50 | } 51 | case key.Event: 52 | if e.Direction == 2 { 53 | continue 54 | } 55 | if e.Rune == '\r' { 56 | e.Rune = '\n' 57 | } 58 | p0, p1 := fr.Dot() 59 | if e.Rune > 0x79 || e.Rune < 0 { 60 | continue 61 | } 62 | if e.Rune == '\x08' { 63 | if p0 == p1 && p0 > 0 { 64 | p0-- 65 | } 66 | fr.Delete(p0, p1) 67 | } else { 68 | if p0 != p1 { 69 | fr.Delete(p0, p1) 70 | } 71 | fr.Insert([]byte{byte(e.Rune)}, p0) 72 | } 73 | dirty = true 74 | ck() 75 | case size.Event: 76 | wind.Upload(image.ZP, b, b.Bounds()) 77 | fr.Refresh() 78 | ck() 79 | case paint.Event: 80 | wind.Upload(fr.Bounds().Min, b, fr.Bounds()) 81 | fr.Flush() 82 | wind.Publish() 83 | case lifecycle.Event: 84 | if e.To == lifecycle.StageDead { 85 | return 86 | } 87 | } 88 | } 89 | }) 90 | } 91 | 92 | func pt(e mouse.Event) image.Point { 93 | return image.Pt(int(e.X), int(e.Y)) 94 | } 95 | 96 | func frameSweep(f *frame.Frame, ep screen.Window, flush func()) { 97 | p0, p1 := f.Dot() 98 | for { 99 | switch e := ep.NextEvent().(type) { 100 | case mouse.Event: 101 | if e.Direction != 0 { 102 | ep.SendFirst(e) 103 | return 104 | } 105 | if p1 = f.IndexOf(pt(e)); p0 > p1 { 106 | f.Select(p1, p0) 107 | } else { 108 | f.Select(p0, p1) 109 | } 110 | flush() 111 | case interface{}: 112 | ep.SendFirst(e) 113 | return 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /example/elastic/elastic.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/draw" 8 | 9 | "github.com/as/font" 10 | "github.com/as/frame" 11 | "golang.org/x/exp/shiny/driver" 12 | "golang.org/x/exp/shiny/screen" 13 | "golang.org/x/mobile/event/key" 14 | "golang.org/x/mobile/event/lifecycle" 15 | "golang.org/x/mobile/event/mouse" 16 | "golang.org/x/mobile/event/paint" 17 | "golang.org/x/mobile/event/size" 18 | ) 19 | 20 | var winSize = image.Pt(1024, 768) 21 | 22 | func pt(e mouse.Event) image.Point { 23 | return image.Pt(int(e.X), int(e.Y)) 24 | } 25 | 26 | func main() { 27 | driver.Main(func(src screen.Screen) { 28 | var dirty = true 29 | wind, _ := src.NewWindow(&screen.NewWindowOptions{winSize.X, winSize.Y, "basic"}) 30 | b, _ := src.NewBuffer(winSize) 31 | draw.Draw(b.RGBA(), b.Bounds(), frame.A.Back, image.ZP, draw.Src) 32 | 33 | fr := frame.New(b.RGBA(), b.Bounds(), &frame.Config{ 34 | Face: font.NewGoRegular(14), 35 | Color: frame.A, 36 | Flag: frame.FrUTF8 | frame.FrElastic, 37 | }, 38 | ) 39 | fr.Refresh() 40 | wind.Send(paint.Event{}) 41 | ck := func() { 42 | if dirty || fr.Dirty() { 43 | wind.Send(paint.Event{}) 44 | } 45 | dirty = false 46 | } 47 | utf := ` 48 | Note: Elastic Tabstop implementation is slow and has missing test coverage 49 | 50 | NAME LENGTH Φ 51 | Go 2 1 52 | Erlang 6 2 53 | Python 6 2 54 | The C++ Programming Language by Bjarne Stroustrup 49 42 55 | ` 56 | fr.Insert([]byte("utf8 test"), fr.Len()) 57 | fr.Insert([]byte(utf), fr.Len()) 58 | fr.Insert([]byte("end"), fr.Len()) 59 | flush := func() { 60 | wind.Upload(fr.Bounds().Min, b, fr.Bounds()) 61 | wind.Publish() 62 | } 63 | for { 64 | switch e := wind.NextEvent().(type) { 65 | case mouse.Event: 66 | if e.Button == 1 && e.Direction == 1 { 67 | p0 := fr.IndexOf(pt(e)) 68 | fr.Select(p0, p0) 69 | 70 | flush() 71 | frameSweep(fr, wind, flush) 72 | wind.Send(paint.Event{}) 73 | } 74 | case key.Event: 75 | if e.Direction == 2 { 76 | continue 77 | } 78 | if e.Rune == '\r' { 79 | e.Rune = '\n' 80 | } 81 | if e.Rune > 0x79 || e.Rune < 0 { 82 | continue 83 | } 84 | p0, p1 := fr.Dot() 85 | if e.Rune == '\x08' { 86 | if p0 == p1 && p0 > 0 { 87 | p0-- 88 | } 89 | fr.Delete(p0, p1) 90 | } else { 91 | fr.Insert([]byte{byte(e.Rune)}, p0) 92 | p0++ 93 | } 94 | fr.Select(p0, p0) 95 | dirty = true 96 | ck() 97 | case size.Event: 98 | wind.Upload(image.ZP, b, b.Bounds()) 99 | fr.Refresh() 100 | ck() 101 | case paint.Event: 102 | flush() 103 | case lifecycle.Event: 104 | if e.To == lifecycle.StageDead { 105 | return 106 | } 107 | 108 | } 109 | } 110 | }) 111 | } 112 | 113 | func frameSweep(f *frame.Frame, ep screen.Window, flush func()) { 114 | p0, p1 := f.Dot() 115 | for { 116 | switch e := ep.NextEvent().(type) { 117 | case mouse.Event: 118 | if e.Direction != 0 { 119 | ep.SendFirst(e) 120 | return 121 | } 122 | if p1 = f.IndexOf(pt(e)); p0 > p1 { 123 | f.Select(p1, p0) 124 | } else { 125 | f.Select(p0, p1) 126 | } 127 | flush() 128 | case interface{}: 129 | ep.SendFirst(e) 130 | return 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /example/utf8/utf8.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "image" 7 | "image/draw" 8 | "sync" 9 | 10 | "github.com/as/font" 11 | "github.com/as/frame" 12 | "golang.org/x/exp/shiny/driver" 13 | "golang.org/x/exp/shiny/screen" 14 | "golang.org/x/mobile/event/key" 15 | "golang.org/x/mobile/event/lifecycle" 16 | "golang.org/x/mobile/event/mouse" 17 | "golang.org/x/mobile/event/paint" 18 | "golang.org/x/mobile/event/size" 19 | ) 20 | 21 | var wg sync.WaitGroup 22 | var winSize = image.Pt(1024, 768) 23 | 24 | func pt(e mouse.Event) image.Point { 25 | return image.Pt(int(e.X), int(e.Y)) 26 | } 27 | 28 | func main() { 29 | driver.Main(func(src screen.Screen) { 30 | var dirty = true 31 | wind, _ := src.NewWindow(&screen.NewWindowOptions{winSize.X, winSize.Y, "basic"}) 32 | b, _ := src.NewBuffer(winSize) 33 | draw.Draw(b.RGBA(), b.Bounds(), frame.A.Back, image.ZP, draw.Src) 34 | fr := frame.New(b.RGBA(), image.Rectangle{image.ZP, winSize}, &frame.Config{Face: font.NewGoMono(14), Color: frame.A, Flag: frame.FrUTF8}) 35 | fr.Refresh() 36 | wind.Send(paint.Event{}) 37 | ck := func() { 38 | if dirty || fr.Dirty() { 39 | wind.Send(paint.Event{}) 40 | } 41 | dirty = false 42 | } 43 | utf := `Π Ρ ΢ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ οπ ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ Ϗ ϐ ϑ ϒ ϓ ϔ ϕ ϖ ϗ Ϙ ϙ Ϛ ϛ Ϝ ϝ Ϟ ϟϠ ϡ Ϣ ϣ Ϥ ϥ Ϧ ϧ Ϩ ϩ Ϫ ϫ Ϭ ϭ Ϯ ϯ ϰ ϱ ϲ ϳ ϴ ϵ ϶ Ϸ ϸ Ϲ Ϻ ϻ ϼ Ͻ Ͼ Ͽ` 44 | fr.Insert([]byte("utf8 test"), fr.Len()) 45 | fr.Insert([]byte(utf), fr.Len()) 46 | fr.Insert([]byte("end"), fr.Len()) 47 | flush := func() { 48 | wind.Upload(fr.Bounds().Min, b, fr.Bounds()) 49 | wind.Publish() 50 | } 51 | for { 52 | switch e := wind.NextEvent().(type) { 53 | case mouse.Event: 54 | if e.Button == 1 && e.Direction == 1 { 55 | p0 := fr.IndexOf(pt(e)) 56 | fr.Select(p0, p0) 57 | flush() 58 | frameSweep(fr, wind, flush) 59 | wind.Send(paint.Event{}) 60 | } 61 | case key.Event: 62 | if e.Direction == 2 { 63 | continue 64 | } 65 | if e.Rune == '\r' { 66 | e.Rune = '\n' 67 | } 68 | if e.Rune > 0x79 || e.Rune < 0 { 69 | continue 70 | } 71 | p0, p1 := fr.Dot() 72 | if e.Rune == '\x08' { 73 | if p0 == p1 && p0 > 0 { 74 | p0-- 75 | } 76 | fr.Delete(p0, p1) 77 | } else { 78 | fr.Insert([]byte{byte(e.Rune)}, p0) 79 | p0++ 80 | } 81 | fr.Select(p0, p0) 82 | dirty = true 83 | ck() 84 | case size.Event: 85 | wind.Upload(image.ZP, b, b.Bounds()) 86 | fr.Refresh() 87 | ck() 88 | case paint.Event: 89 | flush() 90 | case lifecycle.Event: 91 | if e.To == lifecycle.StageDead { 92 | return 93 | } 94 | 95 | } 96 | } 97 | }) 98 | } 99 | 100 | func frameSweep(f *frame.Frame, ep screen.Window, flush func()) { 101 | p0, p1 := f.Dot() 102 | for { 103 | switch e := ep.NextEvent().(type) { 104 | case mouse.Event: 105 | if e.Direction != 0 { 106 | ep.SendFirst(e) 107 | return 108 | } 109 | if p1 = f.IndexOf(pt(e)); p0 > p1 { 110 | f.Select(p1, p0) 111 | } else { 112 | f.Select(p0, p1) 113 | } 114 | flush() 115 | case interface{}: 116 | ep.SendFirst(e) 117 | return 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "image/draw" 7 | 8 | "github.com/as/font" 9 | "github.com/as/frame/box" 10 | ) 11 | 12 | const ( 13 | FrElastic = 1 << iota 14 | FrUTF8 15 | ) 16 | 17 | var ( 18 | ForceElastic bool 19 | ForceUTF8 bool 20 | ) 21 | 22 | var ( 23 | ErrBadDst = errors.New("bad dst") 24 | ) 25 | 26 | // Frame is a write-only container for editable text 27 | type Frame struct { 28 | box.Run 29 | p0 int64 30 | p1 int64 31 | b draw.Image 32 | r image.Rectangle 33 | ir *box.Run 34 | 35 | Face font.Face 36 | Color 37 | Ticked bool 38 | Scroll func(int) 39 | Drawer 40 | op draw.Op 41 | 42 | mintab int 43 | maxtab int 44 | full int 45 | 46 | tick draw.Image 47 | tickback draw.Image 48 | tickoff bool 49 | maxlines int 50 | modified bool 51 | 52 | pts [][2]image.Point 53 | 54 | flags int 55 | } 56 | 57 | func New(dst draw.Image, r image.Rectangle, conf *Config) *Frame { 58 | if dst == nil { 59 | return nil 60 | } 61 | if conf == nil { 62 | conf = &Config{} 63 | } 64 | conf.check() 65 | fl := conf.Flag 66 | face := negotiateFace(conf.Face, fl) 67 | mintab, maxtab := tabMinMax(face, fl&FrElastic != 0) 68 | f := &Frame{ 69 | Face: face, 70 | Color: conf.Color, 71 | Drawer: conf.Drawer, 72 | Run: box.NewRun(mintab, 5000, face), 73 | op: draw.Src, 74 | mintab: mintab, 75 | maxtab: maxtab, 76 | flags: fl, 77 | } 78 | f.setrects(r, dst) 79 | f.inittick() 80 | run := box.NewRun(mintab, 5000, face) 81 | f.ir = &run 82 | return f 83 | } 84 | 85 | func (f *Frame) Config() *Config { 86 | return &Config{ 87 | Flag: f.flags, 88 | Color: f.Color, 89 | Face: f.Face, 90 | Drawer: f.Drawer, 91 | } 92 | } 93 | 94 | var zc Color 95 | 96 | // Flags returns the flags currently set for the frame 97 | func (f *Frame) Flags() int { 98 | return f.flags 99 | } 100 | 101 | // Flag sets the flags for the frame. At this time 102 | // only FrElastic is supported. 103 | func (f *Frame) SetFlags(flags int) { 104 | fl := getflag(flags) 105 | f.flags = fl 106 | f.mintab, f.maxtab = tabMinMax(f.Face, f.elastic()) 107 | // f.Reset( f.r, f.RGBA(),f.Font) 108 | // f.mintab, f.maxtab = tabMinMax(f.Font, f.elastic()) 109 | } 110 | 111 | func (f *Frame) elastic() bool { 112 | return f.flags&FrElastic != 0 113 | } 114 | 115 | func tabMinMax(ft font.Face, elastic bool) (min, max int) { 116 | mintab := ft.Dx([]byte{' '}) 117 | maxtab := mintab * 4 118 | if elastic { 119 | mintab = maxtab 120 | } 121 | return mintab, maxtab 122 | } 123 | 124 | func (f *Frame) RGBA() *image.RGBA { 125 | rgba, _ := f.b.(*image.RGBA) 126 | return rgba 127 | } 128 | func (f *Frame) Size() image.Point { 129 | return f.r.Size() 130 | } 131 | 132 | // Dirty returns true if the contents of the frame have changes since the last redraw 133 | func (f *Frame) Dirty() bool { 134 | return f.modified 135 | } 136 | 137 | // SetDirty alters the frame's internal state 138 | func (f *Frame) SetDirty(dirty bool) { 139 | f.modified = dirty 140 | } 141 | 142 | func (f *Frame) SetFont(ft font.Face) { 143 | f.Face = font.Open(ft) 144 | f.Run.Reset(f.Face) 145 | f.Refresh() 146 | } 147 | 148 | func (f *Frame) SetOp(op draw.Op) { 149 | f.op = op 150 | } 151 | 152 | // Close closes the frame 153 | func (f *Frame) Close() error { 154 | return nil 155 | } 156 | 157 | // Reset resets the frame to display on image b with bounds r and font ft. 158 | func (f *Frame) Reset(r image.Rectangle, b *image.RGBA, ft font.Face) { 159 | f.r = r 160 | f.b = b 161 | f.SetFont(ft) 162 | } 163 | 164 | // Bounds returns the frame's clipping rectangle 165 | func (f *Frame) Bounds() image.Rectangle { 166 | return f.r.Bounds() 167 | } 168 | 169 | // Full returns true if the last line in the frame is full. 170 | func (f *Frame) Full() bool { 171 | if f == nil { 172 | return true 173 | } 174 | return f.full == 1 175 | } 176 | 177 | // Maxline returns the max number of wrapped lines fitting on the frame 178 | func (f *Frame) MaxLine() int { 179 | if f == nil { 180 | return 0 181 | } 182 | return f.maxlines 183 | } 184 | 185 | // Line returns the number of wrapped lines currently in the frame 186 | func (f *Frame) Line() int { 187 | if f == nil { 188 | return 0 189 | } 190 | return f.Nlines 191 | } 192 | 193 | // Len returns the number of bytes currently in the frame 194 | func (f *Frame) Len() int64 { 195 | if f == nil { 196 | return 0 197 | } 198 | return f.Nchars 199 | } 200 | 201 | // Dot returns the range of the selected text 202 | func (f *Frame) Dot() (p0, p1 int64) { 203 | return f.p0, f.p1 204 | } 205 | 206 | func (f *Frame) setrects(r image.Rectangle, b draw.Image) { 207 | f.b = b 208 | f.r = r 209 | h := f.Face.Dy() 210 | f.r.Max.Y -= f.r.Dy() % h 211 | f.maxlines = f.r.Dy() / h 212 | } 213 | -------------------------------------------------------------------------------- /framefix/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Fix finds Go programs that use old APIs and rewrites them to use 7 | newer ones. After you update to a new Go release, fix helps make 8 | the necessary changes to your programs. 9 | 10 | Usage: 11 | go tool fix [-r name,...] [path ...] 12 | 13 | Without an explicit path, fix reads standard input and writes the 14 | result to standard output. 15 | 16 | If the named path is a file, fix rewrites the named files in place. 17 | If the named path is a directory, fix rewrites all .go files in that 18 | directory tree. When fix rewrites a file, it prints a line to standard 19 | error giving the name of the file and the rewrite applied. 20 | 21 | If the -diff flag is set, no files are rewritten. Instead fix prints 22 | the differences a rewrite would introduce. 23 | 24 | The -r flag restricts the set of rewrites considered to those in the 25 | named list. By default fix considers all known rewrites. Fix's 26 | rewrites are idempotent, so that it is safe to apply fix to updated 27 | or partially updated code even without using the -r flag. 28 | 29 | Fix prints the full list of fixes it can apply in its help output; 30 | to see them, run go tool fix -help. 31 | 32 | Fix does not make backup copies of the files that it edits. 33 | Instead, use a version control system's ``diff'' functionality to inspect 34 | the changes that fix makes before committing them. 35 | */ 36 | package main 37 | -------------------------------------------------------------------------------- /framefix/frame.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 (as). This package was lifted from cmd/fix and 2 | // this file (along with frame_test.go) was added to fix API changes 3 | // made in recent updates. The changes/additions retain the same 4 | // license as the Go programming language (BSD like) 5 | 6 | package main 7 | 8 | import ( 9 | "go/ast" 10 | "go/token" 11 | ) 12 | 13 | func init() { 14 | register(frameFix) 15 | } 16 | 17 | var frameFix = fix{ 18 | name: "frame", 19 | date: "2018-02-03", 20 | f: frame, 21 | desc: `fix frame.New invokation`, 22 | } 23 | 24 | func frame(f *ast.File) bool { 25 | fixed := false 26 | if imports(f, "github.com/as/frame/font") { 27 | defer func() { 28 | rewriteImport(f, "github.com/as/frame/font", "github.com/as/font") 29 | fixed = true 30 | }() 31 | } 32 | if !imports(f, "github.com/as/frame") { 33 | return fixed 34 | } 35 | walk(f, func(n interface{}) { 36 | fcall, ok := n.(*ast.CallExpr) 37 | if !ok { 38 | return 39 | } 40 | se, ok := fcall.Fun.(*ast.SelectorExpr) 41 | if !ok { 42 | return 43 | } 44 | pkgname, ok := se.X.(*ast.Ident) 45 | if !ok { 46 | return 47 | } 48 | if pkgname.Name != "frame" { 49 | return 50 | } 51 | x := se.Sel 52 | if x.Name == "Font" { 53 | x.Name = "Face" 54 | fixed = true 55 | return 56 | } 57 | if x.Name != "New" { 58 | return 59 | } 60 | args := fcall.Args 61 | var flags ast.Expr 62 | switch len(args) { 63 | default: 64 | flags = args[4] 65 | fallthrough 66 | case 4: 67 | dst := args[2] 68 | r := args[0] 69 | ft := args[1] 70 | col := args[3] 71 | fcall.Args = []ast.Expr{dst, r, mkConfig(ft, col, flags)} 72 | fixed = true 73 | case 3: 74 | case 2: 75 | case 1: 76 | case 0: 77 | } 78 | }) 79 | 80 | return fixed 81 | } 82 | 83 | func colorFix(a ast.Expr) ast.Expr { 84 | return a 85 | } 86 | 87 | func mkConfig(ft, col, flags ast.Expr) (exp *ast.UnaryExpr) { 88 | list := []ast.Expr{ 89 | &ast.KeyValueExpr{ 90 | Key: &ast.Ident{ 91 | Name: "Face", 92 | }, 93 | Value: ft, 94 | }, 95 | &ast.KeyValueExpr{ 96 | Key: &ast.Ident{ 97 | Name: "Color", 98 | }, 99 | Value: col, 100 | }, 101 | } 102 | if flags != nil { 103 | list = append(list, &ast.KeyValueExpr{ 104 | Key: &ast.Ident{ 105 | Name: "Flag", 106 | }, 107 | Value: flags, 108 | }) 109 | } 110 | return &ast.UnaryExpr{ 111 | Op: token.AND, 112 | X: &ast.CompositeLit{ 113 | Type: &ast.SelectorExpr{ 114 | X: &ast.Ident{ 115 | Name: "frame", 116 | }, 117 | Sel: &ast.Ident{ 118 | Name: "Config", 119 | }, 120 | }, 121 | Elts: list, 122 | }, 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /framefix/frame_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | func init() { 8 | addTestCases(frameTests, frame) 9 | } 10 | 11 | var frameTests = []testCase{ 12 | { 13 | Name: "frame.0", 14 | In: `package main 15 | 16 | import ( 17 | "image" 18 | 19 | "github.com/as/frame" 20 | "github.com/as/frame/font" 21 | ) 22 | 23 | func main() { 24 | img := image.NewRGBA(image.Rect(0, 0, 100, 100)) 25 | frame.New(img.Bounds(), font.NewGoMono(11), img, frame.Mono, frame.FrElastic) 26 | } 27 | `, 28 | Out: `package main 29 | 30 | import ( 31 | "image" 32 | 33 | "github.com/as/font" 34 | "github.com/as/frame" 35 | ) 36 | 37 | func main() { 38 | img := image.NewRGBA(image.Rect(0, 0, 100, 100)) 39 | frame.New(img, img.Bounds(), &frame.Config{Face: font.NewGoMono(11), Color: frame.Mono, Flag: frame.FrElastic}) 40 | } 41 | `, 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /framefix/import_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import "go/ast" 8 | 9 | func init() { 10 | addTestCases(importTests, nil) 11 | } 12 | 13 | var importTests = []testCase{ 14 | { 15 | Name: "import.0", 16 | Fn: addImportFn("os"), 17 | In: `package main 18 | 19 | import ( 20 | "os" 21 | ) 22 | `, 23 | Out: `package main 24 | 25 | import ( 26 | "os" 27 | ) 28 | `, 29 | }, 30 | { 31 | Name: "import.1", 32 | Fn: addImportFn("os"), 33 | In: `package main 34 | `, 35 | Out: `package main 36 | 37 | import "os" 38 | `, 39 | }, 40 | { 41 | Name: "import.2", 42 | Fn: addImportFn("os"), 43 | In: `package main 44 | 45 | // Comment 46 | import "C" 47 | `, 48 | Out: `package main 49 | 50 | // Comment 51 | import "C" 52 | import "os" 53 | `, 54 | }, 55 | { 56 | Name: "import.3", 57 | Fn: addImportFn("os"), 58 | In: `package main 59 | 60 | // Comment 61 | import "C" 62 | 63 | import ( 64 | "io" 65 | "utf8" 66 | ) 67 | `, 68 | Out: `package main 69 | 70 | // Comment 71 | import "C" 72 | 73 | import ( 74 | "io" 75 | "os" 76 | "utf8" 77 | ) 78 | `, 79 | }, 80 | { 81 | Name: "import.4", 82 | Fn: deleteImportFn("os"), 83 | In: `package main 84 | 85 | import ( 86 | "os" 87 | ) 88 | `, 89 | Out: `package main 90 | `, 91 | }, 92 | { 93 | Name: "import.5", 94 | Fn: deleteImportFn("os"), 95 | In: `package main 96 | 97 | // Comment 98 | import "C" 99 | import "os" 100 | `, 101 | Out: `package main 102 | 103 | // Comment 104 | import "C" 105 | `, 106 | }, 107 | { 108 | Name: "import.6", 109 | Fn: deleteImportFn("os"), 110 | In: `package main 111 | 112 | // Comment 113 | import "C" 114 | 115 | import ( 116 | "io" 117 | "os" 118 | "utf8" 119 | ) 120 | `, 121 | Out: `package main 122 | 123 | // Comment 124 | import "C" 125 | 126 | import ( 127 | "io" 128 | "utf8" 129 | ) 130 | `, 131 | }, 132 | { 133 | Name: "import.7", 134 | Fn: deleteImportFn("io"), 135 | In: `package main 136 | 137 | import ( 138 | "io" // a 139 | "os" // b 140 | "utf8" // c 141 | ) 142 | `, 143 | Out: `package main 144 | 145 | import ( 146 | // a 147 | "os" // b 148 | "utf8" // c 149 | ) 150 | `, 151 | }, 152 | { 153 | Name: "import.8", 154 | Fn: deleteImportFn("os"), 155 | In: `package main 156 | 157 | import ( 158 | "io" // a 159 | "os" // b 160 | "utf8" // c 161 | ) 162 | `, 163 | Out: `package main 164 | 165 | import ( 166 | "io" // a 167 | // b 168 | "utf8" // c 169 | ) 170 | `, 171 | }, 172 | { 173 | Name: "import.9", 174 | Fn: deleteImportFn("utf8"), 175 | In: `package main 176 | 177 | import ( 178 | "io" // a 179 | "os" // b 180 | "utf8" // c 181 | ) 182 | `, 183 | Out: `package main 184 | 185 | import ( 186 | "io" // a 187 | "os" // b 188 | // c 189 | ) 190 | `, 191 | }, 192 | { 193 | Name: "import.10", 194 | Fn: deleteImportFn("io"), 195 | In: `package main 196 | 197 | import ( 198 | "io" 199 | "os" 200 | "utf8" 201 | ) 202 | `, 203 | Out: `package main 204 | 205 | import ( 206 | "os" 207 | "utf8" 208 | ) 209 | `, 210 | }, 211 | { 212 | Name: "import.11", 213 | Fn: deleteImportFn("os"), 214 | In: `package main 215 | 216 | import ( 217 | "io" 218 | "os" 219 | "utf8" 220 | ) 221 | `, 222 | Out: `package main 223 | 224 | import ( 225 | "io" 226 | "utf8" 227 | ) 228 | `, 229 | }, 230 | { 231 | Name: "import.12", 232 | Fn: deleteImportFn("utf8"), 233 | In: `package main 234 | 235 | import ( 236 | "io" 237 | "os" 238 | "utf8" 239 | ) 240 | `, 241 | Out: `package main 242 | 243 | import ( 244 | "io" 245 | "os" 246 | ) 247 | `, 248 | }, 249 | { 250 | Name: "import.13", 251 | Fn: rewriteImportFn("utf8", "encoding/utf8"), 252 | In: `package main 253 | 254 | import ( 255 | "io" 256 | "os" 257 | "utf8" // thanks ken 258 | ) 259 | `, 260 | Out: `package main 261 | 262 | import ( 263 | "encoding/utf8" // thanks ken 264 | "io" 265 | "os" 266 | ) 267 | `, 268 | }, 269 | { 270 | Name: "import.14", 271 | Fn: rewriteImportFn("asn1", "encoding/asn1"), 272 | In: `package main 273 | 274 | import ( 275 | "asn1" 276 | "crypto" 277 | "crypto/rsa" 278 | _ "crypto/sha1" 279 | "crypto/x509" 280 | "crypto/x509/pkix" 281 | "time" 282 | ) 283 | 284 | var x = 1 285 | `, 286 | Out: `package main 287 | 288 | import ( 289 | "crypto" 290 | "crypto/rsa" 291 | _ "crypto/sha1" 292 | "crypto/x509" 293 | "crypto/x509/pkix" 294 | "encoding/asn1" 295 | "time" 296 | ) 297 | 298 | var x = 1 299 | `, 300 | }, 301 | { 302 | Name: "import.15", 303 | Fn: rewriteImportFn("url", "net/url"), 304 | In: `package main 305 | 306 | import ( 307 | "bufio" 308 | "net" 309 | "path" 310 | "url" 311 | ) 312 | 313 | var x = 1 // comment on x, not on url 314 | `, 315 | Out: `package main 316 | 317 | import ( 318 | "bufio" 319 | "net" 320 | "net/url" 321 | "path" 322 | ) 323 | 324 | var x = 1 // comment on x, not on url 325 | `, 326 | }, 327 | { 328 | Name: "import.16", 329 | Fn: rewriteImportFn("http", "net/http", "template", "text/template"), 330 | In: `package main 331 | 332 | import ( 333 | "flag" 334 | "http" 335 | "log" 336 | "template" 337 | ) 338 | 339 | var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 340 | `, 341 | Out: `package main 342 | 343 | import ( 344 | "flag" 345 | "log" 346 | "net/http" 347 | "text/template" 348 | ) 349 | 350 | var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 351 | `, 352 | }, 353 | { 354 | Name: "import.17", 355 | Fn: addImportFn("x/y/z", "x/a/c"), 356 | In: `package main 357 | 358 | // Comment 359 | import "C" 360 | 361 | import ( 362 | "a" 363 | "b" 364 | 365 | "x/w" 366 | 367 | "d/f" 368 | ) 369 | `, 370 | Out: `package main 371 | 372 | // Comment 373 | import "C" 374 | 375 | import ( 376 | "a" 377 | "b" 378 | 379 | "x/a/c" 380 | "x/w" 381 | "x/y/z" 382 | 383 | "d/f" 384 | ) 385 | `, 386 | }, 387 | { 388 | Name: "import.18", 389 | Fn: addDelImportFn("e", "o"), 390 | In: `package main 391 | 392 | import ( 393 | "f" 394 | "o" 395 | "z" 396 | ) 397 | `, 398 | Out: `package main 399 | 400 | import ( 401 | "e" 402 | "f" 403 | "z" 404 | ) 405 | `, 406 | }, 407 | } 408 | 409 | func addImportFn(path ...string) func(*ast.File) bool { 410 | return func(f *ast.File) bool { 411 | fixed := false 412 | for _, p := range path { 413 | if !imports(f, p) { 414 | addImport(f, p) 415 | fixed = true 416 | } 417 | } 418 | return fixed 419 | } 420 | } 421 | 422 | func deleteImportFn(path string) func(*ast.File) bool { 423 | return func(f *ast.File) bool { 424 | if imports(f, path) { 425 | deleteImport(f, path) 426 | return true 427 | } 428 | return false 429 | } 430 | } 431 | 432 | func addDelImportFn(p1 string, p2 string) func(*ast.File) bool { 433 | return func(f *ast.File) bool { 434 | fixed := false 435 | if !imports(f, p1) { 436 | addImport(f, p1) 437 | fixed = true 438 | } 439 | if imports(f, p2) { 440 | deleteImport(f, p2) 441 | fixed = true 442 | } 443 | return fixed 444 | } 445 | } 446 | 447 | func rewriteImportFn(oldnew ...string) func(*ast.File) bool { 448 | return func(f *ast.File) bool { 449 | fixed := false 450 | for i := 0; i < len(oldnew); i += 2 { 451 | if imports(f, oldnew[i]) { 452 | rewriteImport(f, oldnew[i], oldnew[i+1]) 453 | fixed = true 454 | } 455 | } 456 | return fixed 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /framefix/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "flag" 10 | "fmt" 11 | "go/ast" 12 | "go/format" 13 | "go/parser" 14 | "go/scanner" 15 | "go/token" 16 | "io/ioutil" 17 | "os" 18 | "os/exec" 19 | "path/filepath" 20 | "runtime" 21 | "sort" 22 | "strings" 23 | ) 24 | 25 | var ( 26 | fset = token.NewFileSet() 27 | exitCode = 0 28 | ) 29 | 30 | var allowedRewrites = flag.String("r", "", 31 | "restrict the rewrites to this comma-separated list") 32 | 33 | var forceRewrites = flag.String("force", "", 34 | "force these fixes to run even if the code looks updated") 35 | 36 | var allowed, force map[string]bool 37 | 38 | var doDiff = flag.Bool("diff", false, "display diffs instead of rewriting files") 39 | 40 | // enable for debugging fix failures 41 | const debug = false // display incorrectly reformatted source and exit 42 | 43 | func usage() { 44 | fmt.Fprintf(os.Stderr, "usage: go tool fix [-diff] [-r fixname,...] [-force fixname,...] [path ...]\n") 45 | flag.PrintDefaults() 46 | fmt.Fprintf(os.Stderr, "\nAvailable rewrites are:\n") 47 | sort.Sort(byName(fixes)) 48 | for _, f := range fixes { 49 | if f.disabled { 50 | fmt.Fprintf(os.Stderr, "\n%s (disabled)\n", f.name) 51 | } else { 52 | fmt.Fprintf(os.Stderr, "\n%s\n", f.name) 53 | } 54 | desc := strings.TrimSpace(f.desc) 55 | desc = strings.Replace(desc, "\n", "\n\t", -1) 56 | fmt.Fprintf(os.Stderr, "\t%s\n", desc) 57 | } 58 | os.Exit(2) 59 | } 60 | 61 | func main() { 62 | flag.Usage = usage 63 | flag.Parse() 64 | 65 | sort.Sort(byDate(fixes)) 66 | 67 | if *allowedRewrites != "" { 68 | allowed = make(map[string]bool) 69 | for _, f := range strings.Split(*allowedRewrites, ",") { 70 | allowed[f] = true 71 | } 72 | } 73 | 74 | if *forceRewrites != "" { 75 | force = make(map[string]bool) 76 | for _, f := range strings.Split(*forceRewrites, ",") { 77 | force[f] = true 78 | } 79 | } 80 | 81 | if flag.NArg() == 0 { 82 | if err := processFile("standard input", true); err != nil { 83 | report(err) 84 | } 85 | os.Exit(exitCode) 86 | } 87 | 88 | for i := 0; i < flag.NArg(); i++ { 89 | path := flag.Arg(i) 90 | switch dir, err := os.Stat(path); { 91 | case err != nil: 92 | report(err) 93 | case dir.IsDir(): 94 | walkDir(path) 95 | default: 96 | if err := processFile(path, false); err != nil { 97 | report(err) 98 | } 99 | } 100 | } 101 | 102 | os.Exit(exitCode) 103 | } 104 | 105 | const parserMode = parser.ParseComments 106 | 107 | func gofmtFile(f *ast.File) ([]byte, error) { 108 | var buf bytes.Buffer 109 | if err := format.Node(&buf, fset, f); err != nil { 110 | return nil, err 111 | } 112 | return buf.Bytes(), nil 113 | } 114 | 115 | func processFile(filename string, useStdin bool) error { 116 | var f *os.File 117 | var err error 118 | var fixlog bytes.Buffer 119 | 120 | if useStdin { 121 | f = os.Stdin 122 | } else { 123 | f, err = os.Open(filename) 124 | if err != nil { 125 | return err 126 | } 127 | defer f.Close() 128 | } 129 | 130 | src, err := ioutil.ReadAll(f) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | file, err := parser.ParseFile(fset, filename, src, parserMode) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | // Apply all fixes to file. 141 | newFile := file 142 | fixed := false 143 | for _, fix := range fixes { 144 | if allowed != nil && !allowed[fix.name] { 145 | continue 146 | } 147 | if fix.disabled && !force[fix.name] { 148 | continue 149 | } 150 | if fix.f(newFile) { 151 | fixed = true 152 | fmt.Fprintf(&fixlog, " %s", fix.name) 153 | 154 | // AST changed. 155 | // Print and parse, to update any missing scoping 156 | // or position information for subsequent fixers. 157 | newSrc, err := gofmtFile(newFile) 158 | if err != nil { 159 | return err 160 | } 161 | newFile, err = parser.ParseFile(fset, filename, newSrc, parserMode) 162 | if err != nil { 163 | if debug { 164 | fmt.Printf("%s", newSrc) 165 | report(err) 166 | os.Exit(exitCode) 167 | } 168 | return err 169 | } 170 | } 171 | } 172 | if !fixed { 173 | return nil 174 | } 175 | fmt.Fprintf(os.Stderr, "%s: fixed %s\n", filename, fixlog.String()[1:]) 176 | 177 | // Print AST. We did that after each fix, so this appears 178 | // redundant, but it is necessary to generate gofmt-compatible 179 | // source code in a few cases. The official gofmt style is the 180 | // output of the printer run on a standard AST generated by the parser, 181 | // but the source we generated inside the loop above is the 182 | // output of the printer run on a mangled AST generated by a fixer. 183 | newSrc, err := gofmtFile(newFile) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | if *doDiff { 189 | data, err := diff(src, newSrc) 190 | if err != nil { 191 | return fmt.Errorf("computing diff: %s", err) 192 | } 193 | fmt.Printf("diff %s fixed/%s\n", filename, filename) 194 | os.Stdout.Write(data) 195 | return nil 196 | } 197 | 198 | if useStdin { 199 | os.Stdout.Write(newSrc) 200 | return nil 201 | } 202 | 203 | return ioutil.WriteFile(f.Name(), newSrc, 0) 204 | } 205 | 206 | var gofmtBuf bytes.Buffer 207 | 208 | func gofmt(n interface{}) string { 209 | gofmtBuf.Reset() 210 | if err := format.Node(&gofmtBuf, fset, n); err != nil { 211 | return "<" + err.Error() + ">" 212 | } 213 | return gofmtBuf.String() 214 | } 215 | 216 | func report(err error) { 217 | scanner.PrintError(os.Stderr, err) 218 | exitCode = 2 219 | } 220 | 221 | func walkDir(path string) { 222 | filepath.Walk(path, visitFile) 223 | } 224 | 225 | func visitFile(path string, f os.FileInfo, err error) error { 226 | if err == nil && isGoFile(f) { 227 | err = processFile(path, false) 228 | } 229 | if err != nil { 230 | report(err) 231 | } 232 | return nil 233 | } 234 | 235 | func isGoFile(f os.FileInfo) bool { 236 | // ignore non-Go files 237 | name := f.Name() 238 | return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") 239 | } 240 | 241 | func writeTempFile(dir, prefix string, data []byte) (string, error) { 242 | file, err := ioutil.TempFile(dir, prefix) 243 | if err != nil { 244 | return "", err 245 | } 246 | _, err = file.Write(data) 247 | if err1 := file.Close(); err == nil { 248 | err = err1 249 | } 250 | if err != nil { 251 | os.Remove(file.Name()) 252 | return "", err 253 | } 254 | return file.Name(), nil 255 | } 256 | 257 | func diff(b1, b2 []byte) (data []byte, err error) { 258 | f1, err := writeTempFile("", "go-fix", b1) 259 | if err != nil { 260 | return 261 | } 262 | defer os.Remove(f1) 263 | 264 | f2, err := writeTempFile("", "go-fix", b2) 265 | if err != nil { 266 | return 267 | } 268 | defer os.Remove(f2) 269 | 270 | cmd := "diff" 271 | if runtime.GOOS == "plan9" { 272 | cmd = "/bin/ape/diff" 273 | } 274 | 275 | data, err = exec.Command(cmd, "-u", f1, f2).CombinedOutput() 276 | if len(data) > 0 { 277 | // diff exits with a non-zero status when the files don't match. 278 | // Ignore that failure as long as we get output. 279 | err = nil 280 | } 281 | return 282 | } 283 | -------------------------------------------------------------------------------- /framefix/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "go/ast" 9 | "go/parser" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | type testCase struct { 15 | Name string 16 | Fn func(*ast.File) bool 17 | In string 18 | Out string 19 | } 20 | 21 | var testCases []testCase 22 | 23 | func addTestCases(t []testCase, fn func(*ast.File) bool) { 24 | // Fill in fn to avoid repetition in definitions. 25 | if fn != nil { 26 | for i := range t { 27 | if t[i].Fn == nil { 28 | t[i].Fn = fn 29 | } 30 | } 31 | } 32 | testCases = append(testCases, t...) 33 | } 34 | 35 | func fnop(*ast.File) bool { return false } 36 | 37 | func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string, mustBeGofmt bool) (out string, fixed, ok bool) { 38 | file, err := parser.ParseFile(fset, desc, in, parserMode) 39 | if err != nil { 40 | t.Errorf("%s: parsing: %v", desc, err) 41 | return 42 | } 43 | 44 | outb, err := gofmtFile(file) 45 | if err != nil { 46 | t.Errorf("%s: printing: %v", desc, err) 47 | return 48 | } 49 | if s := string(outb); in != s && mustBeGofmt { 50 | t.Errorf("%s: not gofmt-formatted.\n--- %s\n%s\n--- %s | gofmt\n%s", 51 | desc, desc, in, desc, s) 52 | tdiff(t, in, s) 53 | return 54 | } 55 | 56 | if fn == nil { 57 | for _, fix := range fixes { 58 | if fix.f(file) { 59 | fixed = true 60 | } 61 | } 62 | } else { 63 | fixed = fn(file) 64 | } 65 | 66 | outb, err = gofmtFile(file) 67 | if err != nil { 68 | t.Errorf("%s: printing: %v", desc, err) 69 | return 70 | } 71 | 72 | return string(outb), fixed, true 73 | } 74 | 75 | func TestRewrite(t *testing.T) { 76 | for _, tt := range testCases { 77 | // Apply fix: should get tt.Out. 78 | out, fixed, ok := parseFixPrint(t, tt.Fn, tt.Name, tt.In, true) 79 | if !ok { 80 | continue 81 | } 82 | 83 | // reformat to get printing right 84 | out, _, ok = parseFixPrint(t, fnop, tt.Name, out, false) 85 | if !ok { 86 | continue 87 | } 88 | 89 | if out != tt.Out { 90 | t.Errorf("%s: incorrect output.\n", tt.Name) 91 | if !strings.HasPrefix(tt.Name, "testdata/") { 92 | t.Errorf("--- have\n%s\n--- want\n%s", out, tt.Out) 93 | } 94 | tdiff(t, out, tt.Out) 95 | continue 96 | } 97 | 98 | if changed := out != tt.In; changed != fixed { 99 | t.Errorf("%s: changed=%v != fixed=%v", tt.Name, changed, fixed) 100 | continue 101 | } 102 | 103 | // Should not change if run again. 104 | out2, fixed2, ok := parseFixPrint(t, tt.Fn, tt.Name+" output", out, true) 105 | if !ok { 106 | continue 107 | } 108 | 109 | if fixed2 { 110 | t.Errorf("%s: applied fixes during second round", tt.Name) 111 | continue 112 | } 113 | 114 | if out2 != out { 115 | t.Errorf("%s: changed output after second round of fixes.\n--- output after first round\n%s\n--- output after second round\n%s", 116 | tt.Name, out, out2) 117 | tdiff(t, out, out2) 118 | } 119 | } 120 | } 121 | 122 | func tdiff(t *testing.T, a, b string) { 123 | data, err := diff([]byte(a), []byte(b)) 124 | if err != nil { 125 | t.Error(err) 126 | return 127 | } 128 | t.Error(string(data)) 129 | } 130 | -------------------------------------------------------------------------------- /fuzz_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "image" 7 | "io/ioutil" 8 | "math/rand" 9 | "testing" 10 | "time" 11 | 12 | "github.com/as/etch" 13 | "github.com/as/io/spaz" 14 | ) 15 | 16 | func TestFuzz(t *testing.T) { 17 | // t.Skip("warning: fuzz test skipped") 18 | // The inverse of Insert is Delete. We can use this assumption 19 | // to create a graphical fuzz test. 20 | var ( 21 | err error 22 | n int 23 | B [327 * 777]byte 24 | ) 25 | buf := B[:] 26 | N := 128 // number of rounds 27 | sr := spaz.NewReader(bufio.NewReader(reader{})) 28 | fr, fr2, a, b := abtestbg(image.Rect(0, 0, 327, 771)) 29 | for i := 0; i < N; i++ { 30 | // Sync: Start by inserting something into to both frames 31 | n, err = sr.Read(buf) 32 | if err != nil { 33 | t.Log(err) 34 | t.Fail() 35 | } 36 | in := buf[:n] 37 | p0 := clamp(rand.Int63(), 0, fr.Len()) 38 | fr.Insert(in, p0) 39 | fr2.Insert(in, p0) 40 | 41 | // Insert something random into the first frame 42 | // and see if Deleting that gets us coherent with the 43 | // second frame 44 | 45 | prior := buf[:n] // In case it fails, we need a record of the random data 46 | n, err = sr.Read(buf) 47 | if err != nil { 48 | t.Log(err) 49 | t.Fail() 50 | } 51 | in = buf[:n] 52 | p0 = clamp(rand.Int63(), 0, fr.Len()) 53 | ni := int64(fr.Insert(in, p0)) 54 | for i := 0; i < 1000; i++ { 55 | fr.Select(clamp(rand.Int63(), 0, fr.Len()), clamp(rand.Int63(), 0, fr.Len())) 56 | } 57 | nd := fr.Delete(p0, p0+ni) 58 | delta, ok := etch.Delta(a, b) 59 | if !ok { 60 | name := fmt.Sprintf("TestFuzz.Len%dIns%dCharsAt%d", len(prior), len(in), p0) 61 | result := etch.Report(a, b, delta) 62 | info := fmt.Sprintf("bufprior = %q\n", prior) 63 | info += fmt.Sprintf("insert = %q\n", in) 64 | info += fmt.Sprintf("p0 = %d\n", p0) 65 | info += fmt.Sprintf("nins = %d\n", ni) 66 | info += fmt.Sprintf("delete = %d:%d\n", p0, p0+ni) 67 | info += fmt.Sprintf("ndel = %d\n", nd) 68 | ioutil.WriteFile(name+".info", []byte(info), 0666) 69 | t.Logf("see %s.png and %s.buf\n", name, name) 70 | etch.WriteFile(t, name+".png", result) 71 | t.FailNow() 72 | } 73 | } 74 | } 75 | func clamp(v, l, h int64) int64 { 76 | if v < l { 77 | return l 78 | } 79 | if v > h { 80 | return h 81 | } 82 | return v 83 | } 84 | 85 | func init() { 86 | rand.Seed(time.Now().Unix()) 87 | } 88 | 89 | type reader struct { 90 | } 91 | 92 | func (reader) Read(p []byte) (n int, err error) { 93 | return rand.Read(p) 94 | } 95 | -------------------------------------------------------------------------------- /indexof.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | // IndexOf returns the chracter index under the 8 | // point pt. 9 | func (f *Frame) IndexOf(pt image.Point) (p int64) { 10 | pt.X += 1 11 | return f.indexOf(pt) 12 | } 13 | 14 | func (f *Frame) indexOf(pt image.Point) (p int64) { 15 | pt = f.grid(pt) 16 | qt := f.r.Min 17 | bn := 0 18 | for ; bn < f.Nbox && qt.Y < pt.Y; bn++ { 19 | b := &f.Box[bn] 20 | qt = f.wrapMax(qt, b) 21 | if qt.Y >= pt.Y { 22 | break 23 | } 24 | qt = f.advance(qt, b) 25 | p += int64(b.Len()) 26 | } 27 | 28 | for ; bn < f.Nbox && qt.X <= pt.X; bn++ { 29 | b := &f.Box[bn] 30 | qt = f.wrapMax(qt, b) 31 | if qt.Y > pt.Y { 32 | break 33 | } 34 | if qt.X+b.Width > pt.X { 35 | if b.Nrune < 0 { 36 | qt = f.advance(qt, b) 37 | } else { 38 | left := pt.X - qt.X 39 | p += int64(f.Face.Fits(b.Ptr, left)) 40 | qt.X += left 41 | } 42 | } else { 43 | p += int64(b.Len()) 44 | qt = f.advance(qt, b) 45 | } 46 | } 47 | return p 48 | } 49 | -------------------------------------------------------------------------------- /indexof_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "testing" 6 | ) 7 | 8 | func TestIndexOfWrap(t *testing.T) {} 9 | 10 | func TestIndexOf(t *testing.T) { 11 | tab := []pointTest{ 12 | {"hello1", 0, 0, image.Pt(0, 0)}, 13 | {"hello2", 0, 1, image.Pt(7, 0)}, 14 | {"he\nsaid2", 0, 3, image.Pt(0, 16)}, 15 | {"he\n\n\n\nsaid2", 0, 4, image.Pt(0, 32)}, 16 | {"\n", 0, 0, image.Pt(0, 0*8)}, 17 | {"\n", 100, 0, image.Pt(0, 1*8)}, 18 | {"\n\n", 0, 1, image.Pt(0, 2*8)}, 19 | {"\n\n\n", 0, 1, image.Pt(0, 3*8)}, 20 | } 21 | for _, v := range tab { 22 | h, _, _, _ := abtestbg(R) 23 | h.Insert([]byte(v.insert), v.p0) 24 | have := h.IndexOf(v.pt) 25 | want := v.c0 26 | if have != want { 27 | t.Logf("%q: have %d, want %d", v.insert, have, want) 28 | t.Fail() 29 | } 30 | } 31 | } 32 | 33 | func TestIndexOfMultiInsert(t *testing.T) { 34 | t.Skip("not finished") 35 | type pointTest struct { 36 | s string 37 | p0 int64 38 | pt image.Point 39 | } 40 | prog := `package main 41 | import "fmt" 42 | 43 | func main(){ 44 | fmt.Println("take me to your leader") 45 | } 46 | ` 47 | 48 | tab := []pointTest{ 49 | {"package(sp)", 0, image.Pt(0, 0)}, 50 | {"package(sp)", 1, image.Pt(7, 0)}, 51 | {"package(ep)", 7, image.Pt(7*7, 0)}, 52 | {"main(sp)", 7 + 1, image.Pt(7*(7+1), 0)}, 53 | {"main(ep)", 7 + 1 + 4, image.Pt(7*(7+1+4), 0)}, 54 | {"nl(1)", 7 + 1 + 4 + 1, image.Pt(0, 16)}, 55 | {"import(sp)", 7 + 1 + 4 + 1 + 1, image.Pt(0, 16)}, 56 | {"import(sp+1)", 7 + 1 + 4 + 1 + 1 + 1, image.Pt(0, 16)}, 57 | } 58 | for _, v := range tab { 59 | h, _, _, _ := abtestbg(R) 60 | for i, c := range []byte(prog) { 61 | h.Insert([]byte{c}, int64(i)) 62 | } 63 | have := h.IndexOf(v.pt) 64 | want := v.p0 65 | if have != want { 66 | t.Logf("%q: have %d, want %d", v.s, have, want) 67 | t.Fail() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /insert.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | ) 7 | 8 | // Insert inserts the contents of s at index p0 in 9 | // the frame and returns the number of characters 10 | // written. 11 | func (f *Frame) Insert(s []byte, p0 int64) (wrote int) { 12 | if p0 > f.Nchars || len(s) == 0 || f.b == nil { 13 | return 14 | } 15 | 16 | // find p0, it's box, and its point in the box its in 17 | b0 := f.Find(0, 0, p0) 18 | // eb := f.StartCell(b0) 19 | //ob0 := b0 20 | cb0 := p0 21 | b1 := b0 22 | pt0 := f.pointOfBox(p0, b0) 23 | 24 | // find p1 25 | ppt0, pt1 := f.boxscan(s, pt0) 26 | opt0 := pt0 27 | ppt1 := pt1 28 | // Line wrap 29 | if b0 < f.Nbox { 30 | b := &f.Box[b0] 31 | pt0 = f.wrapMax(pt0, b) 32 | ppt1 = f.wrapMin(ppt1, b) 33 | } 34 | f.modified = true 35 | 36 | if f.p0 == f.p1 { 37 | f.tickat(f.PointOf(int64(f.p0)), false) 38 | } 39 | 40 | cb0, b0, pt0, pt1 = f.boxalign(cb0, b0, pt0, pt1) 41 | f.boxpush(p0, b0, b1, pt0, pt1, ppt1) 42 | f.bitblt(cb0, b0, pt0, pt1, opt0) 43 | text, back := f.pick(p0, f.p0+1, f.p1+1) 44 | f.Paint(ppt0, ppt1, back) 45 | f.redrawRun0(f.ir, ppt0, text, back) 46 | f.Run.Combine(f.ir, b1) 47 | if b1 > 0 && f.Box[b1-1].Nrune >= 0 && ppt0.X-f.Box[b1-1].Width >= f.r.Min.X { 48 | b1-- 49 | ppt0.X -= f.Box[b1].Width 50 | } 51 | b0 += f.ir.Nbox 52 | if b0 < f.Nbox-1 { 53 | b0++ 54 | } 55 | f.clean(ppt0, b1, b0) 56 | f.Nchars += f.ir.Nchars 57 | if p0 <= f.p0 { 58 | f.p0 += f.ir.Nchars 59 | f.p1 += f.ir.Nchars 60 | } else if p0 < f.p1 { 61 | f.p1 += f.ir.Nchars 62 | } 63 | if f.p0 == f.p1 { 64 | f.tickat(f.PointOf(f.p0), true) 65 | } 66 | f.badElasticAlg() 67 | return int(f.ir.Nchars) 68 | } 69 | 70 | func (f *Frame) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point, op draw.Op) { 71 | if f == nil { 72 | panic("nil frame") 73 | } 74 | if f.Drawer == nil { 75 | panic("nil drawer") 76 | } 77 | f.Drawer.Draw(dst, r, src, sp, op) 78 | f.Flush(r) 79 | } 80 | func (f *Frame) badElasticAlg() { 81 | if f.elastic() { 82 | if f.Nbox <= 1 { 83 | return 84 | } 85 | b := 0 86 | b1 := 0 87 | for b != f.Nbox { 88 | b1 = b 89 | b = f.NextCell(b) 90 | if b == 0 || b == b1 { 91 | break 92 | } 93 | } 94 | f.Stretch(f.Nbox) 95 | b = b1 96 | for ; b1 > 1; b1 = f.Stretch(b1) { 97 | if b == b1 { 98 | break 99 | } 100 | b = b1 101 | } 102 | f.Stretch(b1) 103 | f.Refresh() 104 | } 105 | } 106 | 107 | // Mark marks the frame as dirty 108 | func (f *Frame) Mark() { 109 | f.modified = true 110 | } 111 | 112 | // boxalign collects a list of pts of each box 113 | // on the frame before and after an insertion occurs 114 | func (f *Frame) boxalign(cb0 int64, b0 int, pt0, pt1 image.Point) (int64, int, image.Point, image.Point) { 115 | type Pts [2]image.Point 116 | f.pts = f.pts[:0] 117 | 118 | // collect the start pts for each box on the plane 119 | // before and after the insertion 120 | for { 121 | if pt0.X == pt1.X || pt1.Y == f.r.Max.Y || b0 == f.Nbox { 122 | break 123 | } 124 | b := &f.Box[b0] 125 | pt0 = f.wrapMax(pt0, b) 126 | pt1 = f.wrapMin(pt1, b) 127 | if b.Nrune > 0 { 128 | if n := f.fits(pt1, b); n != b.Nrune { 129 | f.Split(b0, n) 130 | b = &f.Box[b0] 131 | } 132 | } 133 | 134 | // early exit - point went off the frame 135 | if pt1.Y == f.r.Max.Y { 136 | break 137 | } 138 | 139 | f.pts = append(f.pts, Pts{pt0, pt1}) 140 | 141 | pt0 = f.advance(pt0, b) 142 | pt1.X += f.plot(pt1, b) 143 | 144 | cb0 += int64(b.Len()) 145 | b0++ 146 | } 147 | return cb0, b0, pt0, pt1 148 | } 149 | 150 | // boxpush moves boxes down the frame to make room for an insertion 151 | // from pt0 to pt1 152 | func (f *Frame) boxpush(p0 int64, b0, b1 int, pt0, pt1, ppt1 image.Point) { 153 | h := f.Face.Dy() 154 | // delete boxes that ran off the frame 155 | // and update the char count 156 | if pt1.Y == f.r.Max.Y && b0 < f.Nbox { 157 | f.Nchars -= f.Count(b0) 158 | f.Run.Delete(b0, f.Nbox-1) 159 | } 160 | 161 | // update the line count 162 | if b0 == f.Nbox { 163 | f.Nlines = (pt1.Y - f.r.Min.Y) / h 164 | if pt1.X > f.r.Min.X { 165 | f.Nlines++ 166 | } 167 | return 168 | } 169 | 170 | if pt1.Y == pt0.Y { 171 | // insertion won't propagate down 172 | return 173 | } 174 | 175 | qt0 := pt0.Y + h 176 | qt1 := pt1.Y + h 177 | f.Nlines += (qt1 - qt0) / h 178 | if f.Nlines > f.maxlines { 179 | f.trim(ppt1, p0, b1) 180 | } 181 | 182 | // shift down the existing boxes 183 | // on the bitmap 184 | if r := f.r; pt1.Y < r.Max.Y { 185 | r.Min.Y = qt1 186 | 187 | // rectangular group of boxes 188 | if qt1 < f.r.Max.Y { 189 | f.Draw(f.b, r, f.b, image.Pt(f.r.Min.X, qt0), f.op) 190 | } 191 | 192 | // partial line 193 | r.Min = pt1 194 | r.Max.X = pt1.X + (f.r.Max.X - pt0.X) 195 | r.Max.Y = qt1 196 | f.Draw(f.b, r, f.b, pt0, f.op) 197 | } 198 | } 199 | 200 | func (f *Frame) bitblt(cb0 int64, b0 int, pt0, pt1, opt0 image.Point) (res image.Rectangle) { 201 | h := f.Face.Dy() 202 | y := 0 203 | if pt1.Y == f.r.Max.Y { 204 | y = pt1.Y 205 | } 206 | x := len(f.pts) 207 | if x != 0 { 208 | res = image.Rectangle{pt0, pt1} 209 | res.Canon().Max.Add(f.pts[x-1][0]) 210 | } 211 | run := f.Box[b0-x:] 212 | x-- 213 | _, back := f.pick(cb0, f.p0, f.p1) 214 | for ; x >= 0; x-- { 215 | b := &run[x] 216 | br := image.Rect(0, 0, b.Width, h) 217 | pt := f.pts[x] 218 | if b.Nrune > 0 { 219 | f.Draw(f.b, br.Add(pt[1]), f.b, pt[0], f.op) 220 | // clear bit hanging off right 221 | if x == 0 && pt[1].Y > pt0.Y { 222 | 223 | // new char was wider than the old 224 | // one so the line wrapped anyway 225 | 226 | _, back = f.pick(cb0, f.p0, f.p1) 227 | r := br.Add(opt0) 228 | r.Max.X = f.r.Max.X 229 | f.Draw(f.b, r, back, r.Min, f.op) 230 | 231 | } else if pt[1].Y < y { 232 | 233 | // copy from left to right, bottom to top 234 | 235 | _, back = f.pick(cb0, f.p0, f.p1) 236 | r := image.ZR.Add(pt[1]) 237 | r.Min.X += b.Width 238 | r.Max.X += f.r.Max.X 239 | r.Max.Y += h 240 | f.Draw(f.b, r, back, r.Min, f.op) 241 | 242 | } 243 | y = pt[1].Y 244 | cb0 -= int64(b.Nrune) 245 | } else { 246 | r := br.Add(pt[1]) 247 | if r.Max.X >= f.r.Max.X { 248 | r.Max.X = f.r.Max.X 249 | } 250 | cb0-- 251 | _, back = f.pick(cb0, f.p0, f.p1) 252 | f.Draw(f.b, r, back, r.Min, f.op) 253 | y = 0 254 | if pt[1].X == f.r.Min.X { 255 | y = pt[1].Y 256 | } 257 | } 258 | } 259 | return res 260 | } 261 | 262 | func (f *Frame) pick(c, p0, p1 int64) (text, back image.Image) { 263 | if p0 <= c && c < p1 { 264 | return f.Color.Hi.Text, f.Color.Hi.Back 265 | } 266 | return f.Color.Text, f.Color.Back 267 | } 268 | 269 | /* 270 | // 271 | // Below ideas 272 | 273 | type offset struct { 274 | p0 int64 275 | b0 int 276 | cb0 int64 277 | b1 int64 278 | pt0 image.Point 279 | pt1 image.Point 280 | opt0 image.Point 281 | } 282 | 283 | func (f *Frame) zInsertElastic(s []byte, p0 int64) (wrote int) { 284 | type Pts [2]image.Point 285 | if p0 > f.Nchars || len(s) == 0 || f.b == nil { 286 | return 287 | } 288 | 289 | // find p0, it's box, and its point in the box its in 290 | b0 := f.Find(0, 0, p0) 291 | cb0 := p0 292 | 293 | eb0 := f.StartCell(b0) 294 | pt0 := f.pointOfBox(p0, b0) 295 | ppt0, pt1 := f.boxscan(s, pt0) 296 | // ept0 := f.pointOfBox(p0, eb0) 297 | f.Box = append(f.Box[:b0], append(f.ir.Box[:f.ir.Nbox], f.Box[b0:]...)...) 298 | f.Nbox += f.ir.Nbox 299 | 300 | b1 := b0 301 | 302 | for bn := f.NextCell(b1 + f.ir.Nbox); bn > eb0; bn = f.Stretch(bn) { 303 | } 304 | f.Stretch(eb0) 305 | 306 | // pt0 = f.pointOfBox(p0, b0) 307 | // pt1 = f.pointOfBox(65535, b1) 308 | 309 | opt0 := pt0 310 | ppt1 := pt1 311 | // Line wrap 312 | if b0 < f.Nbox { 313 | b := &f.Box[b0] 314 | pt0 = f.wrapMax(pt0, b) 315 | ppt1 = f.wrapMin(ppt1, b) 316 | } 317 | f.modified = true 318 | 319 | if f.p0 == f.p1 { 320 | f.tickat(f.PointOf(int64(f.p0)), false) 321 | } 322 | 323 | cb0, b0, pt0, pt1 = f.boxalign(cb0, b0+f.ir.Nbox, pt0, pt1) 324 | f.boxpush(p0, b0+f.ir.Nbox, b1+f.ir.Nbox, pt0, pt1, ppt1) 325 | f.bitblt(cb0, b0, pt0, pt1, opt0) 326 | text, back := f.pick(p0, f.p0+1, f.p1+1) 327 | f.Paint(ppt0, ppt1, back) 328 | f.redrawRun0(f.ir, ppt0, text, back) 329 | 330 | b1 = b0 331 | if b1 > 0 && f.Box[b1-1].Nrune >= 0 && ppt0.X-f.Box[b1-1].Width >= f.r.Min.X { 332 | b1-- 333 | ppt0.X -= f.Box[b1].Width 334 | } 335 | 336 | b0 += f.ir.Nbox 337 | if b0 < f.Nbox-1 { 338 | b0++ 339 | } 340 | f.clean(f.pointOfBox(p0, b1), b0, b1) 341 | f.Nchars += f.ir.Nchars 342 | 343 | f.p0, f.p1 = coInsert(p0, p0+f.Nchars, f.p0, f.p1) 344 | if f.p0 == f.p1 { 345 | f.tickat(f.PointOf(f.p0), true) 346 | } 347 | return int(f.ir.Nchars) 348 | 349 | } 350 | */ 351 | -------------------------------------------------------------------------------- /insert_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "strings" 6 | "testing" 7 | 8 | . "github.com/as/font" 9 | ) 10 | 11 | func TestInsertOneChar(t *testing.T) { 12 | h, _, have, _ := abtestbg(R) 13 | h.Insert([]byte("1"), 0) 14 | h.Untick() 15 | //etch.WriteFile(t, `testdata/TestInsertOneChar.expected.png`, have) 16 | check(t, have, "TestInsertOneChar", testMode) 17 | } 18 | 19 | func TestInsert10Chars(t *testing.T) { 20 | h, _, have, _ := abtestbg(R) 21 | for i := 0; i < 10; i++ { 22 | h.Insert([]byte("1"), 0) 23 | } 24 | check(t, have, "TestInsert10Chars", testMode) 25 | } 26 | 27 | func TestInsert22Chars2Lines(t *testing.T) { 28 | h, _, have, _ := abtestbg(R) 29 | for j := 0; j < 2; j++ { 30 | for i := 0; i < 10; i++ { 31 | h.Insert([]byte("1"), h.Len()) 32 | } 33 | h.Insert([]byte("\n"), h.Len()) 34 | } 35 | check(t, have, "TestInsert22Chars2Lines", testMode) 36 | } 37 | 38 | func TestInsert1000(t *testing.T) { 39 | h, _, have, _ := abtestbg(R) 40 | for j := 0; j < 1000; j++ { 41 | h.Insert([]byte{byte(j)}, int64(j)) 42 | } 43 | check(t, have, "TestInsert1000", testMode) 44 | } 45 | 46 | func TestInsertTabSpaceNewline(t *testing.T) { 47 | h, _, have, _ := abtestbg(R) 48 | for j := 0; j < 5; j++ { 49 | h.Insert([]byte("abc\t \n\n\t $\n"), int64(j)) 50 | } 51 | check(t, have, "TestInsertTabSpaceNewline", testMode) 52 | } 53 | 54 | type benchOp struct { 55 | name string 56 | data string 57 | at int64 58 | } 59 | 60 | var dst = image.NewRGBA(image.Rect(0, 0, 1024, 768)) 61 | 62 | func BenchmarkInsertGoMono(b *testing.B) { b.Helper(); bInsert(b, withFace(NewGoMono(fsize))) } 63 | func BenchmarkInsertGoMonoCache(b *testing.B) { 64 | if b, ok := interface{}(b).(help); ok { 65 | b.Helper() 66 | } 67 | bInsert(b, withFace(NewCache(NewGoMono(fsize)))) 68 | } 69 | func BenchmarkInsertGoMonoReplaceCache(b *testing.B) { 70 | if b, ok := interface{}(b).(help); ok { 71 | b.Helper() 72 | } 73 | bInsert(b, withFace( 74 | NewCache( 75 | Replacer( 76 | NewGoMono(fsize), NewHex(fsize), nil, 77 | ), 78 | ), 79 | ), 80 | ) 81 | } 82 | func BenchmarkInsertGoMonoCliche(b *testing.B) { 83 | if b, ok := interface{}(b).(help); ok { 84 | b.Helper() 85 | } 86 | bInsert(b, withFace(NewCliche(NewGoMono(fsize)))) 87 | } 88 | func BenchmarkInsertGoMonoRune(b *testing.B) { 89 | if b, ok := interface{}(b).(help); ok { 90 | b.Helper() 91 | } 92 | bInsert(b, withFace(NewRune(NewGoMono(fsize)))) 93 | } 94 | func BenchmarkInsertGoMonoRuneCache(b *testing.B) { 95 | if b, ok := interface{}(b).(help); ok { 96 | b.Helper() 97 | } 98 | bInsert(b, withFace(NewCache(NewRune(NewGoMono(fsize))))) 99 | } 100 | 101 | func withFace(ft Face) *Frame { 102 | return New(dst, dst.Bounds(), &Config{Face: ft, Color: A}) 103 | } 104 | 105 | func bInsert(b *testing.B, f *Frame) { 106 | // b.Skip("not ready") 107 | if b, ok := interface{}(b).(help); ok { 108 | b.Helper() 109 | } 110 | for _, v := range []benchOp{ 111 | {"1", "a", 0}, 112 | {"10", strings.Repeat("a\n", 10), 0}, 113 | {"100", strings.Repeat("a\n", 100), 0}, 114 | {"1000", strings.Repeat("a\n", 1000), 0}, 115 | {"10000", strings.Repeat("a\n", 10000), 0}, 116 | {"100000", strings.Repeat("a\n", 100000), 0}, 117 | } { 118 | b.Run(v.name, func(b *testing.B) { 119 | bb := []byte(v.data) 120 | b.SetBytes(int64(len(bb))) 121 | at := v.at 122 | b.ResetTimer() 123 | for i := 0; i < b.N; i++ { 124 | f.Insert(bb, at) 125 | } 126 | }) 127 | } 128 | } 129 | 130 | // help is an interface that allows this code to use Go1.9's t.Helper() method 131 | // without breaking out of data continuous integration components (CircleCI) which 132 | // run older Go versions not supporting t.Helper(). 133 | // 134 | // 135 | type help interface { 136 | Helper() 137 | } 138 | -------------------------------------------------------------------------------- /pointof.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | ) 6 | 7 | // Grid returns a grid-aligned point on the frame relative to pt 8 | func (f *Frame) Grid(pt image.Point) image.Point { 9 | if f == nil { 10 | return image.ZP 11 | } 12 | return f.grid(pt) 13 | } 14 | 15 | // PointOf returns the point on the closest to index p. 16 | func (f *Frame) PointOf(p int64) image.Point { 17 | if f == nil { 18 | return image.ZP 19 | } 20 | return f.pointOf(p, f.r.Min, 0) 21 | } 22 | 23 | func (f *Frame) grid(pt image.Point) image.Point { 24 | pt.Y -= f.r.Min.Y 25 | pt.Y -= pt.Y % f.Face.Dy() 26 | pt.Y += f.r.Min.Y 27 | if pt.X > f.r.Max.X { 28 | pt.X = f.r.Max.X 29 | } 30 | return pt 31 | } 32 | func (f *Frame) pointOf(p int64, pt image.Point, bn int) (x image.Point) { 33 | for ; bn < f.Nbox; bn++ { 34 | b := &f.Box[bn] 35 | pt = f.wrapMax(pt, b) 36 | l := b.Len() 37 | if p < int64(l) { 38 | if b.Nrune > 0 { 39 | ptr := b.Ptr 40 | if p > 0 { 41 | pt.X += f.Face.Dx(ptr[:p]) 42 | } 43 | } 44 | break 45 | } 46 | p -= int64(l) 47 | pt = f.advance(pt, b) 48 | } 49 | return pt 50 | } 51 | 52 | func (f *Frame) pointOfBox(p int64, nb int) (pt image.Point) { 53 | Nbox := f.Nbox 54 | f.Nbox = nb 55 | pt = f.pointOf(p, f.r.Min, 0) 56 | f.Nbox = Nbox 57 | return pt 58 | } 59 | -------------------------------------------------------------------------------- /pointof_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "testing" 6 | ) 7 | 8 | type pointTest struct { 9 | insert string 10 | p0 int64 11 | c0 int64 12 | pt image.Point 13 | } 14 | 15 | func TestPointOf(t *testing.T) { 16 | 17 | tab := []pointTest{ 18 | {"hello1", 0, 0, image.Pt(0, 0)}, 19 | {"hello2", 0, 1, image.Pt(7, 0)}, 20 | {"he\nsaid2", 0, 3, image.Pt(0, 16)}, 21 | {"he\n\n\n\nsaid2", 0, 4, image.Pt(0, 32)}, 22 | // {"hello3", 2, image.Pt(0, 0)}, 23 | // {"hello4", 3, image.Pt(0, 0)}, 24 | // {"hello5", 4, image.Pt(0, 0)}, 25 | } 26 | for _, v := range tab { 27 | h, _, _, _ := abtestbg(R) 28 | h.Insert([]byte(v.insert), v.p0) 29 | have := h.PointOf(v.c0) 30 | want := v.pt 31 | if have != want { 32 | t.Logf("%q: have %s, want %s", v.insert, have, want) 33 | t.Fail() 34 | } 35 | } 36 | } 37 | 38 | func TestPointOfMultiInsert(t *testing.T) { 39 | t.Skip("not finished") 40 | type pointTest struct { 41 | s string 42 | p0 int64 43 | pt image.Point 44 | } 45 | prog := `package main 46 | import "fmt" 47 | 48 | func main(){ 49 | fmt.Println("take me to your leader") 50 | } 51 | ` 52 | 53 | tab := []pointTest{ 54 | {"package(sp)", 0, image.Pt(0, 0)}, 55 | {"package(ep)", 7, image.Pt(7*7, 0)}, 56 | {"main(sp)", 7 + 1, image.Pt(7*(7+1), 0)}, 57 | {"main(ep)", 7 + 1 + 4, image.Pt(7*(7+1+4), 0)}, 58 | {"nl(1)", 7 + 1 + 4 + 1, image.Pt(0, 16)}, 59 | {"import(sp)", 7 + 1 + 4 + 1 + 1, image.Pt(0, 16)}, 60 | {"import(sp+1)", 7 + 1 + 4 + 1 + 1 + 1, image.Pt(0, 16)}, 61 | } 62 | for _, v := range tab { 63 | h, _, _, _ := abtestbg(R) 64 | for i, c := range []byte(prog) { 65 | h.Insert([]byte{c}, int64(i)) 66 | } 67 | have := h.PointOf(v.p0) 68 | want := v.pt 69 | if have != want { 70 | t.Logf("%q: have %s, want %s", v.s, have, want) 71 | t.Fail() 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /rgba.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | // copied from github.com/as/rgba to avoid a dependency 9 | 10 | // hex converts a 32-bit RGBA quad to a color.RGBA 11 | func hex(rgba uint32) color.RGBA { 12 | return color.RGBA{ 13 | R: uint8(rgba >> 24), 14 | G: uint8(rgba << 8 >> 24), 15 | B: uint8(rgba << 16 >> 24), 16 | A: uint8(rgba << 24 >> 24), 17 | } 18 | } 19 | 20 | // uniform is short for image.NewUniform(hex(rgba)) 21 | func uniform(rgba uint32) *image.Uniform { 22 | return image.NewUniform(hex(rgba)) 23 | } 24 | -------------------------------------------------------------------------------- /scroll/scroll.go: -------------------------------------------------------------------------------- 1 | package scroll 2 | 3 | /* 4 | type Frame interface{ 5 | Dot() (int64, int64) 6 | SetDot(int64, int64) 7 | PointOf(int64) image.Point 8 | IndexOf(image.Point) int64 9 | Insert([]byte, at int64) int64 10 | Delete(int64, int64) 11 | Drawsel(image.Point, int64, int64, bool) 12 | DrawText(image.Point, Color, Color) 13 | SelectPaint(p0, p1 image.Point, col image.Image) 14 | Redraw() 15 | Mark() bool 16 | Bounds() 17 | } 18 | 19 | type Editable interface{ 20 | Frame() *frame.Frame 21 | } 22 | 23 | type Window interface{ 24 | Editable 25 | Handle(e interface{}) 26 | Pos() image.Point 27 | Size() image.Point 28 | } 29 | 30 | type Scroll struct{ 31 | Window 32 | 33 | } 34 | */ 35 | -------------------------------------------------------------------------------- /select.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | ) 7 | 8 | // Paint paints the color col on the frame at points pt0-pt1. The result is a Z shaped fill 9 | // consisting of at-most 3 rectangles. No text is redrawn. 10 | func (f *Frame) Paint(p0, p1 image.Point, col image.Image) { 11 | if f.b == nil { 12 | panic("selectpaint: b == 0") 13 | } 14 | if f.r.Max.Y == p0.Y { 15 | return 16 | } 17 | h := f.Face.Dy() 18 | q0, q1 := p0, p1 19 | q0.Y += h 20 | q1.Y += h 21 | n := (p1.Y - p0.Y) / h 22 | 23 | if n == 0 { // one line 24 | f.Draw(f.b, image.Rectangle{p0, q1}, col, image.ZP, draw.Over) 25 | } else { 26 | if p0.X >= f.r.Max.X { 27 | p0.X = f.r.Max.X // - 1 28 | } 29 | f.Draw(f.b, image.Rect(p0.X, p0.Y, f.r.Max.X, q0.Y), col, image.ZP, draw.Over) 30 | if n > 1 { 31 | f.Draw(f.b, image.Rect(f.r.Min.X, q0.Y, f.r.Max.X, p1.Y), col, image.ZP, draw.Over) 32 | } 33 | f.Draw(f.b, image.Rect(f.r.Min.X, p1.Y, q1.X, q1.Y), col, image.ZP, draw.Over) 34 | } 35 | } 36 | 37 | // Select selects the region [p0:p1). The operation highlights 38 | // the range of text under that region. If p0 = p1, a tick is 39 | // drawn to indicate a null selection. 40 | func (f *Frame) Select(p0, p1 int64) { 41 | pp0, pp1 := f.Dot() 42 | if pp1 <= p0 || p1 <= pp0 || p0 == p1 || pp1 == pp0 { 43 | f.Redraw(f.PointOf(pp0), pp0, pp1, false) 44 | f.Redraw(f.PointOf(p0), p0, p1, true) 45 | } else { 46 | if p0 < pp0 { 47 | f.Redraw(f.PointOf(p0), p0, pp0, true) 48 | } else if p0 > pp0 { 49 | f.Redraw(f.PointOf(pp0), pp0, p0, false) 50 | } 51 | if p1 > pp1 { 52 | f.Redraw(f.PointOf(pp1), pp1, p1, true) 53 | } else if p1 < pp1 { 54 | f.Redraw(f.PointOf(p1), p1, pp1, false) 55 | } 56 | } 57 | f.modified = true 58 | f.p0, f.p1 = p0, p1 59 | } 60 | -------------------------------------------------------------------------------- /select_test.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/as/etch" 7 | ) 8 | 9 | func TestSelectFlow(t *testing.T) { 10 | h, w, have, want := abtestbg(R) 11 | x := []byte("The quick brown fox jumped over the lazy dog") 12 | lx := int64(len(x)) 13 | h.Insert(x, 0) 14 | w.Insert(x, 0) 15 | for i := int64(0); i < lx; i++ { 16 | h.Select(0+i, lx) 17 | h.Select(0+i, 0+i) 18 | h.Insert([]byte("@"), i*2) 19 | h.Delete(i*2, i*2+1) 20 | } 21 | h.Select(h.Len(), h.Len()) 22 | etch.Assert(t, have, want, "TestSelectFlow.png") 23 | } 24 | 25 | func TestTypingFlow(t *testing.T) { 26 | h, _, _, _ := abtestbg(R) 27 | for _, c := range []byte("abcde") { 28 | p0, _ := h.Dot() 29 | h.Insert([]byte{c}, p0) 30 | } 31 | have, _ := h.Dot() 32 | want := int64(5) 33 | if have != want { 34 | t.Logf("typing dot: have %d, want %d\n", have, want) 35 | t.Fail() 36 | } 37 | } 38 | 39 | func testReg(t *testing.T, name, what string, where int) { 40 | t.Helper() 41 | h, _, have, _ := abtestbg(R) 42 | h.Insert([]byte("abcde"), 0) 43 | h.Select(1, 4) 44 | ckDot(t, h, 1, 4) 45 | h.Insert([]byte(what), int64(where)) 46 | p0, p1 := int64(1), int64(4) 47 | if where <= 1 { 48 | p0++ 49 | p1++ 50 | } else if where < 4 { 51 | p1++ 52 | } 53 | ckDot(t, h, p0, p1) 54 | h.Select(p0, p1) 55 | check(t, have, "TestInsertRegion"+name, testMode) 56 | } 57 | 58 | func TestRegion0(t *testing.T) { testReg(t, "0", "0", 0) } 59 | func TestRegion1(t *testing.T) { testReg(t, "1", "1", 1) } 60 | func TestRegion2(t *testing.T) { testReg(t, "2", "2", 2) } 61 | func TestRegion3(t *testing.T) { testReg(t, "3", "3", 3) } 62 | func TestRegion4(t *testing.T) { testReg(t, "4", "4", 4) } 63 | func TestRegion5(t *testing.T) { testReg(t, "5", "5", 5) } 64 | 65 | func ckDot(t *testing.T, f *Frame, p0, p1 int64) { 66 | t.Helper() 67 | q0, q1 := f.Dot() 68 | if q0 != int64(p0) || q1 != int64(p1) { 69 | t.Logf("bad selection: have [%d:%d), want [%d:%d)\n", q0, q1, p0, p1) 70 | t.Fail() 71 | } 72 | } 73 | 74 | func TestInsertExtendsSelection(t *testing.T) { 75 | h, _, _, _ := abtestbg(R) 76 | h.Insert([]byte("abcde"), 0) 77 | h.Select(1, 4) 78 | h.Insert([]byte("x"), 2) 79 | ckDot(t, h, 1, 5) 80 | } 81 | func TestSelect0to0(t *testing.T) { 82 | h, _, have, _ := abtestbg(R) 83 | h.Insert(testSelectData, 0) 84 | h.Select(0, 0) 85 | check(t, have, "TestSelect0to0", testMode) 86 | } 87 | 88 | func TestSelect0to1(t *testing.T) { 89 | h, _, have, _ := abtestbg(R) 90 | h.Insert(testSelectData, 0) 91 | h.Select(0, 1) 92 | check(t, have, "TestSelect0to1", testMode) 93 | } 94 | func TestSelectLine(t *testing.T) { 95 | h, _, have, _ := abtestbg(R) 96 | h.Insert(testSelectData, 0) 97 | h.Select(0, 12) 98 | check(t, have, "TestSelectLine", testMode) 99 | } 100 | 101 | func TestSelectLinePlus(t *testing.T) { 102 | h, _, have, _ := abtestbg(R) 103 | h.Insert(testSelectData, 0) 104 | h.Select(0, 13) 105 | check(t, have, "TestSelectLinePlus", testMode) 106 | } 107 | 108 | func TestSelectLinePlus1(t *testing.T) { 109 | h, _, have, _ := abtestbg(R) 110 | h.Insert(testSelectData, 0) 111 | h.Select(0, 13+1) 112 | check(t, have, "TestSelectLinePlus1", testMode) 113 | } 114 | 115 | func TestSelectAll(t *testing.T) { 116 | h, _, have, _ := abtestbg(R) 117 | h.Insert(testSelectData, 0) 118 | h.Select(0, 9999) 119 | check(t, have, "TestSelectAll", testMode) 120 | } 121 | 122 | func TestSelectAllSub1(t *testing.T) { 123 | h, _, have, _ := abtestbg(R) 124 | h.Insert(testSelectData, 0) 125 | h.Select(0, h.Len()) 126 | p0, p1 := h.Dot() 127 | p1-- 128 | h.Select(p0, p1) 129 | check(t, have, "TestSelectAllSub1", testMode) 130 | } 131 | 132 | func TestSelectAllSubAll(t *testing.T) { 133 | h, _, have, _ := abtestbg(R) 134 | h.Insert(testSelectData, 0) 135 | h.Select(0, h.Len()) 136 | h.Select(0, 0) 137 | check(t, have, "TestSelectAllSubAll", testMode) 138 | } 139 | 140 | func TestMidToEnd(t *testing.T) { 141 | h, _, have, _ := abtestbg(R) 142 | h.Insert(testSelectData, 0) 143 | h.Select(h.Len()/2, h.Len()) 144 | check(t, have, "TestMidToEnd", testMode) 145 | } 146 | func TestMidToEndThenStartToMid(t *testing.T) { 147 | h, _, have, _ := abtestbg(R) 148 | h.Insert(testSelectData, 0) 149 | h.Select(h.Len()/2, h.Len()) 150 | h.Select(0, h.Len()/2) 151 | check(t, have, "TestMidToEndThenStartToMid", testMode) 152 | } 153 | 154 | func TestSelectTabSpaceNewline(t *testing.T) { 155 | h, _, have, _ := abtestbg(R) 156 | for j := 0; j < 5; j++ { 157 | h.Insert([]byte("abc\t \n\n\t $\n"), int64(j)) 158 | } 159 | h.Select(h.Len()/2, h.Len()-5) 160 | check(t, have, "TestSelectTabSpaceNewline", testMode) 161 | } 162 | func TestSelectTabSpaceNewlineSub1(t *testing.T) { 163 | h, _, have, _ := abtestbg(R) 164 | for j := 0; j < 5; j++ { 165 | h.Insert([]byte("abc\t \n\n\t $\n"), int64(j)) 166 | } 167 | h.Select(h.Len()/2, h.Len()-5-1) 168 | check(t, have, "TestSelectTabSpaceNewlineSub1", testMode) 169 | } 170 | func TestSelectEndLineAndDec(t *testing.T) { 171 | h, _, have, _ := abtestbg(R) 172 | h.Insert(testSelectData, 0) 173 | h.Select(167+9, 168+9) 174 | check(t, have, "TestSelectEndLineAndDec", testMode) 175 | } 176 | 177 | var testSelectData = []byte(`Hello world. 178 | Your editor doesn't always know best. 179 | Your empty file directory has been deleted. 180 | func main(){ 181 | for i := 0; i < 100; i++{ 182 | // comment 183 | } 184 | } 185 | $ Editor (vi or emacs)? 186 | Usenet is like letters to the editor, only without an editor. - Larry Wall 187 | Type C-h for help; C-x u to undo changes. ('C-' means use CTRL key.) GNU Emacs comes with ABSOLUTELY NO WARRANTY; type C-h C-w for full details.You may give out copies of Emacs; type C-h C-c to see the conditions.Type C-h t for a tutorial on using Emacs. 188 | 189 | 190 | 191 | 192 | 193 | `) 194 | 195 | // Broken tests that need work 196 | 197 | // TODO(as): regenerate without trailing broken tickmark and rerun this test 198 | func TestSelectNone(t *testing.T) { 199 | h, _, have, _ := abtestbg(R) 200 | h.Insert(testSelectData, 0) 201 | check(t, have, "TestSelectNone", testMode) 202 | } 203 | 204 | // TODO(as): regenerate without trailing broken tickmark and rerun this test 205 | func TestSelectNoneUntick(t *testing.T) { 206 | h, _, have, _ := abtestbg(R) 207 | h.Insert(testSelectData, 0) 208 | check(t, have, "TestSelectNoneUntick", testMode) 209 | } 210 | -------------------------------------------------------------------------------- /testdata/TestInsert1000.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsert1000.expected.png -------------------------------------------------------------------------------- /testdata/TestInsert10Chars.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsert10Chars.expected.png -------------------------------------------------------------------------------- /testdata/TestInsert22Chars2Lines.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsert22Chars2Lines.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertOneChar.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertOneChar.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion0.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion0.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion1.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion1.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion2.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion2.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion3.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion3.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion4.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion4.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertRegion5.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertRegion5.expected.png -------------------------------------------------------------------------------- /testdata/TestInsertTabSpaceNewline.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestInsertTabSpaceNewline.expected.png -------------------------------------------------------------------------------- /testdata/TestMidToEnd.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestMidToEnd.expected.png -------------------------------------------------------------------------------- /testdata/TestMidToEndThenStartToMid.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestMidToEndThenStartToMid.expected.png -------------------------------------------------------------------------------- /testdata/TestSelect.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelect.expected.png -------------------------------------------------------------------------------- /testdata/TestSelect0to0.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelect0to0.expected.png -------------------------------------------------------------------------------- /testdata/TestSelect0to1.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelect0to1.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectAll.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectAll.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectAllSub1.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectAllSub1.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectAllSubAll.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectAllSubAll.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectEndLineAndDec.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectEndLineAndDec.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectLine.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectLine.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectLinePlus.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectLinePlus.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectLinePlus1.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectLinePlus1.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectNone.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectNone.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectNoneUntick.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectNoneUntick.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectTabSpaceNewline.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectTabSpaceNewline.expected.png -------------------------------------------------------------------------------- /testdata/TestSelectTabSpaceNewlineSub1.expected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/as/frame/ee6780d6ceca11270cb64a8798fbedda0e0bf458/testdata/TestSelectTabSpaceNewlineSub1.expected.png -------------------------------------------------------------------------------- /tick.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | ) 7 | 8 | const ( 9 | TickOff = 0 10 | TickOn = 1 11 | ) 12 | 13 | func (f *Frame) Untick() { 14 | if f.p0 == f.p1 { 15 | f.tickat(f.PointOf(int64(f.p0)), false) 16 | } 17 | } 18 | func (f *Frame) Tick() { 19 | if f.p0 == f.p1 { 20 | f.tickat(f.PointOf(int64(f.p0)), true) 21 | } 22 | } 23 | 24 | func (f *Frame) SetTick(style int) { 25 | f.tickoff = style == TickOff 26 | } 27 | func mktick(fontY int) (boxw int, linew int) { 28 | const magic = 12 29 | boxw = 3 + 1*(fontY/magic) 30 | for boxw%3 != 0 { 31 | boxw-- 32 | } 33 | if boxw < 3 { 34 | boxw = 3 35 | } 36 | 37 | linew = boxw / 3 38 | for boxw%linew != 0 { 39 | boxw-- 40 | } 41 | if linew < 1 { 42 | linew = 1 43 | } 44 | return 45 | } 46 | 47 | func (f *Frame) tickbg() image.Image { 48 | return f.Color.Text 49 | /* 50 | r, g, b, a := f.Color.Hi.Back.At(0,0).RGBA() 51 | a=a 52 | return image.NewUniform(color.RGBA{ 53 | uint8(r), 54 | uint8(g), 55 | uint8(b), 56 | uint8(0), 57 | }) 58 | */ 59 | 60 | } 61 | 62 | func (f *Frame) inittick() { 63 | 64 | he := f.Face.Height() 65 | as := f.Face.Ascent() 66 | de := f.Face.Descent() 67 | boxw, linew := mktick(he) 68 | linew2 := linew / 2 69 | if linew < 1 { 70 | linew = 1 71 | } 72 | z0 := de - 2 73 | r := image.Rect(0, z0, boxw, he-(he-as)/2+f.Face.Letting()/2) 74 | r = r.Sub(image.Pt(r.Dx()/2, 0)) 75 | f.tick = image.NewRGBA(r) 76 | f.tickback = image.NewRGBA(r) 77 | draw.Draw(f.tick, f.tick.Bounds(), f.Color.Back, image.ZP, draw.Src) 78 | tbg := f.tickbg() 79 | drawtick := func(x0, y0, x1, y1 int) { 80 | draw.Draw(f.tick, image.Rect(x0, y0, x1, y1), tbg, image.ZP, draw.Src) 81 | } 82 | drawtick(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+boxw) 83 | drawtick(r.Min.X, r.Max.Y-(boxw), r.Max.X, r.Max.Y) 84 | if boxw%2 != 0 { 85 | drawtick(-linew2, 0, linew2+1, r.Max.Y) 86 | } else { 87 | drawtick(-linew2, 0, linew2, r.Max.Y) 88 | } 89 | 90 | } 91 | 92 | // Put 93 | func (f *Frame) tickat(pt image.Point, ticked bool) { 94 | if f.Ticked == ticked || f.tick == nil || !pt.In(f.Bounds().Inset(-1)) { 95 | return 96 | } 97 | pt.X -= 1 98 | //pt.Y -= f.Font.Letting() / 4 99 | r := f.tick.Bounds().Add(pt) 100 | if r.Max.X > f.r.Max.X { 101 | r.Max.X = f.r.Max.X 102 | } 103 | if ticked { 104 | f.Draw(f.tickback, f.tickback.Bounds(), f.b, pt.Add(f.tickback.Bounds().Min), draw.Src) 105 | f.Draw(f.b, r, f.tick, f.tick.Bounds().Min, draw.Src) 106 | } else { 107 | f.Draw(f.b, r, f.tickback, f.tickback.Bounds().Min, draw.Src) 108 | } 109 | //f.Flush(r.Inset(-1)) 110 | f.Ticked = ticked 111 | } 112 | -------------------------------------------------------------------------------- /vendor/github.com/as/etch/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright as (c) 2017, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /vendor/github.com/as/etch/README.md: -------------------------------------------------------------------------------- 1 | # Etch 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/as/etch)](https://goreportcard.com/badge/github.com/as/etch) 3 | 4 | Package etch provides a simple facility to write graphical regression tests. 5 | The `Assert` function handles the common case. Give it the test variable, the 6 | images you have and want, and it will fail your case if they differ. 7 | 8 | # Synopsis 9 | 10 | ``` 11 | have = image.NewRGBA(r) 12 | want = image.NewRGBA(r) 13 | 14 | // fail the test if the images differ, and write the delta to a png 15 | etch.Assert(t, have, want, "Test.png") 16 | ``` 17 | 18 | # Visualize 19 | 20 | Optionally, provide a filename to store the graphical difference as an uncompressed PNG if the test fails. 21 | 22 | ![paint](img/delta.png) 23 | 24 | The `Extra` data in the image (have but don't want) is represented in `Red`. 25 | The `Missing` data (`want`, but dont `have`) is represented in `Blue`. 26 | These can be changed by modifying `Extra` and `Missing` package variables 27 | 28 | # Example 29 | 30 | I observed a bug in A where the text on the last line wasn't cleaned up unless that last line ended in a newline character. 31 | This means if the frame displays `^a\nb\nc$` and `b\n` is deleted, the user would see `^a\nc\nc$`. Nasty. 32 | 33 | We can programatically check for any defect as long as we know how to reproduce it. 34 | 35 | # Step 1: Find Reproduction and Expected Result 36 | Find the reproduction. In this case I also found steps that generate the expected result. You can also use a cached expected result from a previously known good configuration. 37 | 38 | - Insert the multi-line text containing no trailing newline (good: insert a trailing newline) 39 | 40 | ![paint](img/1.png) 41 | 42 | - Select any line but the last 43 | 44 | ![paint](img/2.png) 45 | 46 | - Delete the selection 47 | 48 | ![paint](img/3.png) 49 | 50 | Above you can see the result of the middle line's deletion for both sessions. The window that 51 | did not have the trailing newline did not clean up the last line after copying it up toward the 52 | top of the frame 53 | 54 | # Step 2: Create Images 55 | 56 | Create two images 57 | 58 | ``` 59 | have = image.NewRGBA(r) 60 | want = image.NewRGBA(r) 61 | ``` 62 | 63 | Now for the test case specific stuff. Your steps will replace mine 64 | below depending on what you're actually doing to the images. In my case 65 | the frame draws on them directly, so we really don't care about 66 | its inner workings too much, just that there's a bug and we're 67 | going to test for its existence using these two images: `have` 68 | and `want`. 69 | 70 | 71 | ``` 72 | // Create two frames 73 | h = New(r, font.NewBasic(fsize), have, A) 74 | w = New(r, font.NewBasic(fsize), want, A) 75 | 76 | // Insert some text with and without trailing newlines 77 | w.Insert([]byte("1234\ncccc\ndddd\n"), 0) 78 | h.Insert([]byte("1234\ncccc\ndddd"), 0) 79 | 80 | // Delete the second line 81 | h.Delete(5, 10) 82 | w.Delete(5, 10) 83 | ``` 84 | 85 | By this point, `want` will be an image with the _defect-free_ 86 | state and `have` will be an image with the _defective_ state 87 | 88 | ``` 89 | etch.Assert(t, have, want, "TestDeleteLastLineNoNL.png") 90 | ``` 91 | 92 | # Step 4: Go Test 93 | 94 | We run `go test` 95 | 96 | ``` 97 | --- FAIL: TestDeleteLastLineNoNL (0.03s) 98 | etch.go:64: delta: TestDeleteLastLineNoNL.png 99 | FAIL 100 | exit status 1 101 | FAIL github.com/as/frame 0.125s 102 | ``` 103 | 104 | We can look at the image to see what went wrong: `TestDeleteLastLineNoNL.png` 105 | 106 | ![paint](img/delta.png) 107 | 108 | Although it looks obvious, remember that this test would fail if any of the pixels differ. It's not easy to compare images visually, and you shouldn't avoid automating tests for it. Automating the tests helps prevent regressions from going undetected and speeds up the edit/compile/test cycle. 109 | 110 | # Step 5: Apply the Fix 111 | 112 | ``` 113 | f.Draw(f.b, image.Rect(pt0.X, pt0.Y, pt0.X+(f.r.Max.X-pt1.X), q0), f.b, pt1, f.op) 114 | f.Draw(f.b, image.Rect(f.r.Min.X, q0, f.r.Max.X, q0+(q2-q1)), f.b, image.Pt(f.r.Min.X, q1), f.op) 115 | // f.Paint(image.Pt(pt2.X, pt2.Y-(pt1.Y-pt0.Y)), pt2, f.Color.Back) 116 | 117 | ``` 118 | 119 | The bug is the commented line above. Once the comment is removed, the test passes. Because `go test` 120 | can be run automatically on file changes, this eliminates the manual step of checking the image. The 121 | test passes once `have` and `want` are the same, and when they're not, just open the delta in an image 122 | viewer to see what went wrong. 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /vendor/github.com/as/etch/etch.go: -------------------------------------------------------------------------------- 1 | // Package etch provides a simple facility to write graphical regression tests. 2 | // The Assertf function provides the common case functionality. Provide it 3 | // the test variable, along the image you have and want, and it will fail your 4 | // case if want != have. 5 | // 6 | // Optionally, provide a filename to store the graphical difference as an 7 | // uncompressed PNG if the test fails. 8 | // 9 | // The Extra data in the image (have but don't want) is represented in Red 10 | // The Missing data (want, but dont have) is represented in Blue 11 | // These can be changed by modifying Extra and Missing package variables 12 | // 13 | // To simplify the package, the alpha channel is ignored. A color triplet 14 | // is equal to another if it's R,G,B values are identical. 15 | // 16 | // The foreground variable, fg, is what to paint on the delta image if two pixels match 17 | // The background variable, BG, is the common background color between two images 18 | // 19 | // If two pixels at the same (x,y) coordinate don't match, the ambiguity is resolved 20 | // by comparing the image you have's color value at that coordinate to the background 21 | // color. If the color matches, the pixel you have is missing. Otherwise, it's extra. 22 | // 23 | // This may seem confusing, so a worked example is made available in the README 24 | package etch 25 | 26 | import ( 27 | "github.com/as/font" 28 | "image" 29 | "image/color" 30 | "image/draw" 31 | "image/png" 32 | "os" 33 | "testing" 34 | ) 35 | 36 | var ( 37 | enc = png.Encoder{CompressionLevel: png.NoCompression} 38 | ft = font.NewGoMono(20) 39 | sft = font.NewGoMono(10) 40 | 41 | // Colors from as/frame 42 | Red = image.NewUniform(color.RGBA{255, 0, 0, 255}) 43 | Blue = image.NewUniform(color.RGBA{0, 0, 255, 255}) 44 | Black = image.NewUniform(color.RGBA{0, 0, 0, 255}) 45 | White = image.NewUniform(color.RGBA{255, 255, 255, 255}) 46 | Gray = image.NewUniform(color.RGBA{33, 33, 33, 255}) 47 | Peach = image.NewUniform(color.RGBA{255, 248, 232, 255}) 48 | 49 | // Defaults used by this package 50 | // BG should be the similar background color between two images 51 | BG = Peach 52 | Extra = Red 53 | Missing = Blue 54 | fg = White // Always opaque white 55 | ) 56 | 57 | // Assert compares two test images and fails the provided test if the 58 | // images differ at any pixel(x,y). It saves the delta as a png to 59 | // the given filename (if set) and provides the path to that image in an 60 | // error string upon failure. 61 | func Assert(t *testing.T, have, want image.Image, filename string) { 62 | delta, ok := Delta(have, want) 63 | if ok { 64 | return 65 | } 66 | if filename != "" { 67 | t.Logf("delta: %s", filename) 68 | WriteFile(t, filename, Report(have, want, delta)) 69 | } 70 | t.Fail() 71 | } 72 | 73 | // AssertFile is like assert, but reads the wanted result from the named file 74 | func AssertFile(t *testing.T, have image.Image, wantfile string, filename string) { 75 | want := ReadFile(t, wantfile) 76 | Assert(t, have, want, filename) 77 | } 78 | 79 | // Assertf is like assert, except it logs a custom message with a format string 80 | // and interface parameter list (like fmt.Printf) 81 | func Assertf(t *testing.T, have, want image.Image, filename string, fm string, i ...interface{}) { 82 | delta, ok := Delta(have, want) 83 | if ok { 84 | return 85 | } 86 | t.Logf(fm, i...) 87 | if filename != "" { 88 | WriteFile(t, filename, Report(have, want, delta)) 89 | } 90 | t.Fail() 91 | } 92 | 93 | // Report generates a visual summary of the actual (have) and 94 | // expected (want) results, alongside the delta image. See 95 | // Delta for details on the delta image format. 96 | func Report(have, want, delta image.Image) image.Image { 97 | r := have.Bounds() 98 | r.Max.X = r.Min.X + r.Dx()*3 + 5*4 99 | r.Max.Y += 30 100 | rep := image.NewRGBA(r) 101 | draw.Draw(rep, r, Gray, rep.Bounds().Min, draw.Src) 102 | r.Min.X += 5 103 | s := []string{"Have", "Want", "Delta"} 104 | for i, src := range []image.Image{have, want, delta} { 105 | drawBorder(rep, r.Inset(-1), Black, image.ZP, 2) 106 | font.StringNBG(rep, image.Pt(r.Min.X+5, r.Max.Y-25), White, image.ZP, ft, []byte(s[i])) 107 | draw.Draw(rep, r, src, src.Bounds().Min, draw.Src) 108 | r.Min.X += want.Bounds().Dx() + 5 109 | } 110 | r.Min.X -= want.Bounds().Dx() + 5 111 | font.StringNBG(rep, image.Pt(r.Min.X+100-1, r.Max.Y-25-1), Black, image.ZP, sft, []byte("(Extra")) 112 | font.StringNBG(rep, image.Pt(r.Min.X+100, r.Max.Y-25), Extra, image.ZP, sft, []byte("(Extra")) 113 | 114 | font.StringNBG(rep, image.Pt(r.Min.X+100-1+45, r.Max.Y-25-1), Black, image.ZP, sft, []byte("/Missing)")) 115 | font.StringNBG(rep, image.Pt(r.Min.X+100+45, r.Max.Y-25), Missing, image.ZP, sft, []byte("/Missing)")) 116 | return rep 117 | } 118 | 119 | // Delta computes a difference between image a and b by 120 | // comparing each pixel to the fg and BG colors. If a pixel 121 | // in a and b are equal, the delta pixel is fg. Otherwise 122 | // the pixel is either red or blue depending if its extra 123 | // or missing respectively. 124 | func Delta(a, b image.Image) (delta *image.RGBA, ok bool) { 125 | delta = image.NewRGBA(a.Bounds()) 126 | dirty := false 127 | for y := a.Bounds().Min.Y; y < a.Bounds().Max.Y; y++ { 128 | for x := a.Bounds().Min.X; x < a.Bounds().Max.X; x++ { 129 | h := a.At(x, y) 130 | w := b.At(x, y) 131 | if EqualRGB(h, w) { 132 | delta.Set(x, y, fg) 133 | continue 134 | } 135 | dirty = true 136 | if EqualRGB(h, BG) { 137 | delta.Set(x, y, color.RGBA{0, 0, 255, 255}) 138 | } else { 139 | delta.Set(x, y, color.RGBA{255, 0, 0, 255}) 140 | } 141 | } 142 | } 143 | return delta, !dirty 144 | } 145 | 146 | // EqualRGB returns true if and only if 147 | // the two colors share the same RGB triplets 148 | func EqualRGB(c0, c1 color.Color) bool { 149 | r0, g0, b0, _ := c0.RGBA() 150 | r1, g1, b1, _ := c1.RGBA() 151 | return r0 == r1 && g0 == g1 && b0 == b1 152 | } 153 | 154 | // WriteFile writes the input img to the names file and fails the test. 155 | func WriteFile(t *testing.T, file string, img image.Image) { 156 | fd, err := os.Create(file) 157 | if err != nil { 158 | t.Log(err) 159 | t.FailNow() 160 | } 161 | defer fd.Close() 162 | err = enc.Encode(fd, img) 163 | if err != nil { 164 | t.Log(err) 165 | t.FailNow() 166 | } 167 | } 168 | 169 | // ReadFile reads in the named file and returns it as an image.Image. The supported 170 | // format is an uncompressed PNG. 171 | func ReadFile(t *testing.T, file string) (img image.Image) { 172 | fd, err := os.Open(file) 173 | if err != nil { 174 | t.Log(err) 175 | t.FailNow() 176 | } 177 | defer fd.Close() 178 | img, err = png.Decode(fd) 179 | if err != nil { 180 | t.Log(err) 181 | t.FailNow() 182 | } 183 | return img 184 | } 185 | 186 | func drawBorder(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point, thick int) { 187 | draw.Draw(dst, image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+thick), src, sp, draw.Src) 188 | draw.Draw(dst, image.Rect(r.Min.X, r.Max.Y-thick, r.Max.X, r.Max.Y), src, sp, draw.Src) 189 | draw.Draw(dst, image.Rect(r.Min.X, r.Min.Y, r.Min.X+thick, r.Max.Y), src, sp, draw.Src) 190 | draw.Draw(dst, image.Rect(r.Max.X-thick, r.Min.Y, r.Max.X, r.Max.Y), src, sp, draw.Src) 191 | } 192 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/README.md: -------------------------------------------------------------------------------- 1 | # font 2 | Font contains the IBM vga font 3 | 4 | This API is not ready for general use and is subject to change 5 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/cache.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | 8 | "golang.org/x/image/font" 9 | "golang.org/x/image/math/fixed" 10 | ) 11 | 12 | type Cache interface { 13 | Face 14 | LoadGlyph(r rune, fg, bg color.Color) image.Image 15 | } 16 | 17 | func NewCache(f font.Face) Cache { 18 | var f0 Face 19 | switch f := f.(type) { 20 | case Cache: 21 | return f 22 | case Rune: 23 | return newRuneCache(f) 24 | case Face: 25 | f0 = f 26 | case font.Face: 27 | f0 = Open(f) 28 | } 29 | cf := &cachedFace{ 30 | Face: f0, 31 | cache: make(map[signature]*image.RGBA), 32 | } 33 | for i := range cf.cachewidth { 34 | cf.cachewidth[i] = f0.Dx([]byte{byte(i)}) 35 | } 36 | return cf 37 | } 38 | 39 | type cachedFace struct { 40 | Face 41 | cache map[signature]*image.RGBA 42 | cachewidth [256]int 43 | } 44 | 45 | type signature struct { 46 | r rune 47 | fg color.RGBA 48 | bg color.RGBA 49 | } 50 | 51 | func (f *cachedFace) LoadGlyph(r rune, fg, bg color.Color) image.Image { 52 | sig := signature{r, convert(fg), convert(bg)} 53 | if img, ok := f.cache[sig]; ok { 54 | return img 55 | } 56 | mask, r0 := f.genChar(r) 57 | img := image.NewRGBA(r0) 58 | draw.Draw(img, img.Bounds(), image.NewUniform(bg), image.ZP, draw.Src) 59 | draw.DrawMask(img, img.Bounds(), image.NewUniform(fg), image.ZP, mask, r0.Min, draw.Over) 60 | f.cache[sig] = img 61 | if int(r) < len(f.cache) { 62 | f.cachewidth[byte(r)] = f.Dx([]byte{byte(r)}) 63 | } 64 | return img 65 | } 66 | 67 | func (f *cachedFace) Fits(p []byte, limitDx int) (n int) { 68 | var c byte 69 | for n, c = range p { 70 | limitDx -= f.cachewidth[c] 71 | if limitDx < 0 { 72 | return n 73 | } 74 | } 75 | return n 76 | } 77 | 78 | func (f *cachedFace) Dx(p []byte) (dx int) { 79 | for _, c := range p { 80 | dx += f.cachewidth[c] 81 | } 82 | return dx 83 | } 84 | 85 | func (f *cachedFace) genChar(r rune) (*image.Alpha, image.Rectangle) { 86 | dr, mask, maskp, adv, _ := f.Face.Glyph(fixed.P(0, f.Height()), r) 87 | r0 := image.Rect(0, 0, Fix(adv), f.Dy()) 88 | m := image.NewAlpha(r0) 89 | r0 = r0.Add(image.Pt(dr.Min.X, dr.Min.Y)) 90 | draw.Draw(m, r0, mask, maskp, draw.Src) 91 | return m, m.Bounds() 92 | } 93 | 94 | func convert(c color.Color) color.RGBA { 95 | r, g, b, a := c.RGBA() 96 | return color.RGBA{byte(r >> 8), byte(g >> 8), byte(b >> 8), byte(a >> 8)} 97 | } 98 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/cliche.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | 8 | "golang.org/x/image/font" 9 | ) 10 | 11 | type Cliche interface { 12 | Cache 13 | LoadBox(s []byte, fg, bg color.Color) image.Image 14 | } 15 | 16 | type boxsig struct { 17 | b string 18 | fg color.RGBA 19 | bg color.RGBA 20 | } 21 | 22 | func NewCliche(f font.Face) Cliche { 23 | if f, ok := f.(Cliche); ok { 24 | return f 25 | } 26 | return &cliche{ 27 | Cache: NewCache(f), 28 | cache: make(map[boxsig]image.Image), 29 | } 30 | } 31 | 32 | type cliche struct { 33 | Cache 34 | cache map[boxsig]image.Image 35 | } 36 | 37 | func (c *cliche) LoadBox(b []byte, fg, bg color.Color) image.Image { 38 | if len(b) == 0 { 39 | return nil 40 | } 41 | sig := boxsig{string(b), convert(fg), convert(bg)} 42 | if img, ok := c.cache[sig]; ok { 43 | return img 44 | } 45 | var list []image.Image 46 | dx := 0 47 | for _, v := range b { 48 | img := c.LoadGlyph(rune(v), fg, bg) 49 | dx += img.Bounds().Dx() 50 | list = append(list, img) 51 | } 52 | r := list[0].Bounds() 53 | r.Max.X += dx 54 | boximg := image.NewRGBA(r) 55 | for _, img := range list { 56 | dx := img.Bounds().Dx() 57 | r.Max.X += dx 58 | draw.Draw(boximg, r, img, image.ZP, draw.Src) 59 | r.Min.X += dx 60 | } 61 | c.cache[sig] = boximg 62 | return boximg 63 | } 64 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/draw.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | 8 | "golang.org/x/image/font" 9 | 10 | "golang.org/x/image/math/fixed" 11 | ) 12 | 13 | func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft font.Face, s []byte, bg image.Image, bgp image.Point) int { 14 | if bg == nil { 15 | return StringNBG(dst, p, src, sp, ft, s) 16 | } 17 | if fg, bg, ok := canCache(src, bg); ok { 18 | switch ft := ft.(type) { 19 | case Cliche: 20 | img := ft.LoadBox(s, fg, bg) 21 | dr := img.Bounds().Add(p) 22 | draw.Draw(dst, dr, img, img.Bounds().Min, draw.Src) 23 | return dr.Dx() 24 | case Cache: 25 | switch ft := ft.(type) { 26 | case Rune: 27 | return staticRuneBG(dst, p, ft.(Cache), s, fg, bg) 28 | } 29 | return staticStringBG(dst, p, ft, s, fg, bg) 30 | 31 | } 32 | } 33 | switch ft := ft.(type) { 34 | case *runeface: 35 | return runeBG(dst, p, src, sp, ft, s, bg, bgp) 36 | case Face: 37 | return stringBG(dst, p, src, sp, ft, s, bg, bgp) 38 | } 39 | return stringBG(dst, p, src, sp, Open(ft), s, bg, bgp) 40 | } 41 | 42 | func StringNBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft font.Face, s []byte) int { 43 | var ( 44 | f Face 45 | ok bool 46 | ) 47 | if f, ok = ft.(Face); !ok { 48 | f = Open(ft) 49 | } 50 | p.Y += f.Height() 51 | for _, b := range s { 52 | dr, mask, maskp, advance, _ := f.Glyph(fixed.P(p.X, p.Y), rune(b)) 53 | draw.DrawMask(dst, dr, src, sp, mask, maskp, draw.Over) 54 | p.X += Fix(advance) 55 | } 56 | return p.X 57 | } 58 | 59 | func canCache(f image.Image, b image.Image) (fg, bg color.Color, ok bool) { 60 | if f, ok := f.(*image.Uniform); ok { 61 | if b, ok := b.(*image.Uniform); ok { 62 | return f.C, b.C, true 63 | } 64 | } 65 | return fg, bg, false 66 | } 67 | 68 | func runeBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft Face, s []byte, bg image.Image, bgp image.Point) int { 69 | p.Y += ft.Height() 70 | for _, b := range string(s) { 71 | dr, mask, maskp, advance, _ := ft.Glyph(fixed.P(p.X, p.Y), b) 72 | draw.Draw(dst, dr, bg, bgp, draw.Src) 73 | draw.DrawMask(dst, dr, src, sp, mask, maskp, draw.Over) 74 | p.X += Fix(advance) 75 | } 76 | return p.X 77 | } 78 | func staticRuneBG(dst draw.Image, p image.Point, ft Cache, s []byte, fg, bg color.Color) int { 79 | r := image.Rectangle{p, p} 80 | r.Max.Y += ft.Dy() 81 | 82 | for _, b := range string(s) { 83 | img := ft.LoadGlyph(b, fg, bg) 84 | dx := img.Bounds().Dx() 85 | r.Max.X += dx 86 | draw.Draw(dst, r, img, img.Bounds().Min, draw.Src) 87 | r.Min.X += dx 88 | } 89 | return r.Min.X - p.X 90 | } 91 | 92 | func stringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft Face, s []byte, bg image.Image, bgp image.Point) int { 93 | p.Y += ft.Height() 94 | for _, b := range s { 95 | dr, mask, maskp, advance, _ := ft.Glyph(fixed.P(p.X, p.Y), rune(b)) 96 | draw.Draw(dst, dr, bg, bgp, draw.Src) 97 | draw.DrawMask(dst, dr, src, sp, mask, maskp, draw.Over) 98 | p.X += Fix(advance) 99 | } 100 | return p.X 101 | } 102 | 103 | func staticStringBG(dst draw.Image, p image.Point, ft Cache, s []byte, fg, bg color.Color) int { 104 | r := image.Rectangle{p, p} 105 | r.Max.Y += ft.Dy() 106 | 107 | for _, b := range s { 108 | img := ft.LoadGlyph(rune(b), fg, bg) 109 | dx := img.Bounds().Dx() 110 | r.Max.X += dx 111 | draw.Draw(dst, r, img, img.Bounds().Min, draw.Src) 112 | r.Min.X += dx 113 | } 114 | return r.Min.X - p.X 115 | } 116 | 117 | /* 118 | func StringBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte, bg image.Image, bgp image.Point) int { 119 | for _, b := range s { 120 | mask := ft.Char(b) 121 | if mask == nil { 122 | panic("StringBG") 123 | } 124 | r := mask.Bounds() 125 | //draw.Draw(dst, r.Add(p), bg, bgp, draw.Src) 126 | draw.DrawMask(dst, r.Add(p), src, sp, mask, mask.Bounds().Min, draw.Over) 127 | p.X += r.Dx() 128 | } 129 | return p.X 130 | } 131 | 132 | func StringNBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte) int { 133 | for _, b := range s { 134 | mask := ft.Char(b) 135 | if mask == nil { 136 | panic("StringBG") 137 | } 138 | r := mask.Bounds() 139 | draw.DrawMask(dst, r.Add(p), src, sp, mask, mask.Bounds().Min, draw.Over) 140 | p.X += r.Dx() 141 | } 142 | return p.X 143 | } 144 | 145 | func RuneBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte, bg image.Image, bgp image.Point) int { 146 | p.Y += ft.Size() 147 | for { 148 | b, size := utf8.DecodeRune(s) 149 | dr, mask, maskp, advance, ok := ft.Glyph(fixed.P(p.X, p.Y), b) 150 | if !ok { 151 | panic("RuneBG") 152 | } 153 | //draw.Draw(dst, dr, bg, bgp, draw.Src) 154 | draw.DrawMask(dst, dr, src, sp, mask, maskp, draw.Over) 155 | p.X += Fix(advance) 156 | if len(s)-size == 0 { 157 | break 158 | } 159 | s = s[size:] 160 | } 161 | return p.X 162 | } 163 | 164 | func RuneNBG(dst draw.Image, p image.Point, src image.Image, sp image.Point, ft *Font, s []byte) int { 165 | p.Y += ft.Size() 166 | for { 167 | b, size := utf8.DecodeRune(s) 168 | dr, mask, maskp, advance, ok := ft.Glyph(fixed.P(p.X, p.Y), b) 169 | if !ok { 170 | panic("RuneBG") 171 | } 172 | draw.DrawMask(dst, dr, src, sp, mask, maskp, draw.Over) 173 | p.X += Fix(advance) 174 | if len(s)-size == 0 { 175 | break 176 | } 177 | s = s[size:] 178 | } 179 | return p.X 180 | } 181 | */ 182 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/font.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "golang.org/x/image/font" 5 | "golang.org/x/image/math/fixed" 6 | ) 7 | 8 | func Fix(i fixed.Int26_6) int { 9 | return i.Ceil() 10 | } 11 | 12 | type Face interface { 13 | font.Face 14 | Ruler 15 | } 16 | type Ruler interface { 17 | Ascent() int 18 | Descent() int 19 | Height() int 20 | Letting() int 21 | Stride() int 22 | Dy() int 23 | Dx(p []byte) (dx int) 24 | Fits(p []byte, limitDx int) (n int) 25 | } 26 | 27 | func Open(f font.Face) Face { 28 | if f == nil { 29 | panic("open: nil face") 30 | } 31 | switch f := f.(type) { 32 | case Face: 33 | return f 34 | } 35 | m := f.Metrics() 36 | a := m.Ascent.Ceil() 37 | h := m.Height.Ceil() 38 | d := m.Descent.Ceil() 39 | dy := h + h/2 40 | l := dy / 2 41 | return &face{ 42 | Face: f, 43 | s: 0, 44 | a: a, 45 | d: d, 46 | h: h, 47 | l: l, 48 | dy: dy, 49 | } 50 | } 51 | 52 | type face struct { 53 | h, a, d, l, dy, s int 54 | font.Face 55 | } 56 | 57 | func (f *face) Stride() int { return f.s } 58 | func (f *face) Letting() int { return f.l } 59 | func (f *face) Height() int { return f.h } 60 | func (f *face) Ascent() int { return f.a } 61 | func (f *face) Descent() int { return f.d } 62 | func (f *face) Dy() int { return f.dy } 63 | func (f *face) Dx(p []byte) (dx int) { 64 | for _, c := range p { 65 | w, _ := f.Face.GlyphAdvance(rune(c)) 66 | dx += Fix(w) 67 | } 68 | return dx + f.Stride()*len(p) 69 | } 70 | func (f *face) Fits(p []byte, limitDx int) (n int) { 71 | var c byte 72 | stride := f.Stride() 73 | for n, c = range p { 74 | w, _ := f.Face.GlyphAdvance(rune(c)) 75 | limitDx -= Fix(w) + stride 76 | if limitDx < 0 { 77 | return n 78 | } 79 | } 80 | return n 81 | } 82 | 83 | /* 84 | // NewBasic always returns a 7x13 basic font 85 | func NewRaster(f Face, size int) *Font { 86 | ft := &Font{ 87 | Face: f, 88 | size: size, 89 | ascent: 2, 90 | descent: 1, 91 | letting: 0, 92 | stride: 0, 93 | } 94 | ft.dy = ft.ascent + ft.descent + ft.size 95 | hexFt := makefont(gomono.TTF, ft.Dy()/4+3) 96 | ft.hexDx = ft.genChar('_').Bounds().Dx() 97 | for i := 0; i != 256; i++ { 98 | ft.cache[i] = ft.genChar(byte(i)) 99 | if ft.cache[i] == nil { 100 | ft.cache[i] = hexFt.genHexChar(ft.Dy(), byte(i)) 101 | } 102 | } 103 | return ft 104 | } 105 | 106 | func makefont(data []byte, size int) *Font { 107 | reply := make(chan interface{}) 108 | fontIRQ <- fontPKT{ 109 | id: string(crc32.NewIEEE().Sum(data)), 110 | reply: reply, 111 | data: data, 112 | } 113 | rx := <-reply 114 | switch rx := rx.(type) { 115 | case error: 116 | println(rx) 117 | return nil 118 | case *truetype.Font: 119 | ft := FromFace(truetype.NewFace(rx, 120 | &truetype.Options{ 121 | Size: float64(size), 122 | GlyphCacheEntries: 512, 123 | SubPixelsX: 1, 124 | }), size) 125 | ft.data = data 126 | ft.dy = ft.ascent + ft.descent + ft.size 127 | return ft 128 | } 129 | panic("makefont") 130 | } 131 | 132 | func (f *Font) genChar(b byte) *Glyph { 133 | dr, mask, maskp, adv, _ := f.Glyph(fixed.P(0, f.size), rune(b)) 134 | if !f.Printable(b) { 135 | return nil 136 | } 137 | r := image.Rect(0, 0, Fix(adv), f.Dy()) 138 | m := image.NewAlpha(r) 139 | r = r.Add(image.Pt(dr.Min.X, dr.Min.Y)) 140 | draw.Draw(m, r, mask, maskp, draw.Src) 141 | return &Glyph{mask: m, Rectangle: m.Bounds()} 142 | } 143 | 144 | func (f *Font) genHexChar(dy int, b byte) *Glyph { 145 | s := fmt.Sprintf("%02x", b) 146 | g0 := f.genChar(s[0]) 147 | g1 := f.genChar(s[1]) 148 | r := image.Rect(2, f.descent+f.ascent, g0.Bounds().Dx()+g1.Bounds().Dx()+6, dy) 149 | m := image.NewAlpha(r) 150 | draw.Draw(m, r, g0.Mask(), image.ZP, draw.Over) 151 | r.Min.X += g0.Mask().Bounds().Dx() 152 | draw.Draw(m, r.Add(image.Pt(-f.descent/4, f.descent*2)), g1.Mask(), image.ZP, draw.Over) 153 | return &Glyph{mask: m, Rectangle: m.Bounds()} 154 | } 155 | */ 156 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/gofont.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "unicode" 5 | 6 | "github.com/as/font/gomedium" 7 | "github.com/as/font/gomono" 8 | "github.com/as/font/goregular" 9 | "github.com/golang/freetype/truetype" 10 | ) 11 | 12 | func notGraphic(r rune) bool { return !unicode.IsGraphic(r) } 13 | 14 | // New returns a GoMedium font that is cached and replaces 15 | // non-printable characters with their hex equivalent encodings 16 | func NewFace(size int) Face { 17 | return NewCache(Replacer(NewGoMedium(size), NewHex(size), nil)) 18 | // return NewCache(Replacer(NewGoMedium(size), NewHex(size), nil)) 19 | // return NewCache(NewRune(NewGoMedium(size))) 20 | // var fn func(size int) Face 21 | // fn = func(size int) Face{ 22 | // return &Resizer{ 23 | // Face: NewCache(Replacer(NewGoMedium(size), NewHex(size), nil)), 24 | // New: fn, 25 | // } 26 | // } 27 | // return fn(size) 28 | } 29 | 30 | func NewGoMedium(size int) Face { 31 | return Open(truetype.NewFace(gomedium.Font, &truetype.Options{ 32 | SubPixelsX: 64, 33 | SubPixelsY: 64, 34 | Size: float64(size), 35 | })) 36 | } 37 | 38 | func NewGoMono(size int) Face { 39 | return Open(truetype.NewFace(gomono.Font, &truetype.Options{ 40 | SubPixelsX: 64, 41 | SubPixelsY: 64, 42 | Size: float64(size), 43 | })) 44 | } 45 | 46 | func NewGoRegular(size int) Face { 47 | return Open(truetype.NewFace(goregular.Font, &truetype.Options{ 48 | SubPixelsX: 64, 49 | SubPixelsY: 64, 50 | Size: float64(size), 51 | })) 52 | } 53 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/gomedium/gomedium.go: -------------------------------------------------------------------------------- 1 | package gomedium 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/golang/freetype/truetype" 7 | . "golang.org/x/image/font/gofont/gomedium" 8 | ) 9 | 10 | var Font, err = truetype.Parse(TTF) 11 | 12 | func init() { 13 | if err != nil { 14 | log.Fatalln("gomedium", err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/gomono/gomono.go: -------------------------------------------------------------------------------- 1 | package gomono 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/golang/freetype/truetype" 7 | . "golang.org/x/image/font/gofont/gomono" 8 | ) 9 | 10 | var Font, err = truetype.Parse(TTF) 11 | 12 | func init() { 13 | if err != nil { 14 | log.Fatalln("goregular", err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/goregular/goregular.go: -------------------------------------------------------------------------------- 1 | package goregular 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/golang/freetype/truetype" 7 | . "golang.org/x/image/font/gofont/goregular" 8 | ) 9 | 10 | var Font, err = truetype.Parse(TTF) 11 | 12 | func init() { 13 | if err != nil { 14 | log.Fatalln("goregular", err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/hex.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | 7 | "golang.org/x/image/font" 8 | "golang.org/x/image/math/fixed" 9 | ) 10 | 11 | func NewHex(dy int) Face { 12 | f := &hex{} 13 | f.genHexChars(dy) 14 | return f 15 | } 16 | 17 | type hex struct { 18 | glyphs [256]*image.Alpha 19 | h, a, d, l, dx, dy, s int 20 | } 21 | 22 | func (f *hex) Stride() int { return f.s } 23 | func (f *hex) Letting() int { return f.l } 24 | func (f *hex) Height() int { return f.h } 25 | func (f *hex) Ascent() int { return f.a } 26 | func (f *hex) Descent() int { return f.d } 27 | func (f *hex) Dy() int { return f.dy } 28 | 29 | func (f *hex) Dx(p []byte) (dx int) { 30 | return (f.dx * f.s) + (f.dx * len(p)) 31 | } 32 | 33 | func (f *hex) Fits(p []byte, limitDx int) (n int) { 34 | n = limitDx / f.dx 35 | if n > len(p) { 36 | n = len(p) 37 | } 38 | return n 39 | } 40 | func (f *hex) Close() error { 41 | return nil 42 | } 43 | func (f *hex) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { 44 | if r > 255 { 45 | return image.ZR, nil, image.ZP, 0, false 46 | } 47 | dot0 := image.Pt(dot.X.Ceil(), dot.Y.Ceil()) 48 | dot0 = dot0.Add(image.Pt(0, -10)) 49 | img := f.glyphs[byte(r)] 50 | r0 := img.Bounds() 51 | dr.Max.X = r0.Dx() 52 | dr.Max.Y = r0.Dy() 53 | dr = dr.Add(dot0) 54 | return dr, img, image.ZP, fixed.I(f.dx), true 55 | } 56 | func (f *hex) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { 57 | if r > 255 { 58 | return 59 | } 60 | r0 := f.glyphs[byte(r)].Bounds() 61 | bounds.Max = bounds.Max.Add(fixed.P(r0.Dx(), r0.Dy())) 62 | return bounds, fixed.I(f.dx), true 63 | } 64 | func (f *hex) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { 65 | if r > 255 { 66 | return 67 | } 68 | //r0 := f.glyphs[byte(r)].Bounds() 69 | return fixed.I(f.dx), true //fixed.I(r0.Dx()), true 70 | } 71 | func (f *hex) Kern(r0, r1 rune) fixed.Int26_6 { return 0 } 72 | func (f *hex) Metrics() (m font.Metrics) { return } 73 | func (f *hex) genHexChars(dy int) { 74 | 75 | var helper [16]*image.Alpha 76 | 77 | { 78 | ft := NewGoMedium(dy/5 + dy/3 + 3) 79 | 80 | for i, c := range []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} { 81 | dr, mask, maskp, adv, _ := ft.Glyph(fixed.P(0, ft.Height()), c) 82 | r := image.Rect(0, 0, Fix(adv), ft.Dy()) 83 | m := image.NewAlpha(r) 84 | r = r.Add(image.Pt(dr.Min.X, dr.Min.Y)) 85 | draw.Draw(m, r, mask, maskp, draw.Src) 86 | helper[i] = m 87 | } 88 | } 89 | 90 | d0 := f.Descent() 91 | for i := 0; i != 256; i++ { 92 | g0 := helper[i/16] 93 | g1 := helper[i%16] 94 | r := image.Rect(2, d0, g0.Bounds().Dx()+g1.Bounds().Dx()+7, dy-3) 95 | m := image.NewAlpha(r) 96 | draw.Draw(m, r.Add(image.Pt(2, 0)), g0, image.ZP, draw.Over) 97 | r.Min.X += g0.Bounds().Dx() 98 | draw.Draw(m, r.Add(image.Pt(-d0/4+2, d0-d0*2)), g1, image.ZP, draw.Over) 99 | f.glyphs[i] = m 100 | } 101 | 102 | ft := NewGoMedium(dy) 103 | m := ft.Metrics() 104 | f.a = m.Ascent.Ceil() 105 | f.h = m.Height.Ceil() 106 | f.d = m.Descent.Ceil() 107 | f.dy = f.h + f.h/2 108 | f.l = dy / 2 109 | f.dx = f.glyphs[0].Bounds().Dx() 110 | } 111 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/replacer.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "unicode" 6 | 7 | "golang.org/x/image/font" 8 | "golang.org/x/image/math/fixed" 9 | ) 10 | 11 | // Replacer returns a face that conditionally replaces a glyph's 12 | // mask and dimensions based on a decision function. For best 13 | // results, the returned Face should be cached with NewCache; the functions 14 | // that compute Fits and Dx are particularly inefficient when used 15 | // without caching. 16 | // 17 | // Note: The current implementation currently returns a cached 18 | // face, but callers shouldn't assume this will always be true 19 | // and use NewCache(NewReplacer(a,b,cond)) anyway. 20 | func Replacer(a, b Face, cond func(r rune) bool) Face { 21 | if cond == nil { 22 | cond = func(r rune) bool { 23 | return r > 127 || r <= 0 || !unicode.IsGraphic(r) 24 | } 25 | } 26 | return NewCache(&repl{ 27 | Face: a, 28 | b: b, 29 | fn: cond, 30 | }) 31 | } 32 | 33 | type repl struct { 34 | Face 35 | b Face 36 | fn func(r rune) bool 37 | } 38 | 39 | func (f *repl) Dx(p []byte) (dx int) { 40 | for n, c := range p { 41 | if f.fn(rune(c)) { 42 | dx += f.b.Dx(p[n : n+1]) 43 | } else { 44 | dx += f.Face.Dx(p[n : n+1]) 45 | } 46 | } 47 | return dx 48 | } 49 | 50 | func (f *repl) Fits(p []byte, limitDx int) (n int) { 51 | var c byte 52 | for n, c = range p { 53 | if f.fn(rune(c)) { 54 | limitDx -= f.b.Dx(p[n : n+1]) 55 | } else { 56 | limitDx -= f.Face.Dx(p[n : n+1]) 57 | } 58 | if limitDx < 0 { 59 | return n 60 | } 61 | } 62 | return n 63 | } 64 | func (f *repl) Close() error { 65 | return nil 66 | } 67 | func (f *repl) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { 68 | if f.fn(r) { 69 | return f.b.Glyph(dot, r) 70 | } 71 | return f.Face.Glyph(dot, r) 72 | } 73 | func (f *repl) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { 74 | if f.fn(r) { 75 | return f.b.GlyphBounds(r) 76 | } 77 | return f.Face.GlyphBounds(r) 78 | } 79 | func (f *repl) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { 80 | if f.fn(r) { 81 | return f.b.GlyphAdvance(r) 82 | } 83 | return f.Face.GlyphAdvance(r) 84 | } 85 | func (f *repl) Kern(r0, r1 rune) fixed.Int26_6 { 86 | if f.fn(r0) { 87 | return f.b.Kern(r0, r1) 88 | } 89 | return f.Face.Kern(r0, r1) 90 | } 91 | 92 | func (f *repl) Metrics() (m font.Metrics) { 93 | return f.Face.Metrics() 94 | } 95 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/resizer.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | // Resizer knows how to return an enlarged version of its face 4 | type Resizer struct { 5 | Face 6 | 7 | // New returns a new face with the given size. The current 8 | // size is not closed and is still usable by callers 9 | New func(int) Face 10 | } 11 | -------------------------------------------------------------------------------- /vendor/github.com/as/font/rune.go: -------------------------------------------------------------------------------- 1 | package font 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | 8 | "golang.org/x/image/font" 9 | ) 10 | 11 | // Rune is a Face aware of UTF8 text encoding. It measures runes 12 | // rather than bytes and draws runs of UTF8 text when used with 13 | // StringBG 14 | type Rune interface { 15 | Face 16 | canrune() 17 | } 18 | 19 | type runeface struct { 20 | Face 21 | } 22 | 23 | func NewRune(f font.Face) Face { 24 | if f == nil { 25 | panic("open: nil face") 26 | } 27 | return &runeface{Open(f)} 28 | } 29 | 30 | func (*runeface) canrune() {} 31 | 32 | func (f *runeface) Dx(p []byte) (dx int) { 33 | for _, r := range string(p) { 34 | w, _ := f.Face.GlyphAdvance(r) 35 | dx += Fix(w) 36 | } 37 | return dx + f.Stride()*len(p) 38 | } 39 | 40 | func (f *runeface) Fits(p []byte, limitDx int) (n int) { 41 | var r rune 42 | stride := f.Stride() 43 | for n, r = range string(p) { 44 | w, _ := f.Face.GlyphAdvance(r) 45 | limitDx -= Fix(w) + stride 46 | if limitDx < 0 { 47 | return n 48 | } 49 | } 50 | return n 51 | } 52 | 53 | func newRuneCache(r Rune) Cache { 54 | cf := &cachedRuneFace{ 55 | &cachedFace{ 56 | Face: r, 57 | cache: make(map[signature]*image.RGBA), 58 | }, 59 | } 60 | for i := range cf.cachewidth { 61 | cf.cachewidth[i] = r.Dx([]byte{byte(i)}) 62 | } 63 | return cf 64 | } 65 | 66 | type cachedRuneFace struct { 67 | *cachedFace 68 | } 69 | 70 | func (f *cachedRuneFace) canrune() {} 71 | 72 | func (f *cachedRuneFace) Dx(p []byte) (dx int) { 73 | for _, c := range string(p) { 74 | if int(c) < len(f.cachewidth) && c > -1 { 75 | dx += f.cachewidth[c] 76 | } else { 77 | w, _ := f.Face.GlyphAdvance(c) 78 | dx += Fix(w) 79 | } 80 | } 81 | return dx 82 | } 83 | 84 | func (f *cachedRuneFace) LoadGlyph(r rune, fg, bg color.Color) image.Image { 85 | sig := signature{r, convert(fg), convert(bg)} 86 | if img, ok := f.cache[sig]; ok { 87 | return img 88 | } 89 | mask, r0 := f.genChar(r) 90 | img := image.NewRGBA(r0) 91 | draw.Draw(img, img.Bounds(), image.NewUniform(bg), image.ZP, draw.Src) 92 | draw.DrawMask(img, img.Bounds(), image.NewUniform(fg), image.ZP, mask, r0.Min, draw.Over) 93 | f.cache[sig] = img 94 | if int(r) < len(f.cache) { 95 | f.cachewidth[r] = f.Dx([]byte(string(r))) 96 | } 97 | return img 98 | } 99 | 100 | func (f *cachedRuneFace) Fits(p []byte, limitDx int) (n int) { 101 | var r rune 102 | stride := f.Stride() 103 | for n, r = range string(p) { 104 | if int(r) < len(f.cachewidth) && r > -1 { 105 | limitDx -= f.cachewidth[r] 106 | } else { 107 | w, _ := f.Face.GlyphAdvance(r) 108 | limitDx -= Fix(w) + stride 109 | } 110 | if limitDx < 0 { 111 | return n 112 | } 113 | } 114 | return n 115 | } 116 | -------------------------------------------------------------------------------- /vendor/github.com/as/io/spaz/spaz.go: -------------------------------------------------------------------------------- 1 | package spaz 2 | 3 | import ( 4 | "io" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type Reader struct{ 10 | ur io.Reader 11 | rs *rand.Rand 12 | } 13 | 14 | func NewReader(r io.Reader) *Reader{ 15 | return &Reader{ur: r, rs: rand.New(rand.NewSource(time.Now().Unix()))} 16 | } 17 | func (r *Reader) intn(n int)int{ 18 | return int(r.rs.Int31n(int32(n))) 19 | } 20 | 21 | func (r *Reader) Read(p []byte) (n int, err error) { 22 | rn := r.intn(len(p)) 23 | return r.ur.Read(p[:rn]) 24 | } 25 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Freetype-Go authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | # 5 | # Freetype-Go is derived from Freetype, which is written in C. The latter 6 | # is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg. 7 | 8 | # Names should be added to this file as 9 | # Name or Organization 10 | # The email address is not required for organizations. 11 | 12 | # Please keep the list sorted. 13 | 14 | Google Inc. 15 | Jeff R. Allen 16 | Maksim Kochkin 17 | Michael Fogleman 18 | Rémy Oudompheng 19 | Roger Peppe 20 | Steven Edwards 21 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the official list of people who can contribute 2 | # (and typically have contributed) code to the Freetype-Go repository. 3 | # The AUTHORS file lists the copyright holders; this file 4 | # lists people. For example, Google employees are listed here 5 | # but not in AUTHORS, because Google holds the copyright. 6 | # 7 | # The submission process automatically checks to make sure 8 | # that people submitting code are listed in this file (by email address). 9 | # 10 | # Names should be added to this file only after verifying that 11 | # the individual or the individual's organization has agreed to 12 | # the appropriate Contributor License Agreement, found here: 13 | # 14 | # http://code.google.com/legal/individual-cla-v1.0.html 15 | # http://code.google.com/legal/corporate-cla-v1.0.html 16 | # 17 | # The agreement for individuals can be filled out on the web. 18 | # 19 | # When adding J Random Contributor's name to this file, 20 | # either J's name or J's organization's name should be 21 | # added to the AUTHORS file, depending on whether the 22 | # individual or corporate CLA was used. 23 | 24 | # Names should be added to this file like so: 25 | # Name 26 | 27 | # Please keep the list sorted. 28 | 29 | Andrew Gerrand 30 | Jeff R. Allen 31 | Maksim Kochkin 32 | Michael Fogleman 33 | Nigel Tao 34 | Rémy Oudompheng 35 | Rob Pike 36 | Roger Peppe 37 | Russ Cox 38 | Steven Edwards 39 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/LICENSE: -------------------------------------------------------------------------------- 1 | Use of the Freetype-Go software is subject to your choice of exactly one of 2 | the following two licenses: 3 | * The FreeType License, which is similar to the original BSD license with 4 | an advertising clause, or 5 | * The GNU General Public License (GPL), version 2 or later. 6 | 7 | The text of these licenses are available in the licenses/ftl.txt and the 8 | licenses/gpl.txt files respectively. They are also available at 9 | http://freetype.sourceforge.net/license.html 10 | 11 | The Luxi fonts in the testdata directory are licensed separately. See the 12 | testdata/COPYING file for details. 13 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/raster/geom.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package raster 7 | 8 | import ( 9 | "fmt" 10 | "math" 11 | 12 | "golang.org/x/image/math/fixed" 13 | ) 14 | 15 | // maxAbs returns the maximum of abs(a) and abs(b). 16 | func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 { 17 | if a < 0 { 18 | a = -a 19 | } 20 | if b < 0 { 21 | b = -b 22 | } 23 | if a < b { 24 | return b 25 | } 26 | return a 27 | } 28 | 29 | // pNeg returns the vector -p, or equivalently p rotated by 180 degrees. 30 | func pNeg(p fixed.Point26_6) fixed.Point26_6 { 31 | return fixed.Point26_6{-p.X, -p.Y} 32 | } 33 | 34 | // pDot returns the dot product p·q. 35 | func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 { 36 | px, py := int64(p.X), int64(p.Y) 37 | qx, qy := int64(q.X), int64(q.Y) 38 | return fixed.Int52_12(px*qx + py*qy) 39 | } 40 | 41 | // pLen returns the length of the vector p. 42 | func pLen(p fixed.Point26_6) fixed.Int26_6 { 43 | // TODO(nigeltao): use fixed point math. 44 | x := float64(p.X) 45 | y := float64(p.Y) 46 | return fixed.Int26_6(math.Sqrt(x*x + y*y)) 47 | } 48 | 49 | // pNorm returns the vector p normalized to the given length, or zero if p is 50 | // degenerate. 51 | func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 { 52 | d := pLen(p) 53 | if d == 0 { 54 | return fixed.Point26_6{} 55 | } 56 | s, t := int64(length), int64(d) 57 | x := int64(p.X) * s / t 58 | y := int64(p.Y) * s / t 59 | return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)} 60 | } 61 | 62 | // pRot45CW returns the vector p rotated clockwise by 45 degrees. 63 | // 64 | // Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}. 65 | func pRot45CW(p fixed.Point26_6) fixed.Point26_6 { 66 | // 181/256 is approximately 1/√2, or sin(π/4). 67 | px, py := int64(p.X), int64(p.Y) 68 | qx := (+px - py) * 181 / 256 69 | qy := (+px + py) * 181 / 256 70 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 71 | } 72 | 73 | // pRot90CW returns the vector p rotated clockwise by 90 degrees. 74 | // 75 | // Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}. 76 | func pRot90CW(p fixed.Point26_6) fixed.Point26_6 { 77 | return fixed.Point26_6{-p.Y, p.X} 78 | } 79 | 80 | // pRot135CW returns the vector p rotated clockwise by 135 degrees. 81 | // 82 | // Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}. 83 | func pRot135CW(p fixed.Point26_6) fixed.Point26_6 { 84 | // 181/256 is approximately 1/√2, or sin(π/4). 85 | px, py := int64(p.X), int64(p.Y) 86 | qx := (-px - py) * 181 / 256 87 | qy := (+px - py) * 181 / 256 88 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 89 | } 90 | 91 | // pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees. 92 | // 93 | // Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}. 94 | func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 { 95 | // 181/256 is approximately 1/√2, or sin(π/4). 96 | px, py := int64(p.X), int64(p.Y) 97 | qx := (+px + py) * 181 / 256 98 | qy := (-px + py) * 181 / 256 99 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 100 | } 101 | 102 | // pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees. 103 | // 104 | // Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}. 105 | func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 { 106 | return fixed.Point26_6{p.Y, -p.X} 107 | } 108 | 109 | // pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees. 110 | // 111 | // Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}. 112 | func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 { 113 | // 181/256 is approximately 1/√2, or sin(π/4). 114 | px, py := int64(p.X), int64(p.Y) 115 | qx := (-px + py) * 181 / 256 116 | qy := (-px - py) * 181 / 256 117 | return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} 118 | } 119 | 120 | // An Adder accumulates points on a curve. 121 | type Adder interface { 122 | // Start starts a new curve at the given point. 123 | Start(a fixed.Point26_6) 124 | // Add1 adds a linear segment to the current curve. 125 | Add1(b fixed.Point26_6) 126 | // Add2 adds a quadratic segment to the current curve. 127 | Add2(b, c fixed.Point26_6) 128 | // Add3 adds a cubic segment to the current curve. 129 | Add3(b, c, d fixed.Point26_6) 130 | } 131 | 132 | // A Path is a sequence of curves, and a curve is a start point followed by a 133 | // sequence of linear, quadratic or cubic segments. 134 | type Path []fixed.Int26_6 135 | 136 | // String returns a human-readable representation of a Path. 137 | func (p Path) String() string { 138 | s := "" 139 | for i := 0; i < len(p); { 140 | if i != 0 { 141 | s += " " 142 | } 143 | switch p[i] { 144 | case 0: 145 | s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) 146 | i += 4 147 | case 1: 148 | s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3])) 149 | i += 4 150 | case 2: 151 | s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5])) 152 | i += 6 153 | case 3: 154 | s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7])) 155 | i += 8 156 | default: 157 | panic("freetype/raster: bad path") 158 | } 159 | } 160 | return s 161 | } 162 | 163 | // Clear cancels any previous calls to p.Start or p.AddXxx. 164 | func (p *Path) Clear() { 165 | *p = (*p)[:0] 166 | } 167 | 168 | // Start starts a new curve at the given point. 169 | func (p *Path) Start(a fixed.Point26_6) { 170 | *p = append(*p, 0, a.X, a.Y, 0) 171 | } 172 | 173 | // Add1 adds a linear segment to the current curve. 174 | func (p *Path) Add1(b fixed.Point26_6) { 175 | *p = append(*p, 1, b.X, b.Y, 1) 176 | } 177 | 178 | // Add2 adds a quadratic segment to the current curve. 179 | func (p *Path) Add2(b, c fixed.Point26_6) { 180 | *p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2) 181 | } 182 | 183 | // Add3 adds a cubic segment to the current curve. 184 | func (p *Path) Add3(b, c, d fixed.Point26_6) { 185 | *p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3) 186 | } 187 | 188 | // AddPath adds the Path q to p. 189 | func (p *Path) AddPath(q Path) { 190 | *p = append(*p, q...) 191 | } 192 | 193 | // AddStroke adds a stroked Path. 194 | func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) { 195 | Stroke(p, q, width, cr, jr) 196 | } 197 | 198 | // firstPoint returns the first point in a non-empty Path. 199 | func (p Path) firstPoint() fixed.Point26_6 { 200 | return fixed.Point26_6{p[1], p[2]} 201 | } 202 | 203 | // lastPoint returns the last point in a non-empty Path. 204 | func (p Path) lastPoint() fixed.Point26_6 { 205 | return fixed.Point26_6{p[len(p)-3], p[len(p)-2]} 206 | } 207 | 208 | // addPathReversed adds q reversed to p. 209 | // For example, if q consists of a linear segment from A to B followed by a 210 | // quadratic segment from B to C to D, then the values of q looks like: 211 | // index: 01234567890123 212 | // value: 0AA01BB12CCDD2 213 | // So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A). 214 | func addPathReversed(p Adder, q Path) { 215 | if len(q) == 0 { 216 | return 217 | } 218 | i := len(q) - 1 219 | for { 220 | switch q[i] { 221 | case 0: 222 | return 223 | case 1: 224 | i -= 4 225 | p.Add1( 226 | fixed.Point26_6{q[i-2], q[i-1]}, 227 | ) 228 | case 2: 229 | i -= 6 230 | p.Add2( 231 | fixed.Point26_6{q[i+2], q[i+3]}, 232 | fixed.Point26_6{q[i-2], q[i-1]}, 233 | ) 234 | case 3: 235 | i -= 8 236 | p.Add3( 237 | fixed.Point26_6{q[i+4], q[i+5]}, 238 | fixed.Point26_6{q[i+2], q[i+3]}, 239 | fixed.Point26_6{q[i-2], q[i-1]}, 240 | ) 241 | default: 242 | panic("freetype/raster: bad path") 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/raster/paint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package raster 7 | 8 | import ( 9 | "image" 10 | "image/color" 11 | "image/draw" 12 | "math" 13 | ) 14 | 15 | // A Span is a horizontal segment of pixels with constant alpha. X0 is an 16 | // inclusive bound and X1 is exclusive, the same as for slices. A fully opaque 17 | // Span has Alpha == 0xffff. 18 | type Span struct { 19 | Y, X0, X1 int 20 | Alpha uint32 21 | } 22 | 23 | // A Painter knows how to paint a batch of Spans. Rasterization may involve 24 | // Painting multiple batches, and done will be true for the final batch. The 25 | // Spans' Y values are monotonically increasing during a rasterization. Paint 26 | // may use all of ss as scratch space during the call. 27 | type Painter interface { 28 | Paint(ss []Span, done bool) 29 | } 30 | 31 | // The PainterFunc type adapts an ordinary function to the Painter interface. 32 | type PainterFunc func(ss []Span, done bool) 33 | 34 | // Paint just delegates the call to f. 35 | func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) } 36 | 37 | // An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using 38 | // the Over Porter-Duff composition operator. 39 | type AlphaOverPainter struct { 40 | Image *image.Alpha 41 | } 42 | 43 | // Paint satisfies the Painter interface. 44 | func (r AlphaOverPainter) Paint(ss []Span, done bool) { 45 | b := r.Image.Bounds() 46 | for _, s := range ss { 47 | if s.Y < b.Min.Y { 48 | continue 49 | } 50 | if s.Y >= b.Max.Y { 51 | return 52 | } 53 | if s.X0 < b.Min.X { 54 | s.X0 = b.Min.X 55 | } 56 | if s.X1 > b.Max.X { 57 | s.X1 = b.Max.X 58 | } 59 | if s.X0 >= s.X1 { 60 | continue 61 | } 62 | base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X 63 | p := r.Image.Pix[base+s.X0 : base+s.X1] 64 | a := int(s.Alpha >> 8) 65 | for i, c := range p { 66 | v := int(c) 67 | p[i] = uint8((v*255 + (255-v)*a) / 255) 68 | } 69 | } 70 | } 71 | 72 | // NewAlphaOverPainter creates a new AlphaOverPainter for the given image. 73 | func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter { 74 | return AlphaOverPainter{m} 75 | } 76 | 77 | // An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using 78 | // the Src Porter-Duff composition operator. 79 | type AlphaSrcPainter struct { 80 | Image *image.Alpha 81 | } 82 | 83 | // Paint satisfies the Painter interface. 84 | func (r AlphaSrcPainter) Paint(ss []Span, done bool) { 85 | b := r.Image.Bounds() 86 | for _, s := range ss { 87 | if s.Y < b.Min.Y { 88 | continue 89 | } 90 | if s.Y >= b.Max.Y { 91 | return 92 | } 93 | if s.X0 < b.Min.X { 94 | s.X0 = b.Min.X 95 | } 96 | if s.X1 > b.Max.X { 97 | s.X1 = b.Max.X 98 | } 99 | if s.X0 >= s.X1 { 100 | continue 101 | } 102 | base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X 103 | p := r.Image.Pix[base+s.X0 : base+s.X1] 104 | color := uint8(s.Alpha >> 8) 105 | for i := range p { 106 | p[i] = color 107 | } 108 | } 109 | } 110 | 111 | // NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image. 112 | func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter { 113 | return AlphaSrcPainter{m} 114 | } 115 | 116 | // An RGBAPainter is a Painter that paints Spans onto a *image.RGBA. 117 | type RGBAPainter struct { 118 | // Image is the image to compose onto. 119 | Image *image.RGBA 120 | // Op is the Porter-Duff composition operator. 121 | Op draw.Op 122 | // cr, cg, cb and ca are the 16-bit color to paint the spans. 123 | cr, cg, cb, ca uint32 124 | } 125 | 126 | // Paint satisfies the Painter interface. 127 | func (r *RGBAPainter) Paint(ss []Span, done bool) { 128 | b := r.Image.Bounds() 129 | for _, s := range ss { 130 | if s.Y < b.Min.Y { 131 | continue 132 | } 133 | if s.Y >= b.Max.Y { 134 | return 135 | } 136 | if s.X0 < b.Min.X { 137 | s.X0 = b.Min.X 138 | } 139 | if s.X1 > b.Max.X { 140 | s.X1 = b.Max.X 141 | } 142 | if s.X0 >= s.X1 { 143 | continue 144 | } 145 | // This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go. 146 | ma := s.Alpha 147 | const m = 1<<16 - 1 148 | i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4 149 | i1 := i0 + (s.X1-s.X0)*4 150 | if r.Op == draw.Over { 151 | for i := i0; i < i1; i += 4 { 152 | dr := uint32(r.Image.Pix[i+0]) 153 | dg := uint32(r.Image.Pix[i+1]) 154 | db := uint32(r.Image.Pix[i+2]) 155 | da := uint32(r.Image.Pix[i+3]) 156 | a := (m - (r.ca * ma / m)) * 0x101 157 | r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8) 158 | r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8) 159 | r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8) 160 | r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8) 161 | } 162 | } else { 163 | for i := i0; i < i1; i += 4 { 164 | r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8) 165 | r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8) 166 | r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8) 167 | r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8) 168 | } 169 | } 170 | } 171 | } 172 | 173 | // SetColor sets the color to paint the spans. 174 | func (r *RGBAPainter) SetColor(c color.Color) { 175 | r.cr, r.cg, r.cb, r.ca = c.RGBA() 176 | } 177 | 178 | // NewRGBAPainter creates a new RGBAPainter for the given image. 179 | func NewRGBAPainter(m *image.RGBA) *RGBAPainter { 180 | return &RGBAPainter{Image: m} 181 | } 182 | 183 | // A MonochromePainter wraps another Painter, quantizing each Span's alpha to 184 | // be either fully opaque or fully transparent. 185 | type MonochromePainter struct { 186 | Painter Painter 187 | y, x0, x1 int 188 | } 189 | 190 | // Paint delegates to the wrapped Painter after quantizing each Span's alpha 191 | // value and merging adjacent fully opaque Spans. 192 | func (m *MonochromePainter) Paint(ss []Span, done bool) { 193 | // We compact the ss slice, discarding any Spans whose alpha quantizes to zero. 194 | j := 0 195 | for _, s := range ss { 196 | if s.Alpha >= 0x8000 { 197 | if m.y == s.Y && m.x1 == s.X0 { 198 | m.x1 = s.X1 199 | } else { 200 | ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1} 201 | j++ 202 | m.y, m.x0, m.x1 = s.Y, s.X0, s.X1 203 | } 204 | } 205 | } 206 | if done { 207 | // Flush the accumulated Span. 208 | finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1} 209 | if j < len(ss) { 210 | ss[j] = finalSpan 211 | j++ 212 | m.Painter.Paint(ss[:j], true) 213 | } else if j == len(ss) { 214 | m.Painter.Paint(ss, false) 215 | if cap(ss) > 0 { 216 | ss = ss[:1] 217 | } else { 218 | ss = make([]Span, 1) 219 | } 220 | ss[0] = finalSpan 221 | m.Painter.Paint(ss, true) 222 | } else { 223 | panic("unreachable") 224 | } 225 | // Reset the accumulator, so that this Painter can be re-used. 226 | m.y, m.x0, m.x1 = 0, 0, 0 227 | } else { 228 | m.Painter.Paint(ss[:j], false) 229 | } 230 | } 231 | 232 | // NewMonochromePainter creates a new MonochromePainter that wraps the given 233 | // Painter. 234 | func NewMonochromePainter(p Painter) *MonochromePainter { 235 | return &MonochromePainter{Painter: p} 236 | } 237 | 238 | // A GammaCorrectionPainter wraps another Painter, performing gamma-correction 239 | // on each Span's alpha value. 240 | type GammaCorrectionPainter struct { 241 | // Painter is the wrapped Painter. 242 | Painter Painter 243 | // a is the precomputed alpha values for linear interpolation, with fully 244 | // opaque == 0xffff. 245 | a [256]uint16 246 | // gammaIsOne is whether gamma correction is a no-op. 247 | gammaIsOne bool 248 | } 249 | 250 | // Paint delegates to the wrapped Painter after performing gamma-correction on 251 | // each Span. 252 | func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) { 253 | if !g.gammaIsOne { 254 | const n = 0x101 255 | for i, s := range ss { 256 | if s.Alpha == 0 || s.Alpha == 0xffff { 257 | continue 258 | } 259 | p, q := s.Alpha/n, s.Alpha%n 260 | // The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1]. 261 | a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q 262 | ss[i].Alpha = (a + n/2) / n 263 | } 264 | } 265 | g.Painter.Paint(ss, done) 266 | } 267 | 268 | // SetGamma sets the gamma value. 269 | func (g *GammaCorrectionPainter) SetGamma(gamma float64) { 270 | g.gammaIsOne = gamma == 1 271 | if g.gammaIsOne { 272 | return 273 | } 274 | for i := 0; i < 256; i++ { 275 | a := float64(i) / 0xff 276 | a = math.Pow(a, gamma) 277 | g.a[i] = uint16(0xffff * a) 278 | } 279 | } 280 | 281 | // NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps 282 | // the given Painter. 283 | func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter { 284 | g := &GammaCorrectionPainter{Painter: p} 285 | g.SetGamma(gamma) 286 | return g 287 | } 288 | -------------------------------------------------------------------------------- /vendor/github.com/golang/freetype/truetype/opcodes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Freetype-Go Authors. All rights reserved. 2 | // Use of this source code is governed by your choice of either the 3 | // FreeType License or the GNU General Public License version 2 (or 4 | // any later version), both of which can be found in the LICENSE file. 5 | 6 | package truetype 7 | 8 | // The Truetype opcodes are summarized at 9 | // https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html 10 | 11 | const ( 12 | opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis 13 | opSVTCA1 = 0x01 // . 14 | opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis 15 | opSPVTCA1 = 0x03 // . 16 | opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis 17 | opSFVTCA1 = 0x05 // . 18 | opSPVTL0 = 0x06 // Set Projection Vector To Line 19 | opSPVTL1 = 0x07 // . 20 | opSFVTL0 = 0x08 // Set Freedom Vector To Line 21 | opSFVTL1 = 0x09 // . 22 | opSPVFS = 0x0a // Set Projection Vector From Stack 23 | opSFVFS = 0x0b // Set Freedom Vector From Stack 24 | opGPV = 0x0c // Get Projection Vector 25 | opGFV = 0x0d // Get Freedom Vector 26 | opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector 27 | opISECT = 0x0f // moves point p to the InterSECTion of two lines 28 | opSRP0 = 0x10 // Set Reference Point 0 29 | opSRP1 = 0x11 // Set Reference Point 1 30 | opSRP2 = 0x12 // Set Reference Point 2 31 | opSZP0 = 0x13 // Set Zone Pointer 0 32 | opSZP1 = 0x14 // Set Zone Pointer 1 33 | opSZP2 = 0x15 // Set Zone Pointer 2 34 | opSZPS = 0x16 // Set Zone PointerS 35 | opSLOOP = 0x17 // Set LOOP variable 36 | opRTG = 0x18 // Round To Grid 37 | opRTHG = 0x19 // Round To Half Grid 38 | opSMD = 0x1a // Set Minimum Distance 39 | opELSE = 0x1b // ELSE clause 40 | opJMPR = 0x1c // JuMP Relative 41 | opSCVTCI = 0x1d // Set Control Value Table Cut-In 42 | opSSWCI = 0x1e // Set Single Width Cut-In 43 | opSSW = 0x1f // Set Single Width 44 | opDUP = 0x20 // DUPlicate top stack element 45 | opPOP = 0x21 // POP top stack element 46 | opCLEAR = 0x22 // CLEAR the stack 47 | opSWAP = 0x23 // SWAP the top two elements on the stack 48 | opDEPTH = 0x24 // DEPTH of the stack 49 | opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack 50 | opMINDEX = 0x26 // Move the INDEXed element to the top of the stack 51 | opALIGNPTS = 0x27 // ALIGN PoinTS 52 | op_0x28 = 0x28 // deprecated 53 | opUTP = 0x29 // UnTouch Point 54 | opLOOPCALL = 0x2a // LOOP and CALL function 55 | opCALL = 0x2b // CALL function 56 | opFDEF = 0x2c // Function DEFinition 57 | opENDF = 0x2d // END Function definition 58 | opMDAP0 = 0x2e // Move Direct Absolute Point 59 | opMDAP1 = 0x2f // . 60 | opIUP0 = 0x30 // Interpolate Untouched Points through the outline 61 | opIUP1 = 0x31 // . 62 | opSHP0 = 0x32 // SHift Point using reference point 63 | opSHP1 = 0x33 // . 64 | opSHC0 = 0x34 // SHift Contour using reference point 65 | opSHC1 = 0x35 // . 66 | opSHZ0 = 0x36 // SHift Zone using reference point 67 | opSHZ1 = 0x37 // . 68 | opSHPIX = 0x38 // SHift point by a PIXel amount 69 | opIP = 0x39 // Interpolate Point 70 | opMSIRP0 = 0x3a // Move Stack Indirect Relative Point 71 | opMSIRP1 = 0x3b // . 72 | opALIGNRP = 0x3c // ALIGN to Reference Point 73 | opRTDG = 0x3d // Round To Double Grid 74 | opMIAP0 = 0x3e // Move Indirect Absolute Point 75 | opMIAP1 = 0x3f // . 76 | opNPUSHB = 0x40 // PUSH N Bytes 77 | opNPUSHW = 0x41 // PUSH N Words 78 | opWS = 0x42 // Write Store 79 | opRS = 0x43 // Read Store 80 | opWCVTP = 0x44 // Write Control Value Table in Pixel units 81 | opRCVT = 0x45 // Read Control Value Table entry 82 | opGC0 = 0x46 // Get Coordinate projected onto the projection vector 83 | opGC1 = 0x47 // . 84 | opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector 85 | opMD0 = 0x49 // Measure Distance 86 | opMD1 = 0x4a // . 87 | opMPPEM = 0x4b // Measure Pixels Per EM 88 | opMPS = 0x4c // Measure Point Size 89 | opFLIPON = 0x4d // set the auto FLIP Boolean to ON 90 | opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF 91 | opDEBUG = 0x4f // DEBUG call 92 | opLT = 0x50 // Less Than 93 | opLTEQ = 0x51 // Less Than or EQual 94 | opGT = 0x52 // Greater Than 95 | opGTEQ = 0x53 // Greater Than or EQual 96 | opEQ = 0x54 // EQual 97 | opNEQ = 0x55 // Not EQual 98 | opODD = 0x56 // ODD 99 | opEVEN = 0x57 // EVEN 100 | opIF = 0x58 // IF test 101 | opEIF = 0x59 // End IF 102 | opAND = 0x5a // logical AND 103 | opOR = 0x5b // logical OR 104 | opNOT = 0x5c // logical NOT 105 | opDELTAP1 = 0x5d // DELTA exception P1 106 | opSDB = 0x5e // Set Delta Base in the graphics state 107 | opSDS = 0x5f // Set Delta Shift in the graphics state 108 | opADD = 0x60 // ADD 109 | opSUB = 0x61 // SUBtract 110 | opDIV = 0x62 // DIVide 111 | opMUL = 0x63 // MULtiply 112 | opABS = 0x64 // ABSolute value 113 | opNEG = 0x65 // NEGate 114 | opFLOOR = 0x66 // FLOOR 115 | opCEILING = 0x67 // CEILING 116 | opROUND00 = 0x68 // ROUND value 117 | opROUND01 = 0x69 // . 118 | opROUND10 = 0x6a // . 119 | opROUND11 = 0x6b // . 120 | opNROUND00 = 0x6c // No ROUNDing of value 121 | opNROUND01 = 0x6d // . 122 | opNROUND10 = 0x6e // . 123 | opNROUND11 = 0x6f // . 124 | opWCVTF = 0x70 // Write Control Value Table in Funits 125 | opDELTAP2 = 0x71 // DELTA exception P2 126 | opDELTAP3 = 0x72 // DELTA exception P3 127 | opDELTAC1 = 0x73 // DELTA exception C1 128 | opDELTAC2 = 0x74 // DELTA exception C2 129 | opDELTAC3 = 0x75 // DELTA exception C3 130 | opSROUND = 0x76 // Super ROUND 131 | opS45ROUND = 0x77 // Super ROUND 45 degrees 132 | opJROT = 0x78 // Jump Relative On True 133 | opJROF = 0x79 // Jump Relative On False 134 | opROFF = 0x7a // Round OFF 135 | op_0x7b = 0x7b // deprecated 136 | opRUTG = 0x7c // Round Up To Grid 137 | opRDTG = 0x7d // Round Down To Grid 138 | opSANGW = 0x7e // Set ANGle Weight 139 | opAA = 0x7f // Adjust Angle 140 | opFLIPPT = 0x80 // FLIP PoinT 141 | opFLIPRGON = 0x81 // FLIP RanGe ON 142 | opFLIPRGOFF = 0x82 // FLIP RanGe OFF 143 | op_0x83 = 0x83 // deprecated 144 | op_0x84 = 0x84 // deprecated 145 | opSCANCTRL = 0x85 // SCAN conversion ConTRoL 146 | opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line 147 | opSDPVTL1 = 0x87 // . 148 | opGETINFO = 0x88 // GET INFOrmation 149 | opIDEF = 0x89 // Instruction DEFinition 150 | opROLL = 0x8a // ROLL the top three stack elements 151 | opMAX = 0x8b // MAXimum of top two stack elements 152 | opMIN = 0x8c // MINimum of top two stack elements 153 | opSCANTYPE = 0x8d // SCANTYPE 154 | opINSTCTRL = 0x8e // INSTRuction execution ConTRoL 155 | op_0x8f = 0x8f 156 | op_0x90 = 0x90 157 | op_0x91 = 0x91 158 | op_0x92 = 0x92 159 | op_0x93 = 0x93 160 | op_0x94 = 0x94 161 | op_0x95 = 0x95 162 | op_0x96 = 0x96 163 | op_0x97 = 0x97 164 | op_0x98 = 0x98 165 | op_0x99 = 0x99 166 | op_0x9a = 0x9a 167 | op_0x9b = 0x9b 168 | op_0x9c = 0x9c 169 | op_0x9d = 0x9d 170 | op_0x9e = 0x9e 171 | op_0x9f = 0x9f 172 | op_0xa0 = 0xa0 173 | op_0xa1 = 0xa1 174 | op_0xa2 = 0xa2 175 | op_0xa3 = 0xa3 176 | op_0xa4 = 0xa4 177 | op_0xa5 = 0xa5 178 | op_0xa6 = 0xa6 179 | op_0xa7 = 0xa7 180 | op_0xa8 = 0xa8 181 | op_0xa9 = 0xa9 182 | op_0xaa = 0xaa 183 | op_0xab = 0xab 184 | op_0xac = 0xac 185 | op_0xad = 0xad 186 | op_0xae = 0xae 187 | op_0xaf = 0xaf 188 | opPUSHB000 = 0xb0 // PUSH Bytes 189 | opPUSHB001 = 0xb1 // . 190 | opPUSHB010 = 0xb2 // . 191 | opPUSHB011 = 0xb3 // . 192 | opPUSHB100 = 0xb4 // . 193 | opPUSHB101 = 0xb5 // . 194 | opPUSHB110 = 0xb6 // . 195 | opPUSHB111 = 0xb7 // . 196 | opPUSHW000 = 0xb8 // PUSH Words 197 | opPUSHW001 = 0xb9 // . 198 | opPUSHW010 = 0xba // . 199 | opPUSHW011 = 0xbb // . 200 | opPUSHW100 = 0xbc // . 201 | opPUSHW101 = 0xbd // . 202 | opPUSHW110 = 0xbe // . 203 | opPUSHW111 = 0xbf // . 204 | opMDRP00000 = 0xc0 // Move Direct Relative Point 205 | opMDRP00001 = 0xc1 // . 206 | opMDRP00010 = 0xc2 // . 207 | opMDRP00011 = 0xc3 // . 208 | opMDRP00100 = 0xc4 // . 209 | opMDRP00101 = 0xc5 // . 210 | opMDRP00110 = 0xc6 // . 211 | opMDRP00111 = 0xc7 // . 212 | opMDRP01000 = 0xc8 // . 213 | opMDRP01001 = 0xc9 // . 214 | opMDRP01010 = 0xca // . 215 | opMDRP01011 = 0xcb // . 216 | opMDRP01100 = 0xcc // . 217 | opMDRP01101 = 0xcd // . 218 | opMDRP01110 = 0xce // . 219 | opMDRP01111 = 0xcf // . 220 | opMDRP10000 = 0xd0 // . 221 | opMDRP10001 = 0xd1 // . 222 | opMDRP10010 = 0xd2 // . 223 | opMDRP10011 = 0xd3 // . 224 | opMDRP10100 = 0xd4 // . 225 | opMDRP10101 = 0xd5 // . 226 | opMDRP10110 = 0xd6 // . 227 | opMDRP10111 = 0xd7 // . 228 | opMDRP11000 = 0xd8 // . 229 | opMDRP11001 = 0xd9 // . 230 | opMDRP11010 = 0xda // . 231 | opMDRP11011 = 0xdb // . 232 | opMDRP11100 = 0xdc // . 233 | opMDRP11101 = 0xdd // . 234 | opMDRP11110 = 0xde // . 235 | opMDRP11111 = 0xdf // . 236 | opMIRP00000 = 0xe0 // Move Indirect Relative Point 237 | opMIRP00001 = 0xe1 // . 238 | opMIRP00010 = 0xe2 // . 239 | opMIRP00011 = 0xe3 // . 240 | opMIRP00100 = 0xe4 // . 241 | opMIRP00101 = 0xe5 // . 242 | opMIRP00110 = 0xe6 // . 243 | opMIRP00111 = 0xe7 // . 244 | opMIRP01000 = 0xe8 // . 245 | opMIRP01001 = 0xe9 // . 246 | opMIRP01010 = 0xea // . 247 | opMIRP01011 = 0xeb // . 248 | opMIRP01100 = 0xec // . 249 | opMIRP01101 = 0xed // . 250 | opMIRP01110 = 0xee // . 251 | opMIRP01111 = 0xef // . 252 | opMIRP10000 = 0xf0 // . 253 | opMIRP10001 = 0xf1 // . 254 | opMIRP10010 = 0xf2 // . 255 | opMIRP10011 = 0xf3 // . 256 | opMIRP10100 = 0xf4 // . 257 | opMIRP10101 = 0xf5 // . 258 | opMIRP10110 = 0xf6 // . 259 | opMIRP10111 = 0xf7 // . 260 | opMIRP11000 = 0xf8 // . 261 | opMIRP11001 = 0xf9 // . 262 | opMIRP11010 = 0xfa // . 263 | opMIRP11011 = 0xfb // . 264 | opMIRP11100 = 0xfc // . 265 | opMIRP11101 = 0xfd // . 266 | opMIRP11110 = 0xfe // . 267 | opMIRP11111 = 0xff // . 268 | ) 269 | 270 | // popCount is the number of stack elements that each opcode pops. 271 | var popCount = [256]uint8{ 272 | // 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f 273 | 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f 274 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f 275 | 1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f 276 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f 277 | 0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f 278 | 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f 279 | 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f 280 | 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f 281 | 0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f 282 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f 283 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf 284 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf 285 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf 286 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf 287 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef 288 | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff 289 | } 290 | -------------------------------------------------------------------------------- /vendor/golang.org/x/image/AUTHORS: -------------------------------------------------------------------------------- 1 | # This source code refers to The Go Authors for copyright purposes. 2 | # The master list of authors is in the main Go distribution, 3 | # visible at http://tip.golang.org/AUTHORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/image/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This source code was written by the Go contributors. 2 | # The master list of contributors is in the main Go distribution, 3 | # visible at http://tip.golang.org/CONTRIBUTORS. 4 | -------------------------------------------------------------------------------- /vendor/golang.org/x/image/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/image/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/image/math/fixed/fixed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fixed implements fixed-point integer types. 6 | package fixed // import "golang.org/x/image/math/fixed" 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | // TODO: implement fmt.Formatter for %f and %g. 13 | 14 | // I returns the integer value i as an Int26_6. 15 | // 16 | // For example, passing the integer value 2 yields Int26_6(128). 17 | func I(i int) Int26_6 { 18 | return Int26_6(i << 6) 19 | } 20 | 21 | // Int26_6 is a signed 26.6 fixed-point number. 22 | // 23 | // The integer part ranges from -33554432 to 33554431, inclusive. The 24 | // fractional part has 6 bits of precision. 25 | // 26 | // For example, the number one-and-a-quarter is Int26_6(1<<6 + 1<<4). 27 | type Int26_6 int32 28 | 29 | // String returns a human-readable representation of a 26.6 fixed-point number. 30 | // 31 | // For example, the number one-and-a-quarter becomes "1:16". 32 | func (x Int26_6) String() string { 33 | const shift, mask = 6, 1<<6 - 1 34 | if x >= 0 { 35 | return fmt.Sprintf("%d:%02d", int32(x>>shift), int32(x&mask)) 36 | } 37 | x = -x 38 | if x >= 0 { 39 | return fmt.Sprintf("-%d:%02d", int32(x>>shift), int32(x&mask)) 40 | } 41 | return "-33554432:00" // The minimum value is -(1<<25). 42 | } 43 | 44 | // Floor returns the greatest integer value less than or equal to x. 45 | // 46 | // Its return type is int, not Int26_6. 47 | func (x Int26_6) Floor() int { return int((x + 0x00) >> 6) } 48 | 49 | // Round returns the nearest integer value to x. Ties are rounded up. 50 | // 51 | // Its return type is int, not Int26_6. 52 | func (x Int26_6) Round() int { return int((x + 0x20) >> 6) } 53 | 54 | // Ceil returns the least integer value greater than or equal to x. 55 | // 56 | // Its return type is int, not Int26_6. 57 | func (x Int26_6) Ceil() int { return int((x + 0x3f) >> 6) } 58 | 59 | // Mul returns x*y in 26.6 fixed-point arithmetic. 60 | func (x Int26_6) Mul(y Int26_6) Int26_6 { 61 | return Int26_6((int64(x)*int64(y) + 1<<5) >> 6) 62 | } 63 | 64 | // Int52_12 is a signed 52.12 fixed-point number. 65 | // 66 | // The integer part ranges from -2251799813685248 to 2251799813685247, 67 | // inclusive. The fractional part has 12 bits of precision. 68 | // 69 | // For example, the number one-and-a-quarter is Int52_12(1<<12 + 1<<10). 70 | type Int52_12 int64 71 | 72 | // String returns a human-readable representation of a 52.12 fixed-point 73 | // number. 74 | // 75 | // For example, the number one-and-a-quarter becomes "1:1024". 76 | func (x Int52_12) String() string { 77 | const shift, mask = 12, 1<<12 - 1 78 | if x >= 0 { 79 | return fmt.Sprintf("%d:%04d", int64(x>>shift), int64(x&mask)) 80 | } 81 | x = -x 82 | if x >= 0 { 83 | return fmt.Sprintf("-%d:%04d", int64(x>>shift), int64(x&mask)) 84 | } 85 | return "-2251799813685248:0000" // The minimum value is -(1<<51). 86 | } 87 | 88 | // Floor returns the greatest integer value less than or equal to x. 89 | // 90 | // Its return type is int, not Int52_12. 91 | func (x Int52_12) Floor() int { return int((x + 0x000) >> 12) } 92 | 93 | // Round returns the nearest integer value to x. Ties are rounded up. 94 | // 95 | // Its return type is int, not Int52_12. 96 | func (x Int52_12) Round() int { return int((x + 0x800) >> 12) } 97 | 98 | // Ceil returns the least integer value greater than or equal to x. 99 | // 100 | // Its return type is int, not Int52_12. 101 | func (x Int52_12) Ceil() int { return int((x + 0xfff) >> 12) } 102 | 103 | // Mul returns x*y in 52.12 fixed-point arithmetic. 104 | func (x Int52_12) Mul(y Int52_12) Int52_12 { 105 | const M, N = 52, 12 106 | lo, hi := muli64(int64(x), int64(y)) 107 | ret := Int52_12(hi<>N) 108 | ret += Int52_12((lo >> (N - 1)) & 1) // Round to nearest, instead of rounding down. 109 | return ret 110 | } 111 | 112 | // muli64 multiplies two int64 values, returning the 128-bit signed integer 113 | // result as two uint64 values. 114 | // 115 | // This implementation is similar to $GOROOT/src/runtime/softfloat64.go's mullu 116 | // function, which is in turn adapted from Hacker's Delight. 117 | func muli64(u, v int64) (lo, hi uint64) { 118 | const ( 119 | s = 32 120 | mask = 1<> s) 124 | u0 := uint64(u & mask) 125 | v1 := uint64(v >> s) 126 | v0 := uint64(v & mask) 127 | 128 | w0 := u0 * v0 129 | t := u1*v0 + w0>>s 130 | w1 := t & mask 131 | w2 := uint64(int64(t) >> s) 132 | w1 += u0 * v1 133 | return uint64(u) * uint64(v), u1*v1 + w2 + uint64(int64(w1)>>s) 134 | } 135 | 136 | // P returns the integer values x and y as a Point26_6. 137 | // 138 | // For example, passing the integer values (2, -3) yields Point26_6{128, -192}. 139 | func P(x, y int) Point26_6 { 140 | return Point26_6{Int26_6(x << 6), Int26_6(y << 6)} 141 | } 142 | 143 | // Point26_6 is a 26.6 fixed-point coordinate pair. 144 | // 145 | // It is analogous to the image.Point type in the standard library. 146 | type Point26_6 struct { 147 | X, Y Int26_6 148 | } 149 | 150 | // Add returns the vector p+q. 151 | func (p Point26_6) Add(q Point26_6) Point26_6 { 152 | return Point26_6{p.X + q.X, p.Y + q.Y} 153 | } 154 | 155 | // Sub returns the vector p-q. 156 | func (p Point26_6) Sub(q Point26_6) Point26_6 { 157 | return Point26_6{p.X - q.X, p.Y - q.Y} 158 | } 159 | 160 | // Mul returns the vector p*k. 161 | func (p Point26_6) Mul(k Int26_6) Point26_6 { 162 | return Point26_6{p.X * k / 64, p.Y * k / 64} 163 | } 164 | 165 | // Div returns the vector p/k. 166 | func (p Point26_6) Div(k Int26_6) Point26_6 { 167 | return Point26_6{p.X * 64 / k, p.Y * 64 / k} 168 | } 169 | 170 | // In returns whether p is in r. 171 | func (p Point26_6) In(r Rectangle26_6) bool { 172 | return r.Min.X <= p.X && p.X < r.Max.X && r.Min.Y <= p.Y && p.Y < r.Max.Y 173 | } 174 | 175 | // Point52_12 is a 52.12 fixed-point coordinate pair. 176 | // 177 | // It is analogous to the image.Point type in the standard library. 178 | type Point52_12 struct { 179 | X, Y Int52_12 180 | } 181 | 182 | // Add returns the vector p+q. 183 | func (p Point52_12) Add(q Point52_12) Point52_12 { 184 | return Point52_12{p.X + q.X, p.Y + q.Y} 185 | } 186 | 187 | // Sub returns the vector p-q. 188 | func (p Point52_12) Sub(q Point52_12) Point52_12 { 189 | return Point52_12{p.X - q.X, p.Y - q.Y} 190 | } 191 | 192 | // Mul returns the vector p*k. 193 | func (p Point52_12) Mul(k Int52_12) Point52_12 { 194 | return Point52_12{p.X * k / 4096, p.Y * k / 4096} 195 | } 196 | 197 | // Div returns the vector p/k. 198 | func (p Point52_12) Div(k Int52_12) Point52_12 { 199 | return Point52_12{p.X * 4096 / k, p.Y * 4096 / k} 200 | } 201 | 202 | // In returns whether p is in r. 203 | func (p Point52_12) In(r Rectangle52_12) bool { 204 | return r.Min.X <= p.X && p.X < r.Max.X && r.Min.Y <= p.Y && p.Y < r.Max.Y 205 | } 206 | 207 | // R returns the integer values minX, minY, maxX, maxY as a Rectangle26_6. 208 | // 209 | // For example, passing the integer values (0, 1, 2, 3) yields 210 | // Rectangle26_6{Point26_6{0, 64}, Point26_6{128, 192}}. 211 | // 212 | // Like the image.Rect function in the standard library, the returned rectangle 213 | // has minimum and maximum coordinates swapped if necessary so that it is 214 | // well-formed. 215 | func R(minX, minY, maxX, maxY int) Rectangle26_6 { 216 | if minX > maxX { 217 | minX, maxX = maxX, minX 218 | } 219 | if minY > maxY { 220 | minY, maxY = maxY, minY 221 | } 222 | return Rectangle26_6{ 223 | Point26_6{ 224 | Int26_6(minX << 6), 225 | Int26_6(minY << 6), 226 | }, 227 | Point26_6{ 228 | Int26_6(maxX << 6), 229 | Int26_6(maxY << 6), 230 | }, 231 | } 232 | } 233 | 234 | // Rectangle26_6 is a 26.6 fixed-point coordinate rectangle. The Min bound is 235 | // inclusive and the Max bound is exclusive. It is well-formed if Min.X <= 236 | // Max.X and likewise for Y. 237 | // 238 | // It is analogous to the image.Rectangle type in the standard library. 239 | type Rectangle26_6 struct { 240 | Min, Max Point26_6 241 | } 242 | 243 | // Add returns the rectangle r translated by p. 244 | func (r Rectangle26_6) Add(p Point26_6) Rectangle26_6 { 245 | return Rectangle26_6{ 246 | Point26_6{r.Min.X + p.X, r.Min.Y + p.Y}, 247 | Point26_6{r.Max.X + p.X, r.Max.Y + p.Y}, 248 | } 249 | } 250 | 251 | // Sub returns the rectangle r translated by -p. 252 | func (r Rectangle26_6) Sub(p Point26_6) Rectangle26_6 { 253 | return Rectangle26_6{ 254 | Point26_6{r.Min.X - p.X, r.Min.Y - p.Y}, 255 | Point26_6{r.Max.X - p.X, r.Max.Y - p.Y}, 256 | } 257 | } 258 | 259 | // Intersect returns the largest rectangle contained by both r and s. If the 260 | // two rectangles do not overlap then the zero rectangle will be returned. 261 | func (r Rectangle26_6) Intersect(s Rectangle26_6) Rectangle26_6 { 262 | if r.Min.X < s.Min.X { 263 | r.Min.X = s.Min.X 264 | } 265 | if r.Min.Y < s.Min.Y { 266 | r.Min.Y = s.Min.Y 267 | } 268 | if r.Max.X > s.Max.X { 269 | r.Max.X = s.Max.X 270 | } 271 | if r.Max.Y > s.Max.Y { 272 | r.Max.Y = s.Max.Y 273 | } 274 | // Letting r0 and s0 be the values of r and s at the time that the method 275 | // is called, this next line is equivalent to: 276 | // 277 | // if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc } 278 | if r.Empty() { 279 | return Rectangle26_6{} 280 | } 281 | return r 282 | } 283 | 284 | // Union returns the smallest rectangle that contains both r and s. 285 | func (r Rectangle26_6) Union(s Rectangle26_6) Rectangle26_6 { 286 | if r.Empty() { 287 | return s 288 | } 289 | if s.Empty() { 290 | return r 291 | } 292 | if r.Min.X > s.Min.X { 293 | r.Min.X = s.Min.X 294 | } 295 | if r.Min.Y > s.Min.Y { 296 | r.Min.Y = s.Min.Y 297 | } 298 | if r.Max.X < s.Max.X { 299 | r.Max.X = s.Max.X 300 | } 301 | if r.Max.Y < s.Max.Y { 302 | r.Max.Y = s.Max.Y 303 | } 304 | return r 305 | } 306 | 307 | // Empty returns whether the rectangle contains no points. 308 | func (r Rectangle26_6) Empty() bool { 309 | return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y 310 | } 311 | 312 | // In returns whether every point in r is in s. 313 | func (r Rectangle26_6) In(s Rectangle26_6) bool { 314 | if r.Empty() { 315 | return true 316 | } 317 | // Note that r.Max is an exclusive bound for r, so that r.In(s) 318 | // does not require that r.Max.In(s). 319 | return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X && 320 | s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y 321 | } 322 | 323 | // Rectangle52_12 is a 52.12 fixed-point coordinate rectangle. The Min bound is 324 | // inclusive and the Max bound is exclusive. It is well-formed if Min.X <= 325 | // Max.X and likewise for Y. 326 | // 327 | // It is analogous to the image.Rectangle type in the standard library. 328 | type Rectangle52_12 struct { 329 | Min, Max Point52_12 330 | } 331 | 332 | // Add returns the rectangle r translated by p. 333 | func (r Rectangle52_12) Add(p Point52_12) Rectangle52_12 { 334 | return Rectangle52_12{ 335 | Point52_12{r.Min.X + p.X, r.Min.Y + p.Y}, 336 | Point52_12{r.Max.X + p.X, r.Max.Y + p.Y}, 337 | } 338 | } 339 | 340 | // Sub returns the rectangle r translated by -p. 341 | func (r Rectangle52_12) Sub(p Point52_12) Rectangle52_12 { 342 | return Rectangle52_12{ 343 | Point52_12{r.Min.X - p.X, r.Min.Y - p.Y}, 344 | Point52_12{r.Max.X - p.X, r.Max.Y - p.Y}, 345 | } 346 | } 347 | 348 | // Intersect returns the largest rectangle contained by both r and s. If the 349 | // two rectangles do not overlap then the zero rectangle will be returned. 350 | func (r Rectangle52_12) Intersect(s Rectangle52_12) Rectangle52_12 { 351 | if r.Min.X < s.Min.X { 352 | r.Min.X = s.Min.X 353 | } 354 | if r.Min.Y < s.Min.Y { 355 | r.Min.Y = s.Min.Y 356 | } 357 | if r.Max.X > s.Max.X { 358 | r.Max.X = s.Max.X 359 | } 360 | if r.Max.Y > s.Max.Y { 361 | r.Max.Y = s.Max.Y 362 | } 363 | // Letting r0 and s0 be the values of r and s at the time that the method 364 | // is called, this next line is equivalent to: 365 | // 366 | // if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc } 367 | if r.Empty() { 368 | return Rectangle52_12{} 369 | } 370 | return r 371 | } 372 | 373 | // Union returns the smallest rectangle that contains both r and s. 374 | func (r Rectangle52_12) Union(s Rectangle52_12) Rectangle52_12 { 375 | if r.Empty() { 376 | return s 377 | } 378 | if s.Empty() { 379 | return r 380 | } 381 | if r.Min.X > s.Min.X { 382 | r.Min.X = s.Min.X 383 | } 384 | if r.Min.Y > s.Min.Y { 385 | r.Min.Y = s.Min.Y 386 | } 387 | if r.Max.X < s.Max.X { 388 | r.Max.X = s.Max.X 389 | } 390 | if r.Max.Y < s.Max.Y { 391 | r.Max.Y = s.Max.Y 392 | } 393 | return r 394 | } 395 | 396 | // Empty returns whether the rectangle contains no points. 397 | func (r Rectangle52_12) Empty() bool { 398 | return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y 399 | } 400 | 401 | // In returns whether every point in r is in s. 402 | func (r Rectangle52_12) In(s Rectangle52_12) bool { 403 | if r.Empty() { 404 | return true 405 | } 406 | // Note that r.Max is an exclusive bound for r, so that r.In(s) 407 | // does not require that r.Max.In(s). 408 | return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X && 409 | s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y 410 | } 411 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/as/etch v0.6.7 2 | ## explicit 3 | github.com/as/etch 4 | # github.com/as/font v0.6.7 5 | ## explicit 6 | github.com/as/font 7 | github.com/as/font/gomedium 8 | github.com/as/font/gomono 9 | github.com/as/font/goregular 10 | # github.com/as/io v0.1.0 11 | ## explicit 12 | github.com/as/io/spaz 13 | # github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 14 | ## explicit 15 | github.com/golang/freetype/raster 16 | github.com/golang/freetype/truetype 17 | # golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 18 | ## explicit 19 | # golang.org/x/image v0.0.0-20190802002840-cff245a6509b 20 | ## explicit 21 | golang.org/x/image/font 22 | golang.org/x/image/font/gofont/gomedium 23 | golang.org/x/image/font/gofont/gomono 24 | golang.org/x/image/font/gofont/goregular 25 | golang.org/x/image/math/fixed 26 | # golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007 27 | ## explicit 28 | -------------------------------------------------------------------------------- /wrap.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/as/frame/box" 7 | ) 8 | 9 | // wrapMax returns the point where b should go on a plane 10 | // if b doesn't fit entirely on the plane at pt, wrapMax 11 | // returns a pt on the next line 12 | func (f *Frame) wrapMax(pt image.Point, b *box.Box) image.Point { 13 | width := b.Width 14 | if b.Nrune < 0 { 15 | width = b.Minwidth 16 | } 17 | if width > f.r.Max.X-pt.X { 18 | return f.wrap(pt) 19 | } 20 | return pt 21 | } 22 | 23 | // wrapMin is like wrapMax, except it lazily wraps lines if 24 | // no chars in the box fit on the plane at pt. 25 | func (f *Frame) wrapMin(pt image.Point, b *box.Box) image.Point { 26 | if f.fits(pt, b) == 0 { 27 | return f.wrap(pt) 28 | } 29 | return pt 30 | } 31 | 32 | func (f *Frame) wrap(pt image.Point) image.Point { 33 | pt.X = f.r.Min.X 34 | pt.Y += f.Face.Dy() 35 | return pt 36 | } 37 | 38 | func (f *Frame) advance(pt image.Point, b *box.Box) (x image.Point) { 39 | if b.Nrune < 0 && b.Break() == '\n' { 40 | pt = f.wrap(pt) 41 | } else { 42 | pt.X += b.Width 43 | } 44 | return pt 45 | } 46 | 47 | // fits returns the number of runes that can fit on the line at pt. A newline yields 1. 48 | func (f *Frame) fits(pt image.Point, b *box.Box) (nr int) { 49 | left := f.r.Max.X - pt.X 50 | if b.Nrune < 0 { 51 | if b.Minwidth <= left { 52 | return 1 53 | } 54 | return 0 55 | } 56 | if left >= b.Width { 57 | return b.Nrune 58 | } 59 | return f.Face.Fits(b.Ptr, left) 60 | } 61 | func (f *Frame) plot(pt image.Point, b *box.Box) int { 62 | b.Width = f.project(pt, b) 63 | return b.Width 64 | } 65 | func (f *Frame) project(pt image.Point, b *box.Box) int { 66 | c := f.r.Max.X 67 | x := pt.X 68 | if b.Nrune >= 0 || b.Break() != '\t' { // 69 | return b.Width 70 | } 71 | if f.elastic() && b.Break() == '\t' { 72 | return b.Minwidth 73 | } 74 | if x+b.Minwidth > c { 75 | pt.X = f.r.Min.X 76 | x = pt.X 77 | } 78 | x += f.maxtab 79 | x -= (x - f.r.Min.X) % f.maxtab 80 | if x-pt.X < b.Minwidth || x > c { 81 | x = pt.X + b.Minwidth 82 | } 83 | return x - pt.X 84 | } 85 | -------------------------------------------------------------------------------- /write.go: -------------------------------------------------------------------------------- 1 | package frame 2 | 3 | import "io" 4 | 5 | // Write implements io.Writer. The write operation appends to the current 6 | // selection given by Dot(). It returns io.EOF when the entire message doesn't 7 | // fit on the frame. 8 | func (f *Frame) Write(p []byte) (n int, err error) { 9 | _, p1 := f.Dot() 10 | if n = f.Insert(p, p1); n == 0 { 11 | return 0, io.EOF 12 | } 13 | return n, nil 14 | } 15 | --------------------------------------------------------------------------------