|
@@ -1,5 +1,5 @@
|
|
/*
|
|
/*
|
|
- * Copyright (c) 2009-2021 jMonkeyEngine
|
|
|
|
|
|
+ * Copyright (c) 2009-2025 jMonkeyEngine
|
|
* All rights reserved.
|
|
* All rights reserved.
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* Redistribution and use in source and binary forms, with or without
|
|
@@ -31,14 +31,22 @@
|
|
*/
|
|
*/
|
|
package com.jme3.font;
|
|
package com.jme3.font;
|
|
|
|
|
|
-import com.jme3.export.*;
|
|
|
|
|
|
+import com.jme3.export.InputCapsule;
|
|
|
|
+import com.jme3.export.JmeExporter;
|
|
|
|
+import com.jme3.export.JmeImporter;
|
|
|
|
+import com.jme3.export.OutputCapsule;
|
|
|
|
+import com.jme3.export.Savable;
|
|
import com.jme3.material.Material;
|
|
import com.jme3.material.Material;
|
|
|
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator
|
|
|
|
|
|
+ * Represents a font loaded from a bitmap font definition
|
|
|
|
+ * (e.g., generated by <a href="https://libgdx.com/wiki/tools/hiero">AngelCode Bitmap Font Generator</a>).
|
|
|
|
+ * It manages character sets, font pages (textures), and provides utilities for text measurement and rendering.
|
|
|
|
+ *
|
|
* @author dhdd
|
|
* @author dhdd
|
|
|
|
+ * @author Yonghoon
|
|
*/
|
|
*/
|
|
public class BitmapFont implements Savable {
|
|
public class BitmapFont implements Savable {
|
|
|
|
|
|
@@ -87,33 +95,30 @@ public class BitmapFont implements Savable {
|
|
Bottom
|
|
Bottom
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ // The character set containing definitions for each character (glyph) in the font.
|
|
private BitmapCharacterSet charSet;
|
|
private BitmapCharacterSet charSet;
|
|
|
|
+ // An array of materials, where each material corresponds to a font page (texture).
|
|
private Material[] pages;
|
|
private Material[] pages;
|
|
|
|
+ // Indicates whether this font is designed for right-to-left (RTL) text rendering.
|
|
private boolean rightToLeft = false;
|
|
private boolean rightToLeft = false;
|
|
// For cursive bitmap fonts in which letter shape is determined by the adjacent glyphs.
|
|
// For cursive bitmap fonts in which letter shape is determined by the adjacent glyphs.
|
|
private GlyphParser glyphParser;
|
|
private GlyphParser glyphParser;
|
|
|
|
|
|
/**
|
|
/**
|
|
- * @return true, if this is a right-to-left font, otherwise it will return false.
|
|
|
|
|
|
+ * Creates a new instance of `BitmapFont`.
|
|
|
|
+ * This constructor is primarily used for deserialization.
|
|
*/
|
|
*/
|
|
- public boolean isRightToLeft() {
|
|
|
|
- return rightToLeft;
|
|
|
|
|
|
+ public BitmapFont() {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * Specify if this is a right-to-left font. By default it is set to false.
|
|
|
|
- * This can be "overwritten" in the BitmapText constructor.
|
|
|
|
|
|
+ * Creates a new {@link BitmapText} instance initialized with this font.
|
|
|
|
+ * The label's size will be set to the font's rendered size, and its text content
|
|
|
|
+ * will be set to the provided string.
|
|
*
|
|
*
|
|
- * @param rightToLeft true → right-to-left, false → left-to-right
|
|
|
|
- * (default=false)
|
|
|
|
|
|
+ * @param content The initial text content for the label.
|
|
|
|
+ * @return A new {@link BitmapText} instance.
|
|
*/
|
|
*/
|
|
- public void setRightToLeft(boolean rightToLeft) {
|
|
|
|
- this.rightToLeft = rightToLeft;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public BitmapFont() {
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
public BitmapText createLabel(String content) {
|
|
public BitmapText createLabel(String content) {
|
|
BitmapText label = new BitmapText(this);
|
|
BitmapText label = new BitmapText(this);
|
|
label.setSize(getCharSet().getRenderedSize());
|
|
label.setSize(getCharSet().getRenderedSize());
|
|
@@ -121,27 +126,81 @@ public class BitmapFont implements Savable {
|
|
return label;
|
|
return label;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Checks if this font is configured for right-to-left (RTL) text rendering.
|
|
|
|
+ *
|
|
|
|
+ * @return true if this is a right-to-left font, otherwise false (default is left-to-right).
|
|
|
|
+ */
|
|
|
|
+ public boolean isRightToLeft() {
|
|
|
|
+ return rightToLeft;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Specifies whether this font should be rendered as right-to-left (RTL).
|
|
|
|
+ * By default, it is set to false (left-to-right).
|
|
|
|
+ *
|
|
|
|
+ * @param rightToLeft true to enable right-to-left rendering; false for left-to-right.
|
|
|
|
+ */
|
|
|
|
+ public void setRightToLeft(boolean rightToLeft) {
|
|
|
|
+ this.rightToLeft = rightToLeft;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Returns the preferred size of the font, which is typically its rendered size.
|
|
|
|
+ *
|
|
|
|
+ * @return The preferred size of the font in font units.
|
|
|
|
+ */
|
|
public float getPreferredSize() {
|
|
public float getPreferredSize() {
|
|
return getCharSet().getRenderedSize();
|
|
return getCharSet().getRenderedSize();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Sets the character set for this font. The character set contains
|
|
|
|
+ * information about individual glyphs, their positions, and kerning data.
|
|
|
|
+ *
|
|
|
|
+ * @param charSet The {@link BitmapCharacterSet} to associate with this font.
|
|
|
|
+ */
|
|
public void setCharSet(BitmapCharacterSet charSet) {
|
|
public void setCharSet(BitmapCharacterSet charSet) {
|
|
this.charSet = charSet;
|
|
this.charSet = charSet;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Sets the array of materials (font pages) for this font. Each material
|
|
|
|
+ * corresponds to a texture page containing character bitmaps.
|
|
|
|
+ * The character set's page size is also updated based on the number of pages.
|
|
|
|
+ *
|
|
|
|
+ * @param pages An array of {@link Material} objects representing the font pages.
|
|
|
|
+ */
|
|
public void setPages(Material[] pages) {
|
|
public void setPages(Material[] pages) {
|
|
this.pages = pages;
|
|
this.pages = pages;
|
|
charSet.setPageSize(pages.length);
|
|
charSet.setPageSize(pages.length);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Retrieves a specific font page material by its index.
|
|
|
|
+ *
|
|
|
|
+ * @param index The index of the font page to retrieve.
|
|
|
|
+ * @return The {@link Material} for the specified font page.
|
|
|
|
+ * @throws IndexOutOfBoundsException if the index is out of bounds.
|
|
|
|
+ */
|
|
public Material getPage(int index) {
|
|
public Material getPage(int index) {
|
|
return pages[index];
|
|
return pages[index];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Returns the total number of font pages (materials) associated with this font.
|
|
|
|
+ *
|
|
|
|
+ * @return The number of font pages.
|
|
|
|
+ */
|
|
public int getPageSize() {
|
|
public int getPageSize() {
|
|
return pages.length;
|
|
return pages.length;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Retrieves the character set associated with this font.
|
|
|
|
+ *
|
|
|
|
+ * @return The {@link BitmapCharacterSet} of this font.
|
|
|
|
+ */
|
|
public BitmapCharacterSet getCharSet() {
|
|
public BitmapCharacterSet getCharSet() {
|
|
return charSet;
|
|
return charSet;
|
|
}
|
|
}
|
|
@@ -192,26 +251,19 @@ public class BitmapFont implements Savable {
|
|
return c.getKerning(nextChar);
|
|
return c.getKerning(nextChar);
|
|
}
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
|
- public void write(JmeExporter ex) throws IOException {
|
|
|
|
- OutputCapsule oc = ex.getCapsule(this);
|
|
|
|
- oc.write(charSet, "charSet", null);
|
|
|
|
- oc.write(pages, "pages", null);
|
|
|
|
- oc.write(rightToLeft, "rightToLeft", false);
|
|
|
|
- oc.write(glyphParser, "glyphParser", null);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void read(JmeImporter im) throws IOException {
|
|
|
|
- InputCapsule ic = im.getCapsule(this);
|
|
|
|
- charSet = (BitmapCharacterSet) ic.readSavable("charSet", null);
|
|
|
|
- Savable[] pagesSavable = ic.readSavableArray("pages", null);
|
|
|
|
- pages = new Material[pagesSavable.length];
|
|
|
|
- System.arraycopy(pagesSavable, 0, pages, 0, pages.length);
|
|
|
|
- rightToLeft = ic.readBoolean("rightToLeft", false);
|
|
|
|
- glyphParser = (GlyphParser) ic.readSavable("glyphParser", null);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Calculates the width of the given text in font units.
|
|
|
|
+ * This method accounts for character advances, kerning, and line breaks.
|
|
|
|
+ * It also attempts to skip custom color tags (e.g., "\#RRGGBB#" or "\#RRGGBBAA#")
|
|
|
|
+ * based on a specific format.
|
|
|
|
+ * <p>
|
|
|
|
+ * Note: This method calculates width in "font units" where the font's
|
|
|
|
+ * {@link BitmapCharacterSet#getRenderedSize() rendered size} is the base.
|
|
|
|
+ * Actual pixel scaling for display is typically handled by {@link BitmapText}.
|
|
|
|
+ *
|
|
|
|
+ * @param text The text to measure.
|
|
|
|
+ * @return The maximum line width of the text in font units.
|
|
|
|
+ */
|
|
public float getLineWidth(CharSequence text) {
|
|
public float getLineWidth(CharSequence text) {
|
|
// This method will probably always be a bit of a maintenance
|
|
// This method will probably always be a bit of a maintenance
|
|
// nightmare since it bases its calculation on a different
|
|
// nightmare since it bases its calculation on a different
|
|
@@ -252,29 +304,36 @@ public class BitmapFont implements Savable {
|
|
boolean firstCharOfLine = true;
|
|
boolean firstCharOfLine = true;
|
|
// float sizeScale = (float) block.getSize() / charSet.getRenderedSize();
|
|
// float sizeScale = (float) block.getSize() / charSet.getRenderedSize();
|
|
float sizeScale = 1f;
|
|
float sizeScale = 1f;
|
|
- CharSequence characters = glyphParser != null ? glyphParser.parse(text) : text;
|
|
|
|
|
|
|
|
- for (int i = 0; i < characters.length(); i++) {
|
|
|
|
- char theChar = characters.charAt(i);
|
|
|
|
- if (theChar == '\n') {
|
|
|
|
|
|
+ // Use GlyphParser if available for complex script shaping (e.g., cursive fonts).
|
|
|
|
+ CharSequence processedText = glyphParser != null ? glyphParser.parse(text) : text;
|
|
|
|
+
|
|
|
|
+ for (int i = 0; i < processedText.length(); i++) {
|
|
|
|
+ char currChar = processedText.charAt(i);
|
|
|
|
+ if (currChar == '\n') {
|
|
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
|
lineWidth = 0f;
|
|
lineWidth = 0f;
|
|
firstCharOfLine = true;
|
|
firstCharOfLine = true;
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
- BitmapCharacter c = charSet.getCharacter(theChar);
|
|
|
|
|
|
+ BitmapCharacter c = charSet.getCharacter(currChar);
|
|
if (c != null) {
|
|
if (c != null) {
|
|
- if (theChar == '\\' && i < characters.length() - 1 && characters.charAt(i + 1) == '#') {
|
|
|
|
- if (i + 5 < characters.length() && characters.charAt(i + 5) == '#') {
|
|
|
|
|
|
+ // Custom color tag skipping logic:
|
|
|
|
+ // Assumes tags are of the form `\#RRGGBB#` (9 chars total) or `\#RRGGBBAA#` (12 chars total).
|
|
|
|
+ if (currChar == '\\' && i < processedText.length() - 1 && processedText.charAt(i + 1) == '#') {
|
|
|
|
+ // Check for `\#XXXXX#` (6 chars after '\', including final '#')
|
|
|
|
+ if (i + 5 < processedText.length() && processedText.charAt(i + 5) == '#') {
|
|
i += 5;
|
|
i += 5;
|
|
continue;
|
|
continue;
|
|
- } else if (i + 8 < characters.length() && characters.charAt(i + 8) == '#') {
|
|
|
|
|
|
+ }
|
|
|
|
+ // Check for `\#XXXXXXXX#` (9 chars after '\', including final '#')
|
|
|
|
+ else if (i + 8 < processedText.length() && processedText.charAt(i + 8) == '#') {
|
|
i += 8;
|
|
i += 8;
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!firstCharOfLine) {
|
|
if (!firstCharOfLine) {
|
|
- lineWidth += findKerningAmount(lastChar, theChar) * sizeScale;
|
|
|
|
|
|
+ lineWidth += findKerningAmount(lastChar, currChar) * sizeScale;
|
|
} else {
|
|
} else {
|
|
if (rightToLeft) {
|
|
if (rightToLeft) {
|
|
// Ignore offset, so it will be compatible with BitmapText.getLineWidth().
|
|
// Ignore offset, so it will be compatible with BitmapText.getLineWidth().
|
|
@@ -292,7 +351,7 @@ public class BitmapFont implements Savable {
|
|
// If this is the last character of a line, then we really should
|
|
// If this is the last character of a line, then we really should
|
|
// have only added its width. The advance may include extra spacing
|
|
// have only added its width. The advance may include extra spacing
|
|
// that we don't care about.
|
|
// that we don't care about.
|
|
- if (i == characters.length() - 1 || characters.charAt(i + 1) == '\n') {
|
|
|
|
|
|
+ if (i == processedText.length() - 1 || processedText.charAt(i + 1) == '\n') {
|
|
if (rightToLeft) {
|
|
if (rightToLeft) {
|
|
// In RTL text we move the letter x0 by its xAdvance, so
|
|
// In RTL text we move the letter x0 by its xAdvance, so
|
|
// we should add it to lineWidth.
|
|
// we should add it to lineWidth.
|
|
@@ -315,30 +374,54 @@ public class BitmapFont implements Savable {
|
|
return Math.max(maxLineWidth, lineWidth);
|
|
return Math.max(maxLineWidth, lineWidth);
|
|
}
|
|
}
|
|
|
|
|
|
-
|
|
|
|
/**
|
|
/**
|
|
- * Merge two fonts.
|
|
|
|
- * If two font have the same style, merge will fail.
|
|
|
|
- * @param newFont Style must be assigned to this.
|
|
|
|
- * author: Yonghoon
|
|
|
|
|
|
+ * Merges another {@link BitmapFont} into this one.
|
|
|
|
+ * This operation combines the character sets and font pages.
|
|
|
|
+ * If both fonts contain the same style, the merge will fail and throw a RuntimeException.
|
|
|
|
+ *
|
|
|
|
+ * @param newFont The {@link BitmapFont} to merge into this one. It must have a style assigned.
|
|
*/
|
|
*/
|
|
public void merge(BitmapFont newFont) {
|
|
public void merge(BitmapFont newFont) {
|
|
charSet.merge(newFont.charSet);
|
|
charSet.merge(newFont.charSet);
|
|
final int size1 = this.pages.length;
|
|
final int size1 = this.pages.length;
|
|
final int size2 = newFont.pages.length;
|
|
final int size2 = newFont.pages.length;
|
|
|
|
|
|
- Material[] tmp = new Material[size1+size2];
|
|
|
|
|
|
+ Material[] tmp = new Material[size1 + size2];
|
|
System.arraycopy(this.pages, 0, tmp, 0, size1);
|
|
System.arraycopy(this.pages, 0, tmp, 0, size1);
|
|
System.arraycopy(newFont.pages, 0, tmp, size1, size2);
|
|
System.arraycopy(newFont.pages, 0, tmp, size1, size2);
|
|
|
|
|
|
this.pages = tmp;
|
|
this.pages = tmp;
|
|
-
|
|
|
|
-// this.pages = Arrays.copyOf(this.pages, size1+size2);
|
|
|
|
-// System.arraycopy(newFont.pages, 0, this.pages, size1, size2);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Sets the style for the font's character set.
|
|
|
|
+ * This method is typically used when a font file contains only one style
|
|
|
|
+ * but needs to be assigned a specific style identifier for merging
|
|
|
|
+ * with other multi-style fonts.
|
|
|
|
+ *
|
|
|
|
+ * @param style The integer style identifier to set.
|
|
|
|
+ */
|
|
public void setStyle(int style) {
|
|
public void setStyle(int style) {
|
|
charSet.setStyle(style);
|
|
charSet.setStyle(style);
|
|
}
|
|
}
|
|
|
|
|
|
-}
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void write(JmeExporter ex) throws IOException {
|
|
|
|
+ OutputCapsule oc = ex.getCapsule(this);
|
|
|
|
+ oc.write(charSet, "charSet", null);
|
|
|
|
+ oc.write(pages, "pages", null);
|
|
|
|
+ oc.write(rightToLeft, "rightToLeft", false);
|
|
|
|
+ oc.write(glyphParser, "glyphParser", null);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void read(JmeImporter im) throws IOException {
|
|
|
|
+ InputCapsule ic = im.getCapsule(this);
|
|
|
|
+ charSet = (BitmapCharacterSet) ic.readSavable("charSet", null);
|
|
|
|
+ Savable[] pagesSavable = ic.readSavableArray("pages", null);
|
|
|
|
+ pages = new Material[pagesSavable.length];
|
|
|
|
+ System.arraycopy(pagesSavable, 0, pages, 0, pages.length);
|
|
|
|
+ rightToLeft = ic.readBoolean("rightToLeft", false);
|
|
|
|
+ glyphParser = (GlyphParser) ic.readSavable("glyphParser", null);
|
|
|
|
+ }
|
|
|
|
+}
|