Projects STRLCPY termdash Commits 084c5ad2
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    canvas/braille/testbraille/testbraille.go
     1 +// Copyright 2018 Google Inc.
     2 +//
     3 +// Licensed under the Apache License, Version 2.0 (the "License");
     4 +// you may not use this file except in compliance with the License.
     5 +// You may obtain a copy of the License at
     6 +//
     7 +// http://www.apache.org/licenses/LICENSE-2.0
     8 +//
     9 +// Unless required by applicable law or agreed to in writing, software
     10 +// distributed under the License is distributed on an "AS IS" BASIS,
     11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 +// See the License for the specific language governing permissions and
     13 +// limitations under the License.
     14 + 
     15 +// Package testbraille provides helpers for tests that use the braille package.
     16 +package testbraille
     17 + 
     18 +import (
     19 + "fmt"
     20 + "image"
     21 + 
     22 + "github.com/mum4k/termdash/canvas/braille"
     23 + "github.com/mum4k/termdash/cell"
     24 + "github.com/mum4k/termdash/terminal/faketerm"
     25 +)
     26 + 
     27 +// MustNew returns a new canvas or panics.
     28 +func MustNew(area image.Rectangle) *braille.Canvas {
     29 + cvs, err := braille.New(area)
     30 + if err != nil {
     31 + panic(fmt.Sprintf("braille.New => unexpected error: %v", err))
     32 + }
     33 + return cvs
     34 +}
     35 + 
     36 +// MustApply applies the canvas on the terminal or panics.
     37 +func MustApply(bc *braille.Canvas, t *faketerm.Terminal) {
     38 + if err := bc.Apply(t); err != nil {
     39 + panic(fmt.Sprintf("braille.Apply => unexpected error: %v", err))
     40 + }
     41 +}
     42 + 
     43 +// MustSetPixel sets the specified pixel or panics.
     44 +func MustSetPixel(bc *braille.Canvas, p image.Point, opts ...cell.Option) {
     45 + if err := bc.SetPixel(p, opts...); err != nil {
     46 + panic(fmt.Sprintf("braille.SetPixel => unexpected error: %v", err))
     47 + }
     48 +}
     49 + 
  • ■ ■ ■ ■ ■ ■
    draw/braille_line.go
     1 +// Copyright 2018 Google Inc.
     2 +//
     3 +// Licensed under the Apache License, Version 2.0 (the "License");
     4 +// you may not use this file except in compliance with the License.
     5 +// You may obtain a copy of the License at
     6 +//
     7 +// http://www.apache.org/licenses/LICENSE-2.0
     8 +//
     9 +// Unless required by applicable law or agreed to in writing, software
     10 +// distributed under the License is distributed on an "AS IS" BASIS,
     11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 +// See the License for the specific language governing permissions and
     13 +// limitations under the License.
     14 + 
     15 +package draw
     16 + 
     17 +// braille_line.go contains code that draws lines on a braille canvas.
     18 + 
     19 +import (
     20 + "fmt"
     21 + "image"
     22 + 
     23 + "github.com/mum4k/termdash/canvas/braille"
     24 + "github.com/mum4k/termdash/cell"
     25 +)
     26 + 
     27 +// BrailleLineOption is used to provide options to BrailleLine().
     28 +type BrailleLineOption interface {
     29 + // set sets the provided option.
     30 + set(*brailleLineOptions)
     31 +}
     32 + 
     33 +// brailleLineOptions stores the provided options.
     34 +type brailleLineOptions struct {
     35 + cellOpts []cell.Option
     36 +}
     37 + 
     38 +// newBrailleLineOptions returns a new brailleLineOptions instance.
     39 +func newBrailleLineOptions() *brailleLineOptions {
     40 + return &brailleLineOptions{}
     41 +}
     42 + 
     43 +// brailleLineOption implements BrailleLineOption.
     44 +type brailleLineOption func(*brailleLineOptions)
     45 + 
     46 +// set implements BrailleLineOption.set.
     47 +func (o brailleLineOption) set(opts *brailleLineOptions) {
     48 + o(opts)
     49 +}
     50 + 
     51 +// BrailleLineCellOpts sets options on the cells that contain the line.
     52 +func BrailleLineCellOpts(cOpts ...cell.Option) BrailleLineOption {
     53 + return brailleLineOption(func(opts *brailleLineOptions) {
     54 + opts.cellOpts = cOpts
     55 + })
     56 +}
     57 + 
     58 +// BrailleLine draws an approximated line segment on the braille canvas between
     59 +// the two provided points.
     60 +// Both start and end must be valid points within the canvas. Start and end can
     61 +// be the same point in which case only one pixel will be set on the braille
     62 +// canvas.
     63 +// The start or end coordinates must not be negative.
     64 +func BrailleLine(bc *braille.Canvas, start, end image.Point, opts ...BrailleLineOption) error {
     65 + if start.X < 0 || start.Y < 0 {
     66 + return fmt.Errorf("the start coordinates cannot be negative, got: %v", start)
     67 + }
     68 + if end.X < 0 || end.Y < 0 {
     69 + return fmt.Errorf("the end coordinates cannot be negative, got: %v", end)
     70 + }
     71 + 
     72 + opt := newBrailleLineOptions()
     73 + for _, o := range opts {
     74 + o.set(opt)
     75 + }
     76 + 
     77 + // Implements Bresenham's line algorithm.
     78 + // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
     79 + 
     80 + vertProj := abs(end.Y - start.Y)
     81 + horizProj := abs(end.X - start.X)
     82 + if vertProj < horizProj {
     83 + if start.X > end.X {
     84 + return lineLow(bc, end.X, end.Y, start.X, start.Y, opt)
     85 + } else {
     86 + return lineLow(bc, start.X, start.Y, end.X, end.Y, opt)
     87 + }
     88 + } else {
     89 + if start.Y > end.Y {
     90 + return lineHigh(bc, end.X, end.Y, start.X, start.Y, opt)
     91 + } else {
     92 + return lineHigh(bc, start.X, start.Y, end.X, end.Y, opt)
     93 + }
     94 + }
     95 +}
     96 + 
     97 +// lineLow draws a line whose horizontal projection (end.X - start.X) is longer
     98 +// than its vertical projection (end.Y - start.Y).
     99 +func lineLow(bc *braille.Canvas, x0, y0, x1, y1 int, opt *brailleLineOptions) error {
     100 + deltaX := x1 - x0
     101 + deltaY := y1 - y0
     102 + 
     103 + stepY := 1
     104 + if deltaY < 0 {
     105 + stepY = -1
     106 + deltaY = -deltaY
     107 + }
     108 + 
     109 + diff := 2*deltaY - deltaX
     110 + y := y0
     111 + for x := x0; x <= x1; x++ {
     112 + p := image.Point{x, y}
     113 + if err := bc.SetPixel(p, opt.cellOpts...); err != nil {
     114 + return fmt.Errorf("lineLow bc.SetPixel(%v) => %v", p, err)
     115 + }
     116 + 
     117 + if diff > 0 {
     118 + y += stepY
     119 + diff -= 2 * deltaX
     120 + }
     121 + diff += 2 * deltaY
     122 + }
     123 + return nil
     124 +}
     125 + 
     126 +// lineHigh draws a line whose vertical projection (end.Y - start.Y) is longer
     127 +// than its horizontal projection (end.X - start.X).
     128 +func lineHigh(bc *braille.Canvas, x0, y0, x1, y1 int, opt *brailleLineOptions) error {
     129 + deltaX := x1 - x0
     130 + deltaY := y1 - y0
     131 + 
     132 + stepX := 1
     133 + if deltaX < 0 {
     134 + stepX = -1
     135 + deltaX = -deltaX
     136 + }
     137 + 
     138 + diff := 2*deltaX - deltaY
     139 + x := x0
     140 + for y := y0; y <= y1; y++ {
     141 + p := image.Point{x, y}
     142 + if err := bc.SetPixel(p, opt.cellOpts...); err != nil {
     143 + return fmt.Errorf("lineHigh bc.SetPixel(%v) => %v", p, err)
     144 + }
     145 + 
     146 + if diff > 0 {
     147 + x += stepX
     148 + diff -= 2 * deltaY
     149 + }
     150 + diff += 2 * deltaX
     151 + }
     152 + return nil
     153 +}
     154 + 
     155 +// abs returns the absolute value of x.
     156 +func abs(x int) int {
     157 + if x < 0 {
     158 + return -x
     159 + }
     160 + return x
     161 +}
     162 + 
  • ■ ■ ■ ■ ■ ■
    draw/braille_line_test.go
     1 +// Copyright 2018 Google Inc.
     2 +//
     3 +// Licensed under the Apache License, Version 2.0 (the "License");
     4 +// you may not use this file except in compliance with the License.
     5 +// You may obtain a copy of the License at
     6 +//
     7 +// http://www.apache.org/licenses/LICENSE-2.0
     8 +//
     9 +// Unless required by applicable law or agreed to in writing, software
     10 +// distributed under the License is distributed on an "AS IS" BASIS,
     11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 +// See the License for the specific language governing permissions and
     13 +// limitations under the License.
     14 + 
     15 +package draw
     16 + 
     17 +import (
     18 + "image"
     19 + "testing"
     20 + 
     21 + "github.com/mum4k/termdash/area"
     22 + "github.com/mum4k/termdash/canvas/braille"
     23 + "github.com/mum4k/termdash/canvas/braille/testbraille"
     24 + "github.com/mum4k/termdash/cell"
     25 + "github.com/mum4k/termdash/terminal/faketerm"
     26 +)
     27 + 
     28 +func TestBrailleLine(t *testing.T) {
     29 + tests := []struct {
     30 + desc string
     31 + canvas image.Rectangle
     32 + start image.Point
     33 + end image.Point
     34 + opts []BrailleLineOption
     35 + want func(size image.Point) *faketerm.Terminal
     36 + wantErr bool
     37 + }{
     38 + {
     39 + desc: "fails when start has negative X",
     40 + canvas: image.Rect(0, 0, 1, 1),
     41 + start: image.Point{-1, 0},
     42 + end: image.Point{0, 0},
     43 + wantErr: true,
     44 + },
     45 + {
     46 + desc: "fails when start has negative Y",
     47 + canvas: image.Rect(0, 0, 1, 1),
     48 + start: image.Point{0, -1},
     49 + end: image.Point{0, 0},
     50 + wantErr: true,
     51 + },
     52 + {
     53 + desc: "fails when end has negative X",
     54 + canvas: image.Rect(0, 0, 1, 1),
     55 + start: image.Point{0, 0},
     56 + end: image.Point{-1, 0},
     57 + wantErr: true,
     58 + },
     59 + {
     60 + desc: "fails when end has negative Y",
     61 + canvas: image.Rect(0, 0, 1, 1),
     62 + start: image.Point{0, 0},
     63 + end: image.Point{0, -1},
     64 + wantErr: true,
     65 + },
     66 + {
     67 + desc: "high line, fails on start point outside of the canvas",
     68 + canvas: image.Rect(0, 0, 1, 1),
     69 + start: image.Point{2, 2},
     70 + end: image.Point{2, 2},
     71 + wantErr: true,
     72 + },
     73 + {
     74 + desc: "low line, fails on end point outside of the canvas",
     75 + canvas: image.Rect(0, 0, 3, 1),
     76 + start: image.Point{0, 0},
     77 + end: image.Point{6, 3},
     78 + wantErr: true,
     79 + },
     80 + {
     81 + desc: "high line, fails on end point outside of the canvas",
     82 + canvas: image.Rect(0, 0, 1, 1),
     83 + start: image.Point{0, 0},
     84 + end: image.Point{2, 2},
     85 + wantErr: true,
     86 + },
     87 + {
     88 + desc: "draws single point",
     89 + canvas: image.Rect(0, 0, 1, 1),
     90 + start: image.Point{0, 0},
     91 + end: image.Point{0, 0},
     92 + want: func(size image.Point) *faketerm.Terminal {
     93 + ft := faketerm.MustNew(size)
     94 + bc := testbraille.MustNew(ft.Area())
     95 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     96 + testbraille.MustApply(bc, ft)
     97 + return ft
     98 + },
     99 + },
     100 + {
     101 + desc: "draws single point with cell options",
     102 + canvas: image.Rect(0, 0, 1, 1),
     103 + start: image.Point{0, 0},
     104 + end: image.Point{0, 0},
     105 + opts: []BrailleLineOption{
     106 + BrailleLineCellOpts(
     107 + cell.FgColor(cell.ColorRed),
     108 + ),
     109 + },
     110 + want: func(size image.Point) *faketerm.Terminal {
     111 + ft := faketerm.MustNew(size)
     112 + bc := testbraille.MustNew(ft.Area())
     113 + testbraille.MustSetPixel(bc, image.Point{0, 0}, cell.FgColor(cell.ColorRed))
     114 + testbraille.MustApply(bc, ft)
     115 + return ft
     116 + },
     117 + },
     118 + {
     119 + desc: "draws high line, octant SE",
     120 + canvas: image.Rect(0, 0, 1, 1),
     121 + start: image.Point{0, 0},
     122 + end: image.Point{1, 3},
     123 + want: func(size image.Point) *faketerm.Terminal {
     124 + ft := faketerm.MustNew(size)
     125 + bc := testbraille.MustNew(ft.Area())
     126 + 
     127 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     128 + testbraille.MustSetPixel(bc, image.Point{0, 1})
     129 + testbraille.MustSetPixel(bc, image.Point{1, 2})
     130 + testbraille.MustSetPixel(bc, image.Point{1, 3})
     131 + 
     132 + testbraille.MustApply(bc, ft)
     133 + return ft
     134 + },
     135 + },
     136 + {
     137 + desc: "draws high line, octant NW",
     138 + canvas: image.Rect(0, 0, 1, 1),
     139 + start: image.Point{1, 3},
     140 + end: image.Point{0, 0},
     141 + want: func(size image.Point) *faketerm.Terminal {
     142 + ft := faketerm.MustNew(size)
     143 + bc := testbraille.MustNew(ft.Area())
     144 + 
     145 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     146 + testbraille.MustSetPixel(bc, image.Point{0, 1})
     147 + testbraille.MustSetPixel(bc, image.Point{1, 2})
     148 + testbraille.MustSetPixel(bc, image.Point{1, 3})
     149 + 
     150 + testbraille.MustApply(bc, ft)
     151 + return ft
     152 + },
     153 + },
     154 + {
     155 + desc: "draws high line, octant SW",
     156 + canvas: image.Rect(0, 0, 1, 1),
     157 + start: image.Point{1, 0},
     158 + end: image.Point{0, 3},
     159 + want: func(size image.Point) *faketerm.Terminal {
     160 + ft := faketerm.MustNew(size)
     161 + bc := testbraille.MustNew(ft.Area())
     162 + 
     163 + testbraille.MustSetPixel(bc, image.Point{1, 0})
     164 + testbraille.MustSetPixel(bc, image.Point{1, 1})
     165 + testbraille.MustSetPixel(bc, image.Point{0, 2})
     166 + testbraille.MustSetPixel(bc, image.Point{0, 3})
     167 + 
     168 + testbraille.MustApply(bc, ft)
     169 + return ft
     170 + },
     171 + },
     172 + {
     173 + desc: "draws high line, octant NE",
     174 + canvas: image.Rect(0, 0, 1, 1),
     175 + start: image.Point{0, 3},
     176 + end: image.Point{1, 0},
     177 + want: func(size image.Point) *faketerm.Terminal {
     178 + ft := faketerm.MustNew(size)
     179 + bc := testbraille.MustNew(ft.Area())
     180 + 
     181 + testbraille.MustSetPixel(bc, image.Point{1, 0})
     182 + testbraille.MustSetPixel(bc, image.Point{1, 1})
     183 + testbraille.MustSetPixel(bc, image.Point{0, 2})
     184 + testbraille.MustSetPixel(bc, image.Point{0, 3})
     185 + 
     186 + testbraille.MustApply(bc, ft)
     187 + return ft
     188 + },
     189 + },
     190 + {
     191 + desc: "draws low line, octant SE",
     192 + canvas: image.Rect(0, 0, 3, 1),
     193 + start: image.Point{0, 0},
     194 + end: image.Point{4, 3},
     195 + want: func(size image.Point) *faketerm.Terminal {
     196 + ft := faketerm.MustNew(size)
     197 + bc := testbraille.MustNew(ft.Area())
     198 + 
     199 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     200 + testbraille.MustSetPixel(bc, image.Point{1, 1})
     201 + testbraille.MustSetPixel(bc, image.Point{2, 1})
     202 + testbraille.MustSetPixel(bc, image.Point{3, 2})
     203 + testbraille.MustSetPixel(bc, image.Point{4, 3})
     204 + 
     205 + testbraille.MustApply(bc, ft)
     206 + return ft
     207 + },
     208 + },
     209 + {
     210 + desc: "draws low line, octant NW",
     211 + canvas: image.Rect(0, 0, 3, 1),
     212 + start: image.Point{4, 3},
     213 + end: image.Point{0, 0},
     214 + want: func(size image.Point) *faketerm.Terminal {
     215 + ft := faketerm.MustNew(size)
     216 + bc := testbraille.MustNew(ft.Area())
     217 + 
     218 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     219 + testbraille.MustSetPixel(bc, image.Point{1, 1})
     220 + testbraille.MustSetPixel(bc, image.Point{2, 1})
     221 + testbraille.MustSetPixel(bc, image.Point{3, 2})
     222 + testbraille.MustSetPixel(bc, image.Point{4, 3})
     223 + 
     224 + testbraille.MustApply(bc, ft)
     225 + return ft
     226 + },
     227 + },
     228 + {
     229 + desc: "draws high line, octant SW",
     230 + canvas: image.Rect(0, 0, 3, 1),
     231 + start: image.Point{4, 0},
     232 + end: image.Point{0, 3},
     233 + want: func(size image.Point) *faketerm.Terminal {
     234 + ft := faketerm.MustNew(size)
     235 + bc := testbraille.MustNew(ft.Area())
     236 + 
     237 + testbraille.MustSetPixel(bc, image.Point{4, 0})
     238 + testbraille.MustSetPixel(bc, image.Point{3, 1})
     239 + testbraille.MustSetPixel(bc, image.Point{2, 2})
     240 + testbraille.MustSetPixel(bc, image.Point{1, 2})
     241 + testbraille.MustSetPixel(bc, image.Point{0, 3})
     242 + 
     243 + testbraille.MustApply(bc, ft)
     244 + return ft
     245 + },
     246 + },
     247 + {
     248 + desc: "draws high line, octant NE",
     249 + canvas: image.Rect(0, 0, 3, 1),
     250 + start: image.Point{0, 3},
     251 + end: image.Point{4, 0},
     252 + want: func(size image.Point) *faketerm.Terminal {
     253 + ft := faketerm.MustNew(size)
     254 + bc := testbraille.MustNew(ft.Area())
     255 + 
     256 + testbraille.MustSetPixel(bc, image.Point{4, 0})
     257 + testbraille.MustSetPixel(bc, image.Point{3, 1})
     258 + testbraille.MustSetPixel(bc, image.Point{2, 2})
     259 + testbraille.MustSetPixel(bc, image.Point{1, 2})
     260 + testbraille.MustSetPixel(bc, image.Point{0, 3})
     261 + 
     262 + testbraille.MustApply(bc, ft)
     263 + return ft
     264 + },
     265 + },
     266 + {
     267 + desc: "draws horizontal line, octant E",
     268 + canvas: image.Rect(0, 0, 1, 1),
     269 + start: image.Point{0, 0},
     270 + end: image.Point{1, 0},
     271 + want: func(size image.Point) *faketerm.Terminal {
     272 + ft := faketerm.MustNew(size)
     273 + bc := testbraille.MustNew(ft.Area())
     274 + 
     275 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     276 + testbraille.MustSetPixel(bc, image.Point{1, 0})
     277 + 
     278 + testbraille.MustApply(bc, ft)
     279 + return ft
     280 + },
     281 + },
     282 + {
     283 + desc: "draws horizontal line, octant W",
     284 + canvas: image.Rect(0, 0, 1, 1),
     285 + start: image.Point{1, 0},
     286 + end: image.Point{0, 0},
     287 + want: func(size image.Point) *faketerm.Terminal {
     288 + ft := faketerm.MustNew(size)
     289 + bc := testbraille.MustNew(ft.Area())
     290 + 
     291 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     292 + testbraille.MustSetPixel(bc, image.Point{1, 0})
     293 + 
     294 + testbraille.MustApply(bc, ft)
     295 + return ft
     296 + },
     297 + },
     298 + {
     299 + desc: "draws vertical line, octant S",
     300 + canvas: image.Rect(0, 0, 1, 1),
     301 + start: image.Point{0, 0},
     302 + end: image.Point{0, 1},
     303 + want: func(size image.Point) *faketerm.Terminal {
     304 + ft := faketerm.MustNew(size)
     305 + bc := testbraille.MustNew(ft.Area())
     306 + 
     307 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     308 + testbraille.MustSetPixel(bc, image.Point{0, 1})
     309 + 
     310 + testbraille.MustApply(bc, ft)
     311 + return ft
     312 + },
     313 + },
     314 + {
     315 + desc: "draws vertical line, octant N",
     316 + canvas: image.Rect(0, 0, 1, 1),
     317 + start: image.Point{0, 1},
     318 + end: image.Point{0, 0},
     319 + want: func(size image.Point) *faketerm.Terminal {
     320 + ft := faketerm.MustNew(size)
     321 + bc := testbraille.MustNew(ft.Area())
     322 + 
     323 + testbraille.MustSetPixel(bc, image.Point{0, 0})
     324 + testbraille.MustSetPixel(bc, image.Point{0, 1})
     325 + 
     326 + testbraille.MustApply(bc, ft)
     327 + return ft
     328 + },
     329 + },
     330 + }
     331 + 
     332 + for _, tc := range tests {
     333 + t.Run(tc.desc, func(t *testing.T) {
     334 + bc, err := braille.New(tc.canvas)
     335 + if err != nil {
     336 + t.Fatalf("braille.New => unexpected error: %v", err)
     337 + }
     338 + 
     339 + err = BrailleLine(bc, tc.start, tc.end, tc.opts...)
     340 + if (err != nil) != tc.wantErr {
     341 + t.Errorf("BrailleLine => unexpected error: %v, wantErr: %v", err, tc.wantErr)
     342 + }
     343 + if err != nil {
     344 + return
     345 + }
     346 + 
     347 + size := area.Size(tc.canvas)
     348 + want := faketerm.MustNew(size)
     349 + if tc.want != nil {
     350 + want = tc.want(size)
     351 + }
     352 + 
     353 + got, err := faketerm.New(size)
     354 + if err != nil {
     355 + t.Fatalf("faketerm.New => unexpected error: %v", err)
     356 + }
     357 + if err := bc.Apply(got); err != nil {
     358 + t.Fatalf("bc.Apply => unexpected error: %v", err)
     359 + }
     360 + if diff := faketerm.Diff(want, got); diff != "" {
     361 + t.Fatalf("BrailleLine => %v", diff)
     362 + }
     363 + 
     364 + })
     365 + }
     366 +}
     367 + 
Please wait...
Page is in error, reload to recover