init
diff --git a/wrap.go b/wrap.go new file mode 100755 index 0000000..a98375f --- /dev/null +++ b/wrap.go
@@ -0,0 +1,80 @@ +package text + +import ( + "bytes" + "math" +) + +var ( + nl = []byte{'\n'} + sp = []byte{' '} +) + +// Wrap calls WrapBytes and returns the result. +func Wrap(s string, lim int) string { + return string(WrapBytes([]byte(s), lim)) +} + +// WrapBytes splits b on spaces, calls WrapWords, then joins the resulting +// lines with NL. Adjacent spaces are treated as surrounding words of zero +// length. +func WrapBytes(b []byte, lim int) []byte { + words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp) + var lines [][]byte + for _, line := range WrapWords(words, lim) { + lines = append(lines, bytes.Join(line, sp)) + } + return bytes.Join(lines, nl) +} + +// WrapWords splits a list of words into lines with minimal "raggedness", +// treating each byte as one unit, accounting for one unit between adjacent +// words on each line, and attempting to limit lines to lim units. Raggedness +// is the total error over all lines, where error is the square of the +// difference of the length of the line and lim. Too-long lines (which only +// happen when a single word is longer than lim units) get an extra penalty +// added to the error. +func WrapWords(words [][]byte, lim int) [][][]byte { + n := len(words) + + length := make([][]int, n) + for i := 0; i < n; i++ { + length[i] = make([]int, n) + length[i][i] = len(words[i]) + for j := i + 1; j < n; j++ { + length[i][j] = length[i][j-1] + 1 + len(words[j]) + } + } + + nbrk := make([]int, n) + cost := make([]int, n) + for i := range cost { + cost[i] = math.MaxInt32 + } + for i := n - 1; i >= 0; i-- { + if length[i][n-1] <= lim { + cost[i] = 0 + nbrk[i] = n + } else { + for j := i + 1; j < n; j++ { + d := lim - length[i][j-1] + c := d*d + cost[j] + if length[i][j-1] > lim { + c += 1e5 // too-long lines get a worse penalty + } + if c < cost[i] { + cost[i] = c + nbrk[i] = j + } + } + } + } + + var lines [][][]byte + i := 0 + for i < n { + lines = append(lines, words[i:nbrk[i]]) + i = nbrk[i] + } + return lines +}
diff --git a/wrap_test.go b/wrap_test.go new file mode 100644 index 0000000..0adbcb4 --- /dev/null +++ b/wrap_test.go
@@ -0,0 +1,44 @@ +package text + +import ( + "bytes" + "testing" +) + +var text = "The quick brown fox jumps over the lazy dog." + +func TestWrap(t *testing.T) { + exp := [][][]byte{ + {[]byte("The"), []byte("quick"), []byte("brown"), []byte("fox")}, + {[]byte("jumps"), []byte("over"), []byte("the"), []byte("lazy"), []byte("dog.")}, + } + words := bytes.Split([]byte(text), sp) + got := WrapWords(words, 24) + if len(exp) != len(got) { + t.Fail() + } + for i := range exp { + if len(exp[i]) != len(got[i]) { + t.Fail() + } + for j := range exp[i] { + if string(exp[i][j]) != string(got[i][j]) { + t.Fatal(i, exp[i][j], got[i][j]) + } + } + } +} + +func TestWrapNarrow(t *testing.T) { + exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog." + if Wrap(text, 5) != exp { + t.Fail() + } +} + +func TestWrapOneLine(t *testing.T) { + exp := "The quick brown fox jumps over the lazy dog." + if Wrap(text, 500) != exp { + t.Fail() + } +}