phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
436 lines (355 loc) • 11.4 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
/**
* Calculate the full bounds, in local and world space, of a BitmapText Game Object.
*
* Returns a BitmapTextSize object that contains global and local variants of the Game Objects x and y coordinates and
* its width and height. Also includes an array of the line lengths and all word positions.
*
* The global position and size take into account the Game Object's position and scale.
*
* The local position and size just takes into account the font data.
*
* @function GetBitmapTextSize
* @since 3.0.0
* @private
*
* @param {(Phaser.GameObjects.DynamicBitmapText|Phaser.GameObjects.BitmapText)} src - The BitmapText to calculate the bounds values for.
* @param {boolean} [round=false] - Whether to round the positions to the nearest integer.
* @param {boolean} [updateOrigin=false] - Whether to update the origin of the BitmapText after bounds calculations?
* @param {object} [out] - Object to store the results in, to save constant object creation. If not provided an empty object is returned.
*
* @return {Phaser.Types.GameObjects.BitmapText.BitmapTextSize} The calculated bounds values of the BitmapText.
*/
var GetBitmapTextSize = function (src, round, updateOrigin, out)
{
if (updateOrigin === undefined) { updateOrigin = false; }
if (out === undefined)
{
out = {
local: {
x: 0,
y: 0,
width: 0,
height: 0
},
global: {
x: 0,
y: 0,
width: 0,
height: 0
},
lines: {
shortest: 0,
longest: 0,
lengths: null,
height: 0
},
wrappedText: '',
words: [],
characters: [],
scaleX: 0,
scaleY: 0
};
return out;
}
var text = src.text;
var textLength = text.length;
var maxWidth = src.maxWidth;
var wordWrapCharCode = src.wordWrapCharCode;
var bx = Number.MAX_VALUE;
var by = Number.MAX_VALUE;
var bw = 0;
var bh = 0;
var chars = src.fontData.chars;
var lineHeight = src.fontData.lineHeight;
var letterSpacing = src.letterSpacing;
var lineSpacing = src.lineSpacing;
var xAdvance = 0;
var yAdvance = 0;
var charCode = 0;
var glyph = null;
var align = src._align;
var x = 0;
var y = 0;
var scale = (src.fontSize / src.fontData.size);
var sx = scale * src.scaleX;
var sy = scale * src.scaleY;
var lastGlyph = null;
var lastCharCode = 0;
var lineWidths = [];
var shortestLine = Number.MAX_VALUE;
var longestLine = 0;
var currentLine = 0;
var currentLineWidth = 0;
var i;
var j;
var lines;
var words = [];
var characters = [];
var current = null;
// Measure the width of the text
var measureTextWidth = function (text, fontData)
{
var width = 0;
for (var i = 0; i < text.length; i++)
{
var charCode = text.charCodeAt(i);
var glyph = fontData.chars[charCode];
if (glyph)
{
width += glyph.xAdvance;
}
}
return width * sx;
};
// Scan for breach of maxWidth and insert carriage-returns
if (maxWidth > 0)
{
// Split the text into lines
lines = text.split('\n');
var wrappedLines = [];
// Loop through each line
for (i = 0; i < lines.length; i++)
{
var line = lines[i];
var word = '';
var wrappedLine = '';
var lineToCheck = '';
var lineWithWord = '';
// Loop through each character in a line
for (j = 0; j < line.length; j++)
{
charCode = line.charCodeAt(j);
word += line[j];
// White space or end of line?
if (charCode === wordWrapCharCode || j === line.length - 1)
{
lineWithWord = lineToCheck + word;
var textWidth = measureTextWidth(lineWithWord, src.fontData);
if (textWidth <= maxWidth)
{
lineToCheck = lineWithWord;
}
else
{
// If the current word is too long to fit on a line, wrap it
// Remove trailing word wrap char to keep text length the same
wrappedLine = wrappedLine.slice(0, -1);
wrappedLine += (wrappedLine ? '\n' : '') + lineToCheck;
lineToCheck = word;
}
word = '';
}
}
wrappedLine = wrappedLine.slice(0, -1);
wrappedLine += (wrappedLine ? '\n' : '') + lineToCheck;
wrappedLines.push(wrappedLine);
}
text = wrappedLines.join('\n');
out.wrappedText = text;
textLength = text.length;
}
var charIndex = 0;
for (i = 0; i < textLength; i++)
{
charCode = text.charCodeAt(i);
if (charCode === 10)
{
if (current !== null)
{
words.push({
word: current.word,
i: current.i,
x: current.x * sx,
y: current.y * sy,
w: current.w * sx,
h: current.h * sy
});
current = null;
}
lastGlyph = null;
lineWidths[currentLine] = currentLineWidth;
if (currentLineWidth > longestLine)
{
longestLine = currentLineWidth;
}
if (currentLineWidth < shortestLine)
{
shortestLine = currentLineWidth;
}
currentLine++;
currentLineWidth = 0;
xAdvance = 0;
yAdvance = (lineHeight + lineSpacing) * currentLine;
continue;
}
glyph = chars[charCode];
if (!glyph)
{
continue;
}
x = xAdvance;
y = yAdvance;
if (lastGlyph !== null)
{
var kerningOffset = glyph.kerning[lastCharCode];
x += (kerningOffset !== undefined) ? kerningOffset : 0;
}
if (bx > x)
{
bx = x;
}
if (by > y)
{
by = y;
}
var gw = x + glyph.xAdvance;
var gh = y + lineHeight;
if (bw < gw)
{
bw = gw;
}
if (bh < gh)
{
bh = gh;
}
var charWidth = glyph.xOffset + glyph.xAdvance + ((kerningOffset !== undefined) ? kerningOffset : 0);
if (charCode === wordWrapCharCode)
{
if (current !== null)
{
words.push({
word: current.word,
i: current.i,
x: current.x * sx,
y: current.y * sy,
w: current.w * sx,
h: current.h * sy
});
current = null;
}
}
else
{
if (current === null)
{
// We're starting a new word, recording the starting index, etc
current = { word: '', i: charIndex, x: xAdvance, y: yAdvance, w: 0, h: lineHeight };
}
current.word = current.word.concat(text[i]);
current.w += charWidth;
}
characters.push({
i: charIndex,
idx: i,
char: text[i],
code: charCode,
x: (glyph.xOffset + x) * scale,
y: (glyph.yOffset + yAdvance) * scale,
w: glyph.width * scale,
h: glyph.height * scale,
t: yAdvance * scale,
r: gw * scale,
b: lineHeight * scale,
line: currentLine,
glyph: glyph
});
xAdvance += glyph.xAdvance + letterSpacing + ((kerningOffset !== undefined) ? kerningOffset : 0);
lastGlyph = glyph;
lastCharCode = charCode;
currentLineWidth = gw * scale;
charIndex++;
}
// Last word
if (current !== null)
{
words.push({
word: current.word,
i: current.i,
x: current.x * sx,
y: current.y * sy,
w: current.w * sx,
h: current.h * sy
});
}
lineWidths[currentLine] = currentLineWidth;
if (currentLineWidth > longestLine)
{
longestLine = currentLineWidth;
}
if (currentLineWidth < shortestLine)
{
shortestLine = currentLineWidth;
}
// Adjust all of the character positions based on alignment
if (align > 0)
{
for (var c = 0; c < characters.length; c++)
{
var currentChar = characters[c];
if (align === 1)
{
var ax1 = ((longestLine - lineWidths[currentChar.line]) / 2);
currentChar.x += ax1;
currentChar.r += ax1;
}
else if (align === 2)
{
var ax2 = (longestLine - lineWidths[currentChar.line]);
currentChar.x += ax2;
currentChar.r += ax2;
}
}
}
var local = out.local;
var global = out.global;
lines = out.lines;
local.x = bx * scale;
local.y = by * scale;
local.width = bw * scale;
local.height = bh * scale;
global.x = (src.x - src._displayOriginX) + (bx * sx);
global.y = (src.y - src._displayOriginY) + (by * sy);
global.width = bw * sx;
global.height = bh * sy;
lines.shortest = shortestLine;
lines.longest = longestLine;
lines.lengths = lineWidths;
if (round)
{
local.x = Math.ceil(local.x);
local.y = Math.ceil(local.y);
local.width = Math.ceil(local.width);
local.height = Math.ceil(local.height);
global.x = Math.ceil(global.x);
global.y = Math.ceil(global.y);
global.width = Math.ceil(global.width);
global.height = Math.ceil(global.height);
lines.shortest = Math.ceil(shortestLine);
lines.longest = Math.ceil(longestLine);
}
if (updateOrigin)
{
src._displayOriginX = (src.originX * local.width);
src._displayOriginY = (src.originY * local.height);
global.x = src.x - (src._displayOriginX * src.scaleX);
global.y = src.y - (src._displayOriginY * src.scaleY);
if (round)
{
global.x = Math.ceil(global.x);
global.y = Math.ceil(global.y);
}
}
out.words = words;
out.characters = characters;
out.lines.height = lineHeight;
out.scale = scale;
out.scaleX = src.scaleX;
out.scaleY = src.scaleY;
return out;
};
module.exports = GetBitmapTextSize;