wrap.go 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package text
  2. import (
  3. "bytes"
  4. "math"
  5. )
  6. var (
  7. nl = []byte{'\n'}
  8. sp = []byte{' '}
  9. )
  10. const defaultPenalty = 1e5
  11. // Wrap wraps s into a paragraph of lines of length lim, with minimal
  12. // raggedness.
  13. func Wrap(s string, lim int) string {
  14. return string(WrapBytes([]byte(s), lim))
  15. }
  16. // WrapBytes wraps b into a paragraph of lines of length lim, with minimal
  17. // raggedness.
  18. func WrapBytes(b []byte, lim int) []byte {
  19. words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
  20. var lines [][]byte
  21. for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
  22. lines = append(lines, bytes.Join(line, sp))
  23. }
  24. return bytes.Join(lines, nl)
  25. }
  26. // WrapWords is the low-level line-breaking algorithm, useful if you need more
  27. // control over the details of the text wrapping process. For most uses, either
  28. // Wrap or WrapBytes will be sufficient and more convenient.
  29. //
  30. // WrapWords splits a list of words into lines with minimal "raggedness",
  31. // treating each byte as one unit, accounting for spc units between adjacent
  32. // words on each line, and attempting to limit lines to lim units. Raggedness
  33. // is the total error over all lines, where error is the square of the
  34. // difference of the length of the line and lim. Too-long lines (which only
  35. // happen when a single word is longer than lim units) have pen penalty units
  36. // added to the error.
  37. func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
  38. n := len(words)
  39. length := make([][]int, n)
  40. for i := 0; i < n; i++ {
  41. length[i] = make([]int, n)
  42. length[i][i] = len(words[i])
  43. for j := i + 1; j < n; j++ {
  44. length[i][j] = length[i][j-1] + spc + len(words[j])
  45. }
  46. }
  47. nbrk := make([]int, n)
  48. cost := make([]int, n)
  49. for i := range cost {
  50. cost[i] = math.MaxInt32
  51. }
  52. for i := n - 1; i >= 0; i-- {
  53. if length[i][n-1] <= lim || i == n-1 {
  54. cost[i] = 0
  55. nbrk[i] = n
  56. } else {
  57. for j := i + 1; j < n; j++ {
  58. d := lim - length[i][j-1]
  59. c := d*d + cost[j]
  60. if length[i][j-1] > lim {
  61. c += pen // too-long lines get a worse penalty
  62. }
  63. if c < cost[i] {
  64. cost[i] = c
  65. nbrk[i] = j
  66. }
  67. }
  68. }
  69. }
  70. var lines [][][]byte
  71. i := 0
  72. for i < n {
  73. lines = append(lines, words[i:nbrk[i]])
  74. i = nbrk[i]
  75. }
  76. return lines
  77. }