1
2
3
4
5 package comment
6
7 import (
8 "bytes"
9 "fmt"
10 "sort"
11 "strings"
12 "unicode/utf8"
13 )
14
15
16 type textPrinter struct {
17 *Printer
18 long strings.Builder
19 prefix string
20 codePrefix string
21 width int
22 }
23
24
25
26 func (p *Printer) Text(d *Doc) []byte {
27 tp := &textPrinter{
28 Printer: p,
29 prefix: p.TextPrefix,
30 codePrefix: p.TextCodePrefix,
31 width: p.TextWidth,
32 }
33 if tp.codePrefix == "" {
34 tp.codePrefix = p.TextPrefix + "\t"
35 }
36 if tp.width == 0 {
37 tp.width = 80 - utf8.RuneCountInString(tp.prefix)
38 }
39
40 var out bytes.Buffer
41 for i, x := range d.Content {
42 if i > 0 && blankBefore(x) {
43 out.WriteString(tp.prefix)
44 writeNL(&out)
45 }
46 tp.block(&out, x)
47 }
48 anyUsed := false
49 for _, def := range d.Links {
50 if def.Used {
51 anyUsed = true
52 break
53 }
54 }
55 if anyUsed {
56 writeNL(&out)
57 for _, def := range d.Links {
58 if def.Used {
59 fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL)
60 }
61 }
62 }
63 return out.Bytes()
64 }
65
66
67
68 func writeNL(out *bytes.Buffer) {
69
70 data := out.Bytes()
71 n := 0
72 for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') {
73 n++
74 }
75 if n > 0 {
76 out.Truncate(len(data) - n)
77 }
78 out.WriteByte('\n')
79 }
80
81
82 func (p *textPrinter) block(out *bytes.Buffer, x Block) {
83 switch x := x.(type) {
84 default:
85 fmt.Fprintf(out, "?%T\n", x)
86
87 case *Paragraph:
88 out.WriteString(p.prefix)
89 p.text(out, "", x.Text)
90
91 case *Heading:
92 out.WriteString(p.prefix)
93 out.WriteString("# ")
94 p.text(out, "", x.Text)
95
96 case *Code:
97 text := x.Text
98 for text != "" {
99 var line string
100 line, text, _ = strings.Cut(text, "\n")
101 if line != "" {
102 out.WriteString(p.codePrefix)
103 out.WriteString(line)
104 }
105 writeNL(out)
106 }
107
108 case *List:
109 loose := x.BlankBetween()
110 for i, item := range x.Items {
111 if i > 0 && loose {
112 out.WriteString(p.prefix)
113 writeNL(out)
114 }
115 out.WriteString(p.prefix)
116 out.WriteString(" ")
117 if item.Number == "" {
118 out.WriteString(" - ")
119 } else {
120 out.WriteString(item.Number)
121 out.WriteString(". ")
122 }
123 for i, blk := range item.Content {
124 const fourSpace = " "
125 if i > 0 {
126 writeNL(out)
127 out.WriteString(p.prefix)
128 out.WriteString(fourSpace)
129 }
130 p.text(out, fourSpace, blk.(*Paragraph).Text)
131 }
132 }
133 }
134 }
135
136
137 func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) {
138 p.oneLongLine(&p.long, x)
139 words := strings.Fields(p.long.String())
140 p.long.Reset()
141
142 var seq []int
143 if p.width < 0 || len(words) == 0 {
144 seq = []int{0, len(words)}
145 } else {
146 seq = wrap(words, p.width-utf8.RuneCountInString(indent))
147 }
148 for i := 0; i+1 < len(seq); i++ {
149 if i > 0 {
150 out.WriteString(p.prefix)
151 out.WriteString(indent)
152 }
153 for j, w := range words[seq[i]:seq[i+1]] {
154 if j > 0 {
155 out.WriteString(" ")
156 }
157 out.WriteString(w)
158 }
159 writeNL(out)
160 }
161 }
162
163
164
165
166 func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) {
167 for _, t := range x {
168 switch t := t.(type) {
169 case Plain:
170 out.WriteString(string(t))
171 case Italic:
172 out.WriteString(string(t))
173 case *Link:
174 p.oneLongLine(out, t.Text)
175 case *DocLink:
176 p.oneLongLine(out, t.Text)
177 }
178 }
179 }
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196 func wrap(words []string, max int) (seq []int) {
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 type score struct {
228 hi int64
229 lo int64
230 }
231 add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} }
232 cmp := func(s, t score) int {
233 switch {
234 case s.hi < t.hi:
235 return -1
236 case s.hi > t.hi:
237 return +1
238 case s.lo < t.lo:
239 return -1
240 case s.lo > t.lo:
241 return +1
242 }
243 return 0
244 }
245
246
247
248 total := make([]int, len(words)+1)
249 total[0] = 0
250 for i, s := range words {
251 total[1+i] = total[i] + utf8.RuneCountInString(s) + 1
252 }
253
254
255 weight := func(i, j int) score {
256
257 n := total[j] - 1 - total[i]
258 if j == len(words) && n <= max {
259 return score{0, 0}
260 }
261
262
263
264
265
266 p := wrapPenalty(words[j-1])
267 v := int64(max-n) * int64(max-n)
268 if n > max {
269 return score{v, p}
270 }
271 return score{0, v + p}
272 }
273
274
275
276
277 f := []score{{0, 0}}
278 g := func(i, j int) score { return add(f[i], weight(i, j)) }
279
280 bridge := func(a, b, c int) bool {
281 k := c + sort.Search(len(words)+1-c, func(k int) bool {
282 k += c
283 return cmp(g(a, k), g(b, k)) > 0
284 })
285 if k > len(words) {
286 return true
287 }
288 return cmp(g(c, k), g(b, k)) <= 0
289 }
290
291
292 d := make([]int, 1, len(words))
293 d[0] = 0
294 bestleft := make([]int, 1, len(words))
295 bestleft[0] = -1
296 for m := 1; m < len(words); m++ {
297 f = append(f, g(d[0], m))
298 bestleft = append(bestleft, d[0])
299 for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
300 d = d[1:]
301 }
302 for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) {
303 d = d[:len(d)-1]
304 }
305 if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 {
306 d = append(d, m)
307
308
309
310 if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
311 d = d[1:]
312 }
313 }
314 }
315 bestleft = append(bestleft, d[0])
316
317
318 n := 1
319 for m := len(words); m > 0; m = bestleft[m] {
320 n++
321 }
322 seq = make([]int, n)
323 for m := len(words); m > 0; m = bestleft[m] {
324 n--
325 seq[n] = m
326 }
327 return seq
328 }
329
330
331 func wrapPenalty(s string) int64 {
332 switch s[len(s)-1] {
333 case '.', ',', ':', ';':
334 return 0
335 }
336 return 64
337 }
338
View as plain text