Skip to content

Commit

Permalink
Adds letterSpacing property to BitmapText (#4642)
Browse files Browse the repository at this point in the history
  • Loading branch information
ceco-fmedia authored and bigtimebuddy committed May 25, 2018
1 parent e5efccf commit 3ad703a
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 30 deletions.
100 changes: 70 additions & 30 deletions src/extras/BitmapText.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ export default class BitmapText extends core.Container
*/
this._maxLineHeight = 0;

/**
* Letter spacing. This is useful for setting the space between characters.
* @member {number}
* @private
*/
this._letterSpacing = 0;

/**
* Text anchor. read-only
*
Expand Down Expand Up @@ -140,49 +147,37 @@ export default class BitmapText extends core.Container
const pos = new core.Point();
const chars = [];
const lineWidths = [];
const text = this.text.replace(/(?:\r\n|\r)/g, '\n');
const textLength = text.length;
const maxWidth = this._maxWidth * data.size / this._font.size;

let prevCharCode = null;
let lastLineWidth = 0;
let maxLineWidth = 0;
let line = 0;
let lastSpace = -1;
let lastSpaceWidth = 0;
let lastBreakPos = -1;
let lastBreakWidth = 0;
let spacesRemoved = 0;
let maxLineHeight = 0;

for (let i = 0; i < this.text.length; i++)
for (let i = 0; i < textLength; i++)
{
const charCode = this.text.charCodeAt(i);
const charCode = text.charCodeAt(i);
const char = text.charAt(i);

if (/(\s)/.test(this.text.charAt(i)))
if (/(?:\s)/.test(char))
{
lastSpace = i;
lastSpaceWidth = lastLineWidth;
lastBreakPos = i;
lastBreakWidth = lastLineWidth;
}

if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i)))
if (char === '\r' || char === '\n')
{
lineWidths.push(lastLineWidth);
maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
line++;

pos.x = 0;
pos.y += data.lineHeight;
prevCharCode = null;
continue;
}

if (lastSpace !== -1 && this._maxWidth > 0 && pos.x * scale > this._maxWidth)
{
core.utils.removeItems(chars, lastSpace - spacesRemoved, i - lastSpace);
i = lastSpace;
lastSpace = -1;
++line;
++spacesRemoved;

lineWidths.push(lastSpaceWidth);
maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth);
line++;

pos.x = 0;
pos.y += data.lineHeight;
prevCharCode = null;
Expand All @@ -205,16 +200,42 @@ export default class BitmapText extends core.Container
texture: charData.texture,
line,
charCode,
position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
position: new core.Point(pos.x + charData.xOffset + (this._letterSpacing / 2), pos.y + charData.yOffset),
});
lastLineWidth = pos.x + (charData.texture.width + charData.xOffset);
pos.x += charData.xAdvance;
pos.x += charData.xAdvance + this._letterSpacing;
lastLineWidth = pos.x;
maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height));
prevCharCode = charCode;

if (lastBreakPos !== -1 && maxWidth > 0 && pos.x > maxWidth)
{
++spacesRemoved;
core.utils.removeItems(chars, 1 + lastBreakPos - spacesRemoved, 1 + i - lastBreakPos);
i = lastBreakPos;
lastBreakPos = -1;

lineWidths.push(lastBreakWidth);
maxLineWidth = Math.max(maxLineWidth, lastBreakWidth);
line++;

pos.x = 0;
pos.y += data.lineHeight;
prevCharCode = null;
}
}

lineWidths.push(lastLineWidth);
maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
const lastChar = text.charAt(text.length - 1);

if (lastChar !== '\r' && lastChar !== '\n')
{
if (/(?:\s)/.test(lastChar))
{
lastLineWidth = lastBreakWidth;
}

lineWidths.push(lastLineWidth);
maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
}

const lineAlignOffsets = [];

Expand Down Expand Up @@ -484,6 +505,25 @@ export default class BitmapText extends core.Container
return this._textWidth;
}

/**
* Additional space between characters.
*
* @member {number}
*/
get letterSpacing()
{
return this._letterSpacing;
}

set letterSpacing(value) // eslint-disable-line require-jsdoc
{
if (this._letterSpacing !== value)
{
this._letterSpacing = value;
this.dirty = true;
}
}

/**
* The height of the overall text, different from fontSize,
* which is defined in the style object
Expand Down
104 changes: 104 additions & 0 deletions test/extras/BitmapText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use strict';

const path = require('path');
const fs = require('fs');

describe('PIXI.extras.BitmapText', function ()
{
before(function (done)
{
this.fontXML = null;
this.fontImage = null;
this.font = null;

const resolveURL = (url) => path.resolve(this.resources, url);
const loadXML = (url) => new Promise((resolve) =>
fs.readFile(resolveURL(url), 'utf8', (err, data) =>
{
expect(err).to.be.null;
resolve((new window.DOMParser()).parseFromString(data, 'text/xml'));
}));

const loadImage = (url) => new Promise((resolve) =>
{
const image = new Image();

image.onload = () => resolve(image);
image.src = resolveURL(url);
});

this.resources = path.join(__dirname, 'resources');
Promise.all([
loadXML('font.fnt'),
loadImage('font.png'),
]).then(([
fontXML,
fontImage,
]) =>
{
this.fontXML = fontXML;
this.fontImage = fontImage;
const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1));

this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture);
done();
});
});

describe('text', function ()
{
it('should render text even if there are unsupported characters', function ()
{
const text = new PIXI.extras.BitmapText('ABCDEFG', {
font: this.font.font,
});

expect(text.children.length).to.equal(4);
});
it('should break line on space', function ()
{
const bmpText = new PIXI.extras.BitmapText('', {
font: this.font.font,
size: 24,
});

bmpText.maxWidth = 40;
bmpText.text = 'A A A A A A A ';
bmpText.updateText();

expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth);

bmpText.maxWidth = 40;
bmpText.text = 'A A A A A A A';
bmpText.updateText();

expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth);
});
it('letterSpacing should add extra space between characters', function ()
{
const text = 'ABCD zz DCBA';
const bmpText = new PIXI.extras.BitmapText(text, {
font: this.font.font,
});
const positions = [];
const renderedChars = bmpText.children.length;

for (let x = 0; x < renderedChars; ++x)
{
positions.push(bmpText.children[x].x);
}
for (let space = 1; space < 20; ++space)
{
bmpText.letterSpacing = space;
bmpText.updateText();
let prevPos = bmpText.children[0].x;

for (let char = 1; char < renderedChars; ++char)
{
expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]);
prevPos = bmpText.children[char].x;
}
}
});
});
});
3 changes: 3 additions & 0 deletions test/extras/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

require('./BitmapText');
29 changes: 29 additions & 0 deletions test/extras/resources/font.fnt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<font>
<info face="font" size="24" bold="0" italic="0" charset="" unicode="" stretchH="100" smooth="1" aa="1" padding="2,2,2,2" spacing="0,0" outline="0"/>
<common lineHeight="27" base="18" scaleW="46" scaleH="201" pages="1" packed="0"/>
<pages>
<page id="0" file="font.png"/>
</pages>
<chars count="15">
<char id="65" x="2" y="2" width="19" height="20" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15"/>
<char id="66" x="2" y="24" width="15" height="20" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15"/>
<char id="67" x="2" y="46" width="18" height="20" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15"/>
<char id="68" x="19" y="24" width="17" height="20" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15"/>
<char id="91" x="2" y="68" width="7" height="24" xoffset="2" yoffset="0" xadvance="7" page="0" chnl="15"/>
<char id="45" x="23" y="2" width="9" height="4" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15"/>
<char id="92" x="34" y="2" width="10" height="20" xoffset="-1" yoffset="0" xadvance="7" page="0" chnl="15"/>
<char id="47" x="2" y="94" width="10" height="20" xoffset="-1" yoffset="0" xadvance="7" page="0" chnl="15"/>
<char id="46" x="23" y="8" width="5" height="5" xoffset="2" yoffset="15" xadvance="7" page="0" chnl="15"/>
<char id="44" x="11" y="68" width="5" height="8" xoffset="2" yoffset="15" xadvance="7" page="0" chnl="15"/>
<char id="63" x="2" y="116" width="13" height="20" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15"/>
<char id="33" x="14" y="78" width="5" height="20" xoffset="2" yoffset="0" xadvance="7" page="0" chnl="15"/>
<char id="59" x="2" y="138" width="5" height="18" xoffset="2" yoffset="5" xadvance="7" page="0" chnl="15"/>
<char id="58" x="2" y="158" width="5" height="15" xoffset="2" yoffset="5" xadvance="7" page="0" chnl="15"/>
<char id="93" x="2" y="175" width="7" height="24" xoffset="0" yoffset="0" xadvance="7" page="0" chnl="15"/>
<char id="32" x="0" y="0" width="0" height="0" xoffset="0" yoffset="0" xadvance="7" page="0" chnl="15"/>
</chars>
<kernings count="2">
<kerning first="32" second="65" amount="-1"/>
<kerning first="65" second="32" amount="-1"/>
</kernings>
</font>
Binary file added test/extras/resources/font.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ describe('PIXI', function ()
require('./loaders');
require('./renders');
require('./prepare');
require('./extras');
});

0 comments on commit 3ad703a

Please sign in to comment.