blob: 5e8c27d1688ca23bcc84118203ba4e2b17a48199 [file] [log] [blame] [edit]
package text
import (
"bytes"
"math"
"unicode/utf8"
)
var (
nl = []byte{'\n'}
sp = []byte{' '}
)
const defaultPenalty = 1e5
// Wrapper splits a list of words into lines with minimal
// "raggedness", attempting to limit lines to MaxWidth units.
//
// Raggedness is the total error over all lines, where error is
// the square of the difference of the length of the line and
// MaxWidth. Too-long lines have an extra penalty added to the
// error -- that is, it is much better for a line to be too short
// than too long.
//
// The zero value is a valid wrapper, ready to use.
type Wrapper struct {
// MaxWidth is the target for the maximum width of each line
// If 0, it uses 65 * Width(' ').
MaxWidth int
// Penalty is the extra penalty applied to the error
// for lines that would be too long.
// If 0, it uses 1e5 * Width(' ').
Penalty int
// Width computes the width (in arbitrary units)
// of the text in b.
// If nil, it uses utf8.RuneCount.
Width func(b []byte) int
}
// Wrap wraps s into a paragraph of lines of length lim, with minimal
// raggedness.
func Wrap(s string, lim int) string {
return string(WrapBytes([]byte(s), lim))
}
// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
// raggedness.
func WrapBytes(b []byte, lim int) []byte {
w := new(Wrapper)
w.MaxWidth = lim
return w.Wrap(b)
}
// WrapWords is superceded by the Wrapper type.
// It ignores spc.
// It should not be used in new code.
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
w := new(Wrapper)
w.MaxWidth = lim
w.Penalty = pen
return w.wrap(words)
}
// Wrap wraps b into a paragraph of lines of length lim, with minimal
// raggedness.
func (w *Wrapper) Wrap(b []byte) []byte {
words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
var lines [][]byte
for _, line := range w.wrap(words) {
lines = append(lines, bytes.Join(line, sp))
}
return bytes.Join(lines, nl)
}
func (w *Wrapper) wrap(words [][]byte) [][][]byte {
n := len(words)
width := w.Width
if width == nil {
width = utf8.RuneCount
}
spc := width(sp)
wwid := make([]int, len(words))
for i, word := range words {
wwid[i] = width(word)
}
pen := w.Penalty
if pen == 0 {
pen = defaultPenalty * spc
}
lim := w.MaxWidth
if lim == 0 {
lim = 65 * spc
}
length := make([][]int, n)
for i := 0; i < n; i++ {
length[i] = make([]int, n)
length[i][i] = wwid[i]
for j := i + 1; j < n; j++ {
length[i][j] = length[i][j-1] + spc + wwid[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 || i == n-1 {
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 += pen // 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
}