Skip to content

Instantly share code, notes, and snippets.

@hqkirkland
Created September 19, 2018 05:23
Show Gist options
  • Save hqkirkland/f016d2e1790f415f35d672fae45de3ac to your computer and use it in GitHub Desktop.
Save hqkirkland/f016d2e1790f415f35d672fae45de3ac to your computer and use it in GitHub Desktop.
This file overrides the checks for FlxG.renderTile/FlxG.renderBlit
package flixel.text;
import flash.display.BitmapData;
import flixel.FlxBasic;
import flixel.FlxG;
import flixel.FlxSprite;
import flixel.graphics.frames.FlxBitmapFont;
import flixel.graphics.frames.FlxFrame;
import flixel.math.FlxPoint;
import flixel.text.FlxText.FlxTextAlign;
import flixel.text.FlxText.FlxTextBorderStyle;
import flixel.util.FlxColor;
import flixel.util.FlxDestroyUtil;
import haxe.Utf8;
import openfl.geom.ColorTransform;
using flixel.util.FlxColorTransformUtil;
// TODO: use Utf8 util for converting text to upper/lower case
/**
* Extends FlxSprite to support rendering text.
* Can tint, fade, rotate and scale just like a sprite.
* Doesn't really animate though, as far as I know.
*/
class FlxBitmapText extends FlxSprite
{
/**
* Font for text rendering.
*/
public var font(default, set):FlxBitmapFont;
/**
* Text to display.
*/
public var text(default, set):String = "";
/**
* Helper object to avoid many ColorTransform allocations
*/
var _colorParams:ColorTransform = new ColorTransform();
/**
* Helper array which contains actual strings for rendering.
*/
// TODO: switch it to Array<Array<Int>> (for optimizations - i.e. less Utf8 usage)
var _lines:Array<String> = [];
/**
* Helper array which contains width of each displayed lines.
*/
var _linesWidth:Array<Int> = [];
/**
* Specifies how the text field should align text.
* JUSTIFY alignment isn't supported.
* Note: 'autoSize' must be set to false or alignment won't show any visual differences.
*/
public var alignment(default, set):FlxTextAlign = FlxTextAlign.LEFT;
/**
* The distance to add between lines.
*/
public var lineSpacing(default, set):Int = 0;
/**
* The distance to add between letters.
*/
public var letterSpacing(default, set):Int = 0;
/**
* Whether to convert text to upper case or not.
*/
public var autoUpperCase(default, set):Bool = false;
/**
* A Boolean value that indicates whether the text field has word wrap.
*/
public var wordWrap(default, set):Bool = true;
/**
* Whether word wrapping algorithm should wrap lines by words or by single character.
* Default value is true.
*/
public var wrapByWord(default, set):Bool = true;
/**
* Whether this text field have fixed width or not.
* Default value if true.
*/
public var autoSize(default, set):Bool = true;
/**
* Number of pixels between text and text field border
*/
public var padding(default, set):Int = 0;
/**
* Width of the text in this text field.
*/
public var textWidth(get, null):Int;
/**
* Height of the text in this text field.
*/
public var textHeight(get, null):Int;
/**
* Height of the single line of text (without lineSpacing).
*/
public var lineHeight(get, null):Int;
/**
* Number of space characters in one tab.
*/
public var numSpacesInTab(default, set):Int = 4;
/**
* The color of the text in 0xAARRGGBB format.
* Result color of text will be multiplication of textColor and color.
*/
public var textColor(default, set):FlxColor = FlxColor.WHITE;
/**
* Whether to use textColor while rendering or not.
*/
public var useTextColor(default, set):Bool = false;
/**
* Use a border style
*/
public var borderStyle(default, set):FlxTextBorderStyle = NONE;
/**
* The color of the border in 0xAARRGGBB format
*/
public var borderColor(default, set):FlxColor = FlxColor.BLACK;
/**
* The size of the border, in pixels.
*/
public var borderSize(default, set):Float = 1;
/**
* How many iterations do use when drawing the border. 0: only 1 iteration, 1: one iteration for every pixel in borderSize
* A value of 1 will have the best quality for large border sizes, but might reduce performance when changing text.
* NOTE: If the borderSize is 1, borderQuality of 0 or 1 will have the exact same effect (and performance).
*/
public var borderQuality(default, set):Float = 0;
/**
* Offset that is applied to the shadow border style, if active.
* x and y are multiplied by borderSize. Default is (1, 1), or lower-right corner.
*/
public var shadowOffset(default, null):FlxPoint;
/**
* Specifies whether the text should have background
*/
public var background(default, set):Bool = false;
/**
* Specifies the color of background
*/
public var backgroundColor(default, set):FlxColor = FlxColor.TRANSPARENT;
/**
* Specifies whether the text field will break into multiple lines or not on overflow.
*/
public var multiLine(default, set):Bool = true;
/**
* Reflects how many lines have this text field.
*/
public var numLines(get, null):Int = 0;
/**
* The width of the TextField object used for bitmap generation for this FlxText object.
* Use it when you want to change the visible width of text. Enables autoSize if <= 0.
*/
public var fieldWidth(get, set):Int;
var _fieldWidth:Int;
var pendingTextChange:Bool = true;
var pendingTextBitmapChange:Bool = true;
var pendingPixelsChange:Bool = true;
var textData:Array<Float>;
var textDrawData:Array<Float>;
var borderDrawData:Array<Float>;
var overrideDraw:Bool = true;
/**
* Helper bitmap buffer for text pixels but without any color transformations
*/
var textBitmap:BitmapData;
/**
* Constructs a new text field component.
* @param font Optional parameter for component's font prop
*/
public function new(?font:FlxBitmapFont)
{
super();
width = fieldWidth = 2;
alpha = 1;
this.font = (font == null) ? FlxBitmapFont.getDefaultFont() : font;
shadowOffset = FlxPoint.get(1, 1);
if (FlxG.renderBlit || overrideDraw)
{
pixels = new BitmapData(1, 1, true, FlxColor.TRANSPARENT);
}
else
{
textData = [];
textDrawData = [];
borderDrawData = [];
}
}
/**
* Clears all resources used.
*/
override public function destroy():Void
{
font = null;
text = null;
_lines = null;
_linesWidth = null;
shadowOffset = FlxDestroyUtil.put(shadowOffset);
textBitmap = FlxDestroyUtil.dispose(textBitmap);
_colorParams = null;
if (FlxG.renderTile && !overrideDraw)
{
textData = null;
textDrawData = null;
borderDrawData = null;
}
super.destroy();
}
/**
* Forces graphic regeneration for this text field.
*/
override public function drawFrame(Force:Bool = false):Void
{
if (FlxG.renderTile && !overrideDraw)
{
Force = true;
}
pendingTextBitmapChange = pendingTextBitmapChange || Force;
checkPendingChanges(false);
if (FlxG.renderBlit || overrideDraw)
{
super.drawFrame(Force);
}
}
inline function checkPendingChanges(useTiles:Bool = false):Void
{
if (FlxG.renderBlit || overrideDraw)
{
useTiles = false;
}
if (pendingTextChange)
{
updateText();
pendingTextBitmapChange = true;
}
if (pendingTextBitmapChange)
{
updateTextBitmap(useTiles);
pendingPixelsChange = true;
}
if (pendingPixelsChange)
{
updatePixels(useTiles);
}
}
override public function draw():Void
{
if (FlxG.renderBlit || overrideDraw)
{
checkPendingChanges(false);
super.draw();
}
else
{
checkPendingChanges(true);
var textLength:Int = Std.int(textDrawData.length / 3);
var borderLength:Int = Std.int(borderDrawData.length / 3);
var dataPos:Int;
var cr:Float = color.redFloat;
var cg:Float = color.greenFloat;
var cb:Float = color.blueFloat;
var borderRed:Float = borderColor.redFloat * cr;
var borderGreen:Float = borderColor.greenFloat * cg;
var borderBlue:Float = borderColor.blueFloat * cb;
var bAlpha:Float = borderColor.alphaFloat * alpha;
var textRed:Float = cr;
var textGreen:Float = cg;
var textBlue:Float = cb;
var tAlpha:Float = alpha;
if (useTextColor)
{
textRed *= textColor.redFloat;
textGreen *= textColor.greenFloat;
textBlue *= textColor.blueFloat;
tAlpha *= textColor.alpha;
}
var bgRed:Float = cr;
var bgGreen:Float = cg;
var bgBlue:Float = cb;
var bgAlpha:Float = alpha;
if (background)
{
bgRed *= backgroundColor.redFloat;
bgGreen *= backgroundColor.greenFloat;
bgBlue *= backgroundColor.blueFloat;
bgAlpha *= backgroundColor.alphaFloat;
}
var drawItem;
var currFrame:FlxFrame = null;
var currTileX:Float = 0;
var currTileY:Float = 0;
var sx:Float = scale.x * _facingHorizontalMult;
var sy:Float = scale.y * _facingVerticalMult;
var ox:Float = origin.x;
var oy:Float = origin.y;
if (_facingHorizontalMult != 1)
{
ox = frameWidth - ox;
}
if (_facingVerticalMult != 1)
{
oy = frameHeight - oy;
}
for (camera in cameras)
{
if (!camera.visible || !camera.exists || !isOnScreen(camera))
{
continue;
}
getScreenPosition(_point, camera).subtractPoint(offset);
if (isPixelPerfectRender(camera))
{
_point.floor();
}
updateTrig();
if (background)
{
// backround tile transformations
currFrame = FlxG.bitmap.whitePixel;
_matrix.identity();
_matrix.scale(0.1 * frameWidth, 0.1 * frameHeight);
_matrix.translate(-ox, -oy);
_matrix.scale(sx, sy);
if (angle != 0)
{
_matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
_matrix.translate(_point.x + ox, _point.y + oy);
_colorParams.setMultipliers(bgRed, bgGreen, bgBlue, bgAlpha);
camera.drawPixels(currFrame, null, _matrix, _colorParams, blend, antialiasing);
}
var hasColorOffsets:Bool = (colorTransform != null && colorTransform.hasRGBAOffsets());
drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader);
for (j in 0...borderLength)
{
dataPos = j * 3;
currFrame = font.getCharFrame(Std.int(borderDrawData[dataPos]));
currTileX = borderDrawData[dataPos + 1];
currTileY = borderDrawData[dataPos + 2];
currFrame.prepareMatrix(_matrix);
_matrix.translate(currTileX - ox, currTileY - oy);
_matrix.scale(sx, sy);
if (angle != 0)
{
_matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
_matrix.translate(_point.x + ox, _point.y + oy);
_colorParams.setMultipliers(borderRed, borderGreen, borderBlue, bAlpha);
drawItem.addQuad(currFrame, _matrix, _colorParams);
}
for (j in 0...textLength)
{
dataPos = j * 3;
currFrame = font.getCharFrame(Std.int(textDrawData[dataPos]));
currTileX = textDrawData[dataPos + 1];
currTileY = textDrawData[dataPos + 2];
currFrame.prepareMatrix(_matrix);
_matrix.translate(currTileX - ox, currTileY - oy);
_matrix.scale(sx, sy);
if (angle != 0)
{
_matrix.rotateWithTrig(_cosAngle, _sinAngle);
}
_matrix.translate(_point.x + ox, _point.y + oy);
_colorParams.setMultipliers(textRed, textGreen, textBlue, tAlpha);
drawItem.addQuad(currFrame, _matrix, _colorParams);
}
#if FLX_DEBUG
FlxBasic.visibleCount++;
#end
}
#if FLX_DEBUG
if (FlxG.debugger.drawDebug)
{
drawDebug();
}
#end
}
}
override function set_color(Color:FlxColor):FlxColor
{
super.set_color(Color);
if (FlxG.renderBlit || overrideDraw)
{
pendingTextBitmapChange = true;
}
return color;
}
override function set_alpha(value:Float):Float
{
super.set_alpha(value);
if (FlxG.renderBlit || overrideDraw)
{
pendingTextBitmapChange = true;
}
return value;
}
function set_textColor(value:FlxColor):FlxColor
{
if (textColor != value)
{
textColor = value;
if (FlxG.renderBlit || overrideDraw)
{
pendingPixelsChange = true;
}
}
return value;
}
function set_useTextColor(value:Bool):Bool
{
if (useTextColor != value)
{
useTextColor = value;
if (FlxG.renderBlit || overrideDraw)
{
pendingPixelsChange = true;
}
}
return value;
}
override function calcFrame(RunOnCpp:Bool = false):Void
{
if (FlxG.renderTile && !overrideDraw)
{
drawFrame(RunOnCpp);
}
else
{
super.calcFrame(RunOnCpp);
}
}
function set_text(value:String):String
{
if (value != text)
{
text = value;
pendingTextChange = true;
}
return value;
}
function updateText():Void
{
var tmp:String = (autoUpperCase) ? text.toUpperCase() : text;
_lines = tmp.split("\n");
if (!autoSize)
{
if (wordWrap)
{
wrap();
}
else
{
cutLines();
}
}
if (!multiLine)
{
_lines = [_lines[0]];
}
var numLines:Int = _lines.length;
for (i in 0...numLines)
{
_lines[i] = StringTools.rtrim(_lines[i]);
}
pendingTextChange = false;
pendingTextBitmapChange = true;
}
/**
* Calculates the size of text field.
*/
function computeTextSize():Void
{
var txtWidth:Int = textWidth + Std.int(borderSize) * 2;
var txtHeight:Int = textHeight + 2 * padding + Std.int(borderSize) * 2;
if (autoSize)
{
txtWidth += 2 * padding;
}
else
{
txtWidth = fieldWidth;
}
frameWidth = (txtWidth == 0) ? 1 : txtWidth;
frameHeight = (txtHeight == 0) ? 1 : txtHeight;
}
/**
* Calculates width of the line with provided index
*
* @param lineIndex index of the line in _lines array
* @return The width of the line
*/
public function getLineWidth(lineIndex:Int):Int
{
if (lineIndex < 0 || lineIndex >= _lines.length)
{
return 0;
}
return getStringWidth(_lines[lineIndex]);
}
/**
* Calculates width of provided string (for current font).
*
* @param str String to calculate width for
* @return The width of result bitmap text.
*/
public function getStringWidth(str:String):Int
{
var spaceWidth:Int = font.spaceWidth;
var tabWidth:Int = spaceWidth * numSpacesInTab;
var lineLength:Int = Utf8.length(str); // lenght of the current line
var lineWidth:Float = font.minOffsetX;
var charCode:Int; // current character in word
var charWidth:Float; // the width of current character
var charFrame:FlxFrame;
for (c in 0...lineLength)
{
charCode = Utf8.charCodeAt(str, c);
charWidth = 0;
if (charCode == FlxBitmapFont.SPACE_CODE)
{
charWidth = spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
charWidth = tabWidth;
}
else if (font.charExists(charCode))
{
charWidth = font.getCharAdvance(charCode);
if (c == (lineLength - 1))
{
charFrame = font.getCharFrame(charCode);
charWidth = Std.int(charFrame.sourceSize.x);
}
}
lineWidth += (charWidth + letterSpacing);
}
if (lineLength > 0)
{
lineWidth -= letterSpacing;
}
return Std.int(lineWidth);
}
/**
* Just cuts the lines which are too long to fit in the field.
*/
function cutLines():Void
{
var newLines:Array<String> = [];
var lineLength:Int; // lenght of the current line
var c:Int; // char index
var charCode:Int; // code for the current character in word
var charWidth:Float; // the width of current character
var subLine:Utf8; // current subline to assemble
var subLineWidth:Float; // the width of current subline
var spaceWidth:Int = font.spaceWidth;
var tabWidth:Int = spaceWidth * numSpacesInTab;
var startX:Int = font.minOffsetX;
for (line in _lines)
{
lineLength = Utf8.length(line);
subLine = new Utf8();
subLineWidth = startX;
c = 0;
while (c < lineLength)
{
charCode = Utf8.charCodeAt(line, c);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
charWidth = spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
charWidth = tabWidth;
}
else
{
charWidth = font.getCharAdvance(charCode);
}
charWidth += letterSpacing;
if (subLineWidth + charWidth > _fieldWidth - 2 * padding)
{
subLine.addChar(charCode);
newLines.push(subLine.toString());
subLine = new Utf8();
subLineWidth = startX;
c = lineLength;
}
else
{
subLine.addChar(charCode);
subLineWidth += charWidth;
}
c++;
}
}
_lines = newLines;
}
/**
* Automatically wraps text by figuring out how many characters can fit on a
* single line, and splitting the remainder onto a new line.
*/
function wrap():Void
{
// subdivide lines
var newLines:Array<String> = [];
var words:Array<String>; // the array of words in the current line
for (line in _lines)
{
words = [];
// split this line into words
splitLineIntoWords(line, words);
if (wrapByWord)
{
wrapLineByWord(words, newLines);
}
else
{
wrapLineByCharacter(words, newLines);
}
}
_lines = newLines;
}
/**
* Helper function for splitting line of text into separate words.
*
* @param line line to split.
* @param words result array to fill with words.
*/
function splitLineIntoWords(line:String, words:Array<String>):Void
{
var word:String = ""; // current word to process
var wordUtf8:Utf8 = new Utf8();
var isSpaceWord:Bool = false; // whether current word consists of spaces or not
var lineLength:Int = Utf8.length(line); // lenght of the current line
var hyphenCode:Int = Utf8.charCodeAt('-', 0);
var c:Int = 0; // char index on the line
var charCode:Int; // code for the current character in word
var charUtf8:Utf8;
while (c < lineLength)
{
charCode = Utf8.charCodeAt(line, c);
word = wordUtf8.toString();
if (charCode == FlxBitmapFont.SPACE_CODE || charCode == FlxBitmapFont.TAB_CODE)
{
if (!isSpaceWord)
{
isSpaceWord = true;
if (word != "")
{
words.push(word);
wordUtf8 = new Utf8();
}
}
wordUtf8.addChar(charCode);
}
else if (charCode == hyphenCode)
{
if (isSpaceWord && word != "")
{
isSpaceWord = false;
words.push(word);
words.push('-');
}
else if (!isSpaceWord)
{
charUtf8 = new Utf8();
charUtf8.addChar(charCode);
words.push(word + charUtf8.toString());
}
wordUtf8 = new Utf8();
}
else
{
if (isSpaceWord && word != "")
{
isSpaceWord = false;
words.push(word);
wordUtf8 = new Utf8();
}
wordUtf8.addChar(charCode);
}
c++;
}
word = wordUtf8.toString();
if (word != "") words.push(word);
}
/**
* Wraps provided line by words.
*
* @param words The array of words in the line to process.
* @param newLines Array to fill with result lines.
*/
function wrapLineByWord(words:Array<String>, newLines:Array<String>):Void
{
var numWords:Int = words.length; // number of words in the current line
var w:Int; // word index in the current line
var word:String; // current word to process
var wordWidth:Float; // total width of current word
var wordLength:Int; // number of letters in current word
var isSpaceWord:Bool = false; // whether current word consists of spaces or not
var charCode:Int;
var charWidth:Float = 0; // the width of current character
var subLines:Array<String> = []; // helper array for subdividing lines
var subLine:String; // current subline to assemble
var subLineWidth:Float; // the width of current subline
var spaceWidth:Int = font.spaceWidth;
var tabWidth:Int = spaceWidth * numSpacesInTab;
var startX:Int = font.minOffsetX;
if (numWords > 0)
{
w = 0;
subLineWidth = startX;
subLine = "";
while (w < numWords)
{
wordWidth = 0;
word = words[w];
wordLength = Utf8.length(word);
charCode = Utf8.charCodeAt(word, 0);
isSpaceWord = (charCode == FlxBitmapFont.SPACE_CODE || charCode == FlxBitmapFont.TAB_CODE);
for (c in 0...wordLength)
{
charCode = Utf8.charCodeAt(word, c);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
charWidth = spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
charWidth = tabWidth;
}
else
{
charWidth = font.getCharAdvance(charCode);
}
wordWidth += charWidth;
}
wordWidth += (wordLength - 1) * letterSpacing;
if (subLineWidth + wordWidth > _fieldWidth - 2 * padding)
{
if (isSpaceWord)
{
subLines.push(subLine);
subLine = "";
subLineWidth = startX;
}
else if (subLine != "") // new line isn't empty so we should add it to sublines array and start another one
{
subLines.push(subLine);
subLine = word;
subLineWidth = startX + wordWidth + letterSpacing;
}
else // the line is too tight to hold even one word
{
subLine = word;
subLineWidth = startX + wordWidth + letterSpacing;
}
}
else
{
subLine += word;
subLineWidth += wordWidth + letterSpacing;
}
w++;
}
if (subLine != "")
{
subLines.push(subLine);
}
}
for (subline in subLines)
{
newLines.push(subline);
}
}
/**
* Wraps provided line by characters (as in standart flash text fields).
*
* @param words The array of words in the line to process.
* @param newLines Array to fill with result lines.
*/
function wrapLineByCharacter(words:Array<String>, newLines:Array<String>):Void
{
var numWords:Int = words.length; // number of words in the current line
var w:Int; // word index in the current line
var word:String; // current word to process
var wordLength:Int; // number of letters in current word
var isSpaceWord:Bool = false; // whether current word consists of spaces or not
var charCode:Int;
var c:Int; // char index
var charWidth:Float = 0; // the width of current character
var subLines:Array<String> = []; // helper array for subdividing lines
var subLine:String; // current subline to assemble
var subLineUtf8:Utf8;
var subLineWidth:Float; // the width of current subline
var spaceWidth:Int = font.spaceWidth;
var tabWidth:Int = spaceWidth * numSpacesInTab;
var startX:Int = font.minOffsetX;
if (numWords > 0)
{
w = 0;
subLineWidth = startX;
subLineUtf8 = new Utf8();
while (w < numWords)
{
word = words[w];
wordLength = Utf8.length(word);
charCode = Utf8.charCodeAt(word, 0);
isSpaceWord = (charCode == FlxBitmapFont.SPACE_CODE || charCode == FlxBitmapFont.TAB_CODE);
c = 0;
while (c < wordLength)
{
charCode = Utf8.charCodeAt(word, c);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
charWidth = spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
charWidth = tabWidth;
}
else
{
charWidth = font.getCharAdvance(charCode);
}
if (subLineWidth + charWidth > _fieldWidth - 2 * padding)
{
subLine = subLineUtf8.toString();
if (isSpaceWord) // new line ends with space / tab char, so we push it to sublines array, skip all the rest spaces and start another line
{
subLines.push(subLine);
c = wordLength;
subLineUtf8 = new Utf8();
subLineWidth = startX;
}
else if (subLine != "") // new line isn't empty so we should add it to sublines array and start another one
{
subLines.push(subLine);
subLineUtf8 = new Utf8();
subLineUtf8.addChar(charCode);
subLineWidth = startX + charWidth + letterSpacing;
}
else // the line is too tight to hold even one character
{
subLineUtf8 = new Utf8();
subLineUtf8.addChar(charCode);
subLineWidth = startX + charWidth + letterSpacing;
}
}
else
{
subLineUtf8.addChar(charCode);
subLineWidth += (charWidth + letterSpacing);
}
c++;
}
w++;
}
subLine = subLineUtf8.toString();
if (subLine != "")
{
subLines.push(subLine);
}
}
for (subline in subLines)
{
newLines.push(subline);
}
}
/**
* Internal method for updating helper data for text rendering
*/
function updateTextBitmap(useTiles:Bool = false):Void
{
computeTextSize();
if (FlxG.renderBlit || overrideDraw)
{
useTiles = false;
}
if (!useTiles)
{
textBitmap = FlxDestroyUtil.disposeIfNotEqual(textBitmap, frameWidth, frameHeight);
if (textBitmap == null)
{
textBitmap = new BitmapData(frameWidth, frameHeight, true, FlxColor.TRANSPARENT);
}
else
{
textBitmap.fillRect(textBitmap.rect, FlxColor.TRANSPARENT);
}
textBitmap.lock();
}
else if (FlxG.renderTile && !overrideDraw)
{
textData.splice(0, textData.length);
}
_fieldWidth = frameWidth;
var numLines:Int = _lines.length;
var line:String;
var lineWidth:Int;
var ox:Int, oy:Int;
for (i in 0...numLines)
{
line = _lines[i];
lineWidth = _linesWidth[i];
// LEFT
ox = font.minOffsetX;
oy = i * (font.lineHeight + lineSpacing) + padding;
if (alignment == FlxTextAlign.CENTER)
{
ox += Std.int((frameWidth - lineWidth) / 2);
}
else if (alignment == FlxTextAlign.RIGHT)
{
ox += (frameWidth - lineWidth) - padding;
}
else // LEFT OR JUSTIFY
{
ox += padding;
}
drawLine(i, ox, oy, useTiles);
}
if (!useTiles)
{
textBitmap.unlock();
}
pendingTextBitmapChange = false;
}
function drawLine(lineIndex:Int, posX:Int, posY:Int, useTiles:Bool = false):Void
{
if (FlxG.renderBlit || overrideDraw)
{
useTiles = false;
}
if (useTiles)
{
tileLine(lineIndex, posX, posY);
}
else
{
blitLine(lineIndex, posX, posY);
}
}
function blitLine(lineIndex:Int, startX:Int, startY:Int):Void
{
var charFrame:FlxFrame;
var charCode:Int;
var curX:Float = startX;
var curY:Int = startY;
var line:String = _lines[lineIndex];
var spaceWidth:Int = font.spaceWidth;
var lineLength:Int = Utf8.length(line);
var textWidth:Int = this.textWidth;
if (alignment == FlxTextAlign.JUSTIFY)
{
var numSpaces:Int = 0;
for (i in 0...lineLength)
{
charCode = Utf8.charCodeAt(line, i);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
numSpaces++;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
numSpaces += numSpacesInTab;
}
}
var lineWidth:Int = getStringWidth(line);
var totalSpacesWidth:Int = numSpaces * font.spaceWidth;
spaceWidth = Std.int((textWidth - lineWidth + totalSpacesWidth) / numSpaces);
}
var tabWidth:Int = spaceWidth * numSpacesInTab;
for (i in 0...lineLength)
{
charCode = Utf8.charCodeAt(line, i);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
curX += spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
curX += tabWidth;
}
else
{
charFrame = font.getCharFrame(charCode);
if (charFrame != null)
{
_flashPoint.setTo(curX, curY);
charFrame.paint(textBitmap, _flashPoint, true);
var charUt8 = new Utf8();
charUt8.addChar(charCode);
curX += font.getCharAdvance(charCode);
}
}
curX += letterSpacing;
}
}
function tileLine(lineIndex:Int, startX:Int, startY:Int):Void
{
if (!FlxG.renderTile && !overrideDraw) return;
var charFrame:FlxFrame;
var pos:Int = textData.length;
var charCode:Int;
var curX:Float = startX;
var curY:Int = startY;
var line:String = _lines[lineIndex];
var spaceWidth:Int = font.spaceWidth;
var lineLength:Int = Utf8.length(line);
var textWidth:Int = this.textWidth;
if (alignment == FlxTextAlign.JUSTIFY)
{
var numSpaces:Int = 0;
for (i in 0...lineLength)
{
charCode = Utf8.charCodeAt(line, i);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
numSpaces++;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
numSpaces += numSpacesInTab;
}
}
var lineWidth:Int = getStringWidth(line);
var totalSpacesWidth:Int = numSpaces * font.spaceWidth;
spaceWidth = Std.int((textWidth - lineWidth + totalSpacesWidth) / numSpaces);
}
var tabWidth:Int = spaceWidth * numSpacesInTab;
for (i in 0...lineLength)
{
charCode = Utf8.charCodeAt(line, i);
if (charCode == FlxBitmapFont.SPACE_CODE)
{
curX += spaceWidth;
}
else if (charCode == FlxBitmapFont.TAB_CODE)
{
curX += tabWidth;
}
else
{
charFrame = font.getCharFrame(charCode);
if (charFrame != null)
{
textData[pos++] = charCode;
textData[pos++] = curX;
textData[pos++] = curY;
curX += font.getCharAdvance(charCode);
}
}
curX += letterSpacing;
}
}
function updatePixels(useTiles:Bool = false):Void
{
var colorForFill:Int = background ? backgroundColor : FlxColor.TRANSPARENT;
var bitmap:BitmapData = null;
if (FlxG.renderBlit || overrideDraw)
{
if (pixels == null || (frameWidth != pixels.width || frameHeight != pixels.height))
{
pixels = new BitmapData(frameWidth, frameHeight, true, colorForFill);
}
else
{
pixels.fillRect(graphic.bitmap.rect, colorForFill);
}
bitmap = pixels;
}
else
{
if (!useTiles)
{
if (framePixels == null || (frameWidth != framePixels.width || frameHeight != framePixels.height))
{
framePixels = FlxDestroyUtil.dispose(framePixels);
framePixels = new BitmapData(frameWidth, frameHeight, true, colorForFill);
}
else
{
framePixels.fillRect(framePixels.rect, colorForFill);
}
bitmap = framePixels;
}
else
{
textDrawData.splice(0, textDrawData.length);
borderDrawData.splice(0, borderDrawData.length);
}
width = frameWidth;
height = frameHeight;
origin.x = frameWidth * 0.5;
origin.y = frameHeight * 0.5;
}
if (!useTiles)
{
bitmap.lock();
}
var isFront:Bool = false;
var iterations:Int = Std.int(borderSize * borderQuality);
iterations = (iterations <= 0) ? 1 : iterations;
var delta:Int = Std.int(borderSize / iterations);
var iterationsX:Int = 1;
var iterationsY:Int = 1;
var deltaX:Int = 1;
var deltaY:Int = 1;
if (borderStyle == FlxTextBorderStyle.SHADOW)
{
iterationsX = Math.round(Math.abs(shadowOffset.x) * borderQuality);
iterationsX = (iterationsX <= 0) ? 1 : iterationsX;
iterationsY = Math.round(Math.abs(shadowOffset.y) * borderQuality);
iterationsY = (iterationsY <= 0) ? 1 : iterationsY;
deltaX = Math.round(shadowOffset.x / iterationsX);
deltaY = Math.round(shadowOffset.y / iterationsY);
}
// render border
switch (borderStyle)
{
case SHADOW:
for (iterY in 0...iterationsY)
{
for (iterX in 0...iterationsX)
{
drawText(deltaX * (iterX + 1), deltaY * (iterY + 1), isFront, bitmap, useTiles);
}
}
case OUTLINE:
//Render an outline around the text
//(do 8 offset draw calls)
var itd:Int = 0;
for (iter in 0...iterations)
{
itd = delta * (iter + 1);
//upper-left
drawText( -itd, -itd, isFront, bitmap, useTiles);
//upper-middle
drawText(0, -itd, isFront, bitmap, useTiles);
//upper-right
drawText(itd, -itd, isFront, bitmap, useTiles);
//middle-left
drawText( -itd, 0, isFront, bitmap, useTiles);
//middle-right
drawText(itd, 0, isFront, bitmap, useTiles);
//lower-left
drawText( -itd, itd, isFront, bitmap, useTiles);
//lower-middle
drawText(0, itd, isFront, bitmap, useTiles);
//lower-right
drawText(itd, itd, isFront, bitmap, useTiles);
}
case OUTLINE_FAST:
//Render an outline around the text
//(do 4 diagonal offset draw calls)
//(this method might not work with certain narrow fonts)
var itd:Int = 0;
for (iter in 0...iterations)
{
itd = delta * (iter + 1);
//upper-left
drawText( -itd, -itd, isFront, bitmap, useTiles);
//upper-right
drawText(itd, -itd, isFront, bitmap, useTiles);
//lower-left
drawText( -itd, itd, isFront, bitmap, useTiles);
//lower-right
drawText(itd, itd, isFront, bitmap, useTiles);
}
case NONE:
}
isFront = true;
drawText(0, 0, isFront, bitmap, useTiles);
if (!useTiles)
{
bitmap.unlock();
}
if (FlxG.renderBlit || overrideDraw)
{
dirty = true;
}
pendingPixelsChange = false;
}
function drawText(posX:Int, posY:Int, isFront:Bool = true, ?bitmap:BitmapData, useTiles:Bool = false):Void
{
if (FlxG.renderBlit || overrideDraw)
{
useTiles = false;
}
if (useTiles)
{
tileText(posX, posY, isFront);
}
else
{
blitText(posX, posY, isFront, bitmap);
}
}
function blitText(posX:Int, posY:Int, isFront:Bool = true, ?bitmap:BitmapData):Void
{
_matrix.identity();
_matrix.translate(posX, posY);
var colorToApply = FlxColor.WHITE;
if (isFront && useTextColor)
{
colorToApply = textColor;
}
else if (!isFront)
{
colorToApply = borderColor;
}
_colorParams.setMultipliers(
colorToApply.redFloat, colorToApply.greenFloat,
colorToApply.blueFloat, colorToApply.alphaFloat);
if (isFront && !useTextColor)
{
_flashRect.setTo(0, 0, textBitmap.width, textBitmap.height);
bitmap.copyPixels(textBitmap, _flashRect, _flashPointZero, null, null, true);
}
else
{
bitmap.draw(textBitmap, _matrix, _colorParams);
}
}
function tileText(posX:Int, posY:Int, isFront:Bool = true):Void
{
if (!FlxG.renderTile && !overrideDraw) return;
var data:Array<Float> = isFront ? textDrawData : borderDrawData;
var pos:Int = data.length;
var textPos:Int;
var textLen:Int = Std.int(textData.length / 3);
for (i in 0...textLen)
{
textPos = 3 * i;
data[pos++] = textData[textPos];
data[pos++] = textData[textPos + 1] + posX;
data[pos++] = textData[textPos + 2] + posY;
}
}
/**
* Set border's style (shadow, outline, etc), color, and size all in one go!
*
* @param Style outline style
* @param Color outline color in flash 0xAARRGGBB format
* @param Size outline size in pixels
* @param Quality outline quality - # of iterations to use when drawing. 0:just 1, 1:equal number to BorderSize
*/
public inline function setBorderStyle(Style:FlxTextBorderStyle, Color:FlxColor = 0, Size:Float = 1, Quality:Float = 1):Void
{
borderStyle = Style;
borderColor = Color;
borderSize = Size;
borderQuality = Quality;
if (borderStyle == FlxTextBorderStyle.SHADOW)
{
shadowOffset.set(borderSize, borderSize);
}
pendingTextBitmapChange = true;
}
function get_fieldWidth():Int
{
return (autoSize) ? textWidth : _fieldWidth;
}
/**
* Sets the width of the text field. If the text does not fit, it will spread on multiple lines.
*/
function set_fieldWidth(value:Int):Int
{
value = (value > 1) ? value : 1;
if (value != _fieldWidth)
{
if (value <= 0)
{
autoSize = true;
wordWrap = false;
}
pendingTextChange = true;
}
return _fieldWidth = value;
}
function set_alignment(value:FlxTextAlign):FlxTextAlign
{
if (alignment != value && alignment != FlxTextAlign.JUSTIFY)
{
alignment = value;
pendingTextBitmapChange = true;
}
return value;
}
function set_multiLine(value:Bool):Bool
{
if (multiLine != value)
{
multiLine = value;
pendingTextChange = true;
}
return value;
}
function set_font(value:FlxBitmapFont):FlxBitmapFont
{
if (font != value)
{
font = value;
pendingTextChange = true;
}
return value;
}
function set_lineSpacing(value:Int):Int
{
if (lineSpacing != value)
{
lineSpacing = value;
pendingTextBitmapChange = true;
}
return lineSpacing;
}
function set_letterSpacing(value:Int):Int
{
if (value != letterSpacing)
{
letterSpacing = value;
pendingTextChange = true;
}
return letterSpacing;
}
function set_autoUpperCase(value:Bool):Bool
{
if (autoUpperCase != value)
{
autoUpperCase = value;
pendingTextChange = true;
}
return autoUpperCase;
}
function set_wordWrap(value:Bool):Bool
{
if (wordWrap != value)
{
wordWrap = value;
pendingTextChange = true;
}
return wordWrap;
}
function set_wrapByWord(value:Bool):Bool
{
if (wrapByWord != value)
{
wrapByWord = value;
pendingTextChange = true;
}
return value;
}
function set_autoSize(value:Bool):Bool
{
if (autoSize != value)
{
autoSize = value;
pendingTextChange = true;
}
return autoSize;
}
function set_padding(value:Int):Int
{
if (value != padding)
{
padding = value;
pendingTextChange = true;
}
return value;
}
function set_numSpacesInTab(value:Int):Int
{
if (numSpacesInTab != value && value > 0)
{
numSpacesInTab = value;
pendingTextChange = true;
}
return value;
}
function set_background(value:Bool):Bool
{
if (background != value)
{
background = value;
if (FlxG.renderBlit || overrideDraw)
{
pendingPixelsChange = true;
}
}
return value;
}
function set_backgroundColor(value:Int):Int
{
if (backgroundColor != value)
{
backgroundColor = value;
if (FlxG.renderBlit || overrideDraw)
{
pendingPixelsChange = true;
}
}
return value;
}
function set_borderStyle(style:FlxTextBorderStyle):FlxTextBorderStyle
{
if (style != borderStyle)
{
borderStyle = style;
pendingTextBitmapChange = true;
}
return borderStyle;
}
function set_borderColor(value:Int):Int
{
if (borderColor != value)
{
borderColor = value;
if (FlxG.renderBlit || overrideDraw)
{
pendingPixelsChange = true;
}
}
return value;
}
function set_borderSize(value:Float):Float
{
if (value != borderSize)
{
borderSize = value;
if (borderStyle != FlxTextBorderStyle.NONE)
{
pendingTextBitmapChange = true;
}
}
return value;
}
function set_borderQuality(value:Float):Float
{
value = Math.min(1, Math.max(0, value));
if (value != borderQuality)
{
borderQuality = value;
if (borderStyle != FlxTextBorderStyle.NONE)
{
pendingTextBitmapChange = true;
}
}
return value;
}
function get_numLines():Int
{
return _lines.length;
}
/**
* Calculates maximum width of the text.
*
* @return text width.
*/
function get_textWidth():Int
{
var max:Int = 0;
var numLines:Int = _lines.length;
var lineWidth:Int;
_linesWidth = [];
for (i in 0...numLines)
{
lineWidth = getLineWidth(i);
_linesWidth[i] = lineWidth;
max = (max > lineWidth) ? max : lineWidth;
}
return max;
}
function get_textHeight():Int
{
return (lineHeight + lineSpacing) * _lines.length - lineSpacing;
}
function get_lineHeight():Int
{
return font.lineHeight;
}
override function get_width():Float
{
checkPendingChanges(true);
return super.get_width();
}
override function get_height():Float
{
checkPendingChanges(true);
return super.get_height();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment