ASCII up to letters.

This commit is contained in:
Jakub Sobon 2019-02-03 18:24:34 -05:00
parent ed725da9a0
commit dbd6ecf3ad
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 522 additions and 22 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -41,10 +41,8 @@ package sixteen
import (
"bytes"
"errors"
"fmt"
"image"
"log"
"math"
"github.com/mum4k/termdash/area"
@ -110,8 +108,40 @@ const (
)
// characterSegments maps characters that can be displayed on their segments.
// See doc/16-Segment-ASCII-All.jpg and:
// https://www.partsnotincluded.com/electronics/segmented-led-display-ascii-library
var characterSegments = map[rune][]Segment{
' ': nil,
' ': nil,
'"': {J, B},
'#': {J, B, G1, G2, M, C, D1, D2},
'$': {A1, A2, F, J, G1, G2, M, C, D1, D2},
'%': {A1, F, J, K, G1, G2, N, M, C, D2},
'&': {A1, H, J, G1, E, L, D1, D2},
'\'': {J},
'(': {K, L},
')': {H, N},
'*': {H, J, K, G1, G2, N, M, L},
'+': {J, G1, G2, M},
',': {N},
'-': {G1, G2},
'/': {N, K},
'0': {A1, A2, F, K, B, E, N, C, D1, D2},
'1': {K, B, C},
'2': {A1, A2, B, G1, G2, E, D1, D2},
'3': {A1, A2, B, G2, C, D1, D2},
'4': {F, B, G1, G2, C},
'5': {A1, A2, F, G1, L, D1, D2},
'6': {A1, A2, F, G1, G2, E, C, D1, D2},
'7': {A1, A2, B, C},
'8': {A1, A2, F, B, G1, G2, E, C, D1, D2},
'9': {A1, A2, F, B, G1, G2, C, D1, D2},
':': {J, M},
'<': {K, G1, L},
'=': {G1, G2, D1, D2},
'>': {H, G2, N},
'?': {A1, A2, B, G2, M},
'@': {A1, A2, F, J, B, G2, E, D1, D2},
'w': {E, N, L, C},
'W': {F, E, N, L, C, B},
}
@ -244,16 +274,14 @@ func (d *Display) ToggleSegment(s Segment) error {
return nil
}
// ErrUnsupportedCharacter is returned when the provided character cannot be displayed.
var ErrUnsupportedCharacter = errors.New("unsupported character")
// Character sets all the segments that are needed to display the provided character.
// Returns ErrUnsupportedCharacter when the character cannot be displayed.
// The display only supports a subset of ASCII characters, use SupportsChars()
// or Sanitize() to ensure the provided character is supported.
// Doesn't clear the display of segments set previously.
func (d *Display) SetCharacter(c rune) error {
seg, ok := characterSegments[c]
if !ok {
return ErrUnsupportedCharacter
return fmt.Errorf("display doesn't support character %q rune(%v)", c, c)
}
for _, s := range seg {
@ -296,8 +324,8 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
bcAr := area.WithRatio(bc.Area(), aspectRatio)
segW := segWidth(bcAr)
if segW == 4 {
segW = 5
if segW > 3 && segW%2 == 0 {
segW++
}
// Gap between the edge and the first segment.
@ -315,13 +343,14 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
if segW == 2 {
peakToPeak = 2
}
if peakToPeak > 3 && int(peakToPeak)%2 == 0 {
peakToPeak++
}
// Lengths of the short and long segment.
shortL := (bcAr.Dx()-int(numbers.Round(2*edgeSegGap+peakToPeak)))/2 - 1
longL := (bcAr.Dy()-int(numbers.Round(2*edgeSegGap+peakToPeak)))/2 - 1
//log.Printf("dx:%d segW:%d, edgeGap:%d, segGap:%d, shortL:%d, longL:%d, end:%d, mid:%d, midGap:%d segDist:%d", bcAr.Dx(), segW, edgeGap, segGap, shortL, longL, end, mid, midGap, segDist)
eg := int(numbers.Round(edgeSegGap))
ptp := int(numbers.Round(peakToPeak))
@ -378,7 +407,6 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
if !d.segments[segArg.s] {
continue
}
log.Printf("segment.HV for %v, ar:%v", segArg.s, segArg.ar)
sOpts := append(sOpts, segArg.opts...)
if err := segment.HV(bc, segArg.ar, segArg.st, sOpts...); err != nil {
return fmt.Errorf("failed to draw segment %v, segment.HV => %v", segArg.s, err)
@ -426,7 +454,6 @@ func (d *Display) Draw(cvs *canvas.Canvas, opts ...Option) error {
if !d.segments[segArg.s] {
continue
}
log.Printf("segment.Diagonal for %v, ar:%v", segArg.s, segArg.ar)
if err := segment.Diagonal(bc, segArg.ar, segW, segArg.dt, dsOpts...); err != nil {
return fmt.Errorf("failed to draw segment %v, segment.Diagonal => %v", segArg.s, err)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/mum4k/termdash/area"
"github.com/mum4k/termdash/canvas"
"github.com/mum4k/termdash/canvas/braille/testbraille"
"github.com/mum4k/termdash/canvas/testcanvas"
"github.com/mum4k/termdash/cell"
"github.com/mum4k/termdash/draw/segdisp/segment"
"github.com/mum4k/termdash/draw/segdisp/segment/testsegment"
@ -647,6 +648,166 @@ func TestDraw(t *testing.T) {
return d.ClearSegment(A1)
},
},
{
desc: "segment width of two",
cellCanvas: image.Rect(0, 0, MinCols*2, MinRows*2),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
bc := testbraille.MustNew(ft.Area())
testsegment.MustHV(bc, image.Rect(2, 0, 10, 2), segment.Horizontal) // A1
testsegment.MustHV(bc, image.Rect(12, 0, 20, 2), segment.Horizontal) // A2
testsegment.MustHV(bc, image.Rect(0, 2, 2, 18), segment.Vertical) // F
testsegment.MustHV(bc, image.Rect(10, 2, 12, 18), segment.Vertical, segment.SkipSlopesLTE(2)) // J
testsegment.MustHV(bc, image.Rect(20, 2, 22, 18), segment.Vertical, segment.ReverseSlopes()) // B
testsegment.MustHV(bc, image.Rect(2, 18, 10, 20), segment.Horizontal, segment.SkipSlopesLTE(2)) // G1
testsegment.MustHV(bc, image.Rect(12, 18, 20, 20), segment.Horizontal, segment.SkipSlopesLTE(2)) // G2
testsegment.MustHV(bc, image.Rect(0, 20, 2, 36), segment.Vertical) // E
testsegment.MustHV(bc, image.Rect(10, 20, 12, 36), segment.Vertical, segment.SkipSlopesLTE(2)) // M
testsegment.MustHV(bc, image.Rect(20, 20, 22, 36), segment.Vertical, segment.ReverseSlopes()) // C
testsegment.MustHV(bc, image.Rect(2, 36, 10, 38), segment.Horizontal, segment.ReverseSlopes()) // D1
testsegment.MustHV(bc, image.Rect(12, 36, 20, 38), segment.Horizontal, segment.ReverseSlopes()) // D2
testsegment.MustDiagonal(bc, image.Rect(3, 3, 9, 17), 2, segment.LeftToRight) // H
testsegment.MustDiagonal(bc, image.Rect(13, 3, 19, 17), 2, segment.RightToLeft) // K
testsegment.MustDiagonal(bc, image.Rect(3, 21, 9, 35), 2, segment.RightToLeft) // N
testsegment.MustDiagonal(bc, image.Rect(13, 21, 19, 35), 2, segment.LeftToRight) // L
testbraille.MustApply(bc, ft)
return ft
},
},
{
desc: "segment width of three",
cellCanvas: image.Rect(0, 0, MinCols*3, MinRows*3),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
bc := testbraille.MustNew(ft.Area())
testsegment.MustHV(bc, image.Rect(2, 0, 16, 3), segment.Horizontal) // A1
testsegment.MustHV(bc, image.Rect(17, 0, 31, 3), segment.Horizontal) // A2
testsegment.MustHV(bc, image.Rect(0, 2, 3, 28), segment.Vertical) // F
testsegment.MustHV(bc, image.Rect(15, 2, 18, 28), segment.Vertical) // J
testsegment.MustHV(bc, image.Rect(30, 2, 33, 28), segment.Vertical) // B
testsegment.MustHV(bc, image.Rect(2, 27, 16, 30), segment.Horizontal) // G1
testsegment.MustHV(bc, image.Rect(17, 27, 31, 30), segment.Horizontal) // G2
testsegment.MustHV(bc, image.Rect(0, 29, 3, 55), segment.Vertical) // E
testsegment.MustHV(bc, image.Rect(15, 29, 18, 55), segment.Vertical) // M
testsegment.MustHV(bc, image.Rect(30, 29, 33, 55), segment.Vertical) // C
testsegment.MustHV(bc, image.Rect(2, 54, 16, 57), segment.Horizontal) // D1
testsegment.MustHV(bc, image.Rect(17, 54, 31, 57), segment.Horizontal) // D2
testsegment.MustDiagonal(bc, image.Rect(3, 3, 15, 27), 3, segment.LeftToRight) // H
testsegment.MustDiagonal(bc, image.Rect(18, 3, 30, 27), 3, segment.RightToLeft) // K
testsegment.MustDiagonal(bc, image.Rect(3, 30, 15, 54), 3, segment.RightToLeft) // N
testsegment.MustDiagonal(bc, image.Rect(18, 30, 30, 54), 3, segment.LeftToRight) // L
testbraille.MustApply(bc, ft)
return ft
},
},
{
desc: "segment with even width is changed to odd",
cellCanvas: image.Rect(0, 0, MinCols*4, MinRows*4),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
bc := testbraille.MustNew(ft.Area())
testsegment.MustHV(bc, image.Rect(4, 0, 21, 5), segment.Horizontal) // A1
testsegment.MustHV(bc, image.Rect(24, 0, 41, 5), segment.Horizontal) // A2
testsegment.MustHV(bc, image.Rect(0, 4, 5, 37), segment.Vertical) // F
testsegment.MustHV(bc, image.Rect(20, 4, 25, 37), segment.Vertical) // J
testsegment.MustHV(bc, image.Rect(40, 4, 45, 37), segment.Vertical) // B
testsegment.MustHV(bc, image.Rect(4, 36, 21, 41), segment.Horizontal) // G1
testsegment.MustHV(bc, image.Rect(24, 36, 41, 41), segment.Horizontal) // G2
testsegment.MustHV(bc, image.Rect(0, 40, 5, 73), segment.Vertical) // E
testsegment.MustHV(bc, image.Rect(20, 40, 25, 73), segment.Vertical) // M
testsegment.MustHV(bc, image.Rect(40, 40, 45, 73), segment.Vertical) // C
testsegment.MustHV(bc, image.Rect(4, 72, 21, 77), segment.Horizontal) // D1
testsegment.MustHV(bc, image.Rect(24, 72, 41, 77), segment.Horizontal) // D2
testsegment.MustDiagonal(bc, image.Rect(6, 6, 19, 35), 5, segment.LeftToRight) // H
testsegment.MustDiagonal(bc, image.Rect(26, 6, 39, 35), 5, segment.RightToLeft) // K
testsegment.MustDiagonal(bc, image.Rect(6, 42, 19, 71), 5, segment.RightToLeft) // N
testsegment.MustDiagonal(bc, image.Rect(26, 42, 39, 71), 5, segment.LeftToRight) // L
testbraille.MustApply(bc, ft)
return ft
},
},
{
desc: "segment with odd width and e√en peak to peak distance is changed to odd",
cellCanvas: image.Rect(0, 0, MinCols*7, MinRows*7),
update: func(d *Display) error {
for _, s := range AllSegments() {
if err := d.SetSegment(s); err != nil {
return err
}
}
return nil
},
want: func(size image.Point) *faketerm.Terminal {
ft := faketerm.MustNew(size)
bc := testbraille.MustNew(ft.Area())
testsegment.MustHV(bc, image.Rect(7, 0, 39, 9), segment.Horizontal) // A1
testsegment.MustHV(bc, image.Rect(44, 0, 76, 9), segment.Horizontal) // A2
testsegment.MustHV(bc, image.Rect(0, 7, 9, 67), segment.Vertical) // F
testsegment.MustHV(bc, image.Rect(37, 7, 46, 67), segment.Vertical) // J
testsegment.MustHV(bc, image.Rect(74, 7, 83, 67), segment.Vertical) // B
testsegment.MustHV(bc, image.Rect(7, 65, 39, 74), segment.Horizontal) // G1
testsegment.MustHV(bc, image.Rect(44, 65, 76, 74), segment.Horizontal) // G2
testsegment.MustHV(bc, image.Rect(0, 72, 9, 132), segment.Vertical) // E
testsegment.MustHV(bc, image.Rect(37, 72, 46, 132), segment.Vertical) // M
testsegment.MustHV(bc, image.Rect(74, 72, 83, 132), segment.Vertical) // C
testsegment.MustHV(bc, image.Rect(7, 130, 39, 139), segment.Horizontal) // D1
testsegment.MustHV(bc, image.Rect(44, 130, 76, 139), segment.Horizontal) // D2
testsegment.MustDiagonal(bc, image.Rect(10, 10, 36, 64), 9, segment.LeftToRight) // H
testsegment.MustDiagonal(bc, image.Rect(47, 10, 73, 64), 9, segment.RightToLeft) // K
testsegment.MustDiagonal(bc, image.Rect(10, 75, 36, 129), 9, segment.RightToLeft) // N
testsegment.MustDiagonal(bc, image.Rect(47, 75, 73, 129), 9, segment.LeftToRight) // L
testbraille.MustApply(bc, ft)
return ft
},
},
}
for _, tc := range tests {
@ -698,6 +859,318 @@ func TestDraw(t *testing.T) {
}
}
// mustDrawSegments returns a fake terminal of the specified size with the
// segments drawn on it or panics.
func mustDrawSegments(size image.Point, seg ...Segment) *faketerm.Terminal {
ft := faketerm.MustNew(size)
cvs := testcanvas.MustNew(ft.Area())
d := New()
for _, s := range seg {
if err := d.SetSegment(s); err != nil {
panic(err)
}
}
if err := d.Draw(cvs); err != nil {
panic(err)
}
testcanvas.MustApply(cvs, ft)
return ft
}
func TestSetCharacter(t *testing.T) {
tests := []struct {
desc string
char rune
// If not nil, it is called before Draw is called and can set, clear or
// toggle segments or characters.
update func(*Display) error
want func(size image.Point) *faketerm.Terminal
wantErr bool
}{
{
desc: "fails on unsupported character",
char: '!',
wantErr: true,
},
{
desc: "displays ' '",
char: ' ',
},
{
desc: "doesn't clear the display",
update: func(d *Display) error {
return d.SetSegment(A2)
},
char: 'W',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, F, E, N, L, C, B, A2)
},
},
{
desc: "displays '\"'",
char: '"',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, J, B)
},
},
{
desc: "displays '#'",
char: '#',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, J, B, G1, G2, M, C, D1, D2)
},
},
{
desc: "displays '$'",
char: '$',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, J, G1, G2, M, C, D1, D2)
},
},
{
desc: "displays '%'",
char: '%',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, F, J, K, G1, G2, N, M, C, D2)
},
},
{
desc: "displays '&'",
char: '&',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, H, J, G1, E, L, D1, D2)
},
},
{
desc: "displays '",
char: '\'',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, J)
},
},
{
desc: "displays '('",
char: '(',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, K, L)
},
},
{
desc: "displays ')'",
char: ')',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, H, N)
},
},
{
desc: "displays '*'",
char: '*',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, H, J, K, G1, G2, N, M, L)
},
},
{
desc: "displays '+'",
char: '+',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, J, G1, G2, M)
},
},
{
desc: "displays ','",
char: ',',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, N)
},
},
{
desc: "displays '-'",
char: '-',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, G1, G2)
},
},
{
desc: "displays '/'",
char: '/',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, N, K)
},
},
{
desc: "displays '0'",
char: '0',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, K, B, E, N, C, D1, D2)
},
},
{
desc: "displays '1'",
char: '1',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, K, B, C)
},
},
{
desc: "displays '2'",
char: '2',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, B, G1, G2, E, D1, D2)
},
},
{
desc: "displays '3'",
char: '3',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, B, G2, C, D1, D2)
},
},
{
desc: "displays '4'",
char: '4',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, F, B, G1, G2, C)
},
},
{
desc: "displays '5'",
char: '5',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, G1, L, D1, D2)
},
},
{
desc: "displays '6'",
char: '6',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, G1, G2, E, C, D1, D2)
},
},
{
desc: "displays '7'",
char: '7',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, B, C)
},
},
{
desc: "displays '8'",
char: '8',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, B, G1, G2, E, C, D1, D2)
},
},
{
desc: "displays '9'",
char: '9',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, B, G1, G2, C, D1, D2)
},
},
{
desc: "displays ':'",
char: ':',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, J, M)
},
},
{
desc: "displays '<'",
char: '<',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, K, G1, L)
},
},
{
desc: "displays '='",
char: '=',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, G1, G2, D1, D2)
},
},
{
desc: "displays '>'",
char: '>',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, H, G2, N)
},
},
{
desc: "displays '?'",
char: '?',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, B, G2, M)
},
},
{
desc: "displays '@'",
char: '@',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, A1, A2, F, J, B, G2, E, D1, D2)
},
},
{
desc: "displays 'W'",
char: 'W',
want: func(size image.Point) *faketerm.Terminal {
return mustDrawSegments(size, F, E, N, L, C, B)
},
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
d := New()
if tc.update != nil {
err := tc.update(d)
if err != nil {
t.Fatalf("tc.update => unexpected error: %v", err)
}
}
{
err := d.SetCharacter(tc.char)
if (err != nil) != tc.wantErr {
t.Errorf("SetCharacter => unexpected error: %v, wantErr: %v", err, tc.wantErr)
}
if err != nil {
return
}
}
ar := image.Rect(0, 0, MinCols, MinRows)
cvs, err := canvas.New(ar)
if err != nil {
t.Fatalf("canvas.New => unexpected error: %v", err)
}
if err := d.Draw(cvs); err != nil {
t.Fatalf("Draw => unexpected error: %v", err)
}
size := area.Size(ar)
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("SetCharacter => %v", diff)
}
})
}
}
func TestRequired(t *testing.T) {
tests := []struct {
desc string
@ -783,15 +1256,15 @@ func TestSupportsChars(t *testing.T) {
},
{
desc: "supports some chars in the string",
str: " w:!W :",
str: " w!W :",
wantRes: false,
wantUnsupp: []rune{':', '!'},
wantUnsupp: []rune{'!'},
},
{
desc: "supports no chars in the string",
str: ":!()",
str: "!",
wantRes: false,
wantUnsupp: []rune{':', '!', '(', ')'},
wantUnsupp: []rune{'!'},
},
}
@ -831,13 +1304,13 @@ func TestSanitize(t *testing.T) {
},
{
desc: "some characters are supported",
str: " :w!W:",
want: " w W ",
str: " w!W:",
want: " w W:",
},
{
desc: "no characters are supported",
str: ":!()",
want: " ",
str: "!",
want: " ",
},
}