mirror of
https://github.com/photonstorm/phaser
synced 2025-01-11 20:58:56 +00:00
516 lines
13 KiB
JavaScript
516 lines
13 KiB
JavaScript
/**
|
|
* @author Richard Davey <rich@photonstorm.com>
|
|
* @copyright 2020 Photon Storm Ltd.
|
|
* @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 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 words = [];
|
|
var characters = [];
|
|
var current = null;
|
|
|
|
// Scan for breach of maxWidth and insert carriage-returns
|
|
if (maxWidth > 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,
|
|
cr: true
|
|
});
|
|
|
|
current = null;
|
|
}
|
|
|
|
xAdvance = 0;
|
|
yAdvance += lineHeight;
|
|
lastGlyph = null;
|
|
|
|
continue;
|
|
}
|
|
|
|
glyph = chars[charCode];
|
|
|
|
if (!glyph)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (lastGlyph !== null)
|
|
{
|
|
var glyphKerningOffset = glyph.kerning[lastCharCode];
|
|
}
|
|
|
|
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,
|
|
cr: false
|
|
});
|
|
|
|
current = null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (current === null)
|
|
{
|
|
// We're starting a new word, recording the starting index, etc
|
|
current = { word: '', i: i, x: xAdvance, y: yAdvance, w: 0, h: lineHeight, cr: false };
|
|
}
|
|
|
|
current.word = current.word.concat(text[i]);
|
|
current.w += glyph.xOffset + glyph.xAdvance + ((glyphKerningOffset !== undefined) ? glyphKerningOffset : 0);
|
|
}
|
|
|
|
xAdvance += glyph.xAdvance + letterSpacing;
|
|
lastGlyph = glyph;
|
|
lastCharCode = charCode;
|
|
}
|
|
|
|
// 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,
|
|
cr: false
|
|
});
|
|
}
|
|
|
|
// Reset for the next loop
|
|
xAdvance = 0;
|
|
yAdvance = 0;
|
|
lastGlyph = null;
|
|
lastCharCode = 0;
|
|
|
|
// Loop through the words array and see if we've got any > maxWidth
|
|
var prev;
|
|
var offset = 0;
|
|
var crs = [];
|
|
|
|
for (i = 0; i < words.length; i++)
|
|
{
|
|
var entry = words[i];
|
|
var left = entry.x;
|
|
var right = entry.x + entry.w;
|
|
|
|
if (prev)
|
|
{
|
|
var diff = left - (prev.x + prev.w);
|
|
|
|
offset = left - (diff + prev.w);
|
|
|
|
prev = null;
|
|
}
|
|
|
|
var checkLeft = left - offset;
|
|
var checkRight = right - offset;
|
|
|
|
if (checkLeft > maxWidth || checkRight > maxWidth)
|
|
{
|
|
crs.push(entry.i - 1);
|
|
|
|
if (entry.cr)
|
|
{
|
|
crs.push(entry.i + entry.word.length);
|
|
|
|
offset = 0;
|
|
prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev = entry;
|
|
}
|
|
}
|
|
else if (entry.cr)
|
|
{
|
|
crs.push(entry.i + entry.word.length);
|
|
|
|
offset = 0;
|
|
prev = null;
|
|
}
|
|
}
|
|
|
|
var stringInsert = function (str, index, value)
|
|
{
|
|
return str.substr(0, index) + value + str.substr(index + 1);
|
|
};
|
|
|
|
for (i = crs.length - 1; i >= 0; i--)
|
|
{
|
|
// eslint-disable-next-line quotes
|
|
text = stringInsert(text, crs[i], "\n");
|
|
}
|
|
|
|
out.wrappedText = text;
|
|
|
|
textLength = text.length;
|
|
|
|
// Recalculated in the next loop
|
|
words = [];
|
|
current = null;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
xAdvance = 0;
|
|
yAdvance += lineHeight;
|
|
lastGlyph = null;
|
|
|
|
lineWidths[currentLine] = currentLineWidth;
|
|
|
|
if (currentLineWidth > longestLine)
|
|
{
|
|
longestLine = currentLineWidth;
|
|
}
|
|
|
|
if (currentLineWidth < shortestLine)
|
|
{
|
|
shortestLine = currentLineWidth;
|
|
}
|
|
|
|
currentLine++;
|
|
currentLineWidth = 0;
|
|
|
|
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,
|
|
char: text[i],
|
|
code: charCode,
|
|
x: (glyph.xOffset + xAdvance) * 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;
|
|
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;
|
|
var 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;
|