298 lines
13 KiB
ActionScript
298 lines
13 KiB
ActionScript
// =================================================================================================
|
|
//
|
|
// Starling Framework
|
|
// Copyright 2011 Gamua OG. All Rights Reserved.
|
|
//
|
|
// This program is free software. You can redistribute and/or modify it
|
|
// in accordance with the terms of the accompanying license agreement.
|
|
//
|
|
// =================================================================================================
|
|
|
|
package starling.text
|
|
{
|
|
import flash.geom.Rectangle;
|
|
import flash.utils.Dictionary;
|
|
|
|
import starling.display.DisplayObject;
|
|
import starling.display.Image;
|
|
import starling.display.Sprite;
|
|
import starling.textures.Texture;
|
|
import starling.utils.HAlign;
|
|
import starling.utils.VAlign;
|
|
|
|
/** The BitmapFont class parses bitmap font files and arranges the glyphs
|
|
* in the form of a text.
|
|
*
|
|
* The class parses the XML format as it is used in the
|
|
* <a href="http://www.angelcode.com/products/bmfont/">AngelCode Bitmap Font Generator</a> or
|
|
* the <a href="http://glyphdesigner.71squared.com/">Glyph Designer</a>.
|
|
* This is what the file format looks like:
|
|
*
|
|
* <pre>
|
|
* <font>
|
|
* <info face="BranchingMouse" size="40" />
|
|
* <common lineHeight="40" />
|
|
* <pages> <!-- currently, only one page is supported -->
|
|
* <page id="0" file="texture.png" />
|
|
* </pages>
|
|
* <chars>
|
|
* <char id="32" x="60" y="29" width="1" height="1" xoffset="0" yoffset="27" xadvance="8" />
|
|
* <char id="33" x="155" y="144" width="9" height="21" xoffset="0" yoffset="6" xadvance="9" />
|
|
* </chars>
|
|
* <kernings> <!-- Kerning is optional -->
|
|
* <kerning first="83" second="83" amount="-4"/>
|
|
* </kernings>
|
|
* </font>
|
|
* </pre>
|
|
*
|
|
* Pass an instance of this class to the method <code>registerBitmapFont</code> of the
|
|
* TextField class. Then, set the <code>fontName</code> property of the text field to the
|
|
* <code>name</code> value of the bitmap font. This will make the text field use the bitmap
|
|
* font.
|
|
*/
|
|
public class BitmapFont
|
|
{
|
|
/** Use this constant for the <code>fontSize</code> property of the TextField class to
|
|
* render the bitmap font in exactly the size it was created. */
|
|
public static const NATIVE_SIZE:int = -1;
|
|
|
|
private static const CHAR_SPACE:int = 32;
|
|
private static const CHAR_TAB:int = 9;
|
|
private static const CHAR_NEWLINE:int = 10;
|
|
|
|
private var mTexture:Texture;
|
|
private var mChars:Dictionary;
|
|
private var mName:String;
|
|
private var mSize:Number;
|
|
private var mLineHeight:Number;
|
|
|
|
/** Creates a bitmap font by parsing an XML file and uses the specified texture. */
|
|
public function BitmapFont(texture:Texture, fontXml:XML=null)
|
|
{
|
|
mName = "unknown";
|
|
mLineHeight = mSize = 14;
|
|
mTexture = texture;
|
|
mChars = new Dictionary();
|
|
|
|
if (fontXml)
|
|
parseFontXml(fontXml);
|
|
}
|
|
|
|
/** Disposes the texture of the bitmap font! */
|
|
public function dispose():void
|
|
{
|
|
if (mTexture)
|
|
mTexture.dispose();
|
|
}
|
|
|
|
private function parseFontXml(fontXml:XML):void
|
|
{
|
|
mName = fontXml.info.attribute("face");
|
|
mSize = parseFloat(fontXml.info.attribute("size"));
|
|
mLineHeight = parseFloat(fontXml.common.attribute("lineHeight"));
|
|
|
|
if (mSize <= 0)
|
|
{
|
|
trace("[Starling] Warning: invalid font size in '" + mName + "' font.");
|
|
mSize = (mSize == 0.0 ? 16.0 : mSize * -1.0);
|
|
}
|
|
|
|
for each (var charElement:XML in fontXml.chars.char)
|
|
{
|
|
var id:int = parseInt(charElement.attribute("id"));
|
|
var xOffset:Number = parseFloat(charElement.attribute("xoffset"));
|
|
var yOffset:Number = parseFloat(charElement.attribute("yoffset"));
|
|
var xAdvance:Number = parseFloat(charElement.attribute("xadvance"));
|
|
|
|
var region:Rectangle = new Rectangle();
|
|
region.x = parseFloat(charElement.attribute("x"));
|
|
region.y = parseFloat(charElement.attribute("y"));
|
|
region.width = parseFloat(charElement.attribute("width"));
|
|
region.height = parseFloat(charElement.attribute("height"));
|
|
|
|
var texture:Texture = Texture.fromTexture(mTexture, region);
|
|
var bitmapChar:BitmapChar = new BitmapChar(id, texture, xOffset, yOffset, xAdvance);
|
|
addChar(id, bitmapChar);
|
|
}
|
|
|
|
for each (var kerningElement:XML in fontXml.kernings.kerning)
|
|
{
|
|
var first:int = parseInt(kerningElement.attribute("first"));
|
|
var second:int = parseInt(kerningElement.attribute("second"));
|
|
var amount:Number = parseFloat(kerningElement.attribute("amount"));
|
|
getChar(second).addKerning(first, amount);
|
|
}
|
|
}
|
|
|
|
/** Returns a single bitmap char with a certain character ID. */
|
|
public function getChar(charID:int):BitmapChar
|
|
{
|
|
return mChars[charID];
|
|
}
|
|
|
|
/** Adds a bitmap char with a certain character ID. */
|
|
public function addChar(charID:int, bitmapChar:BitmapChar):void
|
|
{
|
|
mChars[charID] = bitmapChar;
|
|
}
|
|
|
|
/** Creates a display object that contains the given text by arranging individual chars. */
|
|
public function createDisplayObject(width:Number, height:Number, text:String,
|
|
fontSize:Number=-1, color:uint=0xffffff,
|
|
hAlign:String="center", vAlign:String="center",
|
|
autoScale:Boolean=true,
|
|
kerning:Boolean=true):DisplayObject
|
|
{
|
|
if (fontSize == NATIVE_SIZE) fontSize = mSize;
|
|
|
|
var lineContainer:Sprite;
|
|
var finished:Boolean = false;
|
|
|
|
while (!finished)
|
|
{
|
|
var scale:Number = fontSize / mSize;
|
|
lineContainer = new Sprite();
|
|
|
|
if (mLineHeight * scale <= height)
|
|
{
|
|
var containerWidth:Number = width / scale;
|
|
var containerHeight:Number = height / scale;
|
|
lineContainer.scaleX = lineContainer.scaleY = scale;
|
|
|
|
var lastWhiteSpace:int = -1;
|
|
var lastCharID:int = -1;
|
|
var currentX:Number = 0;
|
|
var currentLine:Sprite = new Sprite();
|
|
var numChars:int = text.length;
|
|
|
|
for (var i:int=0; i<numChars; ++i)
|
|
{
|
|
var lineFull:Boolean = false;
|
|
|
|
var charID:int = text.charCodeAt(i);
|
|
if (charID == CHAR_NEWLINE)
|
|
{
|
|
lineFull = true;
|
|
}
|
|
else
|
|
{
|
|
var bitmapChar:BitmapChar = getChar(charID);
|
|
|
|
if (bitmapChar == null)
|
|
{
|
|
trace("[Starling] Missing character: " + charID);
|
|
continue;
|
|
}
|
|
|
|
if (charID == CHAR_SPACE || charID == CHAR_TAB)
|
|
lastWhiteSpace = i;
|
|
|
|
var charImage:Image = bitmapChar.createImage();
|
|
|
|
if (kerning)
|
|
currentX += bitmapChar.getKerning(lastCharID);
|
|
|
|
charImage.x = currentX + bitmapChar.xOffset;
|
|
charImage.y = bitmapChar.yOffset;
|
|
charImage.color = color;
|
|
currentLine.addChild(charImage);
|
|
|
|
currentX += bitmapChar.xAdvance;
|
|
lastCharID = charID;
|
|
|
|
if (currentX > containerWidth)
|
|
{
|
|
// remove characters and add them again to next line
|
|
var numCharsToRemove:int = lastWhiteSpace == -1 ? 1 : i - lastWhiteSpace;
|
|
var removeIndex:int = currentLine.numChildren - numCharsToRemove;
|
|
|
|
for (var r:int=0; r<numCharsToRemove; ++r)
|
|
currentLine.removeChildAt(removeIndex);
|
|
|
|
if (currentLine.numChildren == 0)
|
|
break;
|
|
|
|
var lastChar:DisplayObject = currentLine.getChildAt(currentLine.numChildren-1);
|
|
currentX = lastChar.x + lastChar.width;
|
|
|
|
i -= numCharsToRemove;
|
|
lineFull = true;
|
|
}
|
|
}
|
|
|
|
if (i == numChars - 1)
|
|
{
|
|
lineContainer.addChild(currentLine);
|
|
finished = true;
|
|
}
|
|
else if (lineFull)
|
|
{
|
|
lineContainer.addChild(currentLine);
|
|
var nextLineY:Number = currentLine.y + mLineHeight;
|
|
|
|
if (nextLineY + mLineHeight <= containerHeight)
|
|
{
|
|
currentLine = new Sprite();
|
|
currentLine.y = nextLineY;
|
|
currentX = 0;
|
|
lastWhiteSpace = -1;
|
|
lastCharID = -1;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
} // for each char
|
|
} // if (mLineHeight * scale <= height)
|
|
|
|
if (autoScale && !finished)
|
|
{
|
|
fontSize -= 1;
|
|
lineContainer.dispose();
|
|
}
|
|
else
|
|
{
|
|
finished = true;
|
|
}
|
|
|
|
} // while (!finished)
|
|
|
|
if (hAlign != HAlign.LEFT)
|
|
{
|
|
var numLines:int = lineContainer.numChildren;
|
|
for (var l:int=0; l<numLines; ++l)
|
|
{
|
|
var line:Sprite = lineContainer.getChildAt(l) as Sprite;
|
|
var finalChar:DisplayObject = line.getChildAt(line.numChildren-1);
|
|
var lineWidth:Number = finalChar.x + finalChar.width;
|
|
var widthDiff:Number = containerWidth - lineWidth;
|
|
line.x = int(hAlign == HAlign.RIGHT ? widthDiff : widthDiff / 2);
|
|
}
|
|
}
|
|
|
|
var outerContainer:Sprite = new Sprite();
|
|
outerContainer.addChild(lineContainer);
|
|
|
|
if (vAlign != VAlign.TOP)
|
|
{
|
|
var contentHeight:Number = lineContainer.numChildren * mLineHeight * scale;
|
|
var heightDiff:Number = height - contentHeight;
|
|
lineContainer.y = int(vAlign == VAlign.BOTTOM ? heightDiff : heightDiff / 2);
|
|
}
|
|
|
|
outerContainer.flatten();
|
|
return outerContainer;
|
|
}
|
|
|
|
/** The name of the font as it was parsed from the font file. */
|
|
public function get name():String { return mName; }
|
|
|
|
/** The native size of the font. */
|
|
public function get size():Number { return mSize; }
|
|
|
|
/** The height of one line in pixels. */
|
|
public function get lineHeight():Number { return mLineHeight; }
|
|
public function set lineHeight(value:Number):void { mLineHeight = value; }
|
|
}
|
|
} |