/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Lists * org.apache.commons.lang3.mutable.MutableFloat * org.apache.commons.lang3.mutable.MutableInt * org.jspecify.annotations.Nullable */ package net.minecraft.client; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.function.BiConsumer; import java.util.stream.Collectors; import net.minecraft.client.ComponentCollector; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.Style; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.FormattedCharSink; import net.minecraft.util.StringDecomposer; import org.apache.commons.lang3.mutable.MutableFloat; import org.apache.commons.lang3.mutable.MutableInt; import org.jspecify.annotations.Nullable; public class StringSplitter { private final WidthProvider widthProvider; public StringSplitter(WidthProvider widthProvider) { this.widthProvider = widthProvider; } public float stringWidth(@Nullable String str) { if (str == null) { return 0.0f; } MutableFloat result = new MutableFloat(); StringDecomposer.iterateFormatted(str, Style.EMPTY, (position, style, codepoint) -> { result.add(this.widthProvider.getWidth(codepoint, style)); return true; }); return result.floatValue(); } public float stringWidth(FormattedText text) { MutableFloat result = new MutableFloat(); StringDecomposer.iterateFormatted(text, Style.EMPTY, (position, style, codepoint) -> { result.add(this.widthProvider.getWidth(codepoint, style)); return true; }); return result.floatValue(); } public float stringWidth(FormattedCharSequence text) { MutableFloat result = new MutableFloat(); text.accept((position, style, codepoint) -> { result.add(this.widthProvider.getWidth(codepoint, style)); return true; }); return result.floatValue(); } public int plainIndexAtWidth(String str, int maxWidth, Style style) { WidthLimitedCharSink output = new WidthLimitedCharSink(maxWidth); StringDecomposer.iterate(str, style, output); return output.getPosition(); } public String plainHeadByWidth(String str, int maxWidth, Style style) { return str.substring(0, this.plainIndexAtWidth(str, maxWidth, style)); } public String plainTailByWidth(String str, int maxWidth, Style style) { MutableFloat currentWidth = new MutableFloat(); MutableInt result = new MutableInt(str.length()); StringDecomposer.iterateBackwards(str, style, (position, s, codepoint) -> { float w = currentWidth.addAndGet(this.widthProvider.getWidth(codepoint, s)); if (w > (float)maxWidth) { return false; } result.setValue(position); return true; }); return str.substring(result.intValue()); } public FormattedText headByWidth(FormattedText text, int width, Style initialStyle) { final WidthLimitedCharSink output = new WidthLimitedCharSink(width); return text.visit(new FormattedText.StyledContentConsumer(){ private final ComponentCollector collector = new ComponentCollector(); @Override public Optional accept(Style style, String contents) { output.resetPosition(); if (!StringDecomposer.iterateFormatted(contents, style, (FormattedCharSink)output)) { String partial = contents.substring(0, output.getPosition()); if (!partial.isEmpty()) { this.collector.append(FormattedText.of(partial, style)); } return Optional.of(this.collector.getResultOrEmpty()); } if (!contents.isEmpty()) { this.collector.append(FormattedText.of(contents, style)); } return Optional.empty(); } }, initialStyle).orElse(text); } public int findLineBreak(String input, int max, Style initialStyle) { LineBreakFinder finder = new LineBreakFinder(max); StringDecomposer.iterateFormatted(input, initialStyle, (FormattedCharSink)finder); return finder.getSplitPosition(); } public static int getWordPosition(String text, int dir, int from, boolean stripSpaces) { int result = from; boolean reverse = dir < 0; int abs = Math.abs(dir); for (int i = 0; i < abs; ++i) { if (reverse) { while (stripSpaces && result > 0 && (text.charAt(result - 1) == ' ' || text.charAt(result - 1) == '\n')) { --result; } while (result > 0 && text.charAt(result - 1) != ' ' && text.charAt(result - 1) != '\n') { --result; } continue; } int length = text.length(); int index1 = text.indexOf(32, result); int index2 = text.indexOf(10, result); result = index1 == -1 && index2 == -1 ? -1 : (index1 != -1 && index2 != -1 ? Math.min(index1, index2) : (index1 != -1 ? index1 : index2)); if (result == -1) { result = length; continue; } while (stripSpaces && result < length && (text.charAt(result) == ' ' || text.charAt(result) == '\n')) { ++result; } } return result; } public void splitLines(String input, int maxWidth, Style initialStyle, boolean includeAll, LinePosConsumer output) { int start = 0; int size = input.length(); Style workStyle = initialStyle; while (start < size) { LineBreakFinder finder = new LineBreakFinder(maxWidth); boolean endOfText = StringDecomposer.iterateFormatted(input, start, workStyle, initialStyle, finder); if (endOfText) { output.accept(workStyle, start, size); break; } int lineBreak = finder.getSplitPosition(); char firstTailChar = input.charAt(lineBreak); int adjustedBreak = firstTailChar == '\n' || firstTailChar == ' ' ? lineBreak + 1 : lineBreak; output.accept(workStyle, start, includeAll ? adjustedBreak : lineBreak); start = adjustedBreak; workStyle = finder.getSplitStyle(); } } public List splitLines(String input, int maxWidth, Style initialStyle) { ArrayList result = Lists.newArrayList(); this.splitLines(input, maxWidth, initialStyle, false, (style, start, end) -> result.add(FormattedText.of(input.substring(start, end), style))); return result; } public List splitLines(FormattedText input, int maxWidth, Style initialStyle) { ArrayList result = Lists.newArrayList(); this.splitLines(input, maxWidth, initialStyle, (text, wrapped) -> result.add(text)); return result; } public void splitLines(FormattedText input, int maxWidth, Style initialStyle, BiConsumer output) { ArrayList partList = Lists.newArrayList(); input.visit((style, contents) -> { if (!contents.isEmpty()) { partList.add(new LineComponent(contents, style)); } return Optional.empty(); }, initialStyle); FlatComponents parts = new FlatComponents(partList); boolean shouldRestart = true; boolean forceNewLine = false; boolean isWrapped = false; block0: while (shouldRestart) { shouldRestart = false; LineBreakFinder finder = new LineBreakFinder(maxWidth); for (LineComponent part : parts.parts) { boolean endOfText = StringDecomposer.iterateFormatted(part.contents, 0, part.style, initialStyle, finder); if (!endOfText) { int lineBreak = finder.getSplitPosition(); Style lineBreakStyle = finder.getSplitStyle(); char firstTailChar = parts.charAt(lineBreak); boolean isNewLine = firstTailChar == '\n'; boolean skipNextChar = isNewLine || firstTailChar == ' '; forceNewLine = isNewLine; FormattedText result = parts.splitAt(lineBreak, skipNextChar ? 1 : 0, lineBreakStyle); output.accept(result, isWrapped); isWrapped = !isNewLine; shouldRestart = true; continue block0; } finder.addToOffset(part.contents.length()); } } FormattedText lastLine = parts.getRemainder(); if (lastLine != null) { output.accept(lastLine, isWrapped); } else if (forceNewLine) { output.accept(FormattedText.EMPTY, false); } } @FunctionalInterface public static interface WidthProvider { public float getWidth(int var1, Style var2); } private class WidthLimitedCharSink implements FormattedCharSink { private float maxWidth; private int position; public WidthLimitedCharSink(float maxWidth) { this.maxWidth = maxWidth; } @Override public boolean accept(int position, Style style, int codepoint) { this.maxWidth -= StringSplitter.this.widthProvider.getWidth(codepoint, style); if (this.maxWidth >= 0.0f) { this.position = position + Character.charCount(codepoint); return true; } return false; } public int getPosition() { return this.position; } public void resetPosition() { this.position = 0; } } private class LineBreakFinder implements FormattedCharSink { private final float maxWidth; private int lineBreak = -1; private Style lineBreakStyle = Style.EMPTY; private boolean hadNonZeroWidthChar; private float width; private int lastSpace = -1; private Style lastSpaceStyle = Style.EMPTY; private int nextChar; private int offset; public LineBreakFinder(float maxWidth) { this.maxWidth = Math.max(maxWidth, 1.0f); } @Override public boolean accept(int position, Style style, int codepoint) { int adjustedPosition = position + this.offset; switch (codepoint) { case 10: { return this.finishIteration(adjustedPosition, style); } case 32: { this.lastSpace = adjustedPosition; this.lastSpaceStyle = style; } } float charWidth = StringSplitter.this.widthProvider.getWidth(codepoint, style); this.width += charWidth; if (this.hadNonZeroWidthChar && this.width > this.maxWidth) { if (this.lastSpace != -1) { return this.finishIteration(this.lastSpace, this.lastSpaceStyle); } return this.finishIteration(adjustedPosition, style); } this.hadNonZeroWidthChar |= charWidth != 0.0f; this.nextChar = adjustedPosition + Character.charCount(codepoint); return true; } private boolean finishIteration(int lineBreak, Style style) { this.lineBreak = lineBreak; this.lineBreakStyle = style; return false; } private boolean lineBreakFound() { return this.lineBreak != -1; } public int getSplitPosition() { return this.lineBreakFound() ? this.lineBreak : this.nextChar; } public Style getSplitStyle() { return this.lineBreakStyle; } public void addToOffset(int delta) { this.offset += delta; } } @FunctionalInterface public static interface LinePosConsumer { public void accept(Style var1, int var2, int var3); } private static class FlatComponents { private final List parts; private String flatParts; public FlatComponents(List parts) { this.parts = parts; this.flatParts = parts.stream().map(p -> p.contents).collect(Collectors.joining()); } public char charAt(int position) { return this.flatParts.charAt(position); } public FormattedText splitAt(int skipPosition, int skipSize, Style splitStyle) { ComponentCollector result = new ComponentCollector(); ListIterator it = this.parts.listIterator(); int position = skipPosition; boolean inSkip = false; while (it.hasNext()) { LineComponent element = it.next(); String contents = element.contents; int contentsSize = contents.length(); if (!inSkip) { if (position > contentsSize) { result.append(element); it.remove(); position -= contentsSize; } else { String beforeSplit = contents.substring(0, position); if (!beforeSplit.isEmpty()) { result.append(FormattedText.of(beforeSplit, element.style)); } position += skipSize; inSkip = true; } } if (!inSkip) continue; if (position > contentsSize) { it.remove(); position -= contentsSize; continue; } String afterSplit = contents.substring(position); if (afterSplit.isEmpty()) { it.remove(); break; } it.set(new LineComponent(afterSplit, splitStyle)); break; } this.flatParts = this.flatParts.substring(skipPosition + skipSize); return result.getResultOrEmpty(); } public @Nullable FormattedText getRemainder() { ComponentCollector result = new ComponentCollector(); this.parts.forEach(result::append); this.parts.clear(); return result.getResult(); } } private static class LineComponent implements FormattedText { private final String contents; private final Style style; public LineComponent(String contents, Style style) { this.contents = contents; this.style = style; } @Override public Optional visit(FormattedText.ContentConsumer output) { return output.accept(this.contents); } @Override public Optional visit(FormattedText.StyledContentConsumer output, Style parentStyle) { return output.accept(this.style.applyTo(parentStyle), this.contents); } } }