diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 718466c..a66c14d 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -16,7 +16,7 @@ * * @class * @private - * @memberof PIXI + * @memberof PIXI.mesh * @extends ObjectRenderer * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 718466c..a66c14d 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -16,7 +16,7 @@ * * @class * @private - * @memberof PIXI + * @memberof PIXI.mesh * @extends ObjectRenderer * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index af2f4d7..20c61c6 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -3,7 +3,7 @@ /** * @class * @extends Shader - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param shaderManager {ShaderManager} The WebGL shader manager this shader works for. */ function StripShader(shaderManager) diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 718466c..a66c14d 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -16,7 +16,7 @@ * * @class * @private - * @memberof PIXI + * @memberof PIXI.mesh * @extends ObjectRenderer * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index af2f4d7..20c61c6 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -3,7 +3,7 @@ /** * @class * @extends Shader - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param shaderManager {ShaderManager} The WebGL shader manager this shader works for. */ function StripShader(shaderManager) diff --git a/src/text/BitmapText.js b/src/text/BitmapText.js deleted file mode 100644 index d825427..0000000 --- a/src/text/BitmapText.js +++ /dev/null @@ -1,346 +0,0 @@ -var core = require('../core'); - -/** - * A BitmapText object will create a line or multiple lines of text using bitmap font. To - * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: - * - * A BitmapText can only be created when the font is loaded - * - * ```js - * // in this case the font is in a file called 'desyrel.fnt' - * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); - * ``` - * - * - * http://www.angelcode.com/products/bmfont/ for windows or - * http://www.bmglyph.com/ for mac. - * - * @class - * @extends Container - * @memberof PIXI.text - * @param text {string} The copy that you would like the text to display - * @param style {object} The style parameters - * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form - * "24px FontName" or "FontName" or as an object with explicit name/size properties. - * @param [style.font.name] {string} The bitmap font id - * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect - * single line text - * @param [style.tint=0xFFFFFF] {number} The tint color - */ -function BitmapText(text, style) -{ - core.Container.call(this); - - /** - * The width of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {Sprite[]} - * @private - */ - this._glyphs = []; - - /** - * Private tracker for the current style. - * - * @member {object} - * @private - */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; - - /** - * Private tracker for the current font. - * - * @member {object} - * @private - */ - this.font = style.font; // run font setter - - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; - - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); -} - -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; -module.exports = BitmapText; - -Object.defineProperties(BitmapText.prototype, { - /** - * The tint of the BitmapText object - * - * @member {number} - * @memberof BitmapText# - */ - tint: { - get: function () - { - return this._font.tint; - }, - set: function (value) - { - this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; - - this.dirty = true; - } - }, - - /** - * The alignment of the BitmapText object - * - * @member {string} - * @default 'left' - * @memberof BitmapText# - */ - align: { - get: function () - { - return this._font.align; - }, - set: function (value) - { - this._font.align = value; - - this.dirty = true; - } - }, - - /** - * The font descriptor of the BitmapText object - * - * @member {Font} - * @memberof BitmapText# - */ - font: { - get: function () - { - return this._font; - }, - set: function (value) - { - if (typeof value === 'string') { - value = value.split(' '); - - this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); - this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; - } - else { - this._font.name = value.name; - this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); - } - - this.dirty = true; - } - }, - - /** - * The text of the BitmapText object - * - * @member {string} - * @memberof BitmapText# - */ - text: { - get: function () - { - return this._text; - }, - set: function (value) - { - this._text = value; - - this.dirty = true; - } - } -}); - -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.math.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - 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) - { - chars.splice(lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } - - this.containerUpdateTransform(); -}; - -BitmapText.fonts = {}; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 718466c..a66c14d 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -16,7 +16,7 @@ * * @class * @private - * @memberof PIXI + * @memberof PIXI.mesh * @extends ObjectRenderer * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index af2f4d7..20c61c6 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -3,7 +3,7 @@ /** * @class * @extends Shader - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param shaderManager {ShaderManager} The WebGL shader manager this shader works for. */ function StripShader(shaderManager) diff --git a/src/text/BitmapText.js b/src/text/BitmapText.js deleted file mode 100644 index d825427..0000000 --- a/src/text/BitmapText.js +++ /dev/null @@ -1,346 +0,0 @@ -var core = require('../core'); - -/** - * A BitmapText object will create a line or multiple lines of text using bitmap font. To - * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: - * - * A BitmapText can only be created when the font is loaded - * - * ```js - * // in this case the font is in a file called 'desyrel.fnt' - * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); - * ``` - * - * - * http://www.angelcode.com/products/bmfont/ for windows or - * http://www.bmglyph.com/ for mac. - * - * @class - * @extends Container - * @memberof PIXI.text - * @param text {string} The copy that you would like the text to display - * @param style {object} The style parameters - * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form - * "24px FontName" or "FontName" or as an object with explicit name/size properties. - * @param [style.font.name] {string} The bitmap font id - * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect - * single line text - * @param [style.tint=0xFFFFFF] {number} The tint color - */ -function BitmapText(text, style) -{ - core.Container.call(this); - - /** - * The width of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {Sprite[]} - * @private - */ - this._glyphs = []; - - /** - * Private tracker for the current style. - * - * @member {object} - * @private - */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; - - /** - * Private tracker for the current font. - * - * @member {object} - * @private - */ - this.font = style.font; // run font setter - - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; - - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); -} - -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; -module.exports = BitmapText; - -Object.defineProperties(BitmapText.prototype, { - /** - * The tint of the BitmapText object - * - * @member {number} - * @memberof BitmapText# - */ - tint: { - get: function () - { - return this._font.tint; - }, - set: function (value) - { - this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; - - this.dirty = true; - } - }, - - /** - * The alignment of the BitmapText object - * - * @member {string} - * @default 'left' - * @memberof BitmapText# - */ - align: { - get: function () - { - return this._font.align; - }, - set: function (value) - { - this._font.align = value; - - this.dirty = true; - } - }, - - /** - * The font descriptor of the BitmapText object - * - * @member {Font} - * @memberof BitmapText# - */ - font: { - get: function () - { - return this._font; - }, - set: function (value) - { - if (typeof value === 'string') { - value = value.split(' '); - - this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); - this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; - } - else { - this._font.name = value.name; - this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); - } - - this.dirty = true; - } - }, - - /** - * The text of the BitmapText object - * - * @member {string} - * @memberof BitmapText# - */ - text: { - get: function () - { - return this._text; - }, - set: function (value) - { - this._text = value; - - this.dirty = true; - } - } -}); - -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.math.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - 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) - { - chars.splice(lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } - - this.containerUpdateTransform(); -}; - -BitmapText.fonts = {}; diff --git a/src/text/Text.js b/src/text/Text.js deleted file mode 100644 index 9c0df93..0000000 --- a/src/text/Text.js +++ /dev/null @@ -1,591 +0,0 @@ -var core = require('../core'); - -/** - * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, - * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. - * - * A Text can be created directly from a string and a style object - * - * ```js - * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); - * ``` - * - * @class - * @extends Sprite - * @memberof PIXI.text - * @param text {string} The copy that you would like the text to display - * @param [style] {object} The style parameters - * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font - * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text - * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' - * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) - * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used - * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true - * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses - * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow - * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening - * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. - * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve - * spiked text issues. Default is 'miter' (creates a sharp corner). - * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce - * or increase the spikiness of rendered text. - */ -function Text(text, style, resolution) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution of the canvas. - * @member {number} - */ - this.resolution = resolution || core.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = null; - - /** - * Private tracker for the current style. - * - * @member {object} - * @private - */ - this._style = null; - - var texture = core.Texture.fromCanvas(this.canvas); - texture.trim = new core.math.Rectangle(); - core.Sprite.call(this, texture); - - - this.text = text; - this.style = style; -} - -// constructor -Text.prototype = Object.create(core.Sprite.prototype); -Text.prototype.constructor = Text; -module.exports = Text; - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); - -Object.defineProperties(Text.prototype, { - /** - * The width of the Text, setting this will actually modify the scale to achieve the value set - * - * @member {number} - * @memberof Text# - */ - width: { - get: function () - { - if (this.dirty) - { - this.updateText(); - } - - return this.scale.x * this._texture._frame.width; - }, - set: function (value) - { - this.scale.x = value / this._texture._frame.width; - this._width = value; - } - }, - - /** - * The height of the Text, setting this will actually modify the scale to achieve the value set - * - * @member {number} - * @memberof Text# - */ - height: { - get: function () - { - if (this.dirty) - { - this.updateText(); - } - - return this.scale.y * this._texture._frame.height; - }, - set: function (value) - { - this.scale.y = value / this._texture._frame.height; - this._height = value; - } - }, - - /** - * Set the style of the text - * - * @param [style] {object} The style parameters - * @param [style.font='bold 20pt Arial'] {string} The style and size of the font - * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text - * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' - * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) - * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used - * @param [style.wordWrapWidth=100] {number} The width at which text will wrap - * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses - * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow - * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening - * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. - * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve - * spiked text issues. Default is 'miter' (creates a sharp corner). - * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce - * or increase the spikiness of rendered text. - * @memberof Text# - */ - style: { - get: function () - { - return this._style; - }, - set: function (style) - { - style = style || {}; - style.font = style.font || 'bold 20pt Arial'; - style.fill = style.fill || 'black'; - style.align = style.align || 'left'; - style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 - style.strokeThickness = style.strokeThickness || 0; - style.wordWrap = style.wordWrap || false; - style.wordWrapWidth = style.wordWrapWidth || 100; - - style.dropShadow = style.dropShadow || false; - style.dropShadowColor = style.dropShadowColor || '#000000'; - style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; - style.dropShadowDistance = style.dropShadowDistance || 5; - - style.padding = style.padding || 0; - - style.textBaseline = style.textBaseline || 'alphabetic'; - - style.lineJoin = style.lineJoin || 'miter'; - style.miterLimit = style.miterLimit || 10; - - this._style = style; - this.dirty = true; - } - }, - - /** - * Set the copy for the text object. To split a line you can use '\n'. - * - * @param text {string} The copy that you would like the text to display - * @memberof Text# - */ - text: { - get: function() - { - return this._text; - }, - set: function (text){ - text = text.toString() || ' '; - if (this._text === text) - { - return; - } - this._text = text; - this.dirty = true; - } - } -}); - -/** - * Renders text and updates it when needed - * - * @private - */ -Text.prototype.updateText = function () -{ - var style = this._style; - this.context.font = style.font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(style.font); - for (var i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width; - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = lineHeight * lines.length; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - - //this.context.fillStyle="#FF0000"; - //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = style.font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - this.context.fillStyle = style.dropShadowColor; - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); - } - } - } - - //set canvas text styles - this.context.fillStyle = style.fill; - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); - } - - if (style.fill) - { - this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; - texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = 0; - texture.trim.y = -this._style.padding; - - texture.trim.width = texture._frame.width; - texture.trim.height = texture._frame.height - this._style.padding*2; - - this._width = this.canvas.width / this.resolution; - this._height = this.canvas.height / this.resolution; - - texture.update(); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Text.prototype.renderWebGL = function (renderer) -{ - if (this.dirty) - { - //this.resolution = 1//renderer.resolution; - - this.updateText(); - } - - core.Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if (this.dirty) - { - // this.resolution = 1//renderer.resolution; - - this.updateText(); - } - - core.Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {object} - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var 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); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (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 (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. - * - * @param text {string} - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - var wordWidthWithSpace = wordWidth + this.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]; - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the Text - * @return {Rectangle} the framing rectangle - */ -Text.prototype.getBounds = function (matrix) -{ - if (this.dirty) - { - this.updateText(); - } - - return core.Sprite.prototype.getBounds.call(this, matrix); -}; - -/** - * Destroys this text object. - * - * @param destroyBaseTexture {boolean} whether to destroy the base texture as well - */ -Text.prototype.destroy = function (destroyBaseTexture) -{ - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); -}; diff --git a/src/core/const.js b/src/core/const.js index f515d1a..fe5bd07 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -14,6 +14,27 @@ VERSION: require('../../package.json').version, /** + * @property {number} PI_2 - Two Pi + * @constant + * @static + */ + PI_2: Math.PI * 2, + + /** + * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees + * @constant + * @static + */ + RAD_TO_DEG: 180 / Math.PI, + + /** + * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians + * @constant + * @static + */ + DEG_TO_RAD: Math.PI / 180, + + /** * Constant to identify the Renderer Type. * * @static diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index e3115be..ccf2fa0 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -1,6 +1,7 @@ var math = require('../math'), RenderTexture = require('../textures/RenderTexture'), EventEmitter = require('eventemitter3').EventEmitter, + CONST = require('../const'), _tempMatrix = new math.Matrix(); /** @@ -283,7 +284,7 @@ var a, b, c, d, tx, ty; // so if rotation is between 0 then we can simplify the multiplication process... - if (this.rotation % math.PI_2) + if (this.rotation % CONST.PI_2) { // check to see if the rotation is the same as the previous render. This means we only need to use sin and cos when rotation actually changes if (this.rotation !== this.rotationCache) diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index e92eaef..44d8d34 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -13,51 +13,51 @@ */ function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) { - /* + /* * @member {number} the width of the line to draw */ this.lineWidth = lineWidth; - /* + /* * @member {number} the color of the line to draw */ this.lineColor = lineColor; - /* + /* * @member {number} the alpha of the line to draw */ this.lineAlpha = lineAlpha; - /* + /* * @member {number} cached tint of the line to draw */ this._lineTint = lineColor; - /* + /* * @member {number} the color of the fill */ this.fillColor = fillColor; - /* + /* * @member {number} the alpha of the fill */ this.fillAlpha = fillAlpha; - /* + /* * @member {number} cached tint of the fill */ this._fillTint = fillColor; - /* + /* * @member {boolean} whether or not the shape is filled with a colour */ this.fill = fill; - /* + /* * @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw. */ this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, */ this.type = shape.type; } diff --git a/src/core/index.js b/src/core/index.js index e935851..01af152 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -8,25 +8,24 @@ /** * @namespace PIXI */ -var core = { +// export core and const. We assign core to const so that the non-reference types in const remain in-tact +var core = module.exports = Object.assign(require('./const'), require('./math'), { // utils utils: require('./utils'), - math: require('./math'), - CONST: require('./const'), // display DisplayObject: require('./display/DisplayObject'), Container: require('./display/Container'), - // legacy.. - Stage: require('./display/Container'), - DisplayObjectContainer: require('./display/Container'), - + // sprites Sprite: require('./sprites/Sprite'), ParticleContainer: require('./particles/ParticleContainer'), SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), ParticleRenderer: require('./particles/webgl/ParticleRenderer'), + // text + Text: require('./text/Text'), + // primitives Graphics: require('./graphics/Graphics'), GraphicsData: require('./graphics/GraphicsData'), @@ -81,7 +80,4 @@ return new core.CanvasRenderer(width, height, options); } -}; - -// export core and const. We assign core to const so that the non-reference types in const remain in-tact -module.exports = Object.assign(require('./const'), core); +}); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4357e19..e8e74a7 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -8,7 +8,7 @@ * | 0 | 0 | 1 | * * @class - * @memberof PIXI.math + * @memberof PIXI */ function Matrix() { diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 8b0f9ae..7634ae8 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -3,7 +3,7 @@ * the horizontal axis and y represents the vertical axis. * * @class - * @memberof PIXI.math + * @memberof PIXI * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ diff --git a/src/core/math/index.js b/src/core/math/index.js index 0e7a562..22d9e87 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -1,28 +1,4 @@ -/** - * @namespace PIXI.math - */ module.exports = { - /** - * @property {number} PI_2 - Two Pi - * @constant - * @static - */ - PI_2: Math.PI * 2, - - /** - * @property {number} RAD_TO_DEG - Constant conversion factor for converting radians to degrees - * @constant - * @static - */ - RAD_TO_DEG: 180 / Math.PI, - - /** - * @property {Number} DEG_TO_RAD - Constant conversion factor for converting degrees to radians - * @constant - * @static - */ - DEG_TO_RAD: Math.PI / 180, - Point: require('./Point'), Matrix: require('./Matrix'), diff --git a/src/core/text/Text.js b/src/core/text/Text.js new file mode 100644 index 0000000..71b49d4 --- /dev/null +++ b/src/core/text/Text.js @@ -0,0 +1,594 @@ +var Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + math = require('../math'), + CONST = require('../const'); + +/** + * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, + * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. + * + * A Text can be created directly from a string and a style object + * + * ```js + * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); + * ``` + * + * @class + * @extends Sprite + * @memberof PIXI + * @param text {string} The copy that you would like the text to display + * @param [style] {object} The style parameters + * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font + * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + */ +function Text(text, style, resolution) +{ + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution of the canvas. + * @member {number} + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + + var texture = Texture.fromCanvas(this.canvas); + texture.trim = new math.Rectangle(); + Sprite.call(this, texture); + + + this.text = text; + this.style = style; +} + +// constructor +Text.prototype = Object.create(Sprite.prototype); +Text.prototype.constructor = Text; +module.exports = Text; + +Text.fontPropertiesCache = {}; +Text.fontPropertiesCanvas = document.createElement('canvas'); +Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); + +Object.defineProperties(Text.prototype, { + /** + * The width of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + width: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.x * this._texture._frame.width; + }, + set: function (value) + { + this.scale.x = value / this._texture._frame.width; + this._width = value; + } + }, + + /** + * The height of the Text, setting this will actually modify the scale to achieve the value set + * + * @member {number} + * @memberof Text# + */ + height: { + get: function () + { + if (this.dirty) + { + this.updateText(); + } + + return this.scale.y * this._texture._frame.height; + }, + set: function (value) + { + this.scale.y = value / this._texture._frame.height; + this._height = value; + } + }, + + /** + * Set the style of the text + * + * @param [style] {object} The style parameters + * @param [style.font='bold 20pt Arial'] {string} The style and size of the font + * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text + * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' + * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) + * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used + * @param [style.wordWrapWidth=100] {number} The width at which text will wrap + * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses + * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text + * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' + * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow + * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow + * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. + * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve + * spiked text issues. Default is 'miter' (creates a sharp corner). + * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce + * or increase the spikiness of rendered text. + * @memberof Text# + */ + style: { + get: function () + { + return this._style; + }, + set: function (style) + { + style = style || {}; + style.font = style.font || 'bold 20pt Arial'; + style.fill = style.fill || 'black'; + style.align = style.align || 'left'; + style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 + style.strokeThickness = style.strokeThickness || 0; + style.wordWrap = style.wordWrap || false; + style.wordWrapWidth = style.wordWrapWidth || 100; + + style.dropShadow = style.dropShadow || false; + style.dropShadowColor = style.dropShadowColor || '#000000'; + style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; + style.dropShadowDistance = style.dropShadowDistance || 5; + + style.padding = style.padding || 0; + + style.textBaseline = style.textBaseline || 'alphabetic'; + + style.lineJoin = style.lineJoin || 'miter'; + style.miterLimit = style.miterLimit || 10; + + this._style = style; + this.dirty = true; + } + }, + + /** + * Set the copy for the text object. To split a line you can use '\n'. + * + * @param text {string} The copy that you would like the text to display + * @memberof Text# + */ + text: { + get: function() + { + return this._text; + }, + set: function (text){ + text = text.toString() || ' '; + if (this._text === text) + { + return; + } + this._text = text; + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +Text.prototype.updateText = function () +{ + var style = this._style; + this.context.font = style.font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(style.font); + for (var i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width; + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = lineHeight * lines.length; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + //this.context.fillStyle="#FF0000"; + //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = style.font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + this.context.fillStyle = style.dropShadowColor; + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); + } + } + } + + //set canvas text styles + this.context.fillStyle = style.fill; + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); + } + + if (style.fill) + { + this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); + } + } + + this.updateTexture(); +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Text.prototype.updateTexture = function () +{ + var texture = this._texture; + + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; + + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; + texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; + + texture.trim.x = 0; + texture.trim.y = -this._style.padding; + + texture.trim.width = texture._frame.width; + texture.trim.height = texture._frame.height - this._style.padding*2; + + this._width = this.canvas.width / this.resolution; + this._height = this.canvas.height / this.resolution; + + texture.update(); + + this.dirty = false; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Text.prototype.renderWebGL = function (renderer) +{ + if (this.dirty) + { + //this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype.renderWebGL.call(this, renderer); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Text.prototype._renderCanvas = function (renderer) +{ + if (this.dirty) + { + // this.resolution = 1//renderer.resolution; + + this.updateText(); + } + + Sprite.prototype._renderCanvas.call(this, renderer); +}; + +/** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {object} + * @private + */ +Text.prototype.determineFontProperties = function (fontStyle) +{ + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var 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); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (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 (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. + * + * @param text {string} + * @private + */ +Text.prototype.wordWrap = function (text) +{ + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + var wordWidthWithSpace = wordWidth + this.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]; + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; +}; + +/** + * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the Text + * @return {Rectangle} the framing rectangle + */ +Text.prototype.getBounds = function (matrix) +{ + if (this.dirty) + { + this.updateText(); + } + + return Sprite.prototype.getBounds.call(this, matrix); +}; + +/** + * Destroys this text object. + * + * @param destroyBaseTexture {boolean} whether to destroy the base texture as well + */ +Text.prototype.destroy = function (destroyBaseTexture) +{ + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); +}; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 2223a7e..2a454c7 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -187,7 +187,7 @@ 'color: #ff2424; background: #fff; padding:5px 0;', 'color: #ff2424; background: #fff; padding:5px 0;', ]; - + window.console.log.apply(console, args); //jshint ignore:line } else if (window.console) diff --git a/src/deprecation.js b/src/deprecation.js index b28df18..23d76dc 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -1,7 +1,7 @@ /*global console */ var core = require('./core'), mesh = require('./mesh'), - text = require('./text'); + extras = require('./extras'); /** * @class @@ -17,7 +17,7 @@ /** * @class * @name PIXI.AssetLoader - * @see {@link PIXI.Loader} + * @see {@link PIXI.loaders.Loader} * @throws {ReferenceError} The loader system was overhauled in pixi v3, please see the new PIXI.Loader class. */ core.AssetLoader = function () { @@ -57,7 +57,7 @@ /** * @class * @name PIXI.Strip - * @see {@link PIXI.Mesh} + * @see {@link PIXI.mesh.Mesh} * @deprecated since version 3.0 */ Strip: { @@ -84,11 +84,11 @@ /** * @method - * @name PIXI.BitmapText#setText + * @name PIXI.extras.BitmapText#setText * @see {@link PIXI.BitmapText#text} * @deprecated since version 3.0 */ -text.BitmapText.prototype.setText = function (text) +extras.BitmapText.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myBitmapText.text = \'my text\';'); @@ -100,7 +100,7 @@ * @see {@link PIXI.Text#text} * @deprecated since version 3.0 */ -text.Text.prototype.setText = function (text) +core.Text.prototype.setText = function (text) { this.text = text; console.warn('setText is now deprecated, please use the text property, e.g : myText.text = \'my text\';'); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js new file mode 100644 index 0000000..e4a7ddc --- /dev/null +++ b/src/extras/BitmapText.js @@ -0,0 +1,346 @@ +var core = require('../core'); + +/** + * A BitmapText object will create a line or multiple lines of text using bitmap font. To + * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: + * + * A BitmapText can only be created when the font is loaded + * + * ```js + * // in this case the font is in a file called 'desyrel.fnt' + * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); + * ``` + * + * + * http://www.angelcode.com/products/bmfont/ for windows or + * http://www.bmglyph.com/ for mac. + * + * @class + * @extends Container + * @memberof PIXI.extras + * @param text {string} The copy that you would like the text to display + * @param style {object} The style parameters + * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form + * "24px FontName" or "FontName" or as an object with explicit name/size properties. + * @param [style.font.name] {string} The bitmap font id + * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 + * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect + * single line text + * @param [style.tint=0xFFFFFF] {number} The tint color + */ +function BitmapText(text, style) +{ + core.Container.call(this); + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readOnly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); +} + +// constructor +BitmapText.prototype = Object.create(core.Container.prototype); +BitmapText.prototype.constructor = BitmapText; +module.exports = BitmapText; + +Object.defineProperties(BitmapText.prototype, { + /** + * The tint of the BitmapText object + * + * @member {number} + * @memberof BitmapText# + */ + tint: { + get: function () + { + return this._font.tint; + }, + set: function (value) + { + this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; + + this.dirty = true; + } + }, + + /** + * The alignment of the BitmapText object + * + * @member {string} + * @default 'left' + * @memberof BitmapText# + */ + align: { + get: function () + { + return this._font.align; + }, + set: function (value) + { + this._font.align = value; + + this.dirty = true; + } + }, + + /** + * The font descriptor of the BitmapText object + * + * @member {Font} + * @memberof BitmapText# + */ + font: { + get: function () + { + return this._font; + }, + set: function (value) + { + if (typeof value === 'string') { + value = value.split(' '); + + this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); + this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; + } + else { + this._font.name = value.name; + this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); + } + + this.dirty = true; + } + }, + + /** + * The text of the BitmapText object + * + * @member {string} + * @memberof BitmapText# + */ + text: { + get: function () + { + return this._text; + }, + set: function (value) + { + this._text = value; + + this.dirty = true; + } + } +}); + +/** + * Renders text and updates it when needed + * + * @private + */ +BitmapText.prototype.updateText = function () +{ + var data = BitmapText.fonts[this._font.name]; + var pos = new core.math.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + 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) + { + chars.splice(lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; +}; + +/** + * Updates the transform of this object + * + * @private + */ +BitmapText.prototype.updateTransform = function () +{ + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + + this.containerUpdateTransform(); +}; + +BitmapText.fonts = {}; diff --git a/src/extras/index.js b/src/extras/index.js index 29f3ac8..e54848e 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,6 +12,7 @@ Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), + BitmapText: require('./BitmapText'), cacheAsBitmap: require('./cacheAsBitmap'), getChildByName: require('./getChildByName') }; diff --git a/src/index.js b/src/index.js index 5e67696..4bc206e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,15 @@ // run the polyfills require('./polyfill'); -var core = require('./core'), - assign = Object.assign; +var core = module.exports = require('./core'); -assign(core, require('./core/math')); -assign(core, require('./extras')); -assign(core, require('./mesh')); -assign(core, require('./filters')); -assign(core, require('./interaction')); -assign(core, require('./loaders')); -assign(core, require('./spine')); -assign(core, require('./text')); -assign(core, require('./deprecation')); +// add core plugins. +core.extras = require('./extras'); +core.filters = require('./filters'); +core.interaction = require('./interaction'); +core.loaders = require('./loaders'); +core.mesh = require('./mesh'); +core.spine = require('./spine'); -module.exports = core; +// mixin the deprecation features. +Object.assign(core, require('./deprecation')); diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index edfcdec..fa6a4b8 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -1,6 +1,6 @@ var Resource = require('resource-loader').Resource, core = require('../core'), - text = require('../text'), + extras = require('../extras'), path = require('path'); module.exports = function () @@ -119,7 +119,7 @@ // I'm leaving this as a temporary fix so we can test the bitmap fonts in v3 // but it's very likely to change - text.BitmapText.fonts[data.font] = data; + extras.BitmapText.fonts[data.font] = data; next(); }); diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index cb2f38f..23595fb 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -4,7 +4,7 @@ * Base mesh class * @class * @extends Container - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param texture {Texture} The texture to use * @param [vertices] {Float32Arrif you want to specify the vertices * @param [uvs] {Float32Array} if you want to specify the uvs diff --git a/src/mesh/Rope.js b/src/mesh/Rope.js index 8119995..f3224de 100644 --- a/src/mesh/Rope.js +++ b/src/mesh/Rope.js @@ -12,7 +12,7 @@ * * @class * @extends Mesh - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param {Texture} texture - The texture to use on the rope. * @param {Array} points - An array of {Point} objects to construct this rope. * diff --git a/src/mesh/index.js b/src/mesh/index.js index dd37f87..b18a528 100644 --- a/src/mesh/index.js +++ b/src/mesh/index.js @@ -6,11 +6,11 @@ */ /** - * @namespace PIXI.extras + * @namespace PIXI.mesh */ module.exports = { - Mesh: require('./Mesh'), + Mesh: require('./Mesh'), Rope: require('./Rope'), MeshRenderer: require('./webgl/MeshRenderer'), - MeshShader: require('./webgl/MeshShader') + MeshShader: require('./webgl/MeshShader') }; diff --git a/src/mesh/webgl/MeshRenderer.js b/src/mesh/webgl/MeshRenderer.js index 718466c..a66c14d 100644 --- a/src/mesh/webgl/MeshRenderer.js +++ b/src/mesh/webgl/MeshRenderer.js @@ -16,7 +16,7 @@ * * @class * @private - * @memberof PIXI + * @memberof PIXI.mesh * @extends ObjectRenderer * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index af2f4d7..20c61c6 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -3,7 +3,7 @@ /** * @class * @extends Shader - * @memberof PIXI.extras + * @memberof PIXI.mesh * @param shaderManager {ShaderManager} The WebGL shader manager this shader works for. */ function StripShader(shaderManager) diff --git a/src/text/BitmapText.js b/src/text/BitmapText.js deleted file mode 100644 index d825427..0000000 --- a/src/text/BitmapText.js +++ /dev/null @@ -1,346 +0,0 @@ -var core = require('../core'); - -/** - * A BitmapText object will create a line or multiple lines of text using bitmap font. To - * split a line you can use '\n', '\r' or '\r\n' in your string. You can generate the fnt files using: - * - * A BitmapText can only be created when the font is loaded - * - * ```js - * // in this case the font is in a file called 'desyrel.fnt' - * var bitmapText = new PIXI.BitmapText("text using a fancy font!", {font: "35px Desyrel", align: "right"}); - * ``` - * - * - * http://www.angelcode.com/products/bmfont/ for windows or - * http://www.bmglyph.com/ for mac. - * - * @class - * @extends Container - * @memberof PIXI.text - * @param text {string} The copy that you would like the text to display - * @param style {object} The style parameters - * @param style.font {string|object} The font descriptor for the object, can be passed as a string of form - * "24px FontName" or "FontName" or as an object with explicit name/size properties. - * @param [style.font.name] {string} The bitmap font id - * @param [style.font.size] {number} The size of the font in pixels, e.g. 24 - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect - * single line text - * @param [style.tint=0xFFFFFF] {number} The tint color - */ -function BitmapText(text, style) -{ - core.Container.call(this); - - /** - * The width of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readOnly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {Sprite[]} - * @private - */ - this._glyphs = []; - - /** - * Private tracker for the current style. - * - * @member {object} - * @private - */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; - - /** - * Private tracker for the current font. - * - * @member {object} - * @private - */ - this.font = style.font; // run font setter - - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; - - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); -} - -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; -module.exports = BitmapText; - -Object.defineProperties(BitmapText.prototype, { - /** - * The tint of the BitmapText object - * - * @member {number} - * @memberof BitmapText# - */ - tint: { - get: function () - { - return this._font.tint; - }, - set: function (value) - { - this._font.tint = (typeof value === 'number' && value >= 0) ? value : 0xFFFFFF; - - this.dirty = true; - } - }, - - /** - * The alignment of the BitmapText object - * - * @member {string} - * @default 'left' - * @memberof BitmapText# - */ - align: { - get: function () - { - return this._font.align; - }, - set: function (value) - { - this._font.align = value; - - this.dirty = true; - } - }, - - /** - * The font descriptor of the BitmapText object - * - * @member {Font} - * @memberof BitmapText# - */ - font: { - get: function () - { - return this._font; - }, - set: function (value) - { - if (typeof value === 'string') { - value = value.split(' '); - - this._font.name = value.length === 1 ? value[0] : value.slice(1).join(' '); - this._font.size = value.length >= 2 ? parseInt(value[0], 10) : BitmapText.fonts[this._font.name].size; - } - else { - this._font.name = value.name; - this._font.size = typeof value.size === 'number' ? value.size : parseInt(value.size, 10); - } - - this.dirty = true; - } - }, - - /** - * The text of the BitmapText object - * - * @member {string} - * @memberof BitmapText# - */ - text: { - get: function () - { - return this._text; - }, - set: function (value) - { - this._text = value; - - this.dirty = true; - } - } -}); - -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.math.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - lastSpace = /(\s)/.test(this.text.charAt(i)) ? i : lastSpace; - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - 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) - { - chars.splice(lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.math.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } - - this.containerUpdateTransform(); -}; - -BitmapText.fonts = {}; diff --git a/src/text/Text.js b/src/text/Text.js deleted file mode 100644 index 9c0df93..0000000 --- a/src/text/Text.js +++ /dev/null @@ -1,591 +0,0 @@ -var core = require('../core'); - -/** - * A Text Object will create a line or multiple lines of text. To split a line you can use '\n' in your text string, - * or add a wordWrap property set to true and and wordWrapWidth property with a value in the style object. - * - * A Text can be created directly from a string and a style object - * - * ```js - * var text = new PIXI.Text('This is a pixi text',{font : '24px Arial', fill : 0xff1010, align : 'center'}); - * ``` - * - * @class - * @extends Sprite - * @memberof PIXI.text - * @param text {string} The copy that you would like the text to display - * @param [style] {object} The style parameters - * @param [style.font] {string} default 'bold 20px Arial' The style and size of the font - * @param [style.fill='black'] {String|Number} A canvas fillstyle that will be used on the text e.g 'red', '#00FF00' - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text - * @param [style.stroke] {String|Number} A canvas fillstyle that will be used on the text stroke e.g 'blue', '#FCFF00' - * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) - * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used - * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true - * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses - * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/4] {number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow - * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening - * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. - * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve - * spiked text issues. Default is 'miter' (creates a sharp corner). - * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce - * or increase the spikiness of rendered text. - */ -function Text(text, style, resolution) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); - - /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution of the canvas. - * @member {number} - */ - this.resolution = resolution || core.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = null; - - /** - * Private tracker for the current style. - * - * @member {object} - * @private - */ - this._style = null; - - var texture = core.Texture.fromCanvas(this.canvas); - texture.trim = new core.math.Rectangle(); - core.Sprite.call(this, texture); - - - this.text = text; - this.style = style; -} - -// constructor -Text.prototype = Object.create(core.Sprite.prototype); -Text.prototype.constructor = Text; -module.exports = Text; - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); - -Object.defineProperties(Text.prototype, { - /** - * The width of the Text, setting this will actually modify the scale to achieve the value set - * - * @member {number} - * @memberof Text# - */ - width: { - get: function () - { - if (this.dirty) - { - this.updateText(); - } - - return this.scale.x * this._texture._frame.width; - }, - set: function (value) - { - this.scale.x = value / this._texture._frame.width; - this._width = value; - } - }, - - /** - * The height of the Text, setting this will actually modify the scale to achieve the value set - * - * @member {number} - * @memberof Text# - */ - height: { - get: function () - { - if (this.dirty) - { - this.updateText(); - } - - return this.scale.y * this._texture._frame.height; - }, - set: function (value) - { - this.scale.y = value / this._texture._frame.height; - this._height = value; - } - }, - - /** - * Set the style of the text - * - * @param [style] {object} The style parameters - * @param [style.font='bold 20pt Arial'] {string} The style and size of the font - * @param [style.fill='black'] {object} A canvas fillstyle that will be used on the text eg 'red', '#00FF00' - * @param [style.align='left'] {string} Alignment for multiline text ('left', 'center' or 'right'), does not affect single line text - * @param [style.stroke='black'] {string} A canvas fillstyle that will be used on the text stroke eg 'blue', '#FCFF00' - * @param [style.strokeThickness=0] {number} A number that represents the thickness of the stroke. Default is 0 (no stroke) - * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used - * @param [style.wordWrapWidth=100] {number} The width at which text will wrap - * @param [style.lineHeight] {number} The line height, a number that represents the vertical space that a letter uses - * @param [style.dropShadow=false] {boolean} Set a drop shadow for the text - * @param [style.dropShadowColor='#000000'] {string} A fill style to be used on the dropshadow e.g 'red', '#00FF00' - * @param [style.dropShadowAngle=Math.PI/6] {number} Set a angle of the drop shadow - * @param [style.dropShadowDistance=5] {number} Set a distance of the drop shadow - * @param [style.padding=0] {number} Occasionally some fonts are cropped. Adding some padding will prevent this from happening - * @param [style.textBaseline='alphabetic'] {string} The baseline of the text that is rendered. - * @param [style.lineJoin='miter'] {string} The lineJoin property sets the type of corner created, it can resolve - * spiked text issues. Default is 'miter' (creates a sharp corner). - * @param [style.miterLimit=10] {number} The miter limit to use when using the 'miter' lineJoin mode. This can reduce - * or increase the spikiness of rendered text. - * @memberof Text# - */ - style: { - get: function () - { - return this._style; - }, - set: function (style) - { - style = style || {}; - style.font = style.font || 'bold 20pt Arial'; - style.fill = style.fill || 'black'; - style.align = style.align || 'left'; - style.stroke = style.stroke || 'black'; //provide a default, see: https://github.com/GoodBoyDigital/pixi.js/issues/136 - style.strokeThickness = style.strokeThickness || 0; - style.wordWrap = style.wordWrap || false; - style.wordWrapWidth = style.wordWrapWidth || 100; - - style.dropShadow = style.dropShadow || false; - style.dropShadowColor = style.dropShadowColor || '#000000'; - style.dropShadowAngle = style.dropShadowAngle || Math.PI / 6; - style.dropShadowDistance = style.dropShadowDistance || 5; - - style.padding = style.padding || 0; - - style.textBaseline = style.textBaseline || 'alphabetic'; - - style.lineJoin = style.lineJoin || 'miter'; - style.miterLimit = style.miterLimit || 10; - - this._style = style; - this.dirty = true; - } - }, - - /** - * Set the copy for the text object. To split a line you can use '\n'. - * - * @param text {string} The copy that you would like the text to display - * @memberof Text# - */ - text: { - get: function() - { - return this._text; - }, - set: function (text){ - text = text.toString() || ' '; - if (this._text === text) - { - return; - } - this._text = text; - this.dirty = true; - } - } -}); - -/** - * Renders text and updates it when needed - * - * @private - */ -Text.prototype.updateText = function () -{ - var style = this._style; - this.context.font = style.font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(style.font); - for (var i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width; - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - this.canvas.width = ( width + this.context.lineWidth ) * this.resolution; - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = lineHeight * lines.length; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = ( height + this._style.padding * 2 ) * this.resolution; - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - - //this.context.fillStyle="#FF0000"; - //this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = style.font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - this.context.fillStyle = style.dropShadowColor; - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.context.fillText(lines[i], linePositionX + xShadowOffset, linePositionY + yShadowOffset + this._style.padding); - } - } - } - - //set canvas text styles - this.context.fillStyle = style.fill; - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.context.strokeText(lines[i], linePositionX, linePositionY + this._style.padding); - } - - if (style.fill) - { - this.context.fillText(lines[i], linePositionX, linePositionY + this._style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.crop.width = texture._frame.width = this.canvas.width / this.resolution; - texture.crop.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = 0; - texture.trim.y = -this._style.padding; - - texture.trim.width = texture._frame.width; - texture.trim.height = texture._frame.height - this._style.padding*2; - - this._width = this.canvas.width / this.resolution; - this._height = this.canvas.height / this.resolution; - - texture.update(); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Text.prototype.renderWebGL = function (renderer) -{ - if (this.dirty) - { - //this.resolution = 1//renderer.resolution; - - this.updateText(); - } - - core.Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if (this.dirty) - { - // this.resolution = 1//renderer.resolution; - - this.updateText(); - } - - core.Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {object} - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var 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); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (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 (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. - * - * @param text {string} - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - var wordWidthWithSpace = wordWidth + this.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]; - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the Text - * @return {Rectangle} the framing rectangle - */ -Text.prototype.getBounds = function (matrix) -{ - if (this.dirty) - { - this.updateText(); - } - - return core.Sprite.prototype.getBounds.call(this, matrix); -}; - -/** - * Destroys this text object. - * - * @param destroyBaseTexture {boolean} whether to destroy the base texture as well - */ -Text.prototype.destroy = function (destroyBaseTexture) -{ - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._texture.destroy(destroyBaseTexture === undefined ? true : destroyBaseTexture); -}; diff --git a/src/text/index.js b/src/text/index.js deleted file mode 100644 index 22d4562..0000000 --- a/src/text/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file Main export of the PIXI text library - * @author Mat Groves - * @copyright 2013-2015 GoodBoyDigital - * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} - */ - -/** - * @namespace PIXI.text - */ -module.exports = { - Text: require('./Text'), - BitmapText: require('./BitmapText') -};