1 package text
2
3 import (
4 "strings"
5 "unicode/utf8"
6 )
7
8
9
10
11
12
13 func WrapHard(str string, wrapLen int) string {
14 if wrapLen <= 0 {
15 return ""
16 }
17 str = strings.Replace(str, "\t", " ", -1)
18 sLen := utf8.RuneCountInString(str)
19 if sLen <= wrapLen {
20 return str
21 }
22
23 out := &strings.Builder{}
24 out.Grow(sLen + (sLen / wrapLen))
25 for idx, paragraph := range strings.Split(str, "\n\n") {
26 if idx > 0 {
27 out.WriteString("\n\n")
28 }
29 wrapHard(paragraph, wrapLen, out)
30 }
31
32 return out.String()
33 }
34
35
36
37
38
39
40
41 func WrapSoft(str string, wrapLen int) string {
42 if wrapLen <= 0 {
43 return ""
44 }
45 str = strings.Replace(str, "\t", " ", -1)
46 sLen := utf8.RuneCountInString(str)
47 if sLen <= wrapLen {
48 return str
49 }
50
51 out := &strings.Builder{}
52 out.Grow(sLen + (sLen / wrapLen))
53 for idx, paragraph := range strings.Split(str, "\n\n") {
54 if idx > 0 {
55 out.WriteString("\n\n")
56 }
57 wrapSoft(paragraph, wrapLen, out)
58 }
59
60 return out.String()
61 }
62
63
64
65
66
67
68 func WrapText(str string, wrapLen int) string {
69 if wrapLen <= 0 {
70 return ""
71 }
72
73 var out strings.Builder
74 sLen := utf8.RuneCountInString(str)
75 out.Grow(sLen + (sLen / wrapLen))
76 lineIdx, isEscSeq, lastEscSeq := 0, false, ""
77 for _, char := range str {
78 if char == EscapeStartRune {
79 isEscSeq = true
80 lastEscSeq = ""
81 }
82 if isEscSeq {
83 lastEscSeq += string(char)
84 }
85
86 appendChar(char, wrapLen, &lineIdx, isEscSeq, lastEscSeq, &out)
87
88 if isEscSeq && char == EscapeStopRune {
89 isEscSeq = false
90 }
91 if lastEscSeq == EscapeReset {
92 lastEscSeq = ""
93 }
94 }
95 if lastEscSeq != "" && lastEscSeq != EscapeReset {
96 out.WriteString(EscapeReset)
97 }
98 return out.String()
99 }
100
101 func appendChar(char rune, wrapLen int, lineLen *int, inEscSeq bool, lastSeenEscSeq string, out *strings.Builder) {
102
103
104 if (*lineLen == wrapLen && !inEscSeq && char != '\n') || (char == '\n') {
105 if lastSeenEscSeq != "" {
106
107
108 out.WriteString(EscapeReset)
109 out.WriteRune('\n')
110 out.WriteString(lastSeenEscSeq)
111 } else {
112
113 out.WriteRune('\n')
114 }
115
116 *lineLen = 0
117 }
118
119
120 if char != '\n' {
121 out.WriteRune(char)
122
123
124 if !inEscSeq {
125 *lineLen++
126 }
127 }
128 }
129
130 func appendWord(word string, lineIdx *int, lastSeenEscSeq string, wrapLen int, out *strings.Builder) {
131 inEscSeq := false
132 for _, char := range word {
133 if char == EscapeStartRune {
134 inEscSeq = true
135 lastSeenEscSeq = ""
136 }
137 if inEscSeq {
138 lastSeenEscSeq += string(char)
139 }
140
141 appendChar(char, wrapLen, lineIdx, inEscSeq, lastSeenEscSeq, out)
142
143 if inEscSeq && char == EscapeStopRune {
144 inEscSeq = false
145 }
146 if lastSeenEscSeq == EscapeReset {
147 lastSeenEscSeq = ""
148 }
149 }
150 }
151
152 func extractOpenEscapeSeq(str string) string {
153 escapeSeq, inEscSeq := "", false
154 for _, char := range str {
155 if char == EscapeStartRune {
156 inEscSeq = true
157 escapeSeq = ""
158 }
159 if inEscSeq {
160 escapeSeq += string(char)
161 }
162 if char == EscapeStopRune {
163 inEscSeq = false
164 }
165 }
166 if escapeSeq == EscapeReset {
167 escapeSeq = ""
168 }
169 return escapeSeq
170 }
171
172 func terminateLine(wrapLen int, lineLen *int, lastSeenEscSeq string, out *strings.Builder) {
173 if *lineLen < wrapLen {
174 out.WriteString(strings.Repeat(" ", wrapLen-*lineLen))
175 }
176
177 if lastSeenEscSeq != "" {
178 out.WriteString(EscapeReset)
179 }
180 out.WriteRune('\n')
181 out.WriteString(lastSeenEscSeq)
182 *lineLen = 0
183 }
184
185 func terminateOutput(lastSeenEscSeq string, out *strings.Builder) {
186 if lastSeenEscSeq != "" && lastSeenEscSeq != EscapeReset && !strings.HasSuffix(out.String(), EscapeReset) {
187 out.WriteString(EscapeReset)
188 }
189 }
190
191 func wrapHard(paragraph string, wrapLen int, out *strings.Builder) {
192 lineLen, lastSeenEscSeq := 0, ""
193 words := strings.Fields(paragraph)
194 for wordIdx, word := range words {
195 escSeq := extractOpenEscapeSeq(word)
196 if escSeq != "" {
197 lastSeenEscSeq = escSeq
198 }
199 if lineLen > 0 {
200 out.WriteRune(' ')
201 lineLen++
202 }
203
204 wordLen := RuneWidthWithoutEscSequences(word)
205 if lineLen+wordLen <= wrapLen {
206 out.WriteString(word)
207 lineLen += wordLen
208 } else {
209 appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
210 }
211
212
213 if lineLen == wrapLen && wordIdx < len(words)-1 {
214 terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
215 }
216 }
217 terminateOutput(lastSeenEscSeq, out)
218 }
219
220 func wrapSoft(paragraph string, wrapLen int, out *strings.Builder) {
221 lineLen, lastSeenEscSeq := 0, ""
222 words := strings.Fields(paragraph)
223 for wordIdx, word := range words {
224 escSeq := extractOpenEscapeSeq(word)
225 if escSeq != "" {
226 lastSeenEscSeq = escSeq
227 }
228
229 spacing, spacingLen := wrapSoftSpacing(lineLen)
230 wordLen := RuneWidthWithoutEscSequences(word)
231 if lineLen+spacingLen+wordLen <= wrapLen {
232 out.WriteString(spacing)
233 out.WriteString(word)
234 lineLen += spacingLen + wordLen
235 } else {
236 lineLen = wrapSoftLastWordInLine(wrapLen, lineLen, lastSeenEscSeq, wordLen, word, out)
237 }
238
239
240 if lineLen == wrapLen && wordIdx < len(words)-1 {
241 terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
242 }
243 }
244 terminateOutput(lastSeenEscSeq, out)
245 }
246
247 func wrapSoftLastWordInLine(wrapLen int, lineLen int, lastSeenEscSeq string, wordLen int, word string, out *strings.Builder) int {
248 if lineLen > 0 {
249 terminateLine(wrapLen, &lineLen, lastSeenEscSeq, out)
250 }
251 if wordLen <= wrapLen {
252 out.WriteString(word)
253 lineLen = wordLen
254 } else {
255 appendWord(word, &lineLen, lastSeenEscSeq, wrapLen, out)
256 }
257 return lineLen
258 }
259
260 func wrapSoftSpacing(lineLen int) (string, int) {
261 spacing, spacingLen := "", 0
262 if lineLen > 0 {
263 spacing, spacingLen = " ", 1
264 }
265 return spacing, spacingLen
266 }
267
View as plain text