diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 0b4d5fe..470d54b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -125,10 +125,7 @@ return; } - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - this._font = `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + this._font = Text.getFontStyle(style); this.context.font = this._font; @@ -142,7 +139,7 @@ // calculate text width const lineWidths = new Array(lines.length); let maxLineWidth = 0; - const fontProperties = this.determineFontProperties(this._font); + const fontProperties = Text.calculateFontProperties(this._font); for (let i = 0; i < lines.length; i++) { @@ -394,109 +391,6 @@ } /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @private - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - determineFontProperties(fontStyle) - { - let properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; - } - - /** * Applies newlines to a string to have it optimally fit into the horizontal * bounds set by the Text object's wordWrapWidth property. * @@ -819,6 +713,133 @@ this._text = text; this.dirty = true; } + + /** + * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter + * as Text.style. + * + * @static + * @param {object|TextStyle} style - String representing the style of the font + * @return {string} Font style string, for passing to Text.calculateFontProperties() + */ + static getFontStyle(style) + { + style = style || {}; + + if (!(style instanceof TextStyle)) + { + style = new TextStyle(style); + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; + + return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @static + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ + static calculateFontProperties(fontStyle) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (Text.fontPropertiesCache[fontStyle]) + { + return Text.fontPropertiesCache[fontStyle]; + } + + const properties = {}; + + const canvas = Text.fontPropertiesCanvas; + const context = Text.fontPropertiesContext; + + context.font = fontStyle; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + + return properties; + } } Text.fontPropertiesCache = {}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 0b4d5fe..470d54b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -125,10 +125,7 @@ return; } - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - this._font = `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + this._font = Text.getFontStyle(style); this.context.font = this._font; @@ -142,7 +139,7 @@ // calculate text width const lineWidths = new Array(lines.length); let maxLineWidth = 0; - const fontProperties = this.determineFontProperties(this._font); + const fontProperties = Text.calculateFontProperties(this._font); for (let i = 0; i < lines.length; i++) { @@ -394,109 +391,6 @@ } /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @private - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - determineFontProperties(fontStyle) - { - let properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; - } - - /** * Applies newlines to a string to have it optimally fit into the horizontal * bounds set by the Text object's wordWrapWidth property. * @@ -819,6 +713,133 @@ this._text = text; this.dirty = true; } + + /** + * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter + * as Text.style. + * + * @static + * @param {object|TextStyle} style - String representing the style of the font + * @return {string} Font style string, for passing to Text.calculateFontProperties() + */ + static getFontStyle(style) + { + style = style || {}; + + if (!(style instanceof TextStyle)) + { + style = new TextStyle(style); + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; + + return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @static + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ + static calculateFontProperties(fontStyle) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (Text.fontPropertiesCache[fontStyle]) + { + return Text.fontPropertiesCache[fontStyle]; + } + + const properties = {}; + + const canvas = Text.fontPropertiesCanvas; + const context = Text.fontPropertiesContext; + + context.font = fontStyle; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + + return properties; + } } Text.fontPropertiesCache = {}; diff --git a/src/deprecation.js b/src/deprecation.js index 56e45f4..3b17029 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -451,6 +451,19 @@ warn('setStyle is now deprecated, please use the style property, e.g : myText.style = style;'); }; +/** + * @method + * @name PIXI.Text#determineFontProperties + * @see PIXI.Text#calculateFontProperties + * @private + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) +{ + return Text.calculateFontProperties(fontStyle); +}; + Object.defineProperties(core.TextStyle.prototype, { /** * Set all properties of a font as a single string diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 0b4d5fe..470d54b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -125,10 +125,7 @@ return; } - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - this._font = `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + this._font = Text.getFontStyle(style); this.context.font = this._font; @@ -142,7 +139,7 @@ // calculate text width const lineWidths = new Array(lines.length); let maxLineWidth = 0; - const fontProperties = this.determineFontProperties(this._font); + const fontProperties = Text.calculateFontProperties(this._font); for (let i = 0; i < lines.length; i++) { @@ -394,109 +391,6 @@ } /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @private - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - determineFontProperties(fontStyle) - { - let properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; - } - - /** * Applies newlines to a string to have it optimally fit into the horizontal * bounds set by the Text object's wordWrapWidth property. * @@ -819,6 +713,133 @@ this._text = text; this.dirty = true; } + + /** + * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter + * as Text.style. + * + * @static + * @param {object|TextStyle} style - String representing the style of the font + * @return {string} Font style string, for passing to Text.calculateFontProperties() + */ + static getFontStyle(style) + { + style = style || {}; + + if (!(style instanceof TextStyle)) + { + style = new TextStyle(style); + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; + + return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @static + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ + static calculateFontProperties(fontStyle) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (Text.fontPropertiesCache[fontStyle]) + { + return Text.fontPropertiesCache[fontStyle]; + } + + const properties = {}; + + const canvas = Text.fontPropertiesCanvas; + const context = Text.fontPropertiesContext; + + context.font = fontStyle; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + + return properties; + } } Text.fontPropertiesCache = {}; diff --git a/src/deprecation.js b/src/deprecation.js index 56e45f4..3b17029 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -451,6 +451,19 @@ warn('setStyle is now deprecated, please use the style property, e.g : myText.style = style;'); }; +/** + * @method + * @name PIXI.Text#determineFontProperties + * @see PIXI.Text#calculateFontProperties + * @private + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) +{ + return Text.calculateFontProperties(fontStyle); +}; + Object.defineProperties(core.TextStyle.prototype, { /** * Set all properties of a font as a single string diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index bdfcdc5..c6168dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -90,6 +90,9 @@ } this.prepareItems(); }; + + this.register(findText, drawText); + this.register(findTextStyle, calculateTextStyle); } /** @@ -272,3 +275,113 @@ } } + +/** + * Built-in hook to draw PIXI.Text to its texture. + * + * @private + * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler + * @param {PIXI.DisplayObject} item - Item to check + * @return {boolean} If item was uploaded. + */ +function drawText(helper, item) +{ + if (item instanceof core.Text) + { + // updating text will return early if it is not dirty, but we won't know + // so a non-dirty Text will count against the max number of items per frame. + item.updateText(true); + + return true; + } + + return false; +} + +/** + * Built-in hook to calculate a text style for a PIXI.Text object. + * + * @private + * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler + * @param {PIXI.DisplayObject} item - Item to check + * @return {boolean} If item was uploaded. + */ +function calculateTextStyle(helper, item) +{ + if (item instanceof core.TextStyle) + { + const font = core.Text.getFontStyle(item); + + if (!core.Text.fontPropertiesCache[font]) + { + core.Text.calculateTextStyle(font); + + return true; + } + // return false here - if it was already prepared it shouldn't count against the maximum + // number of uploads per frame + + return false; + } + + return false; +} + +/** + * Built-in hook to find Text objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Text object was found. + */ +function findText(item, queue) +{ + if (item instanceof core.Text) + { + // push the text style to prepare it - this can be really expensive + if (queue.indexOf(item.style) === -1) + { + queue.push(item.style); + } + // also push the text object so that we can render it (to canvas/texture) if needed + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + // also push the Text's texture for upload to GPU + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find TextStyle objects. + * + * @private + * @param {PIXI.TextStyle} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.TextStyle object was found. + */ +function findTextStyle(item, queue) +{ + if (item instanceof core.TextStyle) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 0b4d5fe..470d54b 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -125,10 +125,7 @@ return; } - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - this._font = `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + this._font = Text.getFontStyle(style); this.context.font = this._font; @@ -142,7 +139,7 @@ // calculate text width const lineWidths = new Array(lines.length); let maxLineWidth = 0; - const fontProperties = this.determineFontProperties(this._font); + const fontProperties = Text.calculateFontProperties(this._font); for (let i = 0; i < lines.length; i++) { @@ -394,109 +391,6 @@ } /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @private - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - determineFontProperties(fontStyle) - { - let properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; - } - - /** * Applies newlines to a string to have it optimally fit into the horizontal * bounds set by the Text object's wordWrapWidth property. * @@ -819,6 +713,133 @@ this._text = text; this.dirty = true; } + + /** + * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter + * as Text.style. + * + * @static + * @param {object|TextStyle} style - String representing the style of the font + * @return {string} Font style string, for passing to Text.calculateFontProperties() + */ + static getFontStyle(style) + { + style = style || {}; + + if (!(style instanceof TextStyle)) + { + style = new TextStyle(style); + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; + + return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${style.fontFamily}`; + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @static + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ + static calculateFontProperties(fontStyle) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (Text.fontPropertiesCache[fontStyle]) + { + return Text.fontPropertiesCache[fontStyle]; + } + + const properties = {}; + + const canvas = Text.fontPropertiesCanvas; + const context = Text.fontPropertiesContext; + + context.font = fontStyle; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + + return properties; + } } Text.fontPropertiesCache = {}; diff --git a/src/deprecation.js b/src/deprecation.js index 56e45f4..3b17029 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -451,6 +451,19 @@ warn('setStyle is now deprecated, please use the style property, e.g : myText.style = style;'); }; +/** + * @method + * @name PIXI.Text#determineFontProperties + * @see PIXI.Text#calculateFontProperties + * @private + * @param {string} fontStyle - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) +{ + return Text.calculateFontProperties(fontStyle); +}; + Object.defineProperties(core.TextStyle.prototype, { /** * Set all properties of a font as a single string diff --git a/src/prepare/BasePrepare.js b/src/prepare/BasePrepare.js index bdfcdc5..c6168dc 100644 --- a/src/prepare/BasePrepare.js +++ b/src/prepare/BasePrepare.js @@ -90,6 +90,9 @@ } this.prepareItems(); }; + + this.register(findText, drawText); + this.register(findTextStyle, calculateTextStyle); } /** @@ -272,3 +275,113 @@ } } + +/** + * Built-in hook to draw PIXI.Text to its texture. + * + * @private + * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler + * @param {PIXI.DisplayObject} item - Item to check + * @return {boolean} If item was uploaded. + */ +function drawText(helper, item) +{ + if (item instanceof core.Text) + { + // updating text will return early if it is not dirty, but we won't know + // so a non-dirty Text will count against the max number of items per frame. + item.updateText(true); + + return true; + } + + return false; +} + +/** + * Built-in hook to calculate a text style for a PIXI.Text object. + * + * @private + * @param {PIXI.WebGLRenderer|PIXI.CanvasPrepare} helper - Not used by this upload handler + * @param {PIXI.DisplayObject} item - Item to check + * @return {boolean} If item was uploaded. + */ +function calculateTextStyle(helper, item) +{ + if (item instanceof core.TextStyle) + { + const font = core.Text.getFontStyle(item); + + if (!core.Text.fontPropertiesCache[font]) + { + core.Text.calculateTextStyle(font); + + return true; + } + // return false here - if it was already prepared it shouldn't count against the maximum + // number of uploads per frame + + return false; + } + + return false; +} + +/** + * Built-in hook to find Text objects. + * + * @private + * @param {PIXI.DisplayObject} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.Text object was found. + */ +function findText(item, queue) +{ + if (item instanceof core.Text) + { + // push the text style to prepare it - this can be really expensive + if (queue.indexOf(item.style) === -1) + { + queue.push(item.style); + } + // also push the text object so that we can render it (to canvas/texture) if needed + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + // also push the Text's texture for upload to GPU + const texture = item._texture.baseTexture; + + if (queue.indexOf(texture) === -1) + { + queue.push(texture); + } + + return true; + } + + return false; +} + +/** + * Built-in hook to find TextStyle objects. + * + * @private + * @param {PIXI.TextStyle} item - Display object to check + * @param {Array<*>} queue - Collection of items to upload + * @return {boolean} if a PIXI.TextStyle object was found. + */ +function findTextStyle(item, queue) +{ + if (item instanceof core.TextStyle) + { + if (queue.indexOf(item) === -1) + { + queue.push(item); + } + + return true; + } + + return false; +} diff --git a/test/prepare/BasePrepare.js b/test/prepare/BasePrepare.js index 769ce4e..03cf2fa 100644 --- a/test/prepare/BasePrepare.js +++ b/test/prepare/BasePrepare.js @@ -10,8 +10,8 @@ expect(prep.renderer).to.equal(renderer); expect(prep.uploadHookHelper).to.be.null; expect(prep.queue).to.be.empty; - expect(prep.addHooks).to.be.empty; - expect(prep.uploadHooks).to.be.empty; + expect(prep.addHooks).to.have.lengthOf(2); + expect(prep.uploadHooks).to.have.lengthOf(2); expect(prep.completes).to.be.empty; prep.destroy(); @@ -26,9 +26,9 @@ prep.register(addHook, uploadHook); expect(prep.addHooks).to.contain(addHook); - expect(prep.addHooks).to.have.lengthOf(1); + expect(prep.addHooks).to.have.lengthOf(3); expect(prep.uploadHooks).to.contain(uploadHook); - expect(prep.uploadHooks).to.have.lengthOf(1); + expect(prep.uploadHooks).to.have.lengthOf(3); prep.destroy(); });