11/*
2- * Copyright (c) 2009-2021 jMonkeyEngine
2+ * Copyright (c) 2009-2025 jMonkeyEngine
33 * All rights reserved.
44 *
55 * Redistribution and use in source and binary forms, with or without
3131 */
3232package com .jme3 .font ;
3333
34- import com .jme3 .export .*;
34+ import com .jme3 .export .InputCapsule ;
35+ import com .jme3 .export .JmeExporter ;
36+ import com .jme3 .export .JmeImporter ;
37+ import com .jme3 .export .OutputCapsule ;
38+ import com .jme3 .export .Savable ;
3539import com .jme3 .material .Material ;
3640
3741import java .io .IOException ;
3842
3943/**
40- * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator
44+ * Represents a font loaded from a bitmap font definition
45+ * (e.g., generated by <a href="https://libgdx.com/wiki/tools/hiero">AngelCode Bitmap Font Generator</a>).
46+ * It manages character sets, font pages (textures), and provides utilities for text measurement and rendering.
47+ *
4148 * @author dhdd
49+ * @author Yonghoon
4250 */
4351public class BitmapFont implements Savable {
4452
@@ -87,61 +95,112 @@ public enum VAlign {
8795 Bottom
8896 }
8997
98+ // The character set containing definitions for each character (glyph) in the font.
9099 private BitmapCharacterSet charSet ;
100+ // An array of materials, where each material corresponds to a font page (texture).
91101 private Material [] pages ;
102+ // Indicates whether this font is designed for right-to-left (RTL) text rendering.
92103 private boolean rightToLeft = false ;
93104 // For cursive bitmap fonts in which letter shape is determined by the adjacent glyphs.
94105 private GlyphParser glyphParser ;
95106
96107 /**
97- * @return true, if this is a right-to-left font, otherwise it will return false.
108+ * Creates a new instance of `BitmapFont`.
109+ * This constructor is primarily used for deserialization.
98110 */
99- public boolean isRightToLeft () {
100- return rightToLeft ;
111+ public BitmapFont () {
101112 }
102113
103114 /**
104- * Specify if this is a right-to-left font. By default it is set to false.
105- * This can be "overwritten" in the BitmapText constructor.
115+ * Creates a new {@link BitmapText} instance initialized with this font.
116+ * The label's size will be set to the font's rendered size, and its text content
117+ * will be set to the provided string.
106118 *
107- * @param rightToLeft true → right-to-left, false → left-to-right
108- * (default=false)
119+ * @param content The initial text content for the label.
120+ * @return A new {@link BitmapText} instance.
109121 */
110- public void setRightToLeft (boolean rightToLeft ) {
111- this .rightToLeft = rightToLeft ;
112- }
113-
114- public BitmapFont () {
115- }
116-
117122 public BitmapText createLabel (String content ) {
118123 BitmapText label = new BitmapText (this );
119124 label .setSize (getCharSet ().getRenderedSize ());
120125 label .setText (content );
121126 return label ;
122127 }
123128
129+ /**
130+ * Checks if this font is configured for right-to-left (RTL) text rendering.
131+ *
132+ * @return true if this is a right-to-left font, otherwise false (default is left-to-right).
133+ */
134+ public boolean isRightToLeft () {
135+ return rightToLeft ;
136+ }
137+
138+ /**
139+ * Specifies whether this font should be rendered as right-to-left (RTL).
140+ * By default, it is set to false (left-to-right).
141+ *
142+ * @param rightToLeft true to enable right-to-left rendering; false for left-to-right.
143+ */
144+ public void setRightToLeft (boolean rightToLeft ) {
145+ this .rightToLeft = rightToLeft ;
146+ }
147+
148+ /**
149+ * Returns the preferred size of the font, which is typically its rendered size.
150+ *
151+ * @return The preferred size of the font in font units.
152+ */
124153 public float getPreferredSize () {
125154 return getCharSet ().getRenderedSize ();
126155 }
127156
157+ /**
158+ * Sets the character set for this font. The character set contains
159+ * information about individual glyphs, their positions, and kerning data.
160+ *
161+ * @param charSet The {@link BitmapCharacterSet} to associate with this font.
162+ */
128163 public void setCharSet (BitmapCharacterSet charSet ) {
129164 this .charSet = charSet ;
130165 }
131166
167+ /**
168+ * Sets the array of materials (font pages) for this font. Each material
169+ * corresponds to a texture page containing character bitmaps.
170+ * The character set's page size is also updated based on the number of pages.
171+ *
172+ * @param pages An array of {@link Material} objects representing the font pages.
173+ */
132174 public void setPages (Material [] pages ) {
133175 this .pages = pages ;
134176 charSet .setPageSize (pages .length );
135177 }
136178
179+ /**
180+ * Retrieves a specific font page material by its index.
181+ *
182+ * @param index The index of the font page to retrieve.
183+ * @return The {@link Material} for the specified font page.
184+ * @throws IndexOutOfBoundsException if the index is out of bounds.
185+ */
137186 public Material getPage (int index ) {
138187 return pages [index ];
139188 }
140189
190+ /**
191+ * Returns the total number of font pages (materials) associated with this font.
192+ *
193+ * @return The number of font pages.
194+ */
141195 public int getPageSize () {
142196 return pages .length ;
143197 }
144198
199+ /**
200+ * Retrieves the character set associated with this font.
201+ *
202+ * @return The {@link BitmapCharacterSet} of this font.
203+ */
145204 public BitmapCharacterSet getCharSet () {
146205 return charSet ;
147206 }
@@ -192,26 +251,19 @@ private int findKerningAmount(int newLineLastChar, int nextChar) {
192251 return c .getKerning (nextChar );
193252 }
194253
195- @ Override
196- public void write (JmeExporter ex ) throws IOException {
197- OutputCapsule oc = ex .getCapsule (this );
198- oc .write (charSet , "charSet" , null );
199- oc .write (pages , "pages" , null );
200- oc .write (rightToLeft , "rightToLeft" , false );
201- oc .write (glyphParser , "glyphParser" , null );
202- }
203-
204- @ Override
205- public void read (JmeImporter im ) throws IOException {
206- InputCapsule ic = im .getCapsule (this );
207- charSet = (BitmapCharacterSet ) ic .readSavable ("charSet" , null );
208- Savable [] pagesSavable = ic .readSavableArray ("pages" , null );
209- pages = new Material [pagesSavable .length ];
210- System .arraycopy (pagesSavable , 0 , pages , 0 , pages .length );
211- rightToLeft = ic .readBoolean ("rightToLeft" , false );
212- glyphParser = (GlyphParser ) ic .readSavable ("glyphParser" , null );
213- }
214-
254+ /**
255+ * Calculates the width of the given text in font units.
256+ * This method accounts for character advances, kerning, and line breaks.
257+ * It also attempts to skip custom color tags (e.g., "\#RRGGBB#" or "\#RRGGBBAA#")
258+ * based on a specific format.
259+ * <p>
260+ * Note: This method calculates width in "font units" where the font's
261+ * {@link BitmapCharacterSet#getRenderedSize() rendered size} is the base.
262+ * Actual pixel scaling for display is typically handled by {@link BitmapText}.
263+ *
264+ * @param text The text to measure.
265+ * @return The maximum line width of the text in font units.
266+ */
215267 public float getLineWidth (CharSequence text ) {
216268 // This method will probably always be a bit of a maintenance
217269 // nightmare since it bases its calculation on a different
@@ -252,29 +304,36 @@ public float getLineWidth(CharSequence text) {
252304 boolean firstCharOfLine = true ;
253305// float sizeScale = (float) block.getSize() / charSet.getRenderedSize();
254306 float sizeScale = 1f ;
255- CharSequence characters = glyphParser != null ? glyphParser .parse (text ) : text ;
256307
257- for (int i = 0 ; i < characters .length (); i ++) {
258- char theChar = characters .charAt (i );
259- if (theChar == '\n' ) {
308+ // Use GlyphParser if available for complex script shaping (e.g., cursive fonts).
309+ CharSequence processedText = glyphParser != null ? glyphParser .parse (text ) : text ;
310+
311+ for (int i = 0 ; i < processedText .length (); i ++) {
312+ char currChar = processedText .charAt (i );
313+ if (currChar == '\n' ) {
260314 maxLineWidth = Math .max (maxLineWidth , lineWidth );
261315 lineWidth = 0f ;
262316 firstCharOfLine = true ;
263317 continue ;
264318 }
265- BitmapCharacter c = charSet .getCharacter (theChar );
319+ BitmapCharacter c = charSet .getCharacter (currChar );
266320 if (c != null ) {
267- if (theChar == '\\' && i < characters .length () - 1 && characters .charAt (i + 1 ) == '#' ) {
268- if (i + 5 < characters .length () && characters .charAt (i + 5 ) == '#' ) {
321+ // Custom color tag skipping logic:
322+ // Assumes tags are of the form `\#RRGGBB#` (9 chars total) or `\#RRGGBBAA#` (12 chars total).
323+ if (currChar == '\\' && i < processedText .length () - 1 && processedText .charAt (i + 1 ) == '#' ) {
324+ // Check for `\#XXXXX#` (6 chars after '\', including final '#')
325+ if (i + 5 < processedText .length () && processedText .charAt (i + 5 ) == '#' ) {
269326 i += 5 ;
270327 continue ;
271- } else if (i + 8 < characters .length () && characters .charAt (i + 8 ) == '#' ) {
328+ }
329+ // Check for `\#XXXXXXXX#` (9 chars after '\', including final '#')
330+ else if (i + 8 < processedText .length () && processedText .charAt (i + 8 ) == '#' ) {
272331 i += 8 ;
273332 continue ;
274333 }
275334 }
276335 if (!firstCharOfLine ) {
277- lineWidth += findKerningAmount (lastChar , theChar ) * sizeScale ;
336+ lineWidth += findKerningAmount (lastChar , currChar ) * sizeScale ;
278337 } else {
279338 if (rightToLeft ) {
280339 // Ignore offset, so it will be compatible with BitmapText.getLineWidth().
@@ -292,7 +351,7 @@ public float getLineWidth(CharSequence text) {
292351 // If this is the last character of a line, then we really should
293352 // have only added its width. The advance may include extra spacing
294353 // that we don't care about.
295- if (i == characters .length () - 1 || characters .charAt (i + 1 ) == '\n' ) {
354+ if (i == processedText .length () - 1 || processedText .charAt (i + 1 ) == '\n' ) {
296355 if (rightToLeft ) {
297356 // In RTL text we move the letter x0 by its xAdvance, so
298357 // we should add it to lineWidth.
@@ -315,30 +374,54 @@ public float getLineWidth(CharSequence text) {
315374 return Math .max (maxLineWidth , lineWidth );
316375 }
317376
318-
319377 /**
320- * Merge two fonts.
321- * If two font have the same style, merge will fail.
322- * @param newFont Style must be assigned to this.
323- * author: Yonghoon
378+ * Merges another {@link BitmapFont} into this one.
379+ * This operation combines the character sets and font pages.
380+ * If both fonts contain the same style, the merge will fail and throw a RuntimeException.
381+ *
382+ * @param newFont The {@link BitmapFont} to merge into this one. It must have a style assigned.
324383 */
325384 public void merge (BitmapFont newFont ) {
326385 charSet .merge (newFont .charSet );
327386 final int size1 = this .pages .length ;
328387 final int size2 = newFont .pages .length ;
329388
330- Material [] tmp = new Material [size1 + size2 ];
389+ Material [] tmp = new Material [size1 + size2 ];
331390 System .arraycopy (this .pages , 0 , tmp , 0 , size1 );
332391 System .arraycopy (newFont .pages , 0 , tmp , size1 , size2 );
333392
334393 this .pages = tmp ;
335-
336- // this.pages = Arrays.copyOf(this.pages, size1+size2);
337- // System.arraycopy(newFont.pages, 0, this.pages, size1, size2);
338394 }
339395
396+ /**
397+ * Sets the style for the font's character set.
398+ * This method is typically used when a font file contains only one style
399+ * but needs to be assigned a specific style identifier for merging
400+ * with other multi-style fonts.
401+ *
402+ * @param style The integer style identifier to set.
403+ */
340404 public void setStyle (int style ) {
341405 charSet .setStyle (style );
342406 }
343407
344- }
408+ @ Override
409+ public void write (JmeExporter ex ) throws IOException {
410+ OutputCapsule oc = ex .getCapsule (this );
411+ oc .write (charSet , "charSet" , null );
412+ oc .write (pages , "pages" , null );
413+ oc .write (rightToLeft , "rightToLeft" , false );
414+ oc .write (glyphParser , "glyphParser" , null );
415+ }
416+
417+ @ Override
418+ public void read (JmeImporter im ) throws IOException {
419+ InputCapsule ic = im .getCapsule (this );
420+ charSet = (BitmapCharacterSet ) ic .readSavable ("charSet" , null );
421+ Savable [] pagesSavable = ic .readSavableArray ("pages" , null );
422+ pages = new Material [pagesSavable .length ];
423+ System .arraycopy (pagesSavable , 0 , pages , 0 , pages .length );
424+ rightToLeft = ic .readBoolean ("rightToLeft" , false );
425+ glyphParser = (GlyphParser ) ic .readSavable ("glyphParser" , null );
426+ }
427+ }
0 commit comments