diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java b/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java new file mode 100644 index 000000000..c0ea43961 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/ColorCodeBuilder.java @@ -0,0 +1,275 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +import com.google.common.base.Joiner; + +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 wordWrap(builder.toString(), GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH); + } + + /** + * 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.toString()); + 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().toString()); + } + 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)) { + StringBuilder builder = new StringBuilder(); + // Have to set reset code and add back all the formatting codes + builder.append(Style.RESET); + builder.append(getCode(resetTo)); + return builder.toString(); + } else { + if (resetFrom.getColor() != resetTo.getColor()) { + return String.valueOf(resetTo.getColor()); + } + } + + return ""; + } + + /** + * Word wrap the given text and maintain color codes throughout lines. + * + *

This is borrowed from Bukkit.

+ * + * @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 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 { + for (String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})")) { + lines.add(partialWord); + } + } + } 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).length() == 0 || 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.length() == 0 || 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)); + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java b/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java new file mode 100644 index 000000000..03fa0b754 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/CommandListBox.java @@ -0,0 +1,45 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +public class CommandListBox extends MessageBox { + + private boolean first = true; + + /** + * Create a new box. + * + * @param title the title + */ + public CommandListBox(String title) { + super(title); + } + + public CommandListBox appendCommand(String alias, String description) { + if (!first) { + getContents().newLine(); + } + getContents().createFragment(Style.YELLOW_DARK).append(alias).append(": "); + getContents().append(description); + first = false; + return this; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java b/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java new file mode 100644 index 000000000..3d1add7f4 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/Fragment.java @@ -0,0 +1,92 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +/** + * A fragment of text. + */ +public class Fragment { + + private final StringBuilder builder = new StringBuilder(); + + Fragment() { + } + + public Fragment append(String str) { + builder.append(Style.stripColor(str)); + return this; + } + + public Fragment append(Object obj) { + append(String.valueOf(obj)); + return this; + } + + public Fragment append(StringBuffer sb) { + append(String.valueOf(sb)); + return this; + } + + public Fragment append(CharSequence s) { + append(String.valueOf(s)); + return this; + } + + public Fragment append(boolean b) { + append(String.valueOf(b)); + return this; + } + + public Fragment append(char c) { + append(String.valueOf(c)); + return this; + } + + public Fragment append(int i) { + append(String.valueOf(i)); + return this; + } + + public Fragment append(long lng) { + append(String.valueOf(lng)); + return this; + } + + public Fragment append(float f) { + append(String.valueOf(f)); + return this; + } + + public Fragment append(double d) { + append(String.valueOf(d)); + return this; + } + + public Fragment newLine() { + append("\n"); + return this; + } + + @Override + public String toString() { + return builder.toString(); + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java b/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java new file mode 100644 index 000000000..66cf81ab3 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/MessageBox.java @@ -0,0 +1,70 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Makes for a box with a border above and below. + */ +public class MessageBox extends StyledFragment { + + private final StyledFragment contents = new StyledFragment(); + + /** + * Create a new box. + */ + public MessageBox(String title) { + checkNotNull(title); + + int leftOver = ColorCodeBuilder.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH - title.length() - 2; + int leftSide = (int) Math.floor(leftOver * 1.0/3); + int rightSide = (int) Math.floor(leftOver * 2.0/3); + if (leftSide > 0) { + createFragment(Style.YELLOW).append(createBorder(leftSide)); + } + append(" "); + append(title); + append(" "); + if (rightSide > 0) { + createFragment(Style.YELLOW).append(createBorder(rightSide)); + } + newLine(); + append(contents); + } + + private String createBorder(int count) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < count; i++) { + builder.append("-"); + } + return builder.toString(); + } + + /** + * Get the internal contents. + * + * @return the contents + */ + public StyledFragment getContents() { + return contents; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/Style.java b/src/main/java/com/sk89q/worldedit/util/formatting/Style.java new file mode 100644 index 000000000..80774b743 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/Style.java @@ -0,0 +1,274 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +import com.google.common.collect.Maps; +import org.apache.commons.lang.Validate; + +import java.util.Map; +import java.util.regex.Pattern; + +/** + * All supported color values for chat. + * + *

From Bukkit.

+ */ +public enum Style { + /** + * Represents black + */ + BLACK('0', 0x00), + /** + * Represents dark blue + */ + BLUE_DARK('1', 0x1), + /** + * Represents dark green + */ + GREEN_DARK('2', 0x2), + /** + * Represents dark blue (aqua) + */ + CYAN_DARK('3', 0x3), + /** + * Represents dark red + */ + RED_DARK('4', 0x4), + /** + * Represents dark purple + */ + PURPLE_DARK('5', 0x5), + /** + * Represents gold + */ + YELLOW_DARK('6', 0x6), + /** + * Represents gray + */ + GRAY('7', 0x7), + /** + * Represents dark gray + */ + GRAY_DARK('8', 0x8), + /** + * Represents blue + */ + BLUE('9', 0x9), + /** + * Represents green + */ + GREEN('a', 0xA), + /** + * Represents aqua + */ + CYAN('b', 0xB), + /** + * Represents red + */ + RED('c', 0xC), + /** + * Represents light purple + */ + PURPLE('d', 0xD), + /** + * Represents yellow + */ + YELLOW('e', 0xE), + /** + * Represents white + */ + WHITE('f', 0xF), + /** + * Represents magical characters that change around randomly + */ + RANDOMIZE('k', 0x10, true), + /** + * Makes the text bold. + */ + BOLD('l', 0x11, true), + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH('m', 0x12, true), + /** + * Makes the text appear underlined. + */ + UNDERLINE('n', 0x13, true), + /** + * Makes the text italic. + */ + ITALIC('o', 0x14, true), + /** + * Resets all previous chat colors or formats. + */ + RESET('r', 0x15); + + /** + * The special character which prefixes all chat color codes. Use this if you need to dynamically + * convert color codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]"); + + private final int intCode; + private final char code; + private final boolean isFormat; + private final String toString; + private final static Map BY_ID = Maps.newHashMap(); + private final static Map BY_CHAR = Maps.newHashMap(); + + private Style(char code, int intCode) { + this(code, intCode, false); + } + + private Style(char code, int intCode, boolean isFormat) { + this.code = code; + this.intCode = intCode; + this.isFormat = isFormat; + this.toString = new String(new char[] {COLOR_CHAR, code}); + } + + /** + * Gets the char value associated with this color + * + * @return A char value of this color code + */ + public char getChar() { + return code; + } + + @Override + public String toString() { + return toString; + } + + /** + * Checks if this code is a format code as opposed to a color code. + * + * @return the if the code is a formatting code + */ + public boolean isFormat() { + return isFormat; + } + + /** + * Checks if this code is a color code as opposed to a format code. + * + * @return the if the code is a color + */ + public boolean isColor() { + return !isFormat && this != RESET; + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist + */ + public static Style getByChar(char code) { + return BY_CHAR.get(code); + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist + */ + public static Style getByChar(String code) { + Validate.notNull(code, "Code cannot be null"); + Validate.isTrue(code.length() > 0, "Code must have at least one char"); + + return BY_CHAR.get(code.charAt(0)); + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) { + if (input == null) { + return null; + } + + return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); + } + + /** + * Translates a string using an alternate color code character into a string that uses the internal + * ChatColor.COLOR_CODE color code character. The alternate color code character will only be replaced + * if it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r. + * + * @param altColorChar The alternate color code character to replace. Ex: & + * @param textToTranslate Text containing the alternate color code character. + * @return Text containing the ChatColor.COLOR_CODE color code character. + */ + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + for (int i = 0; i < b.length - 1; i++) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) { + b[i] = Style.COLOR_CHAR; + b[i+1] = Character.toLowerCase(b[i+1]); + } + } + return new String(b); + } + + /** + * Gets the ChatColors used at the end of the given input string. + * + * @param input Input string to retrieve the colors from. + * @return Any remaining ChatColors to pass onto the next line. + */ + public static String getLastColors(String input) { + String result = ""; + int length = input.length(); + + // Search backwards from the end as it is faster + for (int index = length - 1; index > -1; index--) { + char section = input.charAt(index); + if (section == COLOR_CHAR && index < length - 1) { + char c = input.charAt(index + 1); + Style color = getByChar(c); + + if (color != null) { + result = color.toString() + result; + + // Once we find a color or reset we can stop searching + if (color.isColor() || color.equals(RESET)) { + break; + } + } + } + } + + return result; + } + + static { + for (Style color : values()) { + BY_ID.put(color.intCode, color); + BY_CHAR.put(color.code, color); + } + } +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java b/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java new file mode 100644 index 000000000..35663fa9e --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/StyleSet.java @@ -0,0 +1,249 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +/** + * Represents set of styles, such as color, bold, etc. + */ +public class StyleSet { + + private Boolean bold; + private Boolean italic; + private Boolean underline; + private Boolean strikethrough; + private Style color; + + /** + * Create a new style set with no properties set. + */ + public StyleSet() { + } + + /** + * Create a new style set with the given styles. + * + *

{@link Style#RESET} will be ignored if provided.

+ * + * @param styles a list of styles + */ + public StyleSet(Style... styles) { + for (Style style : styles) { + if (style.isColor()) { + color = style; + } else if (style == Style.BOLD) { + bold = true; + } else if (style == Style.ITALIC) { + italic = true; + } else if (style == Style.UNDERLINE) { + underline = true; + } else if (style == Style.STRIKETHROUGH) { + strikethrough = true; + } + } + } + + /** + * Get whether this style set is bold. + * + * @return true, false, or null if unset + */ + public Boolean getBold() { + return bold; + } + + /** + * Get whether the text is bold. + * + * @return true if bold + */ + public boolean isBold() { + return getBold() != null && getBold() == true; + } + + /** + * Set whether the text is bold. + * + * @param bold true, false, or null to unset + */ + public void setBold(Boolean bold) { + this.bold = bold; + } + + /** + * Get whether this style set is italicized. + * + * @return true, false, or null if unset + */ + public Boolean getItalic() { + return italic; + } + + /** + * Get whether the text is italicized. + * + * @return true if italicized + */ + public boolean isItalic() { + return getItalic() != null && getItalic() == true; + } + + /** + * Set whether the text is italicized. + * + * @param italic false, or null to unset + */ + public void setItalic(Boolean italic) { + this.italic = italic; + } + + /** + * Get whether this style set is underlined. + * + * @return true, false, or null if unset + */ + public Boolean getUnderline() { + return underline; + } + + /** + * Get whether the text is underlined. + * + * @return true if underlined + */ + public boolean isUnderline() { + return getUnderline() != null && getUnderline() == true; + } + + /** + * Set whether the text is underline. + * + * @param underline false, or null to unset + */ + public void setUnderline(Boolean underline) { + this.underline = underline; + } + + /** + * Get whether this style set is stricken through. + * + * @return true, false, or null if unset + */ + public Boolean getStrikethrough() { + return strikethrough; + } + + /** + * Get whether the text is stricken through. + * + * @return true if there is strikethrough applied + */ + public boolean isStrikethrough() { + return getStrikethrough() != null && getStrikethrough() == true; + } + + /** + * Set whether the text is stricken through. + * + * @param strikethrough false, or null to unset + */ + public void setStrikethrough(Boolean strikethrough) { + this.strikethrough = strikethrough; + } + + /** + * Get the color of the text. + * + * @return true, false, or null if unset + */ + public Style getColor() { + return color; + } + + /** + * Set the color of the text. + * + * @param color the color + */ + public void setColor(Style color) { + this.color = color; + } + + /** + * Return whether text formatting (bold, italics, underline, strikethrough) is set. + * + * @return true if formatting is set + */ + public boolean hasFormatting() { + return getBold() != null || getItalic() != null + || getUnderline() != null || getStrikethrough() != null; + } + + /** + * Return where the text formatting of the given style set is different from + * that assigned to this one. + * + * @param other the other style set + * @return true if there is a difference + */ + public boolean hasEqualFormatting(StyleSet other) { + return getBold() == other.getBold() && getItalic() == other.getItalic() + && getUnderline() == other.getUnderline() && + getStrikethrough() == other.getStrikethrough(); + } + + /** + * Create a new instance with styles inherited from this one but with new styles + * from the given style set. + * + * @param style the style set + * @return a new style set instance + */ + public StyleSet extend(StyleSet style) { + StyleSet newStyle = clone(); + if (style.getBold() != null) { + newStyle.setBold(style.getBold()); + } + if (style.getItalic() != null) { + newStyle.setItalic(style.getItalic()); + } + if (style.getUnderline() != null) { + newStyle.setUnderline(style.getUnderline()); + } + if (style.getStrikethrough() != null) { + newStyle.setStrikethrough(style.getStrikethrough()); + } + if (style.getColor() != null) { + newStyle.setColor(style.getColor()); + } + return newStyle; + } + + @Override + public StyleSet clone() { + StyleSet style = new StyleSet(); + style.setBold(getBold()); + style.setItalic(getItalic()); + style.setUnderline(getUnderline()); + style.setStrikethrough(getStrikethrough()); + style.setColor(getColor()); + return style; + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java b/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java new file mode 100644 index 000000000..7674d9bf8 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/formatting/StyledFragment.java @@ -0,0 +1,150 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +package com.sk89q.worldedit.util.formatting; + +import java.util.ArrayList; +import java.util.List; + +/** + * A fragment of text that can be styled. + */ +public class StyledFragment extends Fragment { + + private final List children = new ArrayList(); + private StyleSet style; + private Fragment lastText; + + public StyledFragment() { + style = new StyleSet(); + } + + public StyledFragment(StyleSet style) { + this.style = style; + } + + public StyledFragment(Style... styles) { + this.style = new StyleSet(styles); + } + + public StyleSet getStyle() { + return style; + } + + public void setStyles(StyleSet style) { + this.style = style; + } + + public List getChildren() { + return children; + } + + protected Fragment lastText() { + Fragment text; + if (children.size() > 0) { + text = children.get(children.size() - 1); + if (text == lastText) { + return text; + } + } + + text = new Fragment(); + this.lastText = text; + children.add(text); + return text; + } + + public StyledFragment createFragment(Style... styles) { + StyledFragment fragment = new StyledFragment(styles); + append(fragment); + return fragment; + } + + public StyledFragment append(StyledFragment fragment) { + children.add(fragment); + return this; + } + + @Override + public StyledFragment append(String str) { + lastText().append(str); + return this; + } + + @Override + public StyledFragment append(Object obj) { + append(String.valueOf(obj)); + return this; + } + + @Override + public StyledFragment append(StringBuffer sb) { + append(String.valueOf(sb)); + return this; + } + + @Override + public StyledFragment append(CharSequence s) { + append(String.valueOf(s)); + return this; + } + + @Override + public StyledFragment append(boolean b) { + append(String.valueOf(b)); + return this; + } + + @Override + public StyledFragment append(char c) { + append(String.valueOf(c)); + return this; + } + + @Override + public StyledFragment append(int i) { + append(String.valueOf(i)); + return this; + } + + @Override + public StyledFragment append(long lng) { + append(String.valueOf(lng)); + return this; + } + + @Override + public StyledFragment append(float f) { + append(String.valueOf(f)); + return this; + } + + @Override + public StyledFragment append(double d) { + append(String.valueOf(d)); + return this; + } + + @Override + public StyledFragment newLine() { + append("\n"); + return this; + } + +}