From the bitmap font making tool, you should export it as white. Then set the font colour with Starling.
With respect to per-character colour, remember you can always extend Starling’s capabilities yourself. I’ve created two wrapper classes below for starling.text.BitmapFont and starling.text.TextField, which allow you to set per-character colour for bitmap fonts.
With these, instead of using BitmapFont and TextField, you’d use ExcuisiteBitmapFont and ExcuisiteTextField
ExquisiteBitmapFont.hx
package;
import starling.display.MeshBatch;
import starling.text.BitmapFont;
import starling.text.ITextCompositor;
import starling.text.TextFormat;
import starling.text.TextOptions;
import starling.styles.MeshStyle;
/**
* Wrapper for BitmapFont that adds per-character color support.
* Set the `charColors` field before rendering.
*/
class ExquisiteBitmapFont implements ITextCompositor
{
private var base:BitmapFont;
public var charColors:Map<Int, UInt>; // Set this before calling fillMeshBatch
public function new(base:BitmapFont)
{
this.base = base;
this.charColors = null;
}
/**
* Fills the mesh batch with per-character color support.
* If charColors is provided, it will override the color for the character at that index.
*/
public function fillMeshBatch(
meshBatch:MeshBatch,
width:Float,
height:Float,
text:String,
format:TextFormat,
options:TextOptions = null
):Void
{
var charLocations = base.arrangeChars(width, height, text, format, options);
var charColors = this.charColors;
var defaultColor = format.color;
// Use our own Image for batching
var helperImage:starling.display.Image = null;
if (charLocations.length > 0) {
helperImage = new starling.display.Image(charLocations[0].char.texture);
} else {
return; // nothing to render
}
helperImage.color = defaultColor;
for (i in 0...charLocations.length)
{
var charLocation = charLocations[i];
var charIdx = charLocation.index; // original string index
helperImage.texture = charLocation.char.texture;
helperImage.readjustSize();
helperImage.x = charLocation.x;
helperImage.y = charLocation.y;
helperImage.scale = charLocation.scale;
// Per-char color if present
if (charColors != null && charColors.exists(charIdx))
helperImage.color = charColors[charIdx];
else
helperImage.color = defaultColor;
meshBatch.addMeshAt(helperImage);
}
starling.text.BitmapCharLocation.rechargePool();
}
/**
* Sets character color for a specified range.
*/
public function setCharColorRange(startIndex:Int, endIndex:Int, color:UInt):Void {
if (charColors == null) charColors = new Map();
for (i in startIndex...endIndex) {
charColors.set(i, color);
}
}
public function clearMeshBatch(meshBatch:MeshBatch):Void
base.clearMeshBatch(meshBatch);
public function getDefaultMeshStyle(style:MeshStyle, format:TextFormat, options:TextOptions):MeshStyle
return base.getDefaultMeshStyle(style, format, options);
public function dispose():Void
base.dispose();
}
ExquisiteTextField.hx
package;
import starling.text.TextField;
import starling.text.TextFieldAutoSize;
import starling.text.TextFormat;
import starling.text.TextOptions;
/**
* Wrapper for Starling's TextField, adding per-character color support with ExquisiteBitmapFont.
* Use setCharColor() to override the color of a specific character.
* Use with a font registered as ExquisiteBitmapFont for effect.
*/
class ExquisiteTextField extends TextField {
private var _charColors:Map<Int, UInt>;
public function new(width:Int, height:Int, text:String = "", format:TextFormat = null, options:TextOptions = null) {
super(width, height, text, format, options);
_charColors = new Map();
}
/**
* Sets the color for a specific character index.
*/
public function setCharColor(idx:Int, color:UInt):Void {
_charColors[idx] = color;
setRequiresRecomposition();
}
/**
* Clears all per-character color overrides.
*/
public function clearCharColors():Void {
_charColors = new Map();
setRequiresRecomposition();
}
/**
* Returns the color for a given character index, or null if not overridden.
*/
public function getCharColor(idx:Int):Null<UInt> {
return _charColors.exists(idx) ? _charColors[idx] : null;
}
/**
* Override updateText to pass charColors to the compositor if possible.
*/
override private function updateText():Void {
var width:Float = _hitArea.width;
var height:Float = _hitArea.height;
// Horizontal autoSize does not work for HTML text, since it supports custom alignment.
// What should we do if one line is aligned to the left, another to the right?
if (isHorizontalAutoSize && !_options.isHtmlText)
width = 100000;
if (isVerticalAutoSize)
height = 100000;
_meshBatch.x = _meshBatch.y = 0;
_options.textureScale = starling.core.Starling.current.contentScaleFactor;
// Use our ExquisiteBitmapFont API if compositor supports charColors
var exq:Dynamic = _compositor;
if (Reflect.hasField(exq, "fillMeshBatch")) {
try {
exq.fillMeshBatch(_meshBatch, width, height, _text, _format, _options, _charColors);
} catch (e:Dynamic) {
// fallback
_compositor.fillMeshBatch(_meshBatch, width, height, _text, _format, _options);
}
} else {
_compositor.fillMeshBatch(_meshBatch, width, height, _text, _format, _options);
}
if (_customStyle != null)
_meshBatch.style = _customStyle;
else {
_defaultStyle = _compositor.getDefaultMeshStyle(_defaultStyle, _format, _options);
if (_defaultStyle != null)
_meshBatch.style = _defaultStyle;
}
if (_options.autoSize != TextFieldAutoSize.NONE) {
_textBounds = _meshBatch.getBounds(_meshBatch, _textBounds);
if (isHorizontalAutoSize) {
_meshBatch.x = _textBounds.x = -_textBounds.x;
_hitArea.width = _textBounds.width;
_textBounds.x = 0;
}
if (isVerticalAutoSize) {
_meshBatch.y = _textBounds.y = -_textBounds.y;
_hitArea.height = _textBounds.height;
_textBounds.y = 0;
}
} else {
// hit area doesn't change, and text bounds can be created on demand
_textBounds = null;
}
}
}
Then to use these, for example:
Usage example:
import ExcuisiteBitmapFont;
import ExcuisiteTextField;
import starling.text.BitmapFont;
import starling.text.TextField;
import starling.text.TextFieldAutoSize;
import starling.textures.Texture;
//...
// Step 1: Load the font assets
var fontTexture:Texture = Texture.fromBitmapData(Assets.getBitmapData("img/some-font.png"));
var fontXml:Xml = Xml.parse(Assets.getText("xml/some-font.xml"));
// Step 2: Create and register the custom ExquisiteBitmapFont
var bitmapFont = new ExquisiteBitmapFont(new BitmapFont(fontTexture, fontXml));
TextField.registerCompositor(bitmapFont, "SomeFont");
bitmapFont.charColors = new Map<Int, UInt>();
bitmapFont.charColors.set(0, 0xFF0000); // Red
bitmapFont.charColors.set(1, 0xFF0000); // Green
bitmapFont.charColors.set(2, 0x0000FF); // Blue
bitmapFont.charColors.set(3, 0xFF0000); // Red
bitmapFont.charColors.set(4, 0xFF0000); // Green
bitmapFont.charColors.set(5, 0x0000FF); // Blue
bitmapFont.charColors.set(6, 0xFF0000); // Red
bitmapFont.charColors.set(7, 0xFF0000); // Green
bitmapFont.charColors.set(8, 0x0000FF); // Blue
bitmapFont.charColors.set(9, 0xFF0000); // Red
bitmapFont.charColors.set(10, 0xFF0000); // Green
bitmapFont.setCharColorRange(11, 15, 0xFF00FF); // Pink
// Step 3: Use the ExquisiteBitmapFont in a custom ExquisiteTextField
var textField = new ExquisiteTextField(400, 100, "Hello, Starling!", new TextFormat("SomeFont", 70));
textField.autoSize = TextFieldAutoSize.BOTH_DIRECTIONS;
addChild(textField);

I provide these as examples only. They have not been heavily scrutinised, but it works in my testing.
