Last active
January 27, 2026 16:13
-
-
Save Cryptite/457138367f1f73bbe499d95566e44384 to your computer and use it in GitHub Desktop.
Component Word Wrapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import net.kyori.adventure.text.Component; | |
| import net.kyori.adventure.text.ObjectComponent; | |
| import net.kyori.adventure.text.TextComponent; | |
| import net.kyori.adventure.text.format.Style; | |
| import net.kyori.adventure.text.format.TextDecoration; | |
| import java.util.*; | |
| import static com.lokamc.ui.StringUtils.ICON_MINI_SPACE; | |
| import static net.kyori.adventure.text.Component.empty; | |
| import static net.kyori.adventure.text.Component.text; | |
| import static net.kyori.adventure.text.format.NamedTextColor.DARK_RED; | |
| public final class ComponentSplitting { | |
| private static final TextComponent UNSUPPORTED = text("ERROR WRAPPING").color(DARK_RED); | |
| public static List<Component> wrapLoreLines(final Component component) { | |
| return wrapLoreLines(component, 60); | |
| } | |
| public static List<Component> wrapLoreLines(final Component component, final int length) { | |
| if (!(component instanceof final TextComponent text)) { | |
| return Collections.singletonList(component); | |
| } | |
| final List<Component> wrapped = new ArrayList<>(); | |
| final List<TextComponent> parts = flattenTextComponents(text); | |
| // Pre-process: build list of word segments, each with its own style | |
| final List<StyledWord> styledWords = new ArrayList<>(); | |
| for (final TextComponent part : parts) { | |
| final Style style = part.style(); | |
| final String content = part.content(); | |
| final String[] words = content.split("(?<=\\s)|(?=\\n)"); | |
| for (final String word : words) { | |
| if (!word.isEmpty()) { | |
| styledWords.add(new StyledWord(word, style)); | |
| } | |
| } | |
| } | |
| Component currentLine = empty(); | |
| int lineLength = 0; | |
| for (int i = 0; i < styledWords.size(); i++) { | |
| final StyledWord styledWord = styledWords.get(i); | |
| final String word = styledWord.word; | |
| final Style style = styledWord.style; | |
| // Check if this word ends with ICON_MINI_SPACE - calculate combined length with next word | |
| boolean endsWithIconMiniSpace = word.endsWith(ICON_MINI_SPACE); | |
| int combinedLength = word.length(); | |
| if (endsWithIconMiniSpace && i + 1 < styledWords.size()) { | |
| combinedLength += styledWords.get(i + 1).word.length(); | |
| } | |
| final int totalLength = lineLength + combinedLength; | |
| if (totalLength > length || word.contains("\n")) { | |
| if (lineLength > 0) { | |
| wrapped.add(currentLine); | |
| currentLine = empty().style(style); | |
| lineLength = 0; | |
| } | |
| } | |
| if (!word.equals("\n")) { | |
| currentLine = currentLine.append(text(word).style(style)); | |
| lineLength += word.length(); | |
| } | |
| } | |
| if (lineLength > 0) { | |
| wrapped.add(currentLine); | |
| } | |
| return wrapped; | |
| } | |
| private record StyledWord(String word, Style style) { | |
| } | |
| private static List<TextComponent> flattenTextComponents(final TextComponent component) { | |
| final List<TextComponent> flattened = new ArrayList<>(); | |
| final Style style = component.style(); | |
| final Style enforcedState = enforceStyleStates(style); | |
| final TextComponent first = component.style(enforcedState); | |
| final Stack<TextComponent> toCheck = new Stack<>(); | |
| toCheck.add(first); | |
| while (!toCheck.empty()) { | |
| final TextComponent parent = toCheck.pop(); | |
| final String content = parent.content(); | |
| if (!content.isEmpty()) { | |
| flattened.add(parent); | |
| } | |
| final List<Component> children = parent.children(); | |
| final List<Component> reversed = new ArrayList<>(children).reversed(); | |
| for (final Component child : reversed) { | |
| switch (child) { | |
| case TextComponent text -> { | |
| final Style parentStyle = parent.style(); | |
| final Style textStyle = text.style(); | |
| final Style merge = parentStyle.merge(textStyle); | |
| final TextComponent childComponent = text.style(merge); | |
| toCheck.add(childComponent); | |
| } | |
| case ObjectComponent obj -> { | |
| // Not working correctly atm | |
| final Style parentStyle = parent.style(); | |
| final Style objStyle = obj.style(); | |
| final Style merge = parentStyle.merge(objStyle); | |
| final ObjectComponent childComponent = obj.style(merge); | |
| // Wrap in a TextComponent to add to flattened list | |
| flattened.add(text().append(childComponent).build()); | |
| } | |
| default -> { | |
| toCheck.add(UNSUPPORTED); | |
| } | |
| } | |
| } | |
| } | |
| return flattened; | |
| } | |
| private static Style enforceStyleStates(final Style style) { | |
| final Style.Builder builder = style.toBuilder(); | |
| final Map<TextDecoration, TextDecoration.State> map = style.decorations(); | |
| map.forEach((decoration, state) -> { | |
| if (state == TextDecoration.State.NOT_SET) { | |
| builder.decoration(decoration, false); | |
| } | |
| }); | |
| return builder.build(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment