Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 68 additions & 32 deletions jme3-core/src/main/java/com/jme3/font/BitmapText.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ public class BitmapText extends Node {
private BitmapFont font;
// Stores the text content and its layout properties (size, box, alignment, etc.).
private StringBlock block;
// A flag indicating whether the text needs to be re-assembled
// Layout changes rebuild page membership and vertex positions.
private boolean needRefresh = true;
// Color changes can reuse the existing layout and update only vertex colors.
private boolean colorsNeedRefresh;
// An array of `BitmapTextPage` instances, each corresponding to a font page.
private BitmapTextPage[] textPages;
// Manages the individual letter quads, their positions, colors, and styles.
Expand Down Expand Up @@ -89,17 +91,16 @@ public BitmapText(BitmapFont font, boolean rightToLeft) {

/**
* Creates a new `BitmapText` instance with the specified font, text direction,
* and a flag for array-based rendering.
* and a legacy rendering flag.
*
* @param font The {@link BitmapFont} to use for rendering the text (not null).
* @param rightToLeft true for right-to-left text rendering, false for left-to-right.
* @param arrayBased If true, the internal text pages will use array-based buffers for rendering.
* This might affect performance or compatibility depending on the renderer.
* @param arrayBased ignored; array-based buffers are always used.
*/
public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) {
textPages = new BitmapTextPage[font.getPageSize()];
for (int page = 0; page < textPages.length; page++) {
textPages[page] = new BitmapTextPage(font, arrayBased, page);
textPages[page] = new BitmapTextPage(font, page);
attachChild(textPages[page]);
}

Expand Down Expand Up @@ -132,13 +133,11 @@ public void cloneFields(Cloner cloner, Object original) {
//this.block = cloner.clone(block);
this.block = block != null ? block.clone() : null;

// Change in behavior: The 'letters' field was not cloned or recreated
// before. I'm not sure how this worked and suspect BitmapText was just
// not cloneable if you planned to change the text later. -pspeed
this.letters = new Letters(font, block, letters.getQuad().isRightToLeft());

// Just noticed BitmapText is not even writable/readable really...
// so I guess cloning doesn't come up that often.
this.letters = new Letters(letters, block);
// The cloned pages must refer to the cloned letters, including their
// substring styles and colors, before a color-only update can run.
needRefresh = true;
colorsNeedRefresh = false;
}

/**
Expand All @@ -156,6 +155,9 @@ public BitmapFont getFont() {
* @param size The desired text size (e.g., in world units or pixels).
*/
public void setSize(float size) {
if (block.getSize() == size) {
return;
}
block.setSize(size);
needRefresh = true;
letters.invalidate();
Expand Down Expand Up @@ -229,8 +231,7 @@ public ColorRGBA getColor() {
*/
public void setColor(ColorRGBA color) {
letters.setColor(color);
letters.invalidate(); // TODO: Don't have to align.
needRefresh = true;
colorsNeedRefresh = true;
}

/**
Expand All @@ -244,7 +245,7 @@ public void setColor(ColorRGBA color) {
*/
public void setAlpha(float alpha) {
letters.setBaseAlpha(alpha);
needRefresh = true;
colorsNeedRefresh = true;
}

/**
Expand All @@ -264,6 +265,13 @@ public float getAlpha() {
* of the text rendering area.
*/
public void setBox(Rectangle rect) {
Rectangle previous = block.getTextBox();
// Rectangle coordinates are immutable, so equal bounds need no layout.
if (previous == rect || (previous != null && rect != null
&& previous.x == rect.x && previous.y == rect.y
&& previous.width == rect.width && previous.height == rect.height)) {
return;
}
block.setTextBox(rect);
letters.invalidate();
needRefresh = true;
Expand All @@ -285,7 +293,7 @@ public float getLineHeight() {
* @return The total height of the text block.
*/
public float getHeight() {
if (needRefresh) {
if (needRefresh || colorsNeedRefresh) {
assemble();
}
float height = getLineHeight() * block.getLineCount();
Expand All @@ -302,7 +310,7 @@ public float getHeight() {
* @return The maximum line width of the text.
*/
public float getLineWidth() {
if (needRefresh) {
if (needRefresh || colorsNeedRefresh) {
assemble();
}
Rectangle textBox = block.getTextBox();
Expand All @@ -320,7 +328,7 @@ public float getLineWidth() {
* @return The total number of lines.
*/
public int getLineCount() {
if (needRefresh) {
if (needRefresh || colorsNeedRefresh) {
assemble();
}
return block.getLineCount();
Expand All @@ -346,6 +354,9 @@ public void setAlignment(BitmapFont.Align align) {
if (block.getTextBox() == null && align != Align.Left) {
throw new RuntimeException("Bound is not set");
}
if (block.getAlignment() == align) {
return;
}
block.setAlignment(align);
letters.invalidate();
needRefresh = true;
Expand All @@ -362,6 +373,9 @@ public void setVerticalAlignment(BitmapFont.VAlign align) {
if (block.getTextBox() == null && align != VAlign.Top) {
throw new RuntimeException("Bound is not set");
}
if (block.getVerticalAlignment() == align) {
return;
}
block.setVerticalAlignment(align);
letters.invalidate();
needRefresh = true;
Expand Down Expand Up @@ -395,6 +409,8 @@ public BitmapFont.VAlign getVerticalAlignment() {
*/
public void setStyle(int start, int end, int style) {
letters.setStyle(start, end, style);
letters.invalidate();
needRefresh = true;
}

/**
Expand All @@ -421,8 +437,7 @@ public void setStyle(String regexp, int style) {
*/
public void setColor(int start, int end, ColorRGBA color) {
letters.setColor(start, end, color);
letters.invalidate();
needRefresh = true;
colorsNeedRefresh = true;
}

/**
Expand All @@ -437,8 +452,7 @@ public void setColor(String regexp, ColorRGBA color) {
while (m.find()) {
letters.setColor(m.start(), m.end(), color);
}
letters.invalidate();
needRefresh = true;
colorsNeedRefresh = true;
}

/**
Expand All @@ -460,6 +474,9 @@ public void setTabPosition(float... tabs) {
* @param width The default width for tabs in font units.
*/
public void setTabWidth(float width) {
if (block.getTabWidth() == width) {
return;
}
block.setTabWidth(width);
letters.invalidate();
needRefresh = true;
Expand All @@ -472,6 +489,9 @@ public void setTabWidth(float width) {
* @param c The character to use as the ellipsis.
*/
public void setEllipsisChar(char c) {
if (block.getEllipsisChar() == c) {
return;
}
block.setEllipsisChar(c);
letters.invalidate();
needRefresh = true;
Expand Down Expand Up @@ -502,25 +522,41 @@ public void setLineWrapMode(LineWrapMode wrap) {
@Override
public void updateLogicalState(float tpf) {
super.updateLogicalState(tpf);
if (needRefresh) {
if (needRefresh || colorsNeedRefresh) {
assemble();
}
}

/**
* Assembles the text by generating the quad list (character positions and sizes)
* and then populating the vertex buffers of each `BitmapTextPage`.
* This method is called internally when `needRefresh` is true.
* Updates layout and page buffers, or only colors when the layout is valid.
*/
private void assemble() {
// First, generate or update the list of letter quads
// based on current text and layout properties.
letters.update();
// Then, for each font page, assemble its mesh data from the generated quads.
for (BitmapTextPage textPage : textPages) {
textPage.assemble(letters);
if (needRefresh) {
letters.update();
for (BitmapTextPage textPage : textPages) {
textPage.clearQuads();
}
// Distribute letters once, rather than rescanning the entire text
// for each texture page. Keep this membership for color updates.
letters.rewind();
while (letters.nextCharacter()) {
if (letters.isPrintable()) {
int page = letters.getCharacterSetPage();
if (page >= 0 && page < textPages.length) {
textPages[page].addQuad(letters.getQuad());
}
}
}
for (BitmapTextPage textPage : textPages) {
textPage.assemble();
}
} else if (colorsNeedRefresh) {
for (BitmapTextPage textPage : textPages) {
textPage.updateColors();
}
}
needRefresh = false;
colorsNeedRefresh = false;
}

/**
Expand Down
Loading