diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js
index b9b535a..14e7f2b 100644
--- a/packages/text-bitmap/src/BitmapText.js
+++ b/packages/text-bitmap/src/BitmapText.js
@@ -114,6 +114,13 @@
this._maxLineHeight = 0;
/**
+ * Letter spacing. This is useful for setting the space between characters.
+ * @member {number}
+ * @private
+ */
+ this._letterSpacing = 0;
+
+ /**
* Text anchor. read-only
*
* @member {PIXI.ObservablePoint}
@@ -143,49 +150,37 @@
const pos = new 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)
- {
- 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;
@@ -208,16 +203,42 @@
texture: charData.texture,
line,
charCode,
- position: new Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
+ position: new 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;
+ 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 = [];
@@ -491,6 +512,25 @@
}
/**
+ * 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.
*
diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js
index b9b535a..14e7f2b 100644
--- a/packages/text-bitmap/src/BitmapText.js
+++ b/packages/text-bitmap/src/BitmapText.js
@@ -114,6 +114,13 @@
this._maxLineHeight = 0;
/**
+ * Letter spacing. This is useful for setting the space between characters.
+ * @member {number}
+ * @private
+ */
+ this._letterSpacing = 0;
+
+ /**
* Text anchor. read-only
*
* @member {PIXI.ObservablePoint}
@@ -143,49 +150,37 @@
const pos = new 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)
- {
- 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;
@@ -208,16 +203,42 @@
texture: charData.texture,
line,
charCode,
- position: new Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
+ position: new 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;
+ 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 = [];
@@ -491,6 +512,25 @@
}
/**
+ * 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.
*
diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js
new file mode 100644
index 0000000..ff7a82c
--- /dev/null
+++ b/packages/text-bitmap/test/BitmapText.js
@@ -0,0 +1,104 @@
+const path = require('path');
+const fs = require('fs');
+const { BitmapText } = require('../');
+const { Texture, BaseTexture } = require('@pixi/core');
+
+describe('PIXI.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('font1.fnt'),
+ loadImage('font1.png'),
+ ]).then(([
+ fontXML,
+ fontImage,
+ ]) =>
+ {
+ this.fontXML = fontXML;
+ this.fontImage = fontImage;
+ const texture = new Texture(new BaseTexture(this.fontImage, null, 1));
+
+ this.font = BitmapText.registerFont(this.fontXML, texture);
+ done();
+ });
+ });
+
+ describe('text', function ()
+ {
+ it('should render text even if there are unsupported characters', function ()
+ {
+ const text = new BitmapText('ABCDEFG', {
+ font: this.font.font,
+ });
+
+ expect(text.children.length).to.equal(4);
+ });
+ it('should break line on space', function ()
+ {
+ const bmpText = new 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 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;
+ }
+ }
+ });
+ });
+});
diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js
index b9b535a..14e7f2b 100644
--- a/packages/text-bitmap/src/BitmapText.js
+++ b/packages/text-bitmap/src/BitmapText.js
@@ -114,6 +114,13 @@
this._maxLineHeight = 0;
/**
+ * Letter spacing. This is useful for setting the space between characters.
+ * @member {number}
+ * @private
+ */
+ this._letterSpacing = 0;
+
+ /**
* Text anchor. read-only
*
* @member {PIXI.ObservablePoint}
@@ -143,49 +150,37 @@
const pos = new 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)
- {
- 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;
@@ -208,16 +203,42 @@
texture: charData.texture,
line,
charCode,
- position: new Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
+ position: new 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;
+ 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 = [];
@@ -491,6 +512,25 @@
}
/**
+ * 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.
*
diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js
new file mode 100644
index 0000000..ff7a82c
--- /dev/null
+++ b/packages/text-bitmap/test/BitmapText.js
@@ -0,0 +1,104 @@
+const path = require('path');
+const fs = require('fs');
+const { BitmapText } = require('../');
+const { Texture, BaseTexture } = require('@pixi/core');
+
+describe('PIXI.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('font1.fnt'),
+ loadImage('font1.png'),
+ ]).then(([
+ fontXML,
+ fontImage,
+ ]) =>
+ {
+ this.fontXML = fontXML;
+ this.fontImage = fontImage;
+ const texture = new Texture(new BaseTexture(this.fontImage, null, 1));
+
+ this.font = BitmapText.registerFont(this.fontXML, texture);
+ done();
+ });
+ });
+
+ describe('text', function ()
+ {
+ it('should render text even if there are unsupported characters', function ()
+ {
+ const text = new BitmapText('ABCDEFG', {
+ font: this.font.font,
+ });
+
+ expect(text.children.length).to.equal(4);
+ });
+ it('should break line on space', function ()
+ {
+ const bmpText = new 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 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;
+ }
+ }
+ });
+ });
+});
diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js
index 688fee2..6fab55e 100644
--- a/packages/text-bitmap/test/index.js
+++ b/packages/text-bitmap/test/index.js
@@ -1 +1,2 @@
+require('./BitmapText');
require('./BitmapFontLoader');
diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js
index b9b535a..14e7f2b 100644
--- a/packages/text-bitmap/src/BitmapText.js
+++ b/packages/text-bitmap/src/BitmapText.js
@@ -114,6 +114,13 @@
this._maxLineHeight = 0;
/**
+ * Letter spacing. This is useful for setting the space between characters.
+ * @member {number}
+ * @private
+ */
+ this._letterSpacing = 0;
+
+ /**
* Text anchor. read-only
*
* @member {PIXI.ObservablePoint}
@@ -143,49 +150,37 @@
const pos = new 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)
- {
- 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;
@@ -208,16 +203,42 @@
texture: charData.texture,
line,
charCode,
- position: new Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
+ position: new 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;
+ 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 = [];
@@ -491,6 +512,25 @@
}
/**
+ * 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.
*
diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js
new file mode 100644
index 0000000..ff7a82c
--- /dev/null
+++ b/packages/text-bitmap/test/BitmapText.js
@@ -0,0 +1,104 @@
+const path = require('path');
+const fs = require('fs');
+const { BitmapText } = require('../');
+const { Texture, BaseTexture } = require('@pixi/core');
+
+describe('PIXI.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('font1.fnt'),
+ loadImage('font1.png'),
+ ]).then(([
+ fontXML,
+ fontImage,
+ ]) =>
+ {
+ this.fontXML = fontXML;
+ this.fontImage = fontImage;
+ const texture = new Texture(new BaseTexture(this.fontImage, null, 1));
+
+ this.font = BitmapText.registerFont(this.fontXML, texture);
+ done();
+ });
+ });
+
+ describe('text', function ()
+ {
+ it('should render text even if there are unsupported characters', function ()
+ {
+ const text = new BitmapText('ABCDEFG', {
+ font: this.font.font,
+ });
+
+ expect(text.children.length).to.equal(4);
+ });
+ it('should break line on space', function ()
+ {
+ const bmpText = new 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 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;
+ }
+ }
+ });
+ });
+});
diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js
index 688fee2..6fab55e 100644
--- a/packages/text-bitmap/test/index.js
+++ b/packages/text-bitmap/test/index.js
@@ -1 +1,2 @@
+require('./BitmapText');
require('./BitmapFontLoader');
diff --git a/packages/text-bitmap/test/resources/font1.fnt b/packages/text-bitmap/test/resources/font1.fnt
new file mode 100644
index 0000000..74496bb
--- /dev/null
+++ b/packages/text-bitmap/test/resources/font1.fnt
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js
index b9b535a..14e7f2b 100644
--- a/packages/text-bitmap/src/BitmapText.js
+++ b/packages/text-bitmap/src/BitmapText.js
@@ -114,6 +114,13 @@
this._maxLineHeight = 0;
/**
+ * Letter spacing. This is useful for setting the space between characters.
+ * @member {number}
+ * @private
+ */
+ this._letterSpacing = 0;
+
+ /**
* Text anchor. read-only
*
* @member {PIXI.ObservablePoint}
@@ -143,49 +150,37 @@
const pos = new 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)
- {
- 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;
@@ -208,16 +203,42 @@
texture: charData.texture,
line,
charCode,
- position: new Point(pos.x + charData.xOffset, pos.y + charData.yOffset),
+ position: new 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;
+ 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 = [];
@@ -491,6 +512,25 @@
}
/**
+ * 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.
*
diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js
new file mode 100644
index 0000000..ff7a82c
--- /dev/null
+++ b/packages/text-bitmap/test/BitmapText.js
@@ -0,0 +1,104 @@
+const path = require('path');
+const fs = require('fs');
+const { BitmapText } = require('../');
+const { Texture, BaseTexture } = require('@pixi/core');
+
+describe('PIXI.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('font1.fnt'),
+ loadImage('font1.png'),
+ ]).then(([
+ fontXML,
+ fontImage,
+ ]) =>
+ {
+ this.fontXML = fontXML;
+ this.fontImage = fontImage;
+ const texture = new Texture(new BaseTexture(this.fontImage, null, 1));
+
+ this.font = BitmapText.registerFont(this.fontXML, texture);
+ done();
+ });
+ });
+
+ describe('text', function ()
+ {
+ it('should render text even if there are unsupported characters', function ()
+ {
+ const text = new BitmapText('ABCDEFG', {
+ font: this.font.font,
+ });
+
+ expect(text.children.length).to.equal(4);
+ });
+ it('should break line on space', function ()
+ {
+ const bmpText = new 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 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;
+ }
+ }
+ });
+ });
+});
diff --git a/packages/text-bitmap/test/index.js b/packages/text-bitmap/test/index.js
index 688fee2..6fab55e 100644
--- a/packages/text-bitmap/test/index.js
+++ b/packages/text-bitmap/test/index.js
@@ -1 +1,2 @@
+require('./BitmapText');
require('./BitmapFontLoader');
diff --git a/packages/text-bitmap/test/resources/font1.fnt b/packages/text-bitmap/test/resources/font1.fnt
new file mode 100644
index 0000000..74496bb
--- /dev/null
+++ b/packages/text-bitmap/test/resources/font1.fnt
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/text-bitmap/test/resources/font1.png b/packages/text-bitmap/test/resources/font1.png
new file mode 100644
index 0000000..d30d645
--- /dev/null
+++ b/packages/text-bitmap/test/resources/font1.png
Binary files differ