Skip to content

Instantly share code, notes, and snippets.

@Cryptite
Last active January 27, 2026 16:13
Show Gist options
  • Select an option

  • Save Cryptite/457138367f1f73bbe499d95566e44384 to your computer and use it in GitHub Desktop.

Select an option

Save Cryptite/457138367f1f73bbe499d95566e44384 to your computer and use it in GitHub Desktop.
Component Word Wrapping
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