New text justification (some tests missing)
[fanfix.git] / src / be / nikiroo / utils / StringJustifier.java
CommitLineData
cc3e7291
NR
1/*
2 * This file was taken from:
3 * Jexer - Java Text User Interface
4 *
5 * The MIT License (MIT)
6 *
7 * Copyright (C) 2017 Kevin Lamonte
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a
10 * copy of this software and associated documentation files (the "Software"),
11 * to deal in the Software without restriction, including without limitation
12 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 * and/or sell copies of the Software, and to permit persons to whom the
14 * Software is furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 * DEALINGS IN THE SOFTWARE.
26 *
27 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
28 * @version 1
29 */
30package be.nikiroo.utils;
31
32import java.util.LinkedList;
33import java.util.List;
34
35/**
36 * StringJustifier contains methods to convert one or more long lines of strings
37 * into justified text paragraphs.
38 */
39class StringJustifier {
40
41 /**
42 * Left-justify a string into a list of lines.
43 *
44 * @param str
45 * the string
46 * @param n
47 * the maximum number of characters in a line
48 * @return the list of lines
49 */
50 static List<String> left(final String str, final int n) {
51 List<String> result = new LinkedList<String>();
52
53 /*
54 * General procedure:
55 *
56 * 1. Split on '\n' into paragraphs.
57 *
58 * 2. Scan each line, noting the position of the last
59 * beginning-of-a-word.
60 *
61 * 3. Chop at the last #2 if the next beginning-of-a-word exceeds n.
62 *
63 * 4. Return the lines.
64 */
65
66 String[] rawLines = str.split("\n");
67 for (int i = 0; i < rawLines.length; i++) {
68 StringBuilder line = new StringBuilder();
69 StringBuilder word = new StringBuilder();
70 boolean inWord = false;
71 for (int j = 0; j < rawLines[i].length(); j++) {
72 char ch = rawLines[i].charAt(j);
73 if ((ch == ' ') || (ch == '\t')) {
74 if (inWord == true) {
75 // We have just transitioned from a word to
76 // whitespace. See if we have enough space to add
77 // the word to the line.
78 if (word.length() + line.length() > n) {
79 // This word will exceed the line length. Wrap
80 // at it instead.
81 result.add(line.toString());
82 line = new StringBuilder();
83 }
84 if ((word.toString().startsWith(" "))
85 && (line.length() == 0)) {
86 line.append(word.substring(1));
87 } else {
88 line.append(word);
89 }
90 word = new StringBuilder();
91 word.append(ch);
92 inWord = false;
93 } else {
94 // We are in the whitespace before another word. Do
95 // nothing.
96 }
97 } else {
98 if (inWord == true) {
99 // We are appending to a word.
100 word.append(ch);
101 } else {
102 // We have transitioned from whitespace to a word.
103 word.append(ch);
104 inWord = true;
105 }
106 }
107 } // for (int j = 0; j < rawLines[i].length(); j++)
108
109 if (word.length() + line.length() > n) {
110 // This word will exceed the line length. Wrap at it
111 // instead.
112 result.add(line.toString());
113 line = new StringBuilder();
114 }
115 if ((word.toString().startsWith(" ")) && (line.length() == 0)) {
116 line.append(word.substring(1));
117 } else {
118 line.append(word);
119 }
120 result.add(line.toString());
121 } // for (int i = 0; i < rawLines.length; i++) {
122
123 return result;
124 }
125
126 /**
127 * Right-justify a string into a list of lines.
128 *
129 * @param str
130 * the string
131 * @param n
132 * the maximum number of characters in a line
133 * @return the list of lines
134 */
135 static List<String> right(final String str, final int n) {
136 List<String> result = new LinkedList<String>();
137
138 /*
139 * Same as left(), but preceed each line with spaces to make it n chars
140 * long.
141 */
142 List<String> lines = left(str, n);
143 for (String line : lines) {
144 StringBuilder sb = new StringBuilder();
145 for (int i = 0; i < n - line.length(); i++) {
146 sb.append(' ');
147 }
148 sb.append(line);
149 result.add(sb.toString());
150 }
151
152 return result;
153 }
154
155 /**
156 * Center a string into a list of lines.
157 *
158 * @param str
159 * the string
160 * @param n
161 * the maximum number of characters in a line
162 * @return the list of lines
163 */
164 static List<String> center(final String str, final int n) {
165 List<String> result = new LinkedList<String>();
166
167 /*
168 * Same as left(), but preceed/succeed each line with spaces to make it
169 * n chars long.
170 */
171 List<String> lines = left(str, n);
172 for (String line : lines) {
173 StringBuilder sb = new StringBuilder();
174 int l = (n - line.length()) / 2;
175 int r = n - line.length() - l;
176 for (int i = 0; i < l; i++) {
177 sb.append(' ');
178 }
179 sb.append(line);
180 for (int i = 0; i < r; i++) {
181 sb.append(' ');
182 }
183 result.add(sb.toString());
184 }
185
186 return result;
187 }
188
189 /**
190 * Fully-justify a string into a list of lines.
191 *
192 * @param str
193 * the string
194 * @param n
195 * the maximum number of characters in a line
196 * @return the list of lines
197 */
198 static List<String> full(final String str, final int n) {
199 List<String> result = new LinkedList<String>();
200
201 /*
202 * Same as left(), but insert spaces between words to make each line n
203 * chars long. The "algorithm" here is pretty dumb: it performs a split
204 * on space and then re-inserts multiples of n between words.
205 */
206 List<String> lines = left(str, n);
207 for (int lineI = 0; lineI < lines.size() - 1; lineI++) {
208 String line = lines.get(lineI);
209 String[] words = line.split(" ");
210 if (words.length > 1) {
211 int charCount = 0;
212 for (int i = 0; i < words.length; i++) {
213 charCount += words[i].length();
214 }
215 int spaceCount = n - charCount;
216 int q = spaceCount / (words.length - 1);
217 int r = spaceCount % (words.length - 1);
218 StringBuilder sb = new StringBuilder();
219 for (int i = 0; i < words.length - 1; i++) {
220 sb.append(words[i]);
221 for (int j = 0; j < q; j++) {
222 sb.append(' ');
223 }
224 if (r > 0) {
225 sb.append(' ');
226 r--;
227 }
228 }
229 for (int j = 0; j < r; j++) {
230 sb.append(' ');
231 }
232 sb.append(words[words.length - 1]);
233 result.add(sb.toString());
234 } else {
235 result.add(line);
236 }
237 }
238 if (lines.size() > 0) {
239 result.add(lines.get(lines.size() - 1));
240 }
241
242 return result;
243 }
244}