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()
+	}
+}