diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js index 904f4e6..99748f5 100644 --- a/src/core/text/TextMetrics.js +++ b/src/core/text/TextMetrics.js @@ -1,6 +1,11 @@ /** * The TextMetrics object represents the measurement of a block of text with a specified style. * + * ```js + * let style = new PIXI.TextStyle({fontFamily : 'Arial', fontSize: 24, fill : 0xff1010, align : 'center'}) + * let textMetrics = PIXI.TextMetrics.measureText('Your text', style) + * ``` + * * @class * @memberOf PIXI */ @@ -103,86 +108,151 @@ { const context = canvas.getContext('2d'); - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const firstChar = text.charAt(0); - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - const characterCache = {}; + let line = ''; + let width = 0; + let lines = ''; + const cache = {}; + const ls = style.letterSpacing; - for (let i = 0; i < lines.length; i++) + // ideally there is letterSpacing after every char except the last one + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_! + // so for convenience the above needs to be compared to width + 1 extra space + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_ + // ________________________________________________ + // And then the final space is simply no appended to each line + const wordWrapWidth = style.wordWrapWidth + style.letterSpacing; + + // get the width of a space and add it to cache + const spaceWidth = TextMetrics.getFromCache(' ', ls, cache, context); + + // break text into words + const words = text.split(' '); + + for (let i = 0; i < words.length; i++) { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); + const word = words[i]; - for (let j = 0; j < words.length; j++) + // get word width from cache if possible + const wordWidth = TextMetrics.getFromCache(word, ls, cache, context); + + // word is longer than desired bounds + if (wordWidth > wordWrapWidth) { - const wordWidth = context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) + // break large word over multiple lines + if (style.breakWords) { - // Word should be split in the middle - const characters = words[j].split(''); + // add a space to the start of the word unless its at the beginning of the line + const tmpWord = (line.length > 0) ? ` ${word}` : word; - for (let c = 0; c < characters.length; c++) + // break word into characters + const characters = tmpWord.split(''); + + // loop the characters + for (let j = 0; j < characters.length; j++) { - const character = characters[c]; - let characterWidth = characterCache[character]; + const character = characters[j]; + const characterWidth = TextMetrics.getFromCache(character, ls, cache, context); - if (characterWidth === undefined) + if (characterWidth + width > wordWrapWidth) { - characterWidth = context.measureText(character).width; - characterCache[character] = characterWidth; + lines += TextMetrics.addLine(line); + line = ''; + width = 0; } - if (characterWidth > spaceLeft) - { - result += `\n${character}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0 && (j > 0 || firstChar === ' ')) - { - result += ' '; - } - - result += character; - spaceLeft -= characterWidth; - } + line += character; + width += characterWidth; } } + + // run word out of the bounds + else + { + // if there are words in this line already + // finish that line and start a new one + if (line.length > 0) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // give it its own line + lines += TextMetrics.addLine(word); + line = ''; + width = 0; + } + } + + // word could fit + else + { + // word won't fit, start a new line + if (wordWidth + width > wordWrapWidth) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // add the word to the current line + if (line.length > 0) + { + // add a space if it is not the beginning + line += ` ${word}`; + } else { - const wordWidthWithSpace = wordWidth + context.measureText(' ').width; - - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ` ${words[j]}`; - } + // add without a space if it is the beginning + line += word; } - } - if (i < lines.length - 1) - { - result += '\n'; + width += wordWidth + spaceWidth; } } - return result; + lines += TextMetrics.addLine(line, false); + + return lines; + } + + /** + * Convienience function for logging each line added + * during the wordWrap method + * + * @param {string} line - The line of text to add + * @param {boolean} newLine - Add new line character to end + * @return {string} A formatted line + */ + static addLine(line, newLine = true) + { + line = (newLine) ? `${line}\n` : line; + + return line; + } + + /** + * Gets & sets the widths of calculated characters in a cache object + * + * @param {string} key The key + * @param {number} letterSpacing The letter spacing + * @param {object} cache The cache + * @param {CanvasRenderingContext2D} context The canvas context + * @return {number} The from cache. + */ + static getFromCache(key, letterSpacing, cache, context) + { + let width = cache[key]; + + if (width === undefined) + { + const spacing = ((key.length) * letterSpacing); + + width = context.measureText(key).width + spacing; + cache[key] = width; + } + + return width; } /** diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js index 904f4e6..99748f5 100644 --- a/src/core/text/TextMetrics.js +++ b/src/core/text/TextMetrics.js @@ -1,6 +1,11 @@ /** * The TextMetrics object represents the measurement of a block of text with a specified style. * + * ```js + * let style = new PIXI.TextStyle({fontFamily : 'Arial', fontSize: 24, fill : 0xff1010, align : 'center'}) + * let textMetrics = PIXI.TextMetrics.measureText('Your text', style) + * ``` + * * @class * @memberOf PIXI */ @@ -103,86 +108,151 @@ { const context = canvas.getContext('2d'); - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const firstChar = text.charAt(0); - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - const characterCache = {}; + let line = ''; + let width = 0; + let lines = ''; + const cache = {}; + const ls = style.letterSpacing; - for (let i = 0; i < lines.length; i++) + // ideally there is letterSpacing after every char except the last one + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_! + // so for convenience the above needs to be compared to width + 1 extra space + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_ + // ________________________________________________ + // And then the final space is simply no appended to each line + const wordWrapWidth = style.wordWrapWidth + style.letterSpacing; + + // get the width of a space and add it to cache + const spaceWidth = TextMetrics.getFromCache(' ', ls, cache, context); + + // break text into words + const words = text.split(' '); + + for (let i = 0; i < words.length; i++) { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); + const word = words[i]; - for (let j = 0; j < words.length; j++) + // get word width from cache if possible + const wordWidth = TextMetrics.getFromCache(word, ls, cache, context); + + // word is longer than desired bounds + if (wordWidth > wordWrapWidth) { - const wordWidth = context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) + // break large word over multiple lines + if (style.breakWords) { - // Word should be split in the middle - const characters = words[j].split(''); + // add a space to the start of the word unless its at the beginning of the line + const tmpWord = (line.length > 0) ? ` ${word}` : word; - for (let c = 0; c < characters.length; c++) + // break word into characters + const characters = tmpWord.split(''); + + // loop the characters + for (let j = 0; j < characters.length; j++) { - const character = characters[c]; - let characterWidth = characterCache[character]; + const character = characters[j]; + const characterWidth = TextMetrics.getFromCache(character, ls, cache, context); - if (characterWidth === undefined) + if (characterWidth + width > wordWrapWidth) { - characterWidth = context.measureText(character).width; - characterCache[character] = characterWidth; + lines += TextMetrics.addLine(line); + line = ''; + width = 0; } - if (characterWidth > spaceLeft) - { - result += `\n${character}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0 && (j > 0 || firstChar === ' ')) - { - result += ' '; - } - - result += character; - spaceLeft -= characterWidth; - } + line += character; + width += characterWidth; } } + + // run word out of the bounds + else + { + // if there are words in this line already + // finish that line and start a new one + if (line.length > 0) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // give it its own line + lines += TextMetrics.addLine(word); + line = ''; + width = 0; + } + } + + // word could fit + else + { + // word won't fit, start a new line + if (wordWidth + width > wordWrapWidth) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // add the word to the current line + if (line.length > 0) + { + // add a space if it is not the beginning + line += ` ${word}`; + } else { - const wordWidthWithSpace = wordWidth + context.measureText(' ').width; - - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ` ${words[j]}`; - } + // add without a space if it is the beginning + line += word; } - } - if (i < lines.length - 1) - { - result += '\n'; + width += wordWidth + spaceWidth; } } - return result; + lines += TextMetrics.addLine(line, false); + + return lines; + } + + /** + * Convienience function for logging each line added + * during the wordWrap method + * + * @param {string} line - The line of text to add + * @param {boolean} newLine - Add new line character to end + * @return {string} A formatted line + */ + static addLine(line, newLine = true) + { + line = (newLine) ? `${line}\n` : line; + + return line; + } + + /** + * Gets & sets the widths of calculated characters in a cache object + * + * @param {string} key The key + * @param {number} letterSpacing The letter spacing + * @param {object} cache The cache + * @param {CanvasRenderingContext2D} context The canvas context + * @return {number} The from cache. + */ + static getFromCache(key, letterSpacing, cache, context) + { + let width = cache[key]; + + if (width === undefined) + { + const spacing = ((key.length) * letterSpacing); + + width = context.measureText(key).width + spacing; + cache[key] = width; + } + + return width; } /** diff --git a/test/core/TextMetrics.js b/test/core/TextMetrics.js new file mode 100644 index 0000000..4ed7e27 --- /dev/null +++ b/test/core/TextMetrics.js @@ -0,0 +1,152 @@ +'use strict'; + +/* eslint-disable no-multi-str */ +const longText = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem \ +accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo \ +inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo \ +enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia \ +consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro \ +quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, \ +sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam \ +quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam \ +corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis \ +autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil \ +molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla \ +pariatur?'; + +const breakingWordText = 'Pixi.js - The HTML5 Creation Engine. Create beautiful \ +digital content with the supercalifragilisticexpialidociously fastest, most \ +flexible 2D WebGL renderer.'; + +const fillText = '. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\ +. . . . . . . . . . . . . . . . . . . . . . . . '; + +const intergityText = '012345678901234567890123456789'; + +describe('PIXI.TextMetrics', function () +{ + const defaultStyle = { + breakWords: true, + fontFamily: 'Arial', + fontSize: 20, + fontStyle: 'italic', + fontVariant: 'normal', + fontWeight: 900, + wordWrap: true, + wordWrapWidth: 200, + letterSpacing: 4, + }; + + describe('wordWrap without breakWords', function () + { + it('width should not be greater than wordWrapWidth with longText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(longText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be greater than wordWrapWidth with breakingWordText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(breakingWordText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be within a character width from wordWrapWidth with fillText', function () + { + const charWidth = 4; // it should fill the line to at lease width -4 + + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(fillText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + expect(metrics.width + charWidth).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + }); + + describe('wordWrap with breakWords', function () + { + it('width should not be greater than wordWrapWidth with longText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(longText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should not be greater than wordWrapWidth with breakingWordAtStartText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(breakingWordText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be within a character width from wordWrapWidth with fillText', function () + { + const charWidth = 4; // it should fill the line to at lease width -4 + + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(fillText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + expect(metrics.width + charWidth).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('no words or characters should lost or changed', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(intergityText, new PIXI.TextStyle(style)); + + const lines = metrics.lines.reduce((accumulator, line) => accumulator + line); + + expect(lines).to.equal(intergityText, 'should have the same chars as the original text'); + }); + }); +}); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js index 904f4e6..99748f5 100644 --- a/src/core/text/TextMetrics.js +++ b/src/core/text/TextMetrics.js @@ -1,6 +1,11 @@ /** * The TextMetrics object represents the measurement of a block of text with a specified style. * + * ```js + * let style = new PIXI.TextStyle({fontFamily : 'Arial', fontSize: 24, fill : 0xff1010, align : 'center'}) + * let textMetrics = PIXI.TextMetrics.measureText('Your text', style) + * ``` + * * @class * @memberOf PIXI */ @@ -103,86 +108,151 @@ { const context = canvas.getContext('2d'); - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const firstChar = text.charAt(0); - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - const characterCache = {}; + let line = ''; + let width = 0; + let lines = ''; + const cache = {}; + const ls = style.letterSpacing; - for (let i = 0; i < lines.length; i++) + // ideally there is letterSpacing after every char except the last one + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_! + // so for convenience the above needs to be compared to width + 1 extra space + // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_ + // ________________________________________________ + // And then the final space is simply no appended to each line + const wordWrapWidth = style.wordWrapWidth + style.letterSpacing; + + // get the width of a space and add it to cache + const spaceWidth = TextMetrics.getFromCache(' ', ls, cache, context); + + // break text into words + const words = text.split(' '); + + for (let i = 0; i < words.length; i++) { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); + const word = words[i]; - for (let j = 0; j < words.length; j++) + // get word width from cache if possible + const wordWidth = TextMetrics.getFromCache(word, ls, cache, context); + + // word is longer than desired bounds + if (wordWidth > wordWrapWidth) { - const wordWidth = context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) + // break large word over multiple lines + if (style.breakWords) { - // Word should be split in the middle - const characters = words[j].split(''); + // add a space to the start of the word unless its at the beginning of the line + const tmpWord = (line.length > 0) ? ` ${word}` : word; - for (let c = 0; c < characters.length; c++) + // break word into characters + const characters = tmpWord.split(''); + + // loop the characters + for (let j = 0; j < characters.length; j++) { - const character = characters[c]; - let characterWidth = characterCache[character]; + const character = characters[j]; + const characterWidth = TextMetrics.getFromCache(character, ls, cache, context); - if (characterWidth === undefined) + if (characterWidth + width > wordWrapWidth) { - characterWidth = context.measureText(character).width; - characterCache[character] = characterWidth; + lines += TextMetrics.addLine(line); + line = ''; + width = 0; } - if (characterWidth > spaceLeft) - { - result += `\n${character}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0 && (j > 0 || firstChar === ' ')) - { - result += ' '; - } - - result += character; - spaceLeft -= characterWidth; - } + line += character; + width += characterWidth; } } + + // run word out of the bounds + else + { + // if there are words in this line already + // finish that line and start a new one + if (line.length > 0) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // give it its own line + lines += TextMetrics.addLine(word); + line = ''; + width = 0; + } + } + + // word could fit + else + { + // word won't fit, start a new line + if (wordWidth + width > wordWrapWidth) + { + lines += TextMetrics.addLine(line); + line = ''; + width = 0; + } + + // add the word to the current line + if (line.length > 0) + { + // add a space if it is not the beginning + line += ` ${word}`; + } else { - const wordWidthWithSpace = wordWidth + context.measureText(' ').width; - - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ` ${words[j]}`; - } + // add without a space if it is the beginning + line += word; } - } - if (i < lines.length - 1) - { - result += '\n'; + width += wordWidth + spaceWidth; } } - return result; + lines += TextMetrics.addLine(line, false); + + return lines; + } + + /** + * Convienience function for logging each line added + * during the wordWrap method + * + * @param {string} line - The line of text to add + * @param {boolean} newLine - Add new line character to end + * @return {string} A formatted line + */ + static addLine(line, newLine = true) + { + line = (newLine) ? `${line}\n` : line; + + return line; + } + + /** + * Gets & sets the widths of calculated characters in a cache object + * + * @param {string} key The key + * @param {number} letterSpacing The letter spacing + * @param {object} cache The cache + * @param {CanvasRenderingContext2D} context The canvas context + * @return {number} The from cache. + */ + static getFromCache(key, letterSpacing, cache, context) + { + let width = cache[key]; + + if (width === undefined) + { + const spacing = ((key.length) * letterSpacing); + + width = context.measureText(key).width + spacing; + cache[key] = width; + } + + return width; } /** diff --git a/test/core/TextMetrics.js b/test/core/TextMetrics.js new file mode 100644 index 0000000..4ed7e27 --- /dev/null +++ b/test/core/TextMetrics.js @@ -0,0 +1,152 @@ +'use strict'; + +/* eslint-disable no-multi-str */ +const longText = 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem \ +accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo \ +inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo \ +enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia \ +consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro \ +quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, \ +sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam \ +quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam \ +corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis \ +autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil \ +molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla \ +pariatur?'; + +const breakingWordText = 'Pixi.js - The HTML5 Creation Engine. Create beautiful \ +digital content with the supercalifragilisticexpialidociously fastest, most \ +flexible 2D WebGL renderer.'; + +const fillText = '. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\ +. . . . . . . . . . . . . . . . . . . . . . . . '; + +const intergityText = '012345678901234567890123456789'; + +describe('PIXI.TextMetrics', function () +{ + const defaultStyle = { + breakWords: true, + fontFamily: 'Arial', + fontSize: 20, + fontStyle: 'italic', + fontVariant: 'normal', + fontWeight: 900, + wordWrap: true, + wordWrapWidth: 200, + letterSpacing: 4, + }; + + describe('wordWrap without breakWords', function () + { + it('width should not be greater than wordWrapWidth with longText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(longText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be greater than wordWrapWidth with breakingWordText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(breakingWordText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be within a character width from wordWrapWidth with fillText', function () + { + const charWidth = 4; // it should fill the line to at lease width -4 + + const style = Object.assign({}, defaultStyle, { breakWords: false }); + + const metrics = PIXI.TextMetrics.measureText(fillText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + expect(metrics.width + charWidth).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + }); + + describe('wordWrap with breakWords', function () + { + it('width should not be greater than wordWrapWidth with longText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(longText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should not be greater than wordWrapWidth with breakingWordAtStartText', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(breakingWordText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('width should be within a character width from wordWrapWidth with fillText', function () + { + const charWidth = 4; // it should fill the line to at lease width -4 + + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(fillText, new PIXI.TextStyle(style)); + + expect(metrics.width).to.be.below(style.wordWrapWidth); + expect(metrics.width + charWidth).to.be.above(style.wordWrapWidth); + + metrics.lines.forEach((line) => + { + expect(line[0]).to.not.equal(' ', 'should not have space at the start'); + expect(line[line - 1]).to.not.equal(' ', 'should not have space at the end'); + }); + }); + + it('no words or characters should lost or changed', function () + { + const style = Object.assign({}, defaultStyle, { breakWords: true }); + + const metrics = PIXI.TextMetrics.measureText(intergityText, new PIXI.TextStyle(style)); + + const lines = metrics.lines.reduce((accumulator, line) => accumulator + line); + + expect(lines).to.equal(intergityText, 'should have the same chars as the original text'); + }); + }); +}); diff --git a/test/core/index.js b/test/core/index.js index 2138e7b..65e0a58 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -10,6 +10,7 @@ require('./Spritesheet'); require('./TilingSprite'); require('./TextStyle'); +require('./TextMetrics'); require('./Text'); require('./toGlobal'); require('./toLocal');