Skip to content

Commit

Permalink
Fixed colored text layout when using wrapping to match non-colored te…
Browse files Browse the repository at this point in the history
…xt layout.
  • Loading branch information
NathanSweet committed Jun 1, 2018
1 parent cae2342 commit 59644b7
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 60 deletions.
4 changes: 2 additions & 2 deletions gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java
Expand Up @@ -816,9 +816,9 @@ public void getGlyphs (GlyphRun run, CharSequence str, int start, int end, Glyph
public int getWrapIndex (Array<Glyph> glyphs, int start) {
int i = start - 1;
if (isWhitespace((char)glyphs.get(i).id)) return i;
for (; i >= 1; i--)
for (; i > 0; i--)
if (!isWhitespace((char)glyphs.get(i).id)) break;
for (; i >= 1; i--) {
for (; i > 0; i--) {
char ch = (char)glyphs.get(i).id;
if (isWhitespace(ch) || isBreakChar(ch)) return i + 1;
}
Expand Down
70 changes: 45 additions & 25 deletions gdx/src/com/badlogic/gdx/graphics/g2d/GlyphLayout.java
Expand Up @@ -147,36 +147,31 @@ else if (targetWidth <= fontData.spaceXadvance * 3) //
break runEnded;
}
run.color.set(color);
if (lastGlyph != null) {
float lastGlyphWidth = lastGlyph.fixedWidth ? lastGlyph.xadvance * fontData.scaleX
if (lastGlyph != null) { // Move back the width of the last glyph from the previous run.
x -= lastGlyph.fixedWidth ? lastGlyph.xadvance * fontData.scaleX
: (lastGlyph.width + lastGlyph.xoffset) * fontData.scaleX - fontData.padRight;
x -= lastGlyphWidth;
}
lastGlyph = run.glyphs.peek();
run.x = x;
run.y = y;
if (newline || runEnd == end) adjustLastGlyph(fontData, run);
runs.add(run);

float[] xAdvances = run.xAdvances.items;
if (!wrap) {
// No wrap or truncate.
int n = run.xAdvances.size;
if (!wrap) { // No wrap or truncate.
float runWidth = 0;
for (int i = 0, n = run.xAdvances.size; i < n; i++)
for (int i = 0; i < n; i++)
runWidth += xAdvances[i];
x += runWidth;
run.width = runWidth;
lastGlyph = run.glyphs.peek();
break runEnded;
}

// Wrap or truncate.
int n = run.xAdvances.size;
x += xAdvances[0];
run.width = xAdvances[0];
if (n < 1) {
lastGlyph = run.glyphs.peek();
break runEnded;
}
if (n < 1) break runEnded;
x += xAdvances[1];
run.width += xAdvances[1];
for (int i = 2; i < n; i++) {
Expand All @@ -203,34 +198,52 @@ else if (targetWidth <= fontData.spaceXadvance * 3) //
wrapIndex = i - 1;
}
GlyphRun next;
if (wrapIndex == 0) { // No wrap index, move entire run to next line.
if (wrapIndex == 0) { // Move entire run to next line.
next = run;
run.width = 0;
width = Math.max(width, run.x);

// Remove leading whitespace.
for (int glyphCount = run.glyphs.size; wrapIndex < glyphCount; wrapIndex++)
if (!fontData.isWhitespace((char)run.glyphs.get(wrapIndex).id)) break;
if (wrapIndex > 0) {
run.glyphs.removeRange(0, wrapIndex - 1);
run.xAdvances.removeRange(1, wrapIndex);
run.xAdvances.set(0, -run.glyphs.first().xoffset * fontData.scaleX - fontData.padLeft);
}
run.xAdvances.set(0, -run.glyphs.first().xoffset * fontData.scaleX - fontData.padLeft);

if (runs.size > 1) { // Previous run is now at the end of a line.
// Remove trailing whitespace and adjust last glyph.
GlyphRun previous = runs.get(runs.size - 2);
int lastIndex = previous.glyphs.size - 1;
for (; lastIndex > 0; lastIndex--) {
Glyph g = previous.glyphs.get(lastIndex);
if (!fontData.isWhitespace((char)g.id)) break;
previous.width -= previous.xAdvances.get(lastIndex + 1);
}
previous.glyphs.truncate(lastIndex + 1);
previous.xAdvances.truncate(lastIndex + 2);
adjustLastGlyph(fontData, previous);
width = Math.max(width, previous.x + previous.width);
}
} else {
next = wrap(fontData, run, glyphRunPool, wrapIndex, i);
runs.add(next);
width = Math.max(width, run.x + run.width);
if (next == null) { // All wrapped glyphs were whitespace.
x = 0;
y += fontData.down;
lines++;
lastGlyph = null;
break;
}
runs.add(next);
}

// Start the loop over with the new run on the next line.
n = next.xAdvances.size;
xAdvances = next.xAdvances.items;
x = xAdvances[0];
next.width += xAdvances[0];
if (n > 1) {
x += xAdvances[1];
next.width += xAdvances[1];
}
if (n > 1) x += xAdvances[1];
next.width += x;
y += fontData.down;
lines++;
next.x = 0;
Expand Down Expand Up @@ -339,10 +352,9 @@ private void truncate (BitmapFontData fontData, GlyphRun run, float targetWidth,
glyphRunPool.free(truncateRun);
}

/** Breaks a run into two runs at the specified wrapIndex.
* @return May be null if second run is all whitespace. */
private GlyphRun wrap (BitmapFontData fontData, GlyphRun first, Pool<GlyphRun> glyphRunPool, int wrapIndex, int widthIndex) {
GlyphRun second = glyphRunPool.obtain();
second.color.set(first.color);

Array<Glyph> glyphs2 = first.glyphs; // Starts with all the glyphs.
int glyphCount = first.glyphs.size;
FloatArray xAdvances2 = first.xAdvances; // Starts with all the xAdvances.
Expand All @@ -366,8 +378,12 @@ private GlyphRun wrap (BitmapFontData fontData, GlyphRun first, Pool<GlyphRun> g
first.width -= xAdvances2.get(--widthIndex);

// Copy wrapped glyphs and xAdvances to second run.
// The second run will contain the remaining glyph data, so swap instances rather than copying to reduce large allocations.
// The second run will contain the remaining glyph data, so swap instances rather than copying.
GlyphRun second = null;
if (secondStart < glyphCount) {
second = glyphRunPool.obtain();
second.color.set(first.color);

Array<Glyph> glyphs1 = second.glyphs; // Starts empty.
glyphs1.addAll(glyphs2, 0, firstEnd);
glyphs2.removeRange(0, secondStart - 1);
Expand All @@ -380,6 +396,10 @@ private GlyphRun wrap (BitmapFontData fontData, GlyphRun first, Pool<GlyphRun> g
xAdvances2.set(0, -glyphs2.first().xoffset * fontData.scaleX - fontData.padLeft);
first.xAdvances = xAdvances1;
second.xAdvances = xAdvances2;
} else {
// Second run is empty, just trim whitespace glyphs from end of first run.
glyphs2.truncate(firstEnd);
xAdvances2.truncate(firstEnd + 1);
}

if (firstEnd == 0) {
Expand Down
Expand Up @@ -111,6 +111,7 @@ public void render () {
// text = "How quickly da[RED]ft jumping zebras vex.";
text = "Another font wrap is-sue, this time with multiple whitespace characters.";
// text = "test with AGWlWi AGWlWi issue";
text = "AAA BBB CCC DDD [RED]EEE";
if (true) { // Test wrap.
layout.setText(font, text, 0, text.length(), font.getColor(), w, Align.center, true, null);
} else { // Test truncation.
Expand Down
86 changes: 53 additions & 33 deletions tests/gdx-tests/src/com/badlogic/gdx/tests/LabelTest.java
Expand Up @@ -40,57 +40,76 @@ public class LabelTest extends GdxTest {
Actor root;
ShapeRenderer renderer;

float scale = 1;

@Override
public void create () {
batch = new SpriteBatch();
renderer = new ShapeRenderer();
skin = new Skin(Gdx.files.internal("data/uiskin.json"));
skin.getAtlas().getTextures().iterator().next().setFilter(TextureFilter.Nearest, TextureFilter.Nearest);
skin.getFont("default-font").getData().markupEnabled = true;
float scale = 1;
skin.getFont("default-font").getData().setScale(scale);
stage = new Stage(new ScreenViewport());
Gdx.input.setInputProcessor(stage);

// skin.getFont("default-font").getData().getGlyph('T').xoffset = -20;
skin.getFont("default-font").getData().getGlyph('B').setKerning('B', -5);

Table table = new Table();
Label label;

Table table = new Table().debug();

table.add(new Label("This is regular text.", skin)).row();
table.add(new Label("This is regular text\nwith a newline.", skin)).row();

label = new Label("This is [RED]regular text\n\nwith newlines,\naligned bottom, right.", skin);
label.setColor(Color.GREEN);
label.setAlignment(Align.bottom | Align.right);
table.add(label).minWidth(200 * scale).minHeight(110 * scale).fill().row();

label = new Label("This is regular text with NO newlines, wrap enabled and aligned bottom, right.", skin);
label.setWrap(true);
label.setAlignment(Align.bottom | Align.right);
table.add(label).minWidth(200 * scale).minHeight(110 * scale).fill().row();

label = new Label("This is regular text with\n\nnewlines, wrap\nenabled and aligned bottom, right.", skin);
label.setWrap(true);
label.setAlignment(Align.bottom | Align.right);
table.add(label).minWidth(200 * scale).minHeight(110 * scale).fill().row();

table.setPosition(50, 40 + 25 * scale);
table.pack();
stage.addActor(table);
table.setPosition(200, 65);

table.debug();
table.add(new Label("This is regular text.", skin));
table.row();
table.add(new Label("This is regular text\nwith a newline.", skin));
table.row();
Label label3 = new Label("This is [RED]regular text\n\nwith newlines,\naligned bottom, right.", skin);
label3.setColor(Color.GREEN);
label3.setAlignment(Align.bottom | Align.right);
table.add(label3).minWidth(200 * scale).minHeight(110 * scale).fill();
table.row();
Label label4 = new Label("This is regular text with NO newlines, wrap enabled and aligned bottom, right.", skin);
label4.setWrap(true);
label4.setAlignment(Align.bottom | Align.right);
table.add(label4).minWidth(200 * scale).minHeight(110 * scale).fill();
table.row();
Label label5 = new Label("This is regular text with\n\nnewlines, wrap\nenabled and aligned bottom, right.", skin);
label5.setWrap(true);
label5.setAlignment(Align.bottom | Align.right);
table.add(label5).minWidth(200 * scale).minHeight(110 * scale).fill();
table.row();

//

table = new Table().debug();
stage.addActor(table);

table.add(new Label("This is regular text.", skin)).minWidth(200 * scale).row();

// The color markup text should match the uncolored text exactly.
Label label6 = new Label("AAA BBB CCC DDD EEE", skin);
table.add(label6).align(Align.left);
table.row();
Label label7 = new Label("AAA B[RED]B[]B CCC DDD EEE", skin);
table.add(label7).align(Align.left);
table.row();
Label label8 = new Label("[RED]AAA [BLUE]BBB [RED]CCC [BLUE]DDD [RED]EEE", skin);
table.add(label8).align(Align.left);
label = new Label("AAA BBB CCC DDD EEE", skin);
table.add(label).align(Align.left).row();

label = new Label("AAA B[RED]B[]B CCC DDD EEE", skin);
table.add(label).align(Align.left).row();

label = new Label("[RED]AAA [BLUE]BBB [RED]CCC [BLUE]DDD [RED]EEE", skin);
table.add(label).align(Align.left).row();

label = new Label("AAA BBB CCC DDD EEE", skin);
label.setWrap(true);
table.add(label).align(Align.left).width(150 * scale).row();

label = new Label("[RED]AAA [BLUE]BBB [RED]CCC [BLUE]DDD [RED]EEE", skin);
label.setWrap(true);
table.add(label).align(Align.left).width(150 * scale).row();

table.setPosition(50 + 250 * scale, 40 + 25 * scale);
table.pack();
stage.addActor(table);
}

@Override
Expand All @@ -107,7 +126,7 @@ public void render () {
stage.act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
stage.draw();

float x = 40, y = 40;
float x = 40, y = 15 + 20 * scale;

BitmapFont font = skin.getFont("default-font");
batch.begin();
Expand All @@ -128,5 +147,6 @@ public void drawLine (float x1, float y1, float x2, float y2) {
@Override
public void resize (int width, int height) {
stage.getViewport().update(width, height, true);
batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
}
}

0 comments on commit 59644b7

Please sign in to comment.