Handle CJK wide characters in helper.go
Add multi width text ouput support in widgets
This commit is contained in:
gizak 2015-03-25 18:04:15 -04:00
parent 9e538ed468
commit b436024302
10 changed files with 93 additions and 27 deletions

6
bar.go
View File

@ -91,14 +91,16 @@ func (bc *BarChart) Buffer() []Point {
} }
} }
// plot text // plot text
for j := 0; j < len(bc.labels[i]); j++ { for j, k := 0, 0; j < len(bc.labels[i]); j++ {
w := charWidth(bc.labels[i][j])
p := Point{} p := Point{}
p.Ch = bc.labels[i][j] p.Ch = bc.labels[i][j]
p.Bg = bc.BgColor p.Bg = bc.BgColor
p.Fg = bc.TextColor p.Fg = bc.TextColor
p.Y = bc.innerY + bc.innerHeight - 1 p.Y = bc.innerY + bc.innerHeight - 1
p.X = bc.innerX + oftX + j p.X = bc.innerX + oftX + k
ps = append(ps, p) ps = append(ps, p)
k += w
} }
// plot num // plot num
for j := 0; j < len(bc.dataNum[i]); j++ { for j := 0; j < len(bc.dataNum[i]); j++ {

6
box.go
View File

@ -107,8 +107,10 @@ func (lb labeledBorder) Buffer() []Point {
maxTxtW := lb.Width - 2 maxTxtW := lb.Width - 2
rs := trimStr2Runes(lb.Label, maxTxtW) rs := trimStr2Runes(lb.Label, maxTxtW)
for i := 0; i < len(rs); i++ { for i, j, w := 0, 0, 0; i < len(rs); i++ {
ps = append(ps, newPointWithAttrs(rs[i], lb.X+1+i, lb.Y, lb.LabelFgColor, lb.LabelBgColor)) w = charWidth(rs[i])
ps = append(ps, newPointWithAttrs(rs[i], lb.X+1+j, lb.Y, lb.LabelFgColor, lb.LabelBgColor))
j += w
} }
return ps return ps

View File

@ -47,8 +47,8 @@ var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
type LineChart struct { type LineChart struct {
Block Block
Data []float64 Data []float64
DataLabels []string DataLabels []string // if unset, the data indices will be used
Mode string // braille | dot Mode string // braille | dot
DotStyle rune DotStyle rune
LineColor Attribute LineColor Attribute
scale float64 // data span per cell on y-axis scale float64 // data span per cell on y-axis
@ -149,20 +149,22 @@ func (lc *LineChart) calcLabelX() {
} }
s := str2runes(lc.DataLabels[l]) s := str2runes(lc.DataLabels[l])
if l+len(s) <= lc.axisXWidth { w := strWidth(lc.DataLabels[l])
if l+w <= lc.axisXWidth {
lc.labelX = append(lc.labelX, s) lc.labelX = append(lc.labelX, s)
} }
l += (len(s) + lc.axisXLebelGap) // -1 needed l += w + lc.axisXLebelGap
} else { } else { // braille
if 2*l >= len(lc.DataLabels) { if 2*l >= len(lc.DataLabels) {
break break
} }
s := str2runes(lc.DataLabels[2*l]) s := str2runes(lc.DataLabels[2*l])
if l+len(s) <= lc.axisXWidth { w := strWidth(lc.DataLabels[2*l])
if l+w <= lc.axisXWidth {
lc.labelX = append(lc.labelX, s) lc.labelX = append(lc.labelX, s)
} }
l += (len(s) + lc.axisXLebelGap) // -1 needed l += w + lc.axisXLebelGap
} }
} }
@ -199,6 +201,7 @@ func (lc *LineChart) calcLabelY() {
} }
func (lc *LineChart) calcLayout() { func (lc *LineChart) calcLayout() {
// set datalabels if it is not provided
if lc.DataLabels == nil || len(lc.DataLabels) == 0 { if lc.DataLabels == nil || len(lc.DataLabels) == 0 {
lc.DataLabels = make([]string, len(lc.Data)) lc.DataLabels = make([]string, len(lc.Data))
for i := range lc.Data { for i := range lc.Data {

View File

@ -20,8 +20,8 @@ func main() {
strs := []string{ strs := []string{
"[0] github.com/gizak/termui", "[0] github.com/gizak/termui",
"[1] editbox.go", "[1] 你好,世界",
"[2] iterrupt.go", "[2] こんにちは世界",
"[3] keyboard.go", "[3] keyboard.go",
"[4] output.go", "[4] output.go",
"[5] random_out.go", "[5] random_out.go",

View File

@ -24,11 +24,11 @@ func main() {
par0.Y = 1 par0.Y = 1
par0.HasBorder = false par0.HasBorder = false
par1 := termui.NewPar("Simple Text") par1 := termui.NewPar("你好,世界。")
par1.Height = 3 par1.Height = 3
par1.Width = 17 par1.Width = 17
par1.X = 20 par1.X = 20
par1.Border.Label = "Label" par1.Border.Label = "标签"
par2 := termui.NewPar("Simple text\nwith label. It can be multilined with \\n or break automatically") par2 := termui.NewPar("Simple text\nwith label. It can be multilined with \\n or break automatically")
par2.Height = 5 par2.Height = 5

View File

@ -9,6 +9,7 @@ import rw "github.com/mattn/go-runewidth"
/* ---------------Port from termbox-go --------------------- */ /* ---------------Port from termbox-go --------------------- */
// Attribute is printable cell's color and style.
type Attribute uint16 type Attribute uint16
const ( const (
@ -49,9 +50,16 @@ func trimStr2Runes(s string, w int) []rune {
return []rune{} return []rune{}
} }
sw := rw.StringWidth(s) sw := rw.StringWidth(s)
if sw+dotw >= w { if sw > w {
return []rune(rw.Truncate(s, w, "…")) return []rune(rw.Truncate(s, w, dot))
} else {
return []rune(rw.Truncate(s, w, ""))
} }
return str2runes(s) //[]rune(rw.Truncate(s, w, ""))
}
func strWidth(s string) int {
return rw.StringWidth(s)
}
func charWidth(ch rune) int {
return rw.RuneWidth(ch)
} }

View File

@ -4,7 +4,11 @@
package termui package termui
import "testing" import (
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestStr2Rune(t *testing.T) { func TestStr2Rune(t *testing.T) {
s := "你好,世界." s := "你好,世界."
@ -13,3 +17,42 @@ func TestStr2Rune(t *testing.T) {
t.Error() t.Error()
} }
} }
func TestWidth(t *testing.T) {
s0 := "つのだ☆HIRO"
s1 := "11111111111"
spew.Dump(s0)
spew.Dump(s1)
// above not align for setting East Asian Ambiguous to wide!!
if strWidth(s0) != strWidth(s1) {
t.Error("str len failed")
}
len1 := []rune{'a', '2', '&', '「', 'オ', '。'} //will false: 'ᆵ', 'ᄚ', 'ᄒ'
for _, v := range len1 {
if charWidth(v) != 1 {
t.Error("len1 failed")
}
}
len2 := []rune{'漢', '字', '한', '자', '你', '好', 'だ', '。', '', '', '', 'ョ', '、', 'ヲ'}
for _, v := range len2 {
if charWidth(v) != 2 {
t.Error("len2 failed")
}
}
}
func TestTrim(t *testing.T) {
s := "つのだ☆HIRO"
if string(trimStr2Runes(s, 10)) != "つのだ☆HI"+dot {
t.Error("trim failed")
}
if string(trimStr2Runes(s, 11)) != "つのだ☆HIRO" {
t.Error("avoid tail trim failed")
}
if string(trimStr2Runes(s, 15)) != "つのだ☆HIRO" {
t.Error("avoid trim failed")
}
}

View File

@ -54,7 +54,8 @@ func (l *List) Buffer() []Point {
rs := str2runes(strings.Join(l.Items, "\n")) rs := str2runes(strings.Join(l.Items, "\n"))
i, j, k := 0, 0, 0 i, j, k := 0, 0, 0
for i < l.innerHeight && k < len(rs) { for i < l.innerHeight && k < len(rs) {
if rs[k] == '\n' || j == l.innerWidth { w := charWidth(rs[k])
if rs[k] == '\n' || j+w > l.innerWidth {
i++ i++
j = 0 j = 0
if rs[k] == '\n' { if rs[k] == '\n' {
@ -85,6 +86,7 @@ func (l *List) Buffer() []Point {
j := 0 j := 0
for _, vv := range rs { for _, vv := range rs {
w := charWidth(vv)
p := Point{} p := Point{}
p.X = l.innerX + j p.X = l.innerX + j
p.Y = l.innerY + i p.Y = l.innerY + i
@ -94,7 +96,7 @@ func (l *List) Buffer() []Point {
p.Fg = l.ItemFgColor p.Fg = l.ItemFgColor
ps = append(ps, p) ps = append(ps, p)
j++ j += w
} }
} }
} }

9
p.go
View File

@ -34,9 +34,12 @@ func (p *Par) Buffer() []Point {
rs := str2runes(p.Text) rs := str2runes(p.Text)
i, j, k := 0, 0, 0 i, j, k := 0, 0, 0
for i < p.innerHeight && k < len(rs) { for i < p.innerHeight && k < len(rs) {
if rs[k] == '\n' || j == p.innerWidth { // the width of char is about to print
w := charWidth(rs[k])
if rs[k] == '\n' || j+w > p.innerWidth {
i++ i++
j = 0 j = 0 // set x = 0
if rs[k] == '\n' { if rs[k] == '\n' {
k++ k++
} }
@ -62,7 +65,7 @@ func (p *Par) Buffer() []Point {
ps = append(ps, pi) ps = append(ps, pi)
k++ k++
j++ j += w
} }
return ps return ps
} }

View File

@ -111,7 +111,9 @@ func (sl *Sparklines) Buffer() []Point {
if l.Title != "" { if l.Title != "" {
rs := trimStr2Runes(l.Title, sl.innerWidth) rs := trimStr2Runes(l.Title, sl.innerWidth)
for oftX, v := range rs { oftX := 0
for _, v := range rs {
w := charWidth(v)
p := Point{} p := Point{}
p.Ch = v p.Ch = v
p.Fg = l.TitleColor p.Fg = l.TitleColor
@ -119,6 +121,7 @@ func (sl *Sparklines) Buffer() []Point {
p.X = sl.innerX + oftX p.X = sl.innerX + oftX
p.Y = sl.innerY + oftY p.Y = sl.innerY + oftY
ps = append(ps, p) ps = append(ps, p)
oftX += w
} }
} }
@ -130,7 +133,7 @@ func (sl *Sparklines) Buffer() []Point {
p := Point{} p := Point{}
p.X = sl.innerX + j p.X = sl.innerX + j
p.Y = sl.innerY + oftY + l.Height - jj p.Y = sl.innerY + oftY + l.Height - jj
p.Ch = ' ' //sparks[7] p.Ch = ' ' // => sparks[7]
p.Bg = l.LineColor p.Bg = l.LineColor
//p.Bg = sl.BgColor //p.Bg = sl.BgColor
ps = append(ps, p) ps = append(ps, p)