BlockBookBinder/src/main/java/nl/andrewlalis/blockbookbinder/model/build/BookBuilder.java

130 lines
4.0 KiB
Java

package nl.andrewlalis.blockbookbinder.model.build;
import nl.andrewlalis.blockbookbinder.model.Book;
import nl.andrewlalis.blockbookbinder.model.BookPage;
import nl.andrewlalis.blockbookbinder.util.CharWidthMapper;
import nl.andrewlalis.blockbookbinder.util.ApplicationProperties;
import java.util.ArrayList;
import java.util.List;
public class BookBuilder {
/**
* Builds a full book of pages from the given source text.
* @param source The source text to convert.
* @return A book containing the source text formatted for a minecraft book.
*/
public Book build(String source) {
final int maxLines = ApplicationProperties.getIntProp("book.page_max_lines");
List<String> lines = this.convertSourceToLines(this.cleanSource(source));
Book book = new Book();
BookPage page = new BookPage();
int currentPageLineCount = 0;
for (String line : lines) {
page.addLine(line);
currentPageLineCount++;
if (currentPageLineCount == maxLines) {
book.addPage(page);
page = new BookPage();
currentPageLineCount = 0;
}
}
if (page.hasContent()) {
book.addPage(page);
}
return book;
}
/**
* Cleans a given source text, by removing superfluous newlines that won't
* look too nice in minecraft text, and by erasing manually-imposed single
* newlines that may be in the source text to format it in a normal editor.
* @param source The source text.
* @return The cleaned string.
*/
public String cleanSource(String source) {
return source.trim()
.replaceAll("(?>\\v)+(\\v)", "\n\n") // Replace large chunks of newline with just two.
.replaceAll("\\S\n\\S", " ") // Unwrap previously-imposed single-line wrapping.
.replaceAll("\t", " ") // Replace tabs with single-spaces, due to space constraints.
.replaceAll(" [ ]+", " "); // Remove any superfluous spaces.
}
/**
* Converts the given source string into a formatted list of lines that can
* be copied to a minecraft book.
* @param source The source string.
* @return A list of lines.
*/
private List<String> convertSourceToLines(String source) {
List<String> lines = new ArrayList<>();
final char[] sourceChars = source.toCharArray();
final int maxLinePixelWidth = ApplicationProperties.getIntProp("book.page_max_width");
int sourceIndex = 0;
StringBuilder lineBuilder = new StringBuilder(64);
int linePixelWidth = 0;
StringBuilder symbolBuilder = new StringBuilder(64);
while (sourceIndex < sourceChars.length) {
final char c = sourceChars[sourceIndex];
sourceIndex++;
symbolBuilder.setLength(0);
symbolBuilder.append(c);
int symbolWidth = CharWidthMapper.getInstance().getWidth(c);
// Since there's a 1-pixel gap between characters, add it to the width if this isn't the first char.
if (lineBuilder.length() > 0) {
symbolWidth++;
}
// If we encounter a non-newline whitespace at the beginning of the line, skip it.
if (c == ' ' && lineBuilder.length() == 0) {
continue;
}
// If we encounter a newline, immediately skip to a new line.
if (c == '\n') {
lines.add(lineBuilder.toString());
lineBuilder.setLength(0);
linePixelWidth = 0;
continue;
}
// If we encounter a word, keep accepting characters until we reach the end.
if (Character.isLetterOrDigit(c)) {
while (
sourceIndex < sourceChars.length
&& Character.isLetterOrDigit(sourceChars[sourceIndex])
) {
char nextChar = sourceChars[sourceIndex];
symbolBuilder.append(nextChar);
symbolWidth += 1 + CharWidthMapper.getInstance().getWidth(nextChar);
sourceIndex++;
}
}
final String symbol = symbolBuilder.toString();
// Check if we need to go to the next line to fit the symbol.
if (linePixelWidth + symbolWidth > maxLinePixelWidth) {
lines.add(lineBuilder.toString());
lineBuilder.setLength(0);
linePixelWidth = 0;
}
// Finally, append the symbol.
lineBuilder.append(symbol);
linePixelWidth += symbolWidth;
}
// Append any remaining text.
if (lineBuilder.length() > 0) {
lines.add(lineBuilder.toString());
}
return lines;
}
}