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.
/*
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.
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
| / | \ |
| / | \ |
------- ------- o
D1 D2 DP
------- -------
D1 D2
*/
package segdisp
import (
"errors"
"fmt"
"image"
"log"
"github.com/mum4k/termdash/area"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/braille"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/numbers"
"github.com/mum4k/termdash/segdisp/segment"
)
// Segment represents a single segment in the display.
@ -75,7 +80,6 @@ var segmentNames = map[Segment]string{
L: "L",
M: "M",
N: "M",
DP: "DP",
}
const (
@ -97,7 +101,6 @@ const (
L
M
N
DP
segmentMax // Used for validation.
)
@ -108,6 +111,15 @@ type Option interface {
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.
type option func(*Display)
@ -188,24 +200,65 @@ func (d *Display) ToggleSegment(s Segment) error {
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 (
// MinColPixels is the smallest valid amount of columns in pixels.
MinColPixels = 4 * braille.ColMult
// MinRowPixels is the smallest valid amount of rows in pixels.
MinRowPixels = 3 * braille.RowMult
// MinCols is the smallest valid amount of columns in a cell area.
MinCols = 4
// MinRowPixels is the smallest valid amount of rows in a cell area.
MinRows = 3
)
// 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.
func (d *Display) Draw(bc *braille.Canvas, opts ...Option) error {
if size := bc.Size(); size.X < MinColPixels || size.Y < MinRowPixels {
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)
func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
ar, err := Required(cvs.Area())
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 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.
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) {
t.Skip()
//t.Skip()
tests := []struct {
desc string
cellCanvas image.Rectangle
@ -986,26 +986,26 @@ func TestMultipleSegments(t *testing.T) {
desc: "12-segment display, more spacing",
cellCanvas: image.Rect(0, 0, 16, 12),
hvSegments: []hvSegment{
//{image.Rect(3, 0, 15, 4), SegmentTypeHorizontal}, // A1
//{image.Rect(17, 0, 29, 4), SegmentTypeHorizontal}, // A2
{image.Rect(3, 0, 15, 4), SegmentTypeHorizontal}, // A1
{image.Rect(17, 0, 29, 4), SegmentTypeHorizontal}, // A2
{image.Rect(0, 3, 4, 23), SegmentTypeVertical}, // F
//{image.Rect(14, 3, 18, 23), SegmentTypeVertical}, // J
{image.Rect(0, 3, 4, 23), SegmentTypeVertical}, // F
{image.Rect(14, 3, 18, 23), SegmentTypeVertical}, // J
{image.Rect(28, 3, 32, 23), SegmentTypeVertical}, // B
//{image.Rect(3, 22, 15, 26), SegmentTypeHorizontal}, // G1
//{image.Rect(17, 22, 29, 26), SegmentTypeHorizontal}, // G2
{image.Rect(3, 22, 15, 26), SegmentTypeHorizontal}, // G1
{image.Rect(17, 22, 29, 26), SegmentTypeHorizontal}, // G2
{image.Rect(0, 25, 4, 45), SegmentTypeVertical}, // E
//{image.Rect(14, 25, 18, 45), SegmentTypeVertical}, // M
{image.Rect(0, 25, 4, 45), SegmentTypeVertical}, // E
{image.Rect(14, 25, 18, 45), SegmentTypeVertical}, // M
{image.Rect(28, 25, 32, 45), SegmentTypeVertical}, // C
//{image.Rect(3, 44, 15, 48), SegmentTypeHorizontal}, // D1
//{image.Rect(17, 44, 29, 48), SegmentTypeHorizontal}, // D2
{image.Rect(3, 44, 15, 48), SegmentTypeHorizontal}, // D1
{image.Rect(17, 44, 29, 48), SegmentTypeHorizontal}, // D2
},
diagSegments: []diagSegment{
//{image.Rect(4, 4, 14, 22), 4, DiagonalTypeLeftToRight}, // H
//{image.Rect(18, 22, 28, 4), 4, DiagonalTypeRightToLeft}, // K
{image.Rect(4, 4, 14, 22), 4, DiagonalTypeLeftToRight}, // H
{image.Rect(18, 22, 28, 4), 4, DiagonalTypeRightToLeft}, // K
{image.Rect(4, 44, 14, 26), 4, DiagonalTypeRightToLeft}, // N
{image.Rect(18, 26, 28, 44), 4, DiagonalTypeLeftToRight}, // L
},