diff --git a/segdisp/segdisp.go b/segdisp/segdisp.go index 30a425c..e05cd00 100644 --- a/segdisp/segdisp.go +++ b/segdisp/segdisp.go @@ -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)) } diff --git a/segdisp/segdisp_test.go b/segdisp/segdisp_test.go index 1e31dea..fb3fef3 100644 --- a/segdisp/segdisp_test.go +++ b/segdisp/segdisp_test.go @@ -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) + } + + }) + } +} diff --git a/segdisp/segment/segment_test.go b/segdisp/segment/segment_test.go index d6558d3..683d07a 100644 --- a/segdisp/segment/segment_test.go +++ b/segdisp/segment/segment_test.go @@ -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 },