diff --git a/src/main/java/buttondevteam/chat/ChatProcessing.java b/src/main/java/buttondevteam/chat/ChatProcessing.java index 92ae3f2..8f1b7a5 100644 --- a/src/main/java/buttondevteam/chat/ChatProcessing.java +++ b/src/main/java/buttondevteam/chat/ChatProcessing.java @@ -27,10 +27,9 @@ import org.bukkit.entity.Player; import org.bukkit.scoreboard.Objective; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.regex.Pattern; public class ChatProcessing { @@ -40,7 +39,7 @@ public class ChatProcessing { private static final Pattern CONSOLE_PING_PATTERN = Pattern.compile("(?i)" + Pattern.quote("@console")); private static final Pattern HASHTAG_PATTERN = Pattern.compile("#(\\w+)"); private static final Pattern URL_PATTERN = Pattern.compile("(http[\\w:/?=$\\-_.+!*'(),]+)"); - private static final Pattern ENTIRE_MESSAGE_PATTERN = Pattern.compile(".+"); + public static final Pattern ENTIRE_MESSAGE_PATTERN = Pattern.compile(".+"); private static final Pattern UNDERLINED_PATTERN = Pattern.compile("_"); private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*"); private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*"); @@ -55,14 +54,14 @@ public class ChatProcessing { public static final ChatFormatter ESCAPE_FORMATTER = ChatFormatter.builder().regex(ESCAPE_PATTERN).build(); private static ArrayList commonFormatters = Lists.newArrayList( - ChatFormatter.builder().regex(BOLD_PATTERN).bold(true).removeCharCount((short) 2).range(true) + ChatFormatter.builder().regex(BOLD_PATTERN).bold(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range) .priority(Priority.High).build(), - ChatFormatter.builder().regex(ITALIC_PATTERN).italic(true).removeCharCount((short) 1).range(true).build(), - ChatFormatter.builder().regex(UNDERLINED_PATTERN).underlined(true).removeCharCount((short) 1).range(true) + ChatFormatter.builder().regex(ITALIC_PATTERN).italic(true).removeCharCount((short) 1).type(ChatFormatter.Type.Range).build(), + ChatFormatter.builder().regex(UNDERLINED_PATTERN).underlined(true).removeCharCount((short) 1).type(ChatFormatter.Type.Range) .build(), - ChatFormatter.builder().regex(STRIKETHROUGH_PATTERN).strikethrough(true).removeCharCount((short) 2).range(true) + ChatFormatter.builder().regex(STRIKETHROUGH_PATTERN).strikethrough(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range) .build(), - ESCAPE_FORMATTER, ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").build(), + ESCAPE_FORMATTER, ChatFormatter.builder().regex(URL_PATTERN).underlined(true).openlink("$1").type(ChatFormatter.Type.Excluder).build(), ChatFormatter.builder().regex(NULL_MENTION_PATTERN).color(Color.DarkRed).build(), // Properly added a bug as a feature ChatFormatter.builder().regex(CONSOLE_PING_PATTERN).color(Color.Aqua).onmatch((match, builder) -> { if (!pingedconsole) { @@ -75,7 +74,7 @@ public class ChatProcessing { ChatFormatter.builder().regex(HASHTAG_PATTERN).color(Color.Blue).openlink("https://twitter.com/hashtag/$1") .priority(Priority.High).build(), ChatFormatter.builder().regex(CYAN_PATTERN).color(Color.Aqua).build(), // #55 - ChatFormatter.builder().regex(CODE_PATTERN).color(Color.DarkGray).removeCharCount((short) 1).range(true) + ChatFormatter.builder().regex(CODE_PATTERN).color(Color.DarkGray).removeCharCount((short) 1).type(ChatFormatter.Type.Range) .build(), ChatFormatter.builder().regex(MASKED_LINK_PATTERN).underlined(true).onmatch((match, builder) -> { return match; // TODO! @@ -85,6 +84,7 @@ public class ChatProcessing { .registerTypeHierarchyAdapter(Collection.class, new TellrawSerializer.TwCollection()) .registerTypeAdapter(Boolean.class, new TellrawSerializer.TwBool()) .registerTypeAdapter(boolean.class, new TellrawSerializer.TwBool()).disableHtmlEscaping().create(); + private static final String[] testPlayers = {"Koiiev", "iie", "Alisolarflare", "NorbiPeti", "Arsen_Derby_FTW", "carrot_lynx"}; private ChatProcessing() { } @@ -242,10 +242,15 @@ public class ChatProcessing { formatters.add( ChatFormatter.builder().regex(ENTIRE_MESSAGE_PATTERN).color(colormode).priority(Priority.Low).build()); - if (Bukkit.getOnlinePlayers().size() > 0) { + boolean nottest; //Not assigning a default value, so that it can only be used in the if + if ((nottest = Bukkit.getOnlinePlayers().size() > 0) || Bukkit.getVersion().equals("test")) { StringBuilder namesb = new StringBuilder("(?i)("); - for (Player p : Bukkit.getOnlinePlayers()) - namesb.append(p.getName()).append("|"); + if (nottest) + for (Player p : Bukkit.getOnlinePlayers()) + namesb.append(p.getName()).append("|"); + else + for (String testPlayer : testPlayers) + namesb.append(testPlayer).append("|"); namesb.deleteCharAt(namesb.length() - 1); namesb.append(")"); StringBuilder nicksb = new StringBuilder("(?i)("); @@ -265,23 +270,33 @@ public class ChatProcessing { } nicksb.append(")"); + Consumer error = message -> { + if (PluginMain.Instance != null) + PluginMain.Instance.getLogger().warning(message); + else + System.out.println(message); + }; + formatters.add(ChatFormatter.builder().regex(Pattern.compile(namesb.toString())).color(Color.Aqua) .onmatch((match, builder) -> { Player p = Bukkit.getPlayer(match); - if (p == null) { - PluginMain.Instance.getLogger() - .warning("Error: Can't find player " + match + " but was reported as online."); + Optional pn = nottest ? Optional.empty() + : Arrays.stream(testPlayers).filter(tp -> tp.equalsIgnoreCase(match)).findAny(); + if (nottest ? p == null : !pn.isPresent()) { + error.accept("Error: Can't find player " + match + " but was reported as online."); return "§c" + match + "§r"; } - ChatPlayer mpp = TBMCPlayer.getPlayer(p.getUniqueId(), ChatPlayer.class); - if (PlayerListener.NotificationSound == null) - p.playSound(p.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1.0f, 0.5f); // TODO: Airhorn - else - p.playSound(p.getLocation(), PlayerListener.NotificationSound, 1.0f, - (float) PlayerListener.NotificationPitch); + ChatPlayer mpp = TBMCPlayer.getPlayer(nottest ? p.getUniqueId() : new UUID(0, 0), ChatPlayer.class); + if (nottest) { + if (PlayerListener.NotificationSound == null) + p.playSound(p.getLocation(), Sound.ENTITY_ARROW_HIT_PLAYER, 1.0f, 0.5f); // TODO: Airhorn + else + p.playSound(p.getLocation(), PlayerListener.NotificationSound, 1.0f, + (float) PlayerListener.NotificationPitch); + } String color = String.format("§%x", (mpp.GetFlairColor() == 0x00 ? 0xb : mpp.GetFlairColor())); - return color + p.getName() + "§r"; - }).priority(Priority.High).build()); + return color + (nottest ? p.getName() : pn.get()) + "§r"; //Fix name casing, except when testing + }).priority(Priority.High).type(ChatFormatter.Type.Excluder).build()); if (addNickFormatter) formatters.add(ChatFormatter.builder().regex((Pattern.compile(nicksb.toString()))).color(Color.Aqua) @@ -289,7 +304,7 @@ public class ChatProcessing { if (PlayerListener.nicknames.containsKey(match.toLowerCase())) { //Made a stream and all that but I can actually store it lowercased Player p = Bukkit.getPlayer(PlayerListener.nicknames.get(match.toLowerCase())); if (p == null) { - PluginMain.Instance.getLogger().warning("Error: Can't find player nicknamed " + error.accept("Error: Can't find player nicknamed " + match.toLowerCase() + " but was reported as online."); return "§c" + match + "§r"; } @@ -300,10 +315,10 @@ public class ChatProcessing { (float) PlayerListener.NotificationPitch); return PluginMain.essentials.getUser(p).getNickname(); } - Bukkit.getServer().getLogger().warning("Player nicknamed " + match.toLowerCase() + error.accept("Player nicknamed " + match.toLowerCase() + " not found in nickname map but was reported as online."); return "§c" + match + "§r"; - }).priority(Priority.High).build()); + }).priority(Priority.High).type(ChatFormatter.Type.Excluder).build()); } return formatters; } diff --git a/src/main/java/buttondevteam/chat/formatting/ChatFormatter.java b/src/main/java/buttondevteam/chat/formatting/ChatFormatter.java index 619398e..aaca06e 100644 --- a/src/main/java/buttondevteam/chat/formatting/ChatFormatter.java +++ b/src/main/java/buttondevteam/chat/formatting/ChatFormatter.java @@ -40,27 +40,70 @@ public final class ChatFormatter { @Builder.Default short removeCharCount = 0; @Builder.Default - boolean range = false; + Type type = Type.Normal; + + public enum Type { + Normal, + /** + * Matches a start and an end section which gets converted to one section (for example see italics) + */ + Range, + /** + * Exclude matching area from further processing (besides this formatter) + */ + Excluder + } public static void Combine(List formatters, String str, TellrawPart tp) { /* * This method assumes that there is always a global formatter - */ - header("ChatFormatter.Combine begin"); + */ + header("ChatFormatter.Combine begin"); ArrayList sections = new ArrayList(); + + for (ChatFormatter formatter : formatters) { + if (formatter.type != Type.Excluder) + continue; + Matcher matcher = formatter.regex.matcher(str); + while (matcher.find()) { + DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1)); + DebugCommand.SendDebugMessage("With excluder formatter:" + formatter); + sendMessageWithPointer(str, matcher.start(), matcher.end() - 1); + if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) { + DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder"); + continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards + } + ArrayList groups = new ArrayList(); + for (int i = 0; i < matcher.groupCount(); i++) + groups.add(matcher.group(i + 1)); + if (groups.size() > 0) + DebugCommand.SendDebugMessage("First group: " + groups.get(0)); + FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups, + formatter.type); + sections.add(section); + } + } + + header("Section creation (excluders done)"); for (ChatFormatter formatter : formatters) { + if (formatter.type == Type.Excluder) + continue; Matcher matcher = formatter.regex.matcher(str); while (matcher.find()) { DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1)); DebugCommand.SendDebugMessage("With formatter:" + formatter); sendMessageWithPointer(str, matcher.start(), matcher.end() - 1); + if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) { + DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder"); + continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards + } ArrayList groups = new ArrayList(); for (int i = 0; i < matcher.groupCount(); i++) groups.add(matcher.group(i + 1)); if (groups.size() > 0) DebugCommand.SendDebugMessage("First group: " + groups.get(0)); FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups, - formatter.range); + formatter.type); sections.add(section); } } @@ -84,17 +127,20 @@ public final class ChatFormatter { for (int i = 0; i < sections.size(); i++) { // Set ending to -1 until closed with another 1 long "section" - only do this if IsRange is true final FormattedSection section = sections.get(i); - if (!section.IsRange) { + if (section.type!=Type.Range) { escaped = section.Formatters.contains(ChatProcessing.ESCAPE_FORMATTER) && !escaped; // Enable escaping on first \, disable on second - if (escaped) // Don't add the escape character + if (escaped) {// Don't add the escape character remchars.add(new int[]{section.Start, section.Start}); - combined.add(section); // This will delete the \ - DebugCommand.SendDebugMessage("Added " + (!escaped ? "not " : "") + "escaper section: " + section); + DebugCommand.SendDebugMessage("Found escaper section: " + section); + } else { + combined.add(section); // The above will delete the \ + DebugCommand.SendDebugMessage("Added section: " + section); + } sendMessageWithPointer(str, section.Start, section.End); continue; - } - if (!escaped) { - if (combined.stream().anyMatch(s -> s.IsRange && (s.Start == section.Start + } + if (!escaped) { + if (combined.stream().anyMatch(s -> section.type != Type.Range && (s.Start == section.Start || (s.Start < section.Start ? s.End >= section.Start : s.Start <= section.End)))) { DebugCommand.SendDebugMessage("Range " + section + " overlaps with a combined section, ignoring."); sendMessageWithPointer(str, section.Start, section.End); @@ -140,12 +186,7 @@ public final class ChatFormatter { escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*' } } - for (val sec : nextSection.values()) { - sec.End = str.length() - 1; - combined.add(sec); - DebugCommand.SendDebugMessage("Finished unfinished section: " + sec); - sendMessageWithPointer(str, sec.Start, sec.End); - } + //Do not finish unfinished sections, ignore them sections = combined; header("Adding remove chars (RC)"); // Important to add after the range section conversion @@ -192,7 +233,7 @@ public final class ChatFormatter { origend2 = tmp; } FormattedSection section = new FormattedSection(firstSection.Formatters, sections.get(i).Start, origend, - firstSection.Matches, false); + firstSection.Matches, Type.Normal); section.Formatters.addAll(sections.get(i).Formatters); section.Matches.addAll(sections.get(i).Matches); // TODO: Clean sections.add(i, section); @@ -238,6 +279,7 @@ public final class ChatFormatter { DebugCommand.SendDebugMessage("Removing section: " + sections.get(j)); sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End); sections.remove(j); + j--; found = true; } } @@ -260,15 +302,16 @@ public final class ChatFormatter { } header("Section applying"); - for (int i = 0; i < sections.size(); i++) { - FormattedSection section = sections.get(i); + TellrawPart lasttp = null; String lastlink = null; + for (FormattedSection section : sections) { DebugCommand.SendDebugMessage("Applying section: " + section); String originaltext; int start = section.Start, end = section.End; DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end); sendMessageWithPointer(str, start, end); - val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny(); - val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny(); + val rcs = remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).findAny(); + val rce = remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).findAny(); + val rci = remchars.stream().filter(rc -> start < rc[0] && rc[1] < end).toArray(int[][]::new); int s = start, e = end; if (rcs.isPresent()) s = rcs.get()[1] + 1; @@ -280,39 +323,45 @@ public final class ChatFormatter { continue; } originaltext = str.substring(s, e + 1); + val sb = new StringBuilder(originaltext); + for (int x = rci.length - 1; x >= 0; x--) + sb.delete(rci[x][0] - start - 1, rci[x][1] - start); //Delete going backwards + originaltext = sb.toString(); DebugCommand.SendDebugMessage("Section text: " + originaltext); - Color color = null; - boolean bold = false, italic = false, underlined = false, strikethrough = false, obfuscated = false; - String openlink = null; - section.Formatters.sort((cf2, cf1) -> cf1.priority.compareTo(cf2.priority)); + String openlink = null; + section.Formatters.sort(Comparator.comparing(cf2 -> cf2.priority.GetValue())); //Apply the highest last, to overwrite previous ones + TellrawPart newtp = new TellrawPart(""); for (ChatFormatter formatter : section.Formatters) { DebugCommand.SendDebugMessage("Applying formatter: " + formatter); if (formatter.onmatch != null) originaltext = formatter.onmatch.apply(originaltext, formatter); if (formatter.color != null) - color = formatter.color; + newtp.setColor(formatter.color); if (formatter.bold) - bold = true; + newtp.setBold(formatter.bold); if (formatter.italic) - italic = true; + newtp.setItalic(formatter.italic); if (formatter.underlined) - underlined = true; + newtp.setUnderlined(formatter.underlined); if (formatter.strikethrough) - strikethrough = true; + newtp.setStrikethrough(formatter.strikethrough); if (formatter.obfuscated) - obfuscated = true; + newtp.setObfuscated(formatter.obfuscated); if (formatter.openlink != null) openlink = formatter.openlink; } - TellrawPart newtp = new TellrawPart(""); + if (lasttp != null && newtp.getColor() == lasttp.getColor() + && newtp.isBold() == lasttp.isBold() + && newtp.isItalic() == lasttp.isItalic() + && newtp.isUnderlined() == lasttp.isUnderlined() + && newtp.isStrikethrough() == lasttp.isStrikethrough() + && newtp.isObfuscated() == lasttp.isObfuscated() + && (openlink == null ? lastlink == null : openlink.equals(lastlink))) { + DebugCommand.SendDebugMessage("This part has the same properties as the previous one, combining."); + lasttp.setText(lasttp.getText() + originaltext); + continue; //Combine parts with the same properties + } newtp.setText(originaltext); - if (color != null) - newtp.setColor(color); - newtp.setBold(bold); - newtp.setItalic(italic); - newtp.setUnderlined(underlined); - newtp.setStrikethrough(strikethrough); - newtp.setObfuscated(obfuscated); if (openlink != null && openlink.length() > 0) { newtp.setClickEvent(TellrawEvent.create(TellrawEvent.ClickAction.OPEN_URL, (section.Matches.size() > 0 ? openlink.replace("$1", section.Matches.get(0)) : openlink))) @@ -320,6 +369,7 @@ public final class ChatFormatter { new TellrawPart("Click to open").setColor(Color.Blue))); } tp.addExtra(newtp); + lasttp = newtp; } header("ChatFormatter.Combine done"); } diff --git a/src/main/java/buttondevteam/chat/formatting/FormattedSection.java b/src/main/java/buttondevteam/chat/formatting/FormattedSection.java index fbf7baf..4265726 100644 --- a/src/main/java/buttondevteam/chat/formatting/FormattedSection.java +++ b/src/main/java/buttondevteam/chat/formatting/FormattedSection.java @@ -8,32 +8,29 @@ class FormattedSection { int End; ArrayList Formatters = new ArrayList(); ArrayList Matches = new ArrayList(); - /** - * Is it a 1-long section indicating a start or an end - */ - boolean IsRange; + ChatFormatter.Type type; - FormattedSection(ChatFormatter formatter, int start, int end, ArrayList matches, boolean isrange) { + FormattedSection(ChatFormatter formatter, int start, int end, ArrayList matches, ChatFormatter.Type type) { Start = start; End = end; Formatters.add(formatter); Matches.addAll(matches); - IsRange = isrange; + this.type = type; } FormattedSection(Collection formatters, int start, int end, ArrayList matches, - boolean isrange) { + ChatFormatter.Type type) { Start = start; End = end; Formatters.addAll(formatters); Matches.addAll(matches); - IsRange = isrange; + this.type = type; } @Override public String toString() { - return new StringBuilder("Section(").append(Start).append(", ").append(End).append(", formatters: ") - .append(Formatters.toString()).append(", matches: ").append(Matches.toString()).append(", ") - .append(IsRange).append(")").toString(); + return "Section(" + Start + ", " + End + ", formatters: " + + Formatters.toString() + ", matches: " + Matches.toString() + ", " + + type + ")"; } } \ No newline at end of file diff --git a/src/test/java/buttondevteam/chat/ChatFormatIT.java b/src/test/java/buttondevteam/chat/ChatFormatIT.java index af45011..a1831cd 100644 --- a/src/test/java/buttondevteam/chat/ChatFormatIT.java +++ b/src/test/java/buttondevteam/chat/ChatFormatIT.java @@ -40,14 +40,31 @@ public class ChatFormatIT { new TellrawPart("test").setBold(true).setItalic(true).setUnderlined(true).setColor(Color.White))); list.add(new ChatFormatIT(sender, "***_~~test~~_***", new TellrawPart("test").setBold(true).setItalic(true) .setUnderlined(true).setStrikethrough(true).setColor(Color.White))); - list.add(new ChatFormatIT(sender, "¯\\\\\\_(ツ)\\_/¯", new TellrawPart("¯").setColor(Color.White), - new TellrawPart("\\").setColor(Color.White), new TellrawPart("_(ツ)").setColor(Color.White), - new TellrawPart("_/¯").setColor(Color.White))); + list.add(new ChatFormatIT(sender, "¯\\\\\\_(ツ)\\_/¯", new TellrawPart("¯\\_(ツ)_/¯").setColor(Color.White))); list.add(new ChatFormatIT(sender, "https://google.hu/", new TellrawPart("https://google.hu/").setColor(Color.White).setUnderlined(true) .setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT, new TellrawPart("Click to open").setColor(Color.Blue))) .setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://google.hu/")))); + list.add(new ChatFormatIT(sender, "*test", new TellrawPart("*test").setColor(Color.White))); + list.add(new ChatFormatIT(sender, "**test*", new TellrawPart("**test*").setColor(Color.White))); + list.add(new ChatFormatIT(sender, "***test", new TellrawPart("***test").setColor(Color.White))); + list.add(new ChatFormatIT(sender, "Koiiev", new TellrawPart("§bKoiiev§r").setColor(Color.Aqua))); + list.add(new ChatFormatIT(sender, "norbipeti", new TellrawPart("§bNorbiPeti§r").setColor(Color.Aqua))); + list.add(new ChatFormatIT(sender, "Arsen_Derby_FTW", new TellrawPart("§bArsen_Derby_FTW§r").setColor(Color.Aqua))); + list.add(new ChatFormatIT(sender, "carrot_lynx", new TellrawPart("§bcarrot_lynx§r").setColor(Color.Aqua))); + list.add(new ChatFormatIT(sender, "*carrot_lynx*", new TellrawPart("§bcarrot_lynx§r").setItalic(true).setColor(Color.Aqua))); + list.add(new ChatFormatIT(sender, "https://norbipeti.github.io/", new TellrawPart("https://norbipeti.github.io/") + .setColor(Color.White).setUnderlined(true) + .setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT, + new TellrawPart("Click to open").setColor(Color.Blue))) + .setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/")))); + list.add(new ChatFormatIT(sender, "*https://norbipeti.github.io/ heh*", new TellrawPart("https://norbipeti.github.io/").setItalic(true).setUnderlined(true) + .setHoverEvent(TellrawEvent.create(HoverAction.SHOW_TEXT, + new TellrawPart("Click to open").setColor(Color.Blue))) + .setClickEvent(TellrawEvent.create(ClickAction.OPEN_URL, "https://norbipeti.github.io/")), new TellrawPart(" heh").setItalic(true))); + list.add(new ChatFormatIT(sender, "*test _test_ test*", new TellrawPart("test ").setItalic(true).setColor(Color.White), + new TellrawPart("test").setItalic(true).setUnderlined(true).setColor(Color.White), new TellrawPart(" test").setItalic(true).setColor(Color.White))); return list; }