From de721db65f236fc29078d892d29ece2082381784 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 7 Jan 2026 10:47:14 +0100 Subject: [PATCH] refactor: simplified Style API removing style states --- .../java/org/codejive/twinkle/ansi/Style.java | 30 +++---- .../org/codejive/twinkle/util/Printable.java | 38 ++------ .../codejive/twinkle/util/StyledIterator.java | 35 ++++---- .../org/codejive/twinkle/ansi/TestStyle.java | 6 +- .../twinkle/util/TestStyledIterator.java | 12 +-- .../widgets/graphs/bar/FracBarRenderer.java | 8 +- .../twinkle/widgets/graphs/plot/MathPlot.java | 19 +--- .../twinkle/widgets/graphs/plot/Plot.java | 27 ++---- .../test/java/examples/MathPlotFourDemo.java | 3 +- .../core/decor/SimpleBorderRenderer.java | 29 +++--- .../codejive/twinkle/core/text/Buffer.java | 44 ++++----- .../codejive/twinkle/core/text/Canvas.java | 38 ++------ .../org/codejive/twinkle/core/text/Line.java | 22 ++--- .../twinkle/core/text/LineBuffer.java | 89 ++++++------------- .../org/codejive/twinkle/core/text/Span.java | 8 +- .../org/codejive/twinkle/core/text/Text.java | 12 +-- .../org/codejive/twinkle/widgets/Framed.java | 2 +- .../twinkle/core/text/LineBufferTimings.java | 10 ++- .../twinkle/core/text/TestLineBuffer.java | 10 +-- .../codejive/twinkle/core/text/TestText.java | 8 +- 20 files changed, 152 insertions(+), 298 deletions(-) diff --git a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java index 1d8a044..75f0128 100644 --- a/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java +++ b/twinkle-ansi/src/main/java/org/codejive/twinkle/ansi/Style.java @@ -503,81 +503,81 @@ public String toString() { } @Override - public @NonNull String toAnsiString(long currentStyleState) { + public @NonNull String toAnsiString(Style currentStyle) { try { - return toAnsi(new StringBuilder(), currentStyleState).toString(); + return toAnsi(new StringBuilder(), currentStyle).toString(); } catch (IOException e) { throw new RuntimeException(e); } } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { if (state == F_UNKNOWN) { // Do nothing, we keep the current state return appendable; } - if (currentStyleState == F_UNKNOWN) { + if (currentStyle.state() == F_UNKNOWN) { appendable.append(Ansi.STYLE_RESET); - currentStyleState = F_UNSTYLED; + currentStyle = UNSTYLED; } List styles = new ArrayList<>(); - if ((currentStyleState & (F_BOLD | F_FAINT)) != (state & (F_BOLD | F_FAINT))) { + if ((currentStyle.state() & (F_BOLD | F_FAINT)) != (state & (F_BOLD | F_FAINT))) { // First we switch to NORMAL to clear both BOLD and FAINT - if ((currentStyleState & (F_BOLD | F_FAINT)) != 0) { + if (currentStyle.isBold() || currentStyle.isFaint()) { styles.add(Ansi.NORMAL); } // Now we set the needed styles if (isBold()) styles.add(Ansi.BOLD); if (isFaint()) styles.add(Ansi.FAINT); } - if ((currentStyleState & F_ITALIC) != (state & F_ITALIC)) { + if (currentStyle.isItalic() != isItalic()) { if (isItalic()) { styles.add(Ansi.ITALICIZED); } else { styles.add(Ansi.NOTITALICIZED); } } - if ((currentStyleState & F_UNDERLINED) != (state & F_UNDERLINED)) { + if (currentStyle.isUnderlined() != isUnderlined()) { if (isUnderlined()) { styles.add(Ansi.UNDERLINED); } else { styles.add(Ansi.NOTUNDERLINED); } } - if ((currentStyleState & F_BLINK) != (state & F_BLINK)) { + if (currentStyle.isBlink() != isBlink()) { if (isBlink()) { styles.add(Ansi.BLINK); } else { styles.add(Ansi.STEADY); } } - if ((currentStyleState & F_INVERSE) != (state & F_INVERSE)) { + if (currentStyle.isInverse() != isInverse()) { if (isInverse()) { styles.add(Ansi.INVERSE); } else { styles.add(Ansi.POSITIVE); } } - if ((currentStyleState & F_HIDDEN) != (state & F_HIDDEN)) { + if (currentStyle.isHidden() != isHidden()) { if (isHidden()) { styles.add(Ansi.INVISIBLE); } else { styles.add(Ansi.VISIBLE); } } - if ((currentStyleState & F_STRIKETHROUGH) != (state & F_STRIKETHROUGH)) { + if (currentStyle.isStrikethrough() != isStrikethrough()) { if (isStrikethrough()) { styles.add(Ansi.CROSSEDOUT); } else { styles.add(Ansi.NOTCROSSEDOUT); } } - if ((currentStyleState & MASK_FG_COLOR) != (state & MASK_FG_COLOR)) { + if ((currentStyle.state() & MASK_FG_COLOR) != (state & MASK_FG_COLOR)) { styles.add(fgColor().toAnsiFgArgs()); } - if ((currentStyleState & MASK_BG_COLOR) != (state & MASK_BG_COLOR)) { + if ((currentStyle.state() & MASK_BG_COLOR) != (state & MASK_BG_COLOR)) { styles.add(bgColor().toAnsiBgArgs()); } return Ansi.style(appendable, styles.toArray()); diff --git a/twinkle-ansi/src/main/java/org/codejive/twinkle/util/Printable.java b/twinkle-ansi/src/main/java/org/codejive/twinkle/util/Printable.java index 9780e4e..d430900 100644 --- a/twinkle-ansi/src/main/java/org/codejive/twinkle/util/Printable.java +++ b/twinkle-ansi/src/main/java/org/codejive/twinkle/util/Printable.java @@ -12,7 +12,7 @@ public interface Printable { * @return The ANSI string representation of the object. */ default @NonNull String toAnsiString() { - return toAnsiString(Style.F_UNKNOWN); + return toAnsiString(Style.of(Style.F_UNKNOWN)); } /** @@ -23,7 +23,7 @@ public interface Printable { * @return The Appendable passed as parameter. */ default @NonNull Appendable toAnsi(Appendable appendable) throws IOException { - return toAnsi(appendable, Style.F_UNKNOWN); + return toAnsi(appendable, Style.of(Style.F_UNKNOWN)); } /** @@ -35,36 +35,10 @@ public interface Printable { * @return The ANSI string representation of the object. */ default @NonNull String toAnsiString(Style currentStyle) { - return toAnsiString(currentStyle.state()); - } - - /** - * Outputs the object as an ANSI string, including ANSI escape codes for styles. This method - * takes into account the provided current style to generate a result that is as efficient as - * possible in terms of ANSI codes. - * - * @param appendable The Appendable to write the ANSI output to. - * @param currentStyle The current style to start with. - * @return The Appendable passed as parameter. - */ - default @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) - throws IOException { - return toAnsi(appendable, currentStyle.state()); - } - - /** - * Converts the object to an ANSI string, including ANSI escape codes for styles. This method - * takes into account the provided current style to generate a result that is as efficient as - * possible in terms of ANSI codes. - * - * @param currentStyleState The current style to start with. - * @return The ANSI string representation of the object. - */ - default @NonNull String toAnsiString(long currentStyleState) { - StringBuilder sb = new StringBuilder(); try { - return toAnsi(sb, currentStyleState).toString(); + return toAnsi(new StringBuilder(), currentStyle).toString(); } catch (IOException e) { + // This should never happen since we're not actually doing any I/O here. throw new RuntimeException(e); } } @@ -75,8 +49,8 @@ public interface Printable { * possible in terms of ANSI codes. * * @param appendable The Appendable to write the ANSI output to. - * @param currentStyleState The current style to start with. + * @param currentStyle The current style to start with. * @return The Appendable passed as parameter. */ - @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) throws IOException; + @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException; } diff --git a/twinkle-ansi/src/main/java/org/codejive/twinkle/util/StyledIterator.java b/twinkle-ansi/src/main/java/org/codejive/twinkle/util/StyledIterator.java index 277ad87..2f2a121 100644 --- a/twinkle-ansi/src/main/java/org/codejive/twinkle/util/StyledIterator.java +++ b/twinkle-ansi/src/main/java/org/codejive/twinkle/util/StyledIterator.java @@ -11,7 +11,7 @@ */ public class StyledIterator implements SequenceIterator { private final SequenceIterator delegate; - private long currentStyleState; + private Style currentStyle; private int nextCodePoint = -1; private boolean primed = false; private boolean exhausted = false; @@ -21,16 +21,16 @@ public class StyledIterator implements SequenceIterator { * initial style state. */ protected StyledIterator(SequenceIterator delegate) { - this(delegate, Style.F_UNKNOWN); + this(delegate, Style.of(Style.F_UNKNOWN)); } /** * Creates a StyledIterator that wraps the given SequenceIterator and starts with the given * initial style state. */ - public StyledIterator(SequenceIterator delegate, long currentStyleState) { + public StyledIterator(SequenceIterator delegate, Style currentStyle) { this.delegate = delegate; - this.currentStyleState = currentStyleState; + this.currentStyle = currentStyle; } /** Returns true if there is still input to read. */ @@ -86,12 +86,7 @@ public String sequence() { /** Returns the current style based on the ANSI escape sequences encountered so far. */ public Style style() { - return Style.of(currentStyleState); - } - - /** Returns the current style state based on the ANSI escape sequences encountered so far. */ - public long styleState() { - return currentStyleState; + return currentStyle; } private void primeNext() { @@ -100,7 +95,7 @@ private void primeNext() { if (cp == Ansi.ESC) { String ansiSequence = delegate.sequence(); if (ansiSequence.startsWith(Ansi.CSI) && ansiSequence.endsWith("m")) { - currentStyleState = Style.parse(currentStyleState, ansiSequence); + currentStyle = Style.of(Style.parse(currentStyle.state(), ansiSequence)); } } else { nextCodePoint = cp; @@ -113,26 +108,26 @@ private void primeNext() { } public static StyledIterator of(CharSequence text) { - return of(text, Style.F_UNKNOWN); + return of(text, Style.of(Style.F_UNKNOWN)); } public static StyledIterator of(Reader input) { - return of(input, Style.F_UNKNOWN); + return of(input, Style.of(Style.F_UNKNOWN)); } public static StyledIterator of(SequenceIterator iter) { - return of(iter, Style.F_UNKNOWN); + return of(iter, Style.of(Style.F_UNKNOWN)); } - public static StyledIterator of(CharSequence text, long currentStyleState) { - return of(SequenceIterator.of(text), currentStyleState); + public static StyledIterator of(CharSequence text, Style currentStyle) { + return of(SequenceIterator.of(text), currentStyle); } - public static StyledIterator of(Reader input, long currentStyleState) { - return of(SequenceIterator.of(input), currentStyleState); + public static StyledIterator of(Reader input, Style currentStyle) { + return of(SequenceIterator.of(input), currentStyle); } - public static StyledIterator of(SequenceIterator iter, long currentStyleState) { - return new StyledIterator(iter, currentStyleState); + public static StyledIterator of(SequenceIterator iter, Style currentStyle) { + return new StyledIterator(iter, currentStyle); } } diff --git a/twinkle-ansi/src/test/java/org/codejive/twinkle/ansi/TestStyle.java b/twinkle-ansi/src/test/java/org/codejive/twinkle/ansi/TestStyle.java index 980fadb..84e0029 100644 --- a/twinkle-ansi/src/test/java/org/codejive/twinkle/ansi/TestStyle.java +++ b/twinkle-ansi/src/test/java/org/codejive/twinkle/ansi/TestStyle.java @@ -152,7 +152,8 @@ public void testToAnsiStringAllStylesWithCurrent() { .inverse() .hidden() .strikethrough(); - String ansiCode = style.toAnsiString(Style.F_BOLD | Style.F_UNDERLINED); + Style currentStyle = Style.UNSTYLED.bold().underlined(); + String ansiCode = style.toAnsiString(currentStyle); assertThat(ansiCode) .isEqualTo( Ansi.style( @@ -178,7 +179,8 @@ public void testToAnsiStringAllStylesWithCurrent2() { .inverse() .hidden() .strikethrough(); - String ansiCode = style.toAnsiString(Style.F_BOLD | Style.F_FAINT | Style.F_UNDERLINED); + Style currentStyle = Style.UNSTYLED.bold().faint().underlined(); + String ansiCode = style.toAnsiString(currentStyle); assertThat(ansiCode) .isEqualTo( Ansi.style( diff --git a/twinkle-ansi/src/test/java/org/codejive/twinkle/util/TestStyledIterator.java b/twinkle-ansi/src/test/java/org/codejive/twinkle/util/TestStyledIterator.java index 63aca2a..4bc339b 100644 --- a/twinkle-ansi/src/test/java/org/codejive/twinkle/util/TestStyledIterator.java +++ b/twinkle-ansi/src/test/java/org/codejive/twinkle/util/TestStyledIterator.java @@ -15,7 +15,7 @@ public void testPlainSequence() { assertThat(it.hasNext()).isTrue(); assertThat(it.next()).isEqualTo('a'); assertThat(it.sequence()).isEqualTo("a"); - assertThat(it.styleState()).isEqualTo(Style.F_UNKNOWN); + assertThat(it.style()).isEqualTo(Style.of(Style.F_UNKNOWN)); assertThat(it.hasNext()).isTrue(); assertThat(it.next()).isEqualTo('b'); @@ -40,7 +40,7 @@ public void testStyledSequence() { assertThat(it.sequence()).isEqualTo("a"); // Verify style state changed - assertThat(it.styleState()).isNotEqualTo(Style.F_UNSTYLED); + assertThat(it.style()).isNotEqualTo(Style.UNSTYLED); assertThat(it.hasNext()).isFalse(); } @@ -54,7 +54,7 @@ public void testSkipNonStyleAnsi() { assertThat(it.hasNext()).isTrue(); assertThat(it.next()).isEqualTo('a'); // Style should NOT change for non-SGR sequences - assertThat(it.styleState()).isEqualTo(Style.F_UNKNOWN); + assertThat(it.style()).isEqualTo(Style.of(Style.F_UNKNOWN)); assertThat(it.hasNext()).isFalse(); } @@ -68,14 +68,14 @@ public void testMixedSequences() { // 'a' assertThat(it.next()).isEqualTo('a'); - assertThat(it.styleState()).isEqualTo(Style.F_UNKNOWN); + assertThat(it.style()).isEqualTo(Style.of(Style.F_UNKNOWN)); // 'b' (RED consumed) assertThat(it.next()).isEqualTo('b'); - assertThat(it.styleState()).isNotEqualTo(Style.F_UNSTYLED); + assertThat(it.style()).isNotEqualTo(Style.UNSTYLED); // 'c' (RESET consumed) assertThat(it.next()).isEqualTo('c'); - assertThat(it.styleState()).isEqualTo(Style.F_UNSTYLED); + assertThat(it.style()).isEqualTo(Style.UNSTYLED); } } diff --git a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/bar/FracBarRenderer.java b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/bar/FracBarRenderer.java index c67b212..fadabef 100644 --- a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/bar/FracBarRenderer.java +++ b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/bar/FracBarRenderer.java @@ -121,24 +121,24 @@ public void render(Canvas canvas, Number value) { int y = reversed || horizontal ? 0 : size.height() - 1; // Place full blocks first for (int i = 0; i < fullChunks; i++) { - canvas.setCharAt(x, y, Style.F_UNSTYLED, blocks[0]); + canvas.setCharAt(x, y, Style.UNSTYLED, blocks[0]); x += direction.dx; y += direction.dy; } // Append remainder partial block if any if (remainder > 0) { - canvas.setCharAt(x, y, Style.F_UNSTYLED, blocks[remainder]); + canvas.setCharAt(x, y, Style.UNSTYLED, blocks[remainder]); x += direction.dx; y += direction.dy; } else if (overflow) { // Or an overflow block - canvas.setCharAt(x, y, Style.F_UNSTYLED, BLOCK_OVERFLOW); + canvas.setCharAt(x, y, Style.UNSTYLED, BLOCK_OVERFLOW); x += direction.dx; y += direction.dy; } // Fill the rest with spaces int sizeLeft = maxSize - fullChunks - (overflow || remainder > 0 ? 1 : 0); for (int i = 0; i < sizeLeft; i++) { - canvas.setCharAt(x, y, Style.F_UNSTYLED, ' '); + canvas.setCharAt(x, y, Style.UNSTYLED, ' '); x += direction.dx; y += direction.dy; } diff --git a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/MathPlot.java b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/MathPlot.java index 2127c97..02d8f52 100644 --- a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/MathPlot.java +++ b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/MathPlot.java @@ -162,29 +162,16 @@ public MathPlot ranges(Number minXValue, Number maxXValue, Number minYValue, Num return plot.currentStyle(); } - public long currentStyleState() { - return plot.currentStyleState(); - } - public MathPlot currentStyle(Style currentStyle) { plot.currentStyle(currentStyle); return this; } - public MathPlot currentStyleState(long currentStyleState) { - plot.currentStyleState(currentStyleState); - return this; - } - public MathPlot plot(Function func) { - return plot(func, currentStyleState()); + return plot(func, currentStyle()); } public MathPlot plot(Function func, Style style) { - return plot(func, style.state()); - } - - public MathPlot plot(Function func, long styleState) { double xRange = maxXValue.doubleValue() - minXValue.doubleValue(); double yRange = maxYValue.doubleValue() - minYValue.doubleValue(); Size plotSize = plot.plotSize(); @@ -241,7 +228,7 @@ public MathPlot plot(Function func, long styleState) { while (true) { if (x0 >= 0 && x0 < width && y0 >= 0 && y0 < height) { - plot.plot(x0, y0, styleState); + plot.plot(x0, y0, style); } if (x0 == x1 && y0 == y1) { break; @@ -260,7 +247,7 @@ public MathPlot plot(Function func, long styleState) { prevY = py; } else if (currValid) { // start new segment (or single point) and record as previous - plot.plot(px, py, styleState); + plot.plot(px, py, style); prevX = px; prevY = py; prevValid = true; diff --git a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/Plot.java b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/Plot.java index a33bc1f..7a4bfc2 100644 --- a/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/Plot.java +++ b/twinkle-chart/src/main/java/org/codejive/twinkle/widgets/graphs/plot/Plot.java @@ -12,7 +12,7 @@ public class Plot implements Widget { private final int cOrgX; private final int cOrgY; private final Size plotSize; - private long currentStyleState = Style.F_UNSTYLED; + private Style currentStyle = Style.UNSTYLED; private static final char BLOCK_FULL = '█'; private static final char EMPTY = ' '; @@ -73,20 +73,11 @@ protected Plot(Canvas canvas) { } public @NonNull Style currentStyle() { - return Style.of(currentStyleState); - } - - public long currentStyleState() { - return currentStyleState; + return currentStyle; } public Plot currentStyle(Style currentStyle) { - this.currentStyleState = currentStyle.state(); - return this; - } - - public Plot currentStyleState(long currentStyleState) { - this.currentStyleState = currentStyleState; + this.currentStyle = currentStyle; return this; } @@ -96,14 +87,10 @@ public void render(Canvas canvas) { } public Plot plot(int x, int y) { - return plot(x, y, currentStyleState); + return plot(x, y, currentStyle); } public Plot plot(int x, int y, Style style) { - return plot(x, y, style.state()); - } - - public Plot plot(int x, int y, long styleState) { int cx = cOrgX + x / 2; int cy = cOrgY - y / 2; int rx = x % 2; @@ -111,7 +98,7 @@ public Plot plot(int x, int y, long styleState) { char newDot = selectDot(rx, ry); char existingDot = canvas.charAt(cx, cy); char combinedDot = combineDots(existingDot, newDot); - canvas.setCharAt(cx, cy, styleState, combinedDot); + canvas.setCharAt(cx, cy, style, combinedDot); return this; } @@ -123,14 +110,14 @@ public Plot unplot(int x, int y) { char removeDot = selectDot(rx, ry); char existingDot = canvas.charAt(cx, cy); char combinedDot = uncombineDots(existingDot, removeDot); - canvas.setCharAt(cx, cy, currentStyleState, combinedDot); + canvas.setCharAt(cx, cy, currentStyle, combinedDot); return this; } public Plot clear() { for (int y = 0; y < size().height(); y++) { for (int x = 0; x < size().width(); x++) { - canvas.setCharAt(x, y, currentStyleState, ' '); + canvas.setCharAt(x, y, currentStyle, ' '); } } return this; diff --git a/twinkle-chart/src/test/java/examples/MathPlotFourDemo.java b/twinkle-chart/src/test/java/examples/MathPlotFourDemo.java index d3a4070..5f014ca 100644 --- a/twinkle-chart/src/test/java/examples/MathPlotFourDemo.java +++ b/twinkle-chart/src/test/java/examples/MathPlotFourDemo.java @@ -52,7 +52,8 @@ public static void main(String[] args) throws InterruptedException, IOException Thread.sleep(20); } } finally { - pout.print(Ansi.showCursor()); + pout.print(Ansi.STYLE_RESET + Ansi.showCursor()); + pout.flush(); } } } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/decor/SimpleBorderRenderer.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/decor/SimpleBorderRenderer.java index d749f8d..a872b61 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/decor/SimpleBorderRenderer.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/decor/SimpleBorderRenderer.java @@ -10,7 +10,7 @@ public class SimpleBorderRenderer implements Renderable { private LineStyle topLineStyle; private LineStyle bottomLineStyle; private CornerStyle cornerStyle; - private long styleState; + private Style style; public enum LineStyle { SINGLE('─', '│'), @@ -51,7 +51,7 @@ public SimpleBorderRenderer() { this.topLineStyle = LineStyle.SINGLE; this.bottomLineStyle = LineStyle.SINGLE; this.cornerStyle = CornerStyle.ROUNDED; - this.styleState = Style.F_UNSTYLED; + this.style = Style.UNSTYLED; } public SimpleBorderRenderer lineStyle(LineStyle lineStyle) { @@ -88,48 +88,41 @@ public SimpleBorderRenderer cornerStyle(CornerStyle cornerStyle) { } public SimpleBorderRenderer style(Style style) { - return style(style.state()); - } - - public SimpleBorderRenderer style(long styleState) { - this.styleState = styleState; + this.style = style; return this; } @Override public void render(Canvas canvas) { - canvas.setCharAt( - 0, 0, styleState, corner(cornerStyle.topLeftChar, leftLineStyle, topLineStyle)); - canvas.drawHLineAt( - 1, 0, canvas.size().width() - 1, styleState, topLineStyle.horizontalChar); + canvas.setCharAt(0, 0, style, corner(cornerStyle.topLeftChar, leftLineStyle, topLineStyle)); + canvas.drawHLineAt(1, 0, canvas.size().width() - 1, style, topLineStyle.horizontalChar); canvas.setCharAt( canvas.size().width() - 1, 0, - styleState, + style, corner(cornerStyle.topRightChar, rightLineStyle, topLineStyle)); - canvas.drawVLineAt( - 0, 1, canvas.size().height() - 1, styleState, leftLineStyle.verticalChar); + canvas.drawVLineAt(0, 1, canvas.size().height() - 1, style, leftLineStyle.verticalChar); canvas.setCharAt( 0, canvas.size().height() - 1, - styleState, + style, corner(cornerStyle.bottomLeftChar, leftLineStyle, bottomLineStyle)); canvas.drawHLineAt( 1, canvas.size().height() - 1, canvas.size().width() - 1, - styleState, + style, bottomLineStyle.horizontalChar); canvas.setCharAt( canvas.size().width() - 1, canvas.size().height() - 1, - styleState, + style, corner(cornerStyle.bottomRightChar, rightLineStyle, bottomLineStyle)); canvas.drawVLineAt( canvas.size().width() - 1, 1, canvas.size().height() - 1, - styleState, + style, rightLineStyle.verticalChar); } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Buffer.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Buffer.java index c2af447..461e10c 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Buffer.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Buffer.java @@ -102,43 +102,35 @@ public int codepointAt(int x, int y) { } @Override - public long styleStateAt(int x, int y) { - if (outside(x, y, 0)) { - return Style.F_UNSTYLED; - } - return line(y).styleStateAt(applyXOffset(x)); - } - - @Override - public void setCharAt(int x, int y, long styleState, char c) { + public void setCharAt(int x, int y, @NonNull Style style, char c) { if (outside(x, y, 0)) { return; } - line(y).setCharAt(applyXOffset(x), styleState, c); + line(y).setCharAt(applyXOffset(x), style, c); } @Override - public void setCharAt(int x, int y, long styleState, int cp) { + public void setCharAt(int x, int y, @NonNull Style style, int cp) { if (outside(x, y, 0)) { return; } - line(y).setCharAt(applyXOffset(x), styleState, cp); + line(y).setCharAt(applyXOffset(x), style, cp); } @Override - public void setCharAt(int x, int y, long styleState, @NonNull CharSequence grapheme) { + public void setCharAt(int x, int y, @NonNull Style style, @NonNull CharSequence grapheme) { if (outside(x, y, 0)) { return; } - line(y).setCharAt(applyXOffset(x), styleState, grapheme); + line(y).setCharAt(applyXOffset(x), style, grapheme); } @Override - public int putStringAt(int x, int y, long styleState, @NonNull CharSequence str) { + public int putStringAt(int x, int y, @NonNull Style style, @NonNull CharSequence str) { if (outside(x, y, str.length())) { return str.length(); } - return line(y).putStringAt(applyXOffset(x), styleState, str); + return line(y).putStringAt(applyXOffset(x), style, str); } @Override @@ -147,16 +139,16 @@ public int putStringAt(int x, int y, @NonNull StyledIterator iter) { } @Override - public void drawHLineAt(int x, int y, int x2, long styleState, char c) { + public void drawHLineAt(int x, int y, int x2, @NonNull Style style, char c) { for (int i = x; i < x2; i++) { - setCharAt(i, y, styleState, c); + setCharAt(i, y, style, c); } } @Override - public void drawVLineAt(int x, int y, int y2, long styleState, char c) { + public void drawVLineAt(int x, int y, int y2, @NonNull Style style, char c) { for (int i = y; i < y2; i++) { - setCharAt(x, i, styleState, c); + setCharAt(x, i, style, c); } } @@ -164,7 +156,7 @@ public void drawVLineAt(int x, int y, int y2, long styleState, char c) { public void copyTo(Canvas canvas, int x, int y) { for (int i = 0; i < lines.length; i++) { for (int j = 0; j < lines[i].length(); j++) { - canvas.setCharAt(x + j, y + i, styleStateAt(j, i), charAt(j, i)); + canvas.setCharAt(x + j, y + i, styleAt(j, i), charAt(j, i)); } } } @@ -238,24 +230,24 @@ public String toString() { } @Override - public @NonNull String toAnsiString(long currentStyleState) { + public @NonNull String toAnsiString(Style currentStyle) { // Assuming only single-width characters for capacity estimation // plus 20 extra for escape codes and newline int initialCapacity = (size().width() + 1) * size().height(); StringBuilder sb = new StringBuilder(initialCapacity); try { - return toAnsi(sb, currentStyleState).toString(); + return toAnsi(sb, currentStyle).toString(); } catch (IOException e) { throw new RuntimeException(e); } } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { for (int y = 0; y < size().height(); y++) { - line(y).toAnsi(appendable, currentStyleState); - currentStyleState = line(y).styleStateAt(size().width() - 1); + line(y).toAnsi(appendable, currentStyle); + currentStyle = line(y).styleAt(size().width() - 1); if (y < size().height() - 1) { appendable.append('\n'); } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Canvas.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Canvas.java index 0547328..72a42bc 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Canvas.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Canvas.java @@ -13,47 +13,21 @@ public interface Canvas extends Sized { @NonNull String graphemeAt(int x, int y); - long styleStateAt(int x, int y); - @NonNull Style styleAt(int x, int y); - default void setCharAt(int x, int y, @NonNull Style style, char c) { - setCharAt(x, y, style.state(), c); - } + void setCharAt(int x, int y, @NonNull Style style, char c); - void setCharAt(int x, int y, long styleState, char c); + void setCharAt(int x, int y, @NonNull Style style, int cp); - default void setCharAt(int x, int y, @NonNull Style style, int cp) { - setCharAt(x, y, style.state(), cp); - } + void setCharAt(int x, int y, @NonNull Style style, @NonNull CharSequence grapheme); - void setCharAt(int x, int y, long styleState, int cp); - - default void setCharAt(int x, int y, @NonNull Style style, @NonNull CharSequence grapheme) { - setCharAt(x, y, style.state(), grapheme); - } - - void setCharAt(int x, int y, long styleState, @NonNull CharSequence grapheme); - - default int putStringAt(int x, int y, @NonNull Style style, @NonNull CharSequence str) { - return putStringAt(x, y, style.state(), str); - } - - int putStringAt(int x, int y, long styleState, @NonNull CharSequence str); + int putStringAt(int x, int y, @NonNull Style style, @NonNull CharSequence str); int putStringAt(int x, int y, @NonNull StyledIterator iter); - default void drawHLineAt(int x, int y, int x2, Style style, char c) { - drawHLineAt(x, y, x2, style.state(), c); - } - - void drawHLineAt(int x, int y, int x2, long styleState, char c); - - default void drawVLineAt(int x, int y, int y2, Style style, char c) { - drawVLineAt(x, y, y2, style.state(), c); - } + void drawHLineAt(int x, int y, int x2, @NonNull Style style, char c); - void drawVLineAt(int x, int y, int y2, long styleState, char c); + void drawVLineAt(int x, int y, int y2, @NonNull Style style, char c); void copyTo(Canvas canvas, int x, int y); diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Line.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Line.java index baee037..cbb8a78 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Line.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Line.java @@ -21,11 +21,7 @@ public static Line of(String text) { } public static Line of(String text, Style style) { - return of(text, style.state()); - } - - public static Line of(String text, long styleState) { - return new Line((Span.of(text, styleState))); + return new Line((Span.of(text, style))); } public static Line of(Span... spans) { @@ -40,23 +36,23 @@ protected Line(Span... spans) { public static Line of(StyledIterator iter) { List spans = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - long currentStyleState = Style.F_UNKNOWN; + Style currentStyle = Style.of(Style.F_UNKNOWN); while (iter.hasNext()) { long cp = iter.next(); if (cp == '\n') { break; } - if (iter.styleState() != currentStyleState) { + if (iter.style() != currentStyle) { if (sb.length() > 0) { - spans.add(Span.of(sb.toString(), currentStyleState)); + spans.add(Span.of(sb.toString(), currentStyle)); sb.setLength(0); } - currentStyleState = iter.styleState(); + currentStyle = iter.style(); } sb.appendCodePoint((int) cp); } if (sb.length() > 0) { - spans.add(Span.of(sb.toString(), currentStyleState)); + spans.add(Span.of(sb.toString(), currentStyle)); } return new Line(spans.toArray(new Span[0])); } @@ -73,11 +69,11 @@ public String toString() { } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { for (Span span : spans) { - span.toAnsi(appendable, currentStyleState); - currentStyleState = span.style().state(); + span.toAnsi(appendable, currentStyle); + currentStyle = span.style(); } return appendable; } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/LineBuffer.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/LineBuffer.java index 114fbfc..5e0fe42 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/LineBuffer.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/LineBuffer.java @@ -18,33 +18,15 @@ public interface LineBuffer extends Printable { @NonNull String graphemeAt(int i); - long styleStateAt(int i); - @NonNull Style styleAt(int i); - default void setCharAt(int index, @NonNull Style style, char c) { - setCharAt(index, style.state(), c); - } - - void setCharAt(int index, long styleState, char c); - - default void setCharAt(int index, @NonNull Style style, int cp) { - setCharAt(index, style.state(), cp); - } - - void setCharAt(int index, long styleState, int cp); - - default void setCharAt(int index, @NonNull Style style, @NonNull CharSequence grapheme) { - setCharAt(index, style.state(), grapheme); - } + void setCharAt(int index, @NonNull Style style, char c); - void setCharAt(int index, long styleState, @NonNull CharSequence grapheme); + void setCharAt(int index, @NonNull Style style, int cp); - default int putStringAt(int index, @NonNull Style style, @NonNull CharSequence str) { - return putStringAt(index, style.state(), str); - } + void setCharAt(int index, @NonNull Style style, @NonNull CharSequence grapheme); - int putStringAt(int index, long styleState, @NonNull CharSequence str); + int putStringAt(int index, @NonNull Style style, @NonNull CharSequence str); int putStringAt(int index, @NonNull StyledIterator iter); @@ -125,14 +107,6 @@ public int codepointAt(int index) { return new String(Character.toChars(cpBuffer[index])); } - @Override - public long styleStateAt(int index) { - if (invalidIndex(index)) { - return Style.F_UNSTYLED; - } - return styleBuffer[index]; - } - @Override public @NonNull Style styleAt(int index) { if (invalidIndex(index)) { @@ -142,7 +116,7 @@ public long styleStateAt(int index) { } @Override - public void setCharAt(int index, long styleState, char ch) { + public void setCharAt(int index, @NonNull Style style, char ch) { if (invalidIndex(index)) { return; } @@ -150,53 +124,53 @@ public void setCharAt(int index, long styleState, char ch) { // TODO log warning about surrogate characters not being supported ch = REPLACEMENT_CHAR; } - setCharAt_(index, styleState, ch); + setCharAt_(index, style, ch); } - private void setCharAt_(int index, long styleState, char ch) { + private void setCharAt_(int index, @NonNull Style style, char ch) { if (Character.isSurrogate(ch)) { // TODO log warning about surrogate characters not being supported ch = REPLACEMENT_CHAR; } cpBuffer[index] = ch; graphemeBuffer[index] = null; - styleBuffer[index] = styleState; + styleBuffer[index] = style.state(); } @Override - public void setCharAt(int index, long styleState, int cp) { + public void setCharAt(int index, @NonNull Style style, int cp) { if (invalidIndex(index)) { return; } - setCharAt_(index, styleState, cp); + setCharAt_(index, style, cp); } - private void setCharAt_(int index, long styleState, int cp) { + private void setCharAt_(int index, @NonNull Style style, int cp) { cpBuffer[index] = cp; graphemeBuffer[index] = null; - styleBuffer[index] = styleState; + styleBuffer[index] = style.state(); } @Override - public void setCharAt(int index, long styleState, @NonNull CharSequence grapheme) { + public void setCharAt(int index, @NonNull Style style, @NonNull CharSequence grapheme) { if (invalidIndex(index)) { return; } - setCharAt_(index, styleState, grapheme); + setCharAt_(index, style, grapheme); } - private void setCharAt_(int index, long styleState, @NonNull CharSequence grapheme) { + private void setCharAt_(int index, @NonNull Style style, @NonNull CharSequence grapheme) { if (grapheme.length() == 0) { return; } cpBuffer[index] = -1; graphemeBuffer[index] = grapheme.toString(); - styleBuffer[index] = styleState; + styleBuffer[index] = style.state(); } @Override - public int putStringAt(int index, long styleState, @NonNull CharSequence str) { - return putStringAt(index, StyledIterator.of(str, styleState)); + public int putStringAt(int index, @NonNull Style style, @NonNull CharSequence str) { + return putStringAt(index, StyledIterator.of(str, style)); } @Override @@ -216,7 +190,7 @@ public int putStringAt(int index, @NonNull StyledIterator iter) { // Skip any zero-width characters continue; } - long style = iter.styleState(); + Style style = iter.style(); if (iter.width() == 2 && (cnt + 1) >= len) { // Not enough space for a wide character setCharAt_(startIndex + cnt, style, REPLACEMENT_CHAR); @@ -335,40 +309,27 @@ private boolean outside(int index, int length) { return sb.toString(); } - /** - * Converts the buffer to an ANSI string, including ANSI escape codes for styles and colors. - * This method takes into account the provided current style to generate a result that is as - * efficient as possible in terms of ANSI codes. - * - *

A system property "twinkle.styledbuffer.toAnsi" can be set to "fast" or "short" to choose - * between two strategies for generating the ANSI string. The "fast" strategy generates the ANSI - * string in a single pass, while the "short" strategy generates two ANSI strings and returns - * the shorter one. The default is "short". - * - * @param styleState The current style to start with. - * @return The ANSI string representation of the styled buffer. - */ @Override - public @NonNull String toAnsiString(long styleState) { + public @NonNull String toAnsiString(Style currentStyle) { // Assuming only single-width characters for capacity estimation // plus 20 extra for escape codes int initialCapacity = length() + 20; StringBuilder sb = new StringBuilder(initialCapacity); try { - return toAnsi(sb, styleState).toString(); + return toAnsi(sb, currentStyle).toString(); } catch (IOException e) { throw new RuntimeException(e); } } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long lastStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { for (int i = 0; i < length(); i++) { - if (styleBuffer[i] != lastStyleState) { + if (styleBuffer[i] != currentStyle.state()) { Style style = Style.of(styleBuffer[i]); - style.toAnsi(appendable, lastStyleState); - lastStyleState = styleBuffer[i]; + style.toAnsi(appendable, currentStyle); + currentStyle = Style.of(styleBuffer[i]); } if (graphemeBuffer[i] != null) { appendable.append(graphemeBuffer[i]); diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Span.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Span.java index cb4d770..f0f9e12 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Span.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Span.java @@ -18,10 +18,6 @@ public static Span of(@NonNull String text, Style style) { return new Span(text, style); } - public static Span of(@NonNull String text, long styleState) { - return new Span(text, Style.of(styleState)); - } - protected Span(@NonNull String text, Style style) { this.text = text; this.style = style; @@ -46,9 +42,9 @@ public String toString() { } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { - style.toAnsi(appendable, currentStyleState); + style.toAnsi(appendable, currentStyle); appendable.append(text); return appendable; } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Text.java b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Text.java index a9db003..49d2184 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Text.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/core/text/Text.java @@ -17,11 +17,7 @@ public static Text of(String text) { } public static Text of(String text, Style style) { - return of(text, style.state()); - } - - public static Text of(String text, long styleState) { - return new Text(new Line((Span.of(text, styleState)))); + return new Text(new Line((Span.of(text, style)))); } public static Text of(Line... lines) { @@ -53,16 +49,16 @@ protected Text(Line... lines) { } @Override - public @NonNull Appendable toAnsi(Appendable appendable, long currentStyleState) + public @NonNull Appendable toAnsi(Appendable appendable, Style currentStyle) throws IOException { boolean first = true; for (Line line : lines) { if (!first) { appendable.append('\n'); } - line.toAnsi(appendable, currentStyleState); + line.toAnsi(appendable, currentStyle); if (!line.spans().isEmpty()) { - currentStyleState = line.spans().get(line.spans().size() - 1).style().state(); + currentStyle = line.spans().get(line.spans().size() - 1).style(); } first = false; } diff --git a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/Framed.java b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/Framed.java index 8a63be2..ea7a8d2 100644 --- a/twinkle-core/src/main/java/org/codejive/twinkle/widgets/Framed.java +++ b/twinkle-core/src/main/java/org/codejive/twinkle/widgets/Framed.java @@ -58,7 +58,7 @@ public void render(Canvas canvas) { } if (title != null) { Canvas view = canvas.view(2, 0, canvas.size().width() - 4, 1); - view.putStringAt(0, 0, Style.F_UNKNOWN, title.toAnsiString()); + view.putStringAt(0, 0, Style.of(Style.F_UNKNOWN), title.toAnsiString()); } if (widget != null) { widget.render(canvas.view(Rect.of(canvas.size()).grow(-1, -1, -1, -1))); diff --git a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/LineBufferTimings.java b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/LineBufferTimings.java index d9e0604..b4b7b28 100644 --- a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/LineBufferTimings.java +++ b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/LineBufferTimings.java @@ -1,5 +1,7 @@ package org.codejive.twinkle.core.text; +import org.codejive.twinkle.ansi.Style; + public class LineBufferTimings { private static int iterations = 1_000_000; @@ -50,10 +52,10 @@ private static void timeSimpleString(LineBuffer buffer) { buffer.getClass().getSimpleName(), () -> { for (int i = 0; i < 500; i += 10) { - buffer.putStringAt(i, 0, "0123456789"); + buffer.putStringAt(i, Style.UNSTYLED, "0123456789"); } for (int i = 500; i < 1000; i += 10) { - buffer.putStringAt(i, 0, "0123456789"); + buffer.putStringAt(i, Style.UNSTYLED, "0123456789"); } }); } @@ -63,10 +65,10 @@ private static void timeStringWithSurrogates(LineBuffer buffer) { buffer.getClass().getSimpleName(), () -> { for (int i = 0; i < 500; i += 10) { - buffer.putStringAt(i, 0, "0123456789"); + buffer.putStringAt(i, Style.UNSTYLED, "0123456789"); } for (int i = 500; i < 1000; i += 10) { - buffer.putStringAt(i, 0, "01234\uD83D\uDE8056789"); + buffer.putStringAt(i, Style.UNSTYLED, "01234\uD83D\uDE8056789"); } }); } diff --git a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestLineBuffer.java b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestLineBuffer.java index 0bff2a6..1b5576e 100644 --- a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestLineBuffer.java +++ b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestLineBuffer.java @@ -17,11 +17,11 @@ public void testStyledBufferCreation() { public void testStyledBufferPutGetChar() { LineBuffer buffer = LineBuffer.of(10); for (int i = 0; i < buffer.length(); i++) { - buffer.setCharAt(i, Style.ITALIC.state(), (char) ('a' + i)); + buffer.setCharAt(i, Style.ITALIC, (char) ('a' + i)); } for (int i = 0; i < buffer.length(); i++) { assertThat(buffer.charAt(i)).isEqualTo((char) ('a' + i)); - assertThat(buffer.styleStateAt(i)).isEqualTo(Style.ITALIC.state()); + assertThat(buffer.styleAt(i)).isEqualTo(Style.ITALIC); } } @@ -29,7 +29,7 @@ public void testStyledBufferPutGetChar() { public void testStyledBufferPutCharToString() { LineBuffer buffer = LineBuffer.of(10); for (int i = 0; i < buffer.length(); i++) { - buffer.setCharAt(i, Style.ITALIC.state(), (char) ('a' + i)); + buffer.setCharAt(i, Style.ITALIC, (char) ('a' + i)); } assertThat(buffer.toString()).isEqualTo("abcdefghij"); } @@ -57,7 +57,7 @@ public void testStyledBufferPutCharToAnsiStringWithCurrentStyle() { Style style = i < 5 ? Style.ITALIC : Style.UNDERLINED; buffer.setCharAt(i, style, (char) ('a' + i)); } - assertThat(buffer.toAnsiString(Style.F_ITALIC)) + assertThat(buffer.toAnsiString(Style.ITALIC)) .isEqualTo("abcde" + Ansi.style(Ansi.NOTITALICIZED, Ansi.UNDERLINED) + "fghij"); } @@ -83,7 +83,7 @@ public void testStyledBufferPutStringGetChar() { buffer.putStringAt(0, Style.ITALIC, "abcdefghij"); for (int i = 0; i < buffer.length(); i++) { assertThat(buffer.charAt(i)).isEqualTo((char) ('a' + i)); - assertThat(buffer.styleStateAt(i)).isEqualTo(Style.ITALIC.state()); + assertThat(buffer.styleAt(i)).isEqualTo(Style.ITALIC); } } } diff --git a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestText.java b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestText.java index 73e2de6..8c6298d 100644 --- a/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestText.java +++ b/twinkle-core/src/test/java/org/codejive/twinkle/core/text/TestText.java @@ -19,16 +19,14 @@ public void testOfSimpleString() { public void testOfStyledString() { Style style = Style.BOLD; Text t = Text.of("Hello World", style); - assertThat(t.toAnsiString(Style.F_UNSTYLED)) - .isEqualTo(style.toAnsiString() + "Hello World"); + assertThat(t.toAnsiString(Style.UNSTYLED)).isEqualTo(style.toAnsiString() + "Hello World"); } @Test public void testOfStyleState() { Style style = Style.BOLD; - Text t = Text.of("Hello World", style.state()); - assertThat(t.toAnsiString(Style.F_UNSTYLED)) - .isEqualTo(style.toAnsiString() + "Hello World"); + Text t = Text.of("Hello World", style); + assertThat(t.toAnsiString(Style.UNSTYLED)).isEqualTo(style.toAnsiString() + "Hello World"); } @Test