Plex-FAWE/worldedit-core/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java

272 lines
9.9 KiB
Java

/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.formatting;
import com.google.common.base.Joiner;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class ColorCodeBuilder {
private static final ColorCodeBuilder instance = new ColorCodeBuilder();
private static final Joiner newLineJoiner = Joiner.on("\n");
public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 47;
/**
* Convert a message into color-coded text.
*
* @param message the message
* @return a list of lines
*/
public String[] build(StyledFragment message) {
StringBuilder builder = new StringBuilder();
buildFragment(builder, message, message.getStyle(), new StyleSet());
return builder.toString().split("\r?\n");
}
/**
* Build a fragment.
*
* @param builder the string builder
* @param message the message
* @param parentStyle the parent style
* @param lastStyle the last style
* @return the last style used
*/
private StyleSet buildFragment(StringBuilder builder, StyledFragment message, StyleSet parentStyle, StyleSet lastStyle) {
for (Fragment node : message.getChildren()) {
if (node instanceof StyledFragment) {
StyledFragment fragment = (StyledFragment) node;
lastStyle = buildFragment(
builder, fragment,
parentStyle.extend(message.getStyle()), lastStyle);
} else {
StyleSet style = parentStyle.extend(message.getStyle());
builder.append(getAdditive(style, lastStyle));
builder.append(node);
lastStyle = style;
}
}
return lastStyle;
}
/**
* Get the formatting codes.
*
* @param style the style
* @return the color codes
*/
public static String getFormattingCode(StyleSet style) {
StringBuilder builder = new StringBuilder();
if (style.isBold()) {
builder.append(Style.BOLD);
}
if (style.isItalic()) {
builder.append(Style.ITALIC);
}
if (style.isUnderline()) {
builder.append(Style.UNDERLINE);
}
if (style.isStrikethrough()) {
builder.append(Style.STRIKETHROUGH);
}
return builder.toString();
}
/**
* Get the formatting and color codes.
*
* @param style the style
* @return the color codes
*/
public static String getCode(StyleSet style) {
StringBuilder builder = new StringBuilder();
builder.append(getFormattingCode(style));
if (style.getColor() != null) {
builder.append(style.getColor());
}
return builder.toString();
}
/**
* Get the additional color codes needed to set the given style when the current
* style is the other given one.
*
* @param resetTo the style to reset to
* @param resetFrom the style to reset from
* @return the color codes
*/
public static String getAdditive(StyleSet resetTo, StyleSet resetFrom) {
if (!resetFrom.hasFormatting() && resetTo.hasFormatting()) {
StringBuilder builder = new StringBuilder();
builder.append(getFormattingCode(resetTo));
if (resetFrom.getColor() != resetTo.getColor()) {
builder.append(resetTo.getColor());
}
return builder.toString();
} else if (!resetFrom.hasEqualFormatting(resetTo) ||
(resetFrom.getColor() != null && resetTo.getColor() == null)) {
// Have to set reset code and add back all the formatting codes
return Style.RESET + getCode(resetTo);
} else {
if (resetFrom.getColor() != resetTo.getColor()) {
return String.valueOf(resetTo.getColor());
}
}
return "";
}
/**
* Word wrap the given text and maintain color codes throughout lines.
*
* <p>This is borrowed from Bukkit.</p>
*
* @param rawString the raw string
* @param lineLength the maximum line length
* @return a list of lines
*/
private String[] wordWrap(String rawString, int lineLength) {
// A null string is a single line
if (rawString == null) {
return new String[] {""};
}
// A string shorter than the lineWidth is a single line
if (rawString.length() <= lineLength && !rawString.contains("\n")) {
return new String[] {rawString};
}
char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination
StringBuilder word = new StringBuilder();
StringBuilder line = new StringBuilder();
List<String> lines = new LinkedList<>();
int lineColorChars = 0;
for (int i = 0; i < rawChars.length; i++) {
char c = rawChars[i];
// skip chat color modifiers
if (c == Style.COLOR_CHAR) {
word.append(Style.getByChar(rawChars[i + 1]));
lineColorChars += 2;
i++; // Eat the next character as we have already processed it
continue;
}
if (c == ' ' || c == '\n') {
if (line.length() == 0 && word.length() > lineLength) { // special case: extremely long word begins a line
String wordStr = word.toString();
String transformed;
if ((transformed = transform(wordStr)) != null) {
line.append(transformed);
} else {
lines.addAll(Arrays.asList(word.toString().split("(?<=\\G.{" + lineLength + "})")));
}
} else if (line.length() + word.length() - lineColorChars == lineLength) { // Line exactly the correct length...newline
line.append(' ');
line.append(word);
lines.add(line.toString());
line = new StringBuilder();
lineColorChars = 0;
} else if (line.length() + 1 + word.length() - lineColorChars > lineLength) { // Line too long...break the line
String wordStr = word.toString();
String transformed;
if (word.length() > lineLength && (transformed = transform(wordStr)) != null) {
if (line.length() + 1 + transformed.length() - lineColorChars > lineLength) {
lines.add(line.toString());
line = new StringBuilder(transformed);
lineColorChars = 0;
} else {
if (line.length() > 0) {
line.append(' ');
}
line.append(transformed);
}
} else {
for (String partialWord : wordStr.split("(?<=\\G.{" + lineLength + "})")) {
lines.add(line.toString());
line = new StringBuilder(partialWord);
}
lineColorChars = 0;
}
} else {
if (line.length() > 0) {
line.append(' ');
}
line.append(word);
}
word = new StringBuilder();
if (c == '\n') { // Newline forces the line to flush
lines.add(line.toString());
line = new StringBuilder();
}
} else {
word.append(c);
}
}
if(line.length() > 0) { // Only add the last line if there is anything to add
lines.add(line.toString());
}
// Iterate over the wrapped lines, applying the last color from one line to the beginning of the next
if (lines.get(0).isEmpty() || lines.get(0).charAt(0) != Style.COLOR_CHAR) {
lines.set(0, Style.WHITE + lines.get(0));
}
for (int i = 1; i < lines.size(); i++) {
final String pLine = lines.get(i-1);
final String subLine = lines.get(i);
char color = pLine.charAt(pLine.lastIndexOf(Style.COLOR_CHAR) + 1);
if (subLine.isEmpty() || subLine.charAt(0) != Style.COLOR_CHAR) {
lines.set(i, Style.getByChar(color) + subLine);
}
}
return lines.toArray(new String[lines.size()]);
}
/**
* Callback for transforming a word, such as a URL.
*
* @param word the word
* @return the transformed value, or null to do nothing
*/
protected String transform(String word) {
return null;
}
/**
* Convert the given styled fragment into color codes.
*
* @param fragment the fragment
* @return color codes
*/
public static String asColorCodes(StyledFragment fragment) {
return newLineJoiner.join(instance.build(fragment));
}
}