Partial implementation of segdisp.

This commit is contained in:
Jakub Sobon 2019-01-29 18:26:58 -05:00
parent 5c02803221
commit 8cdc34fb28
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 175 additions and 31 deletions

View File

@ -13,9 +13,9 @@
// limitations under the License. // limitations under the License.
/* /*
Package segdisp simulates a 16-segment display drawn on a braille canvas. Package segdisp simulates a 16-segment display drawn on a canvas.
Given a braille canvas, determines the placement and size of the individual Given a canvas, determines the placement and size of the individual
segments and exposes API that can turn individual segments on and off. segments and exposes API that can turn individual segments on and off.
The following outlines segments in the display and their names. The following outlines segments in the display and their names.
@ -33,17 +33,22 @@ The following outlines segments in the display and their names.
E | N M L | C E | N M L | C
| / | \ | | / | \ |
| / | \ | | / | \ |
------- ------- o ------- -------
D1 D2 DP D1 D2
*/ */
package segdisp package segdisp
import ( import (
"errors"
"fmt" "fmt"
"image"
"log"
"github.com/mum4k/termdash/area"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/braille" "github.com/mum4k/termdash/canvas/braille"
"github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/numbers"
"github.com/mum4k/termdash/segdisp/segment"
) )
// Segment represents a single segment in the display. // Segment represents a single segment in the display.
@ -75,7 +80,6 @@ var segmentNames = map[Segment]string{
L: "L", L: "L",
M: "M", M: "M",
N: "M", N: "M",
DP: "DP",
} }
const ( const (
@ -97,7 +101,6 @@ const (
L L
M M
N N
DP
segmentMax // Used for validation. segmentMax // Used for validation.
) )
@ -108,6 +111,15 @@ type Option interface {
set(*Display) set(*Display)
} }
// AllSegments returns all 16 segments in an undefined order.
func AllSegments() []Segment {
var res []Segment
for s := range segmentNames {
res = append(res, s)
}
return res
}
// option implements Option. // option implements Option.
type option func(*Display) type option func(*Display)
@ -188,24 +200,65 @@ func (d *Display) ToggleSegment(s Segment) error {
return nil return nil
} }
// Minimum valid size of braille canvas in order to draw the segment display. // Minimum valid size of a cell canvas in order to draw the segment display.
const ( const (
// MinColPixels is the smallest valid amount of columns in pixels. // MinCols is the smallest valid amount of columns in a cell area.
MinColPixels = 4 * braille.ColMult MinCols = 4
// MinRowPixels is the smallest valid amount of rows in pixels. // MinRowPixels is the smallest valid amount of rows in a cell area.
MinRowPixels = 3 * braille.RowMult MinRows = 3
) )
// Draw draws the current state of the segment display onto the canvas. // Draw draws the current state of the segment display onto the canvas.
// The canvas must be at 4x3 cells, or an error will be returned. // The canvas must be at least MinCols x MinRows cells, or an error will be
// returned.
// Any options provided to draw overwrite the values provided to New. // Any options provided to draw overwrite the values provided to New.
func (d *Display) Draw(bc *braille.Canvas, opts ...Option) error { func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
if size := bc.Size(); size.X < MinColPixels || size.Y < MinRowPixels { ar, err := Required(cvs.Area())
return fmt.Errorf("the canvas size %v is too small for the segment display, need at least %d columns and %d rows in pixels", size, MinColPixels, MinRowPixels) if err != nil {
return err
}
bc, err := braille.New(ar)
if err != nil {
return fmt.Errorf("braille.New => %v", err)
}
bcAr := bc.Area()
sw := segWidth(bcAr)
half := bcAr.Dx() / 2
log.Printf("bcAr:%v, sw:%d, half:%d", bcAr, sw, half)
a1 := image.Rect(sw-1, 0, half-sw/2, sw)
a2 := image.Rect(half+sw/2, 0, bcAr.Max.X-1, sw)
log.Printf("a1:%v", a1)
log.Printf("a2:%v", a2)
for _, segAr := range []image.Rectangle{a1, a2} {
if err := segment.HV(bc, segAr, segment.SegmentTypeHorizontal); err != nil {
return fmt.Errorf("segment.HV => %v", err)
}
} }
// Determine line width.
// Determine gap width. // Determine gap width.
// Determine length of short and long segment. // Determine length of short and long segment.
return errors.New("unimplemented") return bc.CopyTo(cvs)
}
// Required, when given an area of cells, returns either an area of the same
// size or a smaller area that is required to draw one display.
// Returns a smaller area when the provided area didn;t have the required
// aspect ratio.
// Returns an error if the area is too small to draw a segment display.
func Required(cellArea image.Rectangle) (image.Rectangle, error) {
ar := area.WithRatio(cellArea, image.Point{MinCols, MinRows})
if ar.Empty() {
return image.ZR, fmt.Errorf("cell area %v is to small to draw the segment display, need at least %d x %d cells", cellArea, MinCols, MinRows)
}
return ar, nil
}
// segWidth given an area for the display determines the width of individual segments.
func segWidth(ar image.Rectangle) int {
// widthPerc is the relative width of a segment to the width of the canvas.
const widthPerc = 10
return int(numbers.Round(float64(ar.Dx()) * 10 / 100))
} }

View File

@ -13,3 +13,94 @@
// limitations under the License. // limitations under the License.
package segdisp package segdisp
import (
"image"
"testing"
"github.com/mum4k/termdash/area"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/terminal/faketerm"
)
func TestDraw(t *testing.T) {
tests := []struct {
desc string
opts []Option
drawOpts []Option
cellCanvas image.Rectangle
// If not nil, called before Draw is called - can set, clear or toggle segments.
update func(*Display) error
want func(size image.Point) *faketerm.Terminal
wantErr bool
}{
{
desc: "smallest display, all segments",
cellCanvas: image.Rect(0, 0, 4, 3),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
},
{
desc: "16x12, all segments",
cellCanvas: image.Rect(0, 0, 16, 12),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
d := New(tc.opts...)
if tc.update != nil {
if err := tc.update(d); err != nil {
t.Fatalf("tc.update => unexpected error: %v", err)
}
}
cvs, err := canvas.New(tc.cellCanvas)
if err != nil {
t.Fatalf("canvas.New => unexpected error: %v", err)
}
{
err := d.Draw(cvs)
if (err != nil) != tc.wantErr {
t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr)
}
if err != nil {
return
}
}
size := area.Size(tc.cellCanvas)
want := faketerm.MustNew(size)
if tc.want != nil {
want = tc.want(size)
}
got, err := faketerm.New(size)
if err != nil {
t.Fatalf("faketerm.New => unexpected error: %v", err)
}
if err := cvs.Apply(got); err != nil {
t.Fatalf("bc.Apply => unexpected error: %v", err)
}
if diff := faketerm.Diff(want, got); diff != "" {
t.Fatalf("Draw => %v", diff)
}
})
}
}

View File

@ -974,7 +974,7 @@ type diagSegment struct {
} }
func TestMultipleSegments(t *testing.T) { func TestMultipleSegments(t *testing.T) {
t.Skip() //t.Skip()
tests := []struct { tests := []struct {
desc string desc string
cellCanvas image.Rectangle cellCanvas image.Rectangle
@ -986,26 +986,26 @@ func TestMultipleSegments(t *testing.T) {
desc: "12-segment display, more spacing", desc: "12-segment display, more spacing",
cellCanvas: image.Rect(0, 0, 16, 12), cellCanvas: image.Rect(0, 0, 16, 12),
hvSegments: []hvSegment{ hvSegments: []hvSegment{
//{image.Rect(3, 0, 15, 4), SegmentTypeHorizontal}, // A1 {image.Rect(3, 0, 15, 4), SegmentTypeHorizontal}, // A1
//{image.Rect(17, 0, 29, 4), SegmentTypeHorizontal}, // A2 {image.Rect(17, 0, 29, 4), SegmentTypeHorizontal}, // A2
{image.Rect(0, 3, 4, 23), SegmentTypeVertical}, // F {image.Rect(0, 3, 4, 23), SegmentTypeVertical}, // F
//{image.Rect(14, 3, 18, 23), SegmentTypeVertical}, // J {image.Rect(14, 3, 18, 23), SegmentTypeVertical}, // J
{image.Rect(28, 3, 32, 23), SegmentTypeVertical}, // B {image.Rect(28, 3, 32, 23), SegmentTypeVertical}, // B
//{image.Rect(3, 22, 15, 26), SegmentTypeHorizontal}, // G1 {image.Rect(3, 22, 15, 26), SegmentTypeHorizontal}, // G1
//{image.Rect(17, 22, 29, 26), SegmentTypeHorizontal}, // G2 {image.Rect(17, 22, 29, 26), SegmentTypeHorizontal}, // G2
{image.Rect(0, 25, 4, 45), SegmentTypeVertical}, // E {image.Rect(0, 25, 4, 45), SegmentTypeVertical}, // E
//{image.Rect(14, 25, 18, 45), SegmentTypeVertical}, // M {image.Rect(14, 25, 18, 45), SegmentTypeVertical}, // M
{image.Rect(28, 25, 32, 45), SegmentTypeVertical}, // C {image.Rect(28, 25, 32, 45), SegmentTypeVertical}, // C
//{image.Rect(3, 44, 15, 48), SegmentTypeHorizontal}, // D1 {image.Rect(3, 44, 15, 48), SegmentTypeHorizontal}, // D1
//{image.Rect(17, 44, 29, 48), SegmentTypeHorizontal}, // D2 {image.Rect(17, 44, 29, 48), SegmentTypeHorizontal}, // D2
}, },
diagSegments: []diagSegment{ diagSegments: []diagSegment{
//{image.Rect(4, 4, 14, 22), 4, DiagonalTypeLeftToRight}, // H {image.Rect(4, 4, 14, 22), 4, DiagonalTypeLeftToRight}, // H
//{image.Rect(18, 22, 28, 4), 4, DiagonalTypeRightToLeft}, // K {image.Rect(18, 22, 28, 4), 4, DiagonalTypeRightToLeft}, // K
{image.Rect(4, 44, 14, 26), 4, DiagonalTypeRightToLeft}, // N {image.Rect(4, 44, 14, 26), 4, DiagonalTypeRightToLeft}, // N
{image.Rect(18, 26, 28, 44), 4, DiagonalTypeLeftToRight}, // L {image.Rect(18, 26, 28, 44), 4, DiagonalTypeLeftToRight}, // L
}, },