diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 7601a0c..7a3d6ac 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,7 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); - expect(textures[id].textureCacheId).to.equal(id); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 7601a0c..7a3d6ac 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,7 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); - expect(textures[id].textureCacheId).to.equal(id); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Text.js b/test/core/Text.js index 5799ad4..e678ba4 100644 --- a/test/core/Text.js +++ b/test/core/Text.js @@ -2,40 +2,6 @@ describe('PIXI.Text', function () { - describe('getFontStyle', function () - { - it('should be a valid API', function () - { - expect(PIXI.Text.getFontStyle).to.be.a.function; - }); - - it('should assume pixel fonts', function () - { - const style = PIXI.Text.getFontStyle({ fontSize: 72 }); - - expect(style).to.be.a.string; - expect(style).to.have.string(' 72px '); - }); - - it('should handle multiple fonts as array', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: ['Georgia', 'Arial', 'sans-serif'], - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - - it('should handle multiple fonts as string', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: 'Georgia, "Arial", sans-serif', - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - }); - describe('destroy', function () { it('should call through to Sprite.destroy', function () diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 7601a0c..7a3d6ac 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,7 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); - expect(textures[id].textureCacheId).to.equal(id); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Text.js b/test/core/Text.js index 5799ad4..e678ba4 100644 --- a/test/core/Text.js +++ b/test/core/Text.js @@ -2,40 +2,6 @@ describe('PIXI.Text', function () { - describe('getFontStyle', function () - { - it('should be a valid API', function () - { - expect(PIXI.Text.getFontStyle).to.be.a.function; - }); - - it('should assume pixel fonts', function () - { - const style = PIXI.Text.getFontStyle({ fontSize: 72 }); - - expect(style).to.be.a.string; - expect(style).to.have.string(' 72px '); - }); - - it('should handle multiple fonts as array', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: ['Georgia', 'Arial', 'sans-serif'], - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - - it('should handle multiple fonts as string', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: 'Georgia, "Arial", sans-serif', - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - }); - describe('destroy', function () { it('should call through to Sprite.destroy', function () diff --git a/test/core/TextStyle.js b/test/core/TextStyle.js index b5761ad..49b99ef 100644 --- a/test/core/TextStyle.js +++ b/test/core/TextStyle.js @@ -23,4 +23,31 @@ expect(textStyle.fontSize).to.equal(1000); expect(clonedTextStyle.fontSize).to.equal(textStyle.fontSize); }); + + it('should assume pixel fonts', function () + { + const style = new PIXI.TextStyle({ fontSize: 72 }); + const font = style.toFontString(); + + expect(font).to.be.a.string; + expect(font).to.have.string(' 72px '); + }); + + it('should handle multiple fonts as array', function () + { + const style = new PIXI.TextStyle({ + fontFamily: ['Georgia', 'Arial', 'sans-serif'], + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); + + it('should handle multiple fonts as string', function () + { + const style = new PIXI.TextStyle({ + fontFamily: 'Georgia, "Arial", sans-serif', + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); }); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 7601a0c..7a3d6ac 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,7 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); - expect(textures[id].textureCacheId).to.equal(id); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Text.js b/test/core/Text.js index 5799ad4..e678ba4 100644 --- a/test/core/Text.js +++ b/test/core/Text.js @@ -2,40 +2,6 @@ describe('PIXI.Text', function () { - describe('getFontStyle', function () - { - it('should be a valid API', function () - { - expect(PIXI.Text.getFontStyle).to.be.a.function; - }); - - it('should assume pixel fonts', function () - { - const style = PIXI.Text.getFontStyle({ fontSize: 72 }); - - expect(style).to.be.a.string; - expect(style).to.have.string(' 72px '); - }); - - it('should handle multiple fonts as array', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: ['Georgia', 'Arial', 'sans-serif'], - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - - it('should handle multiple fonts as string', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: 'Georgia, "Arial", sans-serif', - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - }); - describe('destroy', function () { it('should call through to Sprite.destroy', function () diff --git a/test/core/TextStyle.js b/test/core/TextStyle.js index b5761ad..49b99ef 100644 --- a/test/core/TextStyle.js +++ b/test/core/TextStyle.js @@ -23,4 +23,31 @@ expect(textStyle.fontSize).to.equal(1000); expect(clonedTextStyle.fontSize).to.equal(textStyle.fontSize); }); + + it('should assume pixel fonts', function () + { + const style = new PIXI.TextStyle({ fontSize: 72 }); + const font = style.toFontString(); + + expect(font).to.be.a.string; + expect(font).to.have.string(' 72px '); + }); + + it('should handle multiple fonts as array', function () + { + const style = new PIXI.TextStyle({ + fontFamily: ['Georgia', 'Arial', 'sans-serif'], + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); + + it('should handle multiple fonts as string', function () + { + const style = new PIXI.TextStyle({ + fontFamily: 'Georgia, "Arial", sans-serif', + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); }); diff --git a/test/core/Texture.js b/test/core/Texture.js index 241ec3e..995ed40 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -1,11 +1,26 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('PIXI.Texture', function () { it('should register Texture from Loader', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); @@ -19,17 +34,97 @@ it('should remove Texture from cache on destroy', function () { - const NAME = 'foo'; - const NAME2 = 'bar'; + cleanCache(); + const texture = new PIXI.Texture(new PIXI.BaseTexture()); - PIXI.Texture.addTextureToCache(texture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME2); - expect(texture.textureCacheId).to.equal(NAME); + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); texture.destroy(); + expect(texture.textureCacheIds).to.equal(null); expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); }); + + it('should be added to the texture cache correctly, ' + + 'and should remove only itself, not effecting the base texture and its cache', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); + PIXI.Texture.addToCache(texture, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + PIXI.Texture.removeFromCache(NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + }); + + it('should be added to the texture cache correctly using legacy addTextureToCache, ' + + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + PIXI.Texture.removeTextureFromCache(NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + }); + + it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + PIXI.Texture.removeFromCache(texture); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove Texture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + PIXI.Texture.removeFromCache(NAME); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + }); }); diff --git a/package.json b/package.json index 308469f..2466363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixi.js", - "version": "4.4.4", + "version": "4.5.0", "description": "Pixi.js is a fast lightweight 2D library that works across all devices.", "author": "Mat Groves", "contributors": [ @@ -23,7 +23,7 @@ "start": "parallelshell \"npm run watch\" \"npm run watch:lint\" \"npm run watch:lib\"", "watch": "npm run dist -- --watch", "watch:lib": "npm run lib -- --watch", - "watch:lint": "watch \"eslint scripts src test || exit 0\" src --silent", + "watch:lint": "watch \"eslint scripts src test || exit 0\" src", "test": "floss --path test/index.js", "test:debug": "npm test -- --debug", "prerenders": "npm --prefix scripts/renders i scripts/renders", @@ -44,7 +44,8 @@ "publish:minor": "npm version minor && npm publish", "publish:major": "npm version major && npm publish", "postversion": "npm run clean && npm run build && npm run lib && npm test", - "postpublish": "git push && git push --tags" + "postpublish": "git push && git push --tags && npm run release", + "release": "node scripts/release" }, "files": [ "dist/", @@ -60,6 +61,7 @@ "eventemitter3": "^2.0.0", "ismobilejs": "^0.4.0", "object-assign": "^4.0.1", + "pixi-gl-core": "^1.0.3", "resource-loader": "^2.0.6" }, "devDependencies": { @@ -72,6 +74,7 @@ "electron": "^1.4.15", "eslint": "^3.5.0", "floss": "^2.0.1", + "gh-pages": "^0.12.0", "jaguarjs-jsdoc": "^1.0.1", "js-md5": "^0.4.1", "jsdoc": "^3.4.2", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..c4c8e36 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,42 @@ +#!/usr/bin/env node +'use strict'; + +// Publish script to push releases of the bin files +// the normally are gitignored +const ghpages = require('gh-pages'); +const path = require('path'); +const packageInfo = require(path.join(__dirname, '..', 'package.json')); + +const options = { + src: [ + 'dist/**/*', + 'lib/**/*', + 'src/**/*', + 'scripts/**/*', + 'test/**/*', + '*.json', + '*.md', + 'LICENSE', + '.eslintrc', + '.editorconfig', + '.travis.yml', + '.babelrc', + ], + dotfiles: true, + branch: 'release', + message: packageInfo.version, + logger: console.log.bind(console), +}; + +ghpages.publish(process.cwd(), options, (err) => +{ + if (err) + { + console.log(err); + process.exit(1); + + return; + } + + process.exit(0); +}); diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 220427a..dc960f0 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -21,12 +21,16 @@ const DIV_HOOK_ZINDEX = 2; /** +<<<<<<< HEAD * The Accessibility manager recreates the ability to tab and and have content read by screen +======= + * The Accessibility manager recreates the ability to tab and have content read by screen +>>>>>>> dev * readers. This is very important as it can possibly help people with disabilities access pixi * content. * * Much like interaction any DisplayObject can be made accessible. This manager will map the - * events as if the mouse was being used, minimizing the efferot required to implement. + * events as if the mouse was being used, minimizing the effort required to implement. * * An instance of this class is automatically created by default, and can be found at renderer.plugins.accessibility * @@ -80,7 +84,7 @@ this.renderId = 0; /** - * Setting this to true will visually show the divs + * Setting this to true will visually show the divs. * * @type {boolean} */ @@ -110,7 +114,7 @@ this._onMouseMove = this._onMouseMove.bind(this); /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * stores the state of the manager. If there are no accessible objects or the mouse is moving, this will be false. * * @member {Array<*>} * @private @@ -151,7 +155,7 @@ /** * Activating will cause the Accessibility layer to be shown. This is called when a user - * preses the tab key + * preses the tab key. * * @private */ @@ -177,7 +181,7 @@ /** * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves - * the mouse + * the mouse. * * @private */ @@ -202,7 +206,7 @@ } /** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * * @private * @param {PIXI.Container} displayObject - The DisplayObject to check. @@ -409,7 +413,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseover) + * Maps the div focus events to pixi's InteractionManager (mouseover) * * @private * @param {FocusEvent} e - The focus event. @@ -422,7 +426,7 @@ } /** - * Maps the div focus events to pixis InteractionManager (mouseout) + * Maps the div focus events to pixi's InteractionManager (mouseout) * * @private * @param {FocusEvent} e - The focusout event. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index f787c4a..4ffcfe0 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -1071,7 +1071,7 @@ canvasRenderer.render(this, canvasBuffer, true, tempMatrix); - const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode, 'graphics'); texture.baseTexture.resolution = resolution; texture.baseTexture.update(); diff --git a/src/core/index.js b/src/core/index.js index 0cebeb7..eb91185 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -26,6 +26,7 @@ export { default as SpriteRenderer } from './sprites/webgl/SpriteRenderer'; export { default as Text } from './text/Text'; export { default as TextStyle } from './text/TextStyle'; +export { default as TextMetrics } from './text/TextMetrics'; export { default as Graphics } from './graphics/Graphics'; export { default as GraphicsData } from './graphics/GraphicsData'; export { default as GraphicsRenderer } from './graphics/webgl/GraphicsRenderer'; diff --git a/src/core/renderers/webgl/filters/filterTransforms.js b/src/core/renderers/webgl/filters/filterTransforms.js index 5fde3d3..851ad69 100644 --- a/src/core/renderers/webgl/filters/filterTransforms.js +++ b/src/core/renderers/webgl/filters/filterTransforms.js @@ -1,6 +1,6 @@ import { Matrix } from '../../../math'; -/* +/** * Calculates the mapped matrix * @param filterArea {Rectangle} The filter area * @param sprite {Sprite} the target sprite diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index f1f3571..e7b0d05 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -54,21 +54,29 @@ /* * @member {Uint16Array} An array containing the indices of the vertices + * + * @member {Uint16Array} */ this.indices = createIndicesForQuads(1); - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * The vertex buffer + * + * @member {glCore.GLBuffer} */ // this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); - /* - * @member {glCore.GLBuffer} The index buffer + /** + * The index buffer + * + * @member {glCore.GLBuffer} */ // this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - /* - * @member {glCore.VertexArrayObject} The index buffer + /** + * The vertex array object + * + * @member {glCore.VertexArrayObject} */ // this.vao = new glCore.VertexArrayObject(gl, state); @@ -162,9 +170,9 @@ */ destroy() { - const gl = this.gl; + // const gl = this.gl; - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); + // gl.deleteBuffer(this.vertexBuffer); + // gl.deleteBuffer(this.indexBuffer); } } diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 8b9b797..32d9c46 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -6,6 +6,7 @@ import { TEXT_GRADIENT } from '../const'; import settings from '../settings'; import TextStyle from './TextStyle'; +import TextMetrics from './TextMetrics'; import trimCanvas from '../utils/trimCanvas'; const defaultDestroyOptions = { @@ -42,13 +43,16 @@ canvas.width = 3; canvas.height = 3; - const texture = Texture.fromCanvas(canvas); + const texture = Texture.fromCanvas(canvas, settings.SCALE_MODE, 'text'); texture.orig = new Rectangle(); texture.trim = new Rectangle(); super(texture); + // base texture is already automatically added to the cache, now adding the actual texture + Texture.addToCache(this._texture, this._texture.baseTexture.textureCacheIds[0]); + /** * The canvas element that everything is drawn to * @@ -128,50 +132,18 @@ return; } - this._font = Text.getFontStyle(style); + this._font = this._style.toFontString(); - this.context.font = this._font; - - // word wrap - // preserve original text - const outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - const lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - const lineWidths = new Array(lines.length); - let maxLineWidth = 0; - const fontProperties = Text.calculateFontProperties(this._font); - - for (let i = 0; i < lines.length; i++) - { - const lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - let width = maxLineWidth + style.strokeThickness; - - if (style.dropShadow) - { - width += style.dropShadowDistance; - } + const measured = TextMetrics.measureText(this._text, this._style, this._style.wordWrap, this.canvas); + const width = measured.width; + const height = measured.height; + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; this.canvas.width = Math.ceil((width + (style.padding * 2)) * this.resolution); - - // calculate text height - const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) - + ((lines.length - 1) * lineHeight); - - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - this.canvas.height = Math.ceil((height + (style.padding * 2)) * this.resolution); this.context.scale(this.resolution, this.resolution); @@ -264,12 +236,21 @@ if (style.stroke && style.strokeThickness) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding, + true + ); } if (style.fill) { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + this.drawLetterSpacing( + lines[i], + linePositionX + style.padding, + linePositionY + style.padding + ); } } @@ -406,90 +387,6 @@ } /** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @private - * @param {string} text - String to apply word wrapping to - * @return {string} New string with new lines applied where required - */ - wordWrap(text) - { - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - let result = ''; - const style = this._style; - const lines = text.split('\n'); - const wordWrapWidth = style.wordWrapWidth; - - for (let i = 0; i < lines.length; i++) - { - let spaceLeft = wordWrapWidth; - const words = lines[i].split(' '); - - for (let j = 0; j < words.length; j++) - { - const wordWidth = this.context.measureText(words[j]).width; - - if (style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - const characters = words[j].split(''); - - for (let c = 0; c < characters.length; c++) - { - const characterWidth = this.context.measureText(characters[c]).width; - - if (characterWidth > spaceLeft) - { - result += `\n${characters[c]}`; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - const 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; - } - - /** * Gets the local bounds of the text object. * * @param {Rectangle} rect - The output rectangle. @@ -754,157 +651,4 @@ this._text = text; this.dirty = true; } - - /** - * Generates a font style string to use for Text.calculateFontProperties(). Takes the same parameter - * as Text.style. - * - * @static - * @param {object|TextStyle} style - String representing the style of the font - * @return {string} Font style string, for passing to Text.calculateFontProperties() - */ - static getFontStyle(style) - { - style = style || {}; - - if (!(style instanceof TextStyle)) - { - style = new TextStyle(style); - } - - // build canvas api font setting from individual components. Convert a numeric style.fontSize to px - const fontSizeString = (typeof style.fontSize === 'number') ? `${style.fontSize}px` : style.fontSize; - - // Clean-up fontFamily property by quoting each font name - // this will support font names with spaces - let fontFamilies = style.fontFamily; - - if (!Array.isArray(style.fontFamily)) - { - fontFamilies = style.fontFamily.split(','); - } - - for (let i = fontFamilies.length - 1; i >= 0; i--) - { - // Trim any extra white-space - let fontFamily = fontFamilies[i].trim(); - - // Check if font already contains strings - if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) - { - fontFamily = `"${fontFamily}"`; - } - fontFamilies[i] = fontFamily; - } - - return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; - } - - /** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @static - * @param {string} fontStyle - String representing the style of the font - * @return {Object} Font properties object - */ - static calculateFontProperties(fontStyle) - { - // as this method is used for preparing assets, don't recalculate things if we don't need to - if (Text.fontPropertiesCache[fontStyle]) - { - return Text.fontPropertiesCache[fontStyle]; - } - - const properties = {}; - - const canvas = Text.fontPropertiesCanvas; - const context = Text.fontPropertiesContext; - - context.font = fontStyle; - - const width = Math.ceil(context.measureText('|MÉq').width); - let baseline = Math.ceil(context.measureText('M').width); - const height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - const imagedata = context.getImageData(0, 0, width, height).data; - const pixels = imagedata.length; - const line = width * 4; - - let i = 0; - let idx = 0; - let stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; ++i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; --i) - { - for (let j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - - return properties; - } } - -Text.fontPropertiesCache = {}; -Text.fontPropertiesCanvas = document.createElement('canvas'); -Text.fontPropertiesContext = Text.fontPropertiesCanvas.getContext('2d'); diff --git a/src/core/text/TextMetrics.js b/src/core/text/TextMetrics.js new file mode 100644 index 0000000..27bd145 --- /dev/null +++ b/src/core/text/TextMetrics.js @@ -0,0 +1,327 @@ +/** + * The TextMetrics object represents the measurement of a block of text with a specified style. + * + * @class + * @memberOf PIXI + */ +export default class TextMetrics +{ + /** + * @param {string} text - the text that was measured + * @param {PIXI.TextStyle} style - the style that was measured + * @param {number} width - the measured width of the text + * @param {number} height - the measured height of the text + * @param {array} lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param {array} lineWidths - an array of the line widths for each line matched to `lines` + * @param {number} lineHeight - the measured line height for this style + * @param {number} maxLineWidth - the maximum line width for all measured lines + * @param {Object} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) + { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + + /** + * Measures the supplied string of text and returns a Rectangle. + * + * @param {string} text - the text to measure. + * @param {PIXI.TextStyle} style - the text style to use for measuring + * @param {boolean} [wordWrap] - optional override for if word-wrap should be applied to the text. + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {PIXI.TextMetrics} measured width and height of the text. + */ + static measureText(text, style, wordWrap, canvas = TextMetrics._canvas) + { + wordWrap = wordWrap || style.wordWrap; + const font = style.toFontString(); + const fontProperties = TextMetrics.measureFont(font); + const context = canvas.getContext('2d'); + + context.font = font; + + const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + + for (let i = 0; i < lines.length; i++) + { + const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + let width = maxLineWidth + style.strokeThickness; + + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + const lineHeight = style.lineHeight || fontProperties.fontSize + style.strokeThickness; + let height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + + ((lines.length - 1) * lineHeight); + + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + return new TextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight, + maxLineWidth, + fontProperties + ); + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @private + * @param {string} text - String to apply word wrapping to + * @param {PIXI.TextStyle} style - the style to use when wrapping + * @param {HTMLCanvasElement} [canvas] - optional specification of the canvas to use for measuring. + * @return {string} New string with new lines applied where required + */ + static wordWrap(text, style, canvas = TextMetrics._canvas) + { + const context = canvas.getContext('2d'); + + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + let result = ''; + const lines = text.split('\n'); + const wordWrapWidth = style.wordWrapWidth; + const characterCache = {}; + + for (let i = 0; i < lines.length; i++) + { + let spaceLeft = wordWrapWidth; + const words = lines[i].split(' '); + + for (let j = 0; j < words.length; j++) + { + const wordWidth = context.measureText(words[j]).width; + + if (style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + const characters = words[j].split(''); + + for (let c = 0; c < characters.length; c++) + { + const character = characters[c]; + let characterWidth = characterCache[character]; + + if (characterWidth === undefined) + { + characterWidth = context.measureText(character).width; + characterCache[character] = characterWidth; + } + + if (characterWidth > spaceLeft) + { + result += `\n${character}`; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + + result += character; + spaceLeft -= characterWidth; + } + } + } + else + { + const wordWidthWithSpace = wordWidth + context.measureText(' ').width; + + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ` ${words[j]}`; + } + } + } + + if (i < lines.length - 1) + { + result += '\n'; + } + } + + return result; + } + + /** + * Calculates the ascent, descent and fontSize of a given font-style + * + * @static + * @param {string} font - String representing the style of the font + * @return {PIXI.TextMetrics~FontMetrics} Font properties object + */ + static measureFont(font) + { + // as this method is used for preparing assets, don't recalculate things if we don't need to + if (TextMetrics._fonts[font]) + { + return TextMetrics._fonts[font]; + } + + const properties = {}; + + const canvas = TextMetrics._canvas; + const context = TextMetrics._context; + + context.font = font; + + const width = Math.ceil(context.measureText('|MÉq').width); + let baseline = Math.ceil(context.measureText('M').width); + const height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = font; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + const imagedata = context.getImageData(0, 0, width, height).data; + const pixels = imagedata.length; + const line = width * 4; + + let i = 0; + let idx = 0; + let stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; ++i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; --i) + { + for (let j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + TextMetrics._fonts[font] = properties; + + return properties; + } +} + +/** + * Internal return object for {@link PIXI.TextMetrics.measureFont `TextMetrics.measureFont`}. + * @class FontMetrics + * @memberof PIXI.TextMetrics~ + * @property {number} ascent - The ascent distance + * @property {number} descent - The descent distance + * @property {number} fontSize - Font size from ascent to descent + */ + +const canvas = document.createElement('canvas'); + +canvas.width = canvas.height = 10; + +/** + * Cached canvas element for measuring text + * @memberof PIXI.TextMetrics + * @type {HTMLCanvasElement} + * @private + */ +TextMetrics._canvas = canvas; + +/** + * Cache for context to use. + * @memberof PIXI.TextMetrics + * @type {CanvasRenderingContext2D} + * @private + */ +TextMetrics._context = canvas.getContext('2d'); + +/** + * Cache of PIXI.TextMetrics~FontMetrics objects. + * @memberof PIXI.TextMetrics + * @type {Object} + * @private + */ +TextMetrics._fonts = {}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 890950b..4d66953 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -473,6 +473,41 @@ this.styleID++; } } + + /** + * Generates a font style string to use for `TextMetrics.measureFont()`. + * + * @return {string} Font style string, for passing to `TextMetrics.measureFont()` + */ + toFontString() + { + // build canvas api font setting from individual components. Convert a numeric this.fontSize to px + const fontSizeString = (typeof this.fontSize === 'number') ? `${this.fontSize}px` : this.fontSize; + + // Clean-up fontFamily property by quoting each font name + // this will support font names with spaces + let fontFamilies = this.fontFamily; + + if (!Array.isArray(this.fontFamily)) + { + fontFamilies = this.fontFamily.split(','); + } + + for (let i = fontFamilies.length - 1; i >= 0; i--) + { + // Trim any extra white-space + let fontFamily = fontFamilies[i].trim(); + + // Check if font already contains strings + if (!(/([\"\'])[^\'\"]+\1/).test(fontFamily)) + { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + + return `${this.fontStyle} ${this.fontVariant} ${this.fontWeight} ${fontSizeString} ${fontFamilies.join(',')}`; + } } /** diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 8a9c34b..90e457c 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -115,6 +115,8 @@ this.validate(); + this.textureCacheIds = []; + /** * Fired when a not-immediately-available source finishes loading. * @@ -135,6 +137,22 @@ * Fired when BaseTexture is updated. * * @protected + * @event PIXI.BaseTexture#loaded + * @param {PIXI.BaseTexture} baseTexture - Resource loaded. + */ + + /** + * Fired when BaseTexture is destroyed. + * + * @protected + * @event PIXI.BaseTexture#error + * @param {PIXI.BaseTexture} baseTexture - Resource errored. + */ + + /** + * Fired when BaseTexture is updated. + * + * @protected * @event PIXI.BaseTexture#update * @param {PIXI.BaseTexture} baseTexture - Instance of texture being updated. */ @@ -253,8 +271,6 @@ */ destroy() { - // remove from the cache.. - if (this.cacheId) { delete BaseTextureCache[this.cacheId]; @@ -272,7 +288,10 @@ } // finally let the webGL renderer know.. - this.dispose() + this.dispose(); + + BaseTexture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -280,6 +299,7 @@ * This means you can still use the texture later which will upload it to GPU * memory again. * + * @fires PIXI.BaseTexture#dispose */ dispose() { @@ -320,10 +340,9 @@ { baseTexture = new BaseTexture(source); baseTexture.cacheId = cacheId; - BaseTextureCache[cacheId] = baseTexture; + BaseTexture.addToCache(baseTexture, cacheId); } - // lets assume its a base texture! return baseTexture; } @@ -355,6 +374,76 @@ return texture; } + /** + * Adds a BaseTexture to the global BaseTextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param {PIXI.BaseTexture} baseTexture - The BaseTexture to add to the cache. + * @param {string} id - The id that the BaseTexture will be stored against. + */ + static addToCache(baseTexture, id) + { + if (id) + { + if (baseTexture.textureCacheIds.indexOf(id) === -1) + { + baseTexture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (BaseTextureCache[id]) + { + console.warn(`BaseTexture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + BaseTextureCache[id] = baseTexture; + } + } + + /** + * Remove a BaseTexture from the global BaseTextureCache. + * + * @static + * @param {string|PIXI.BaseTexture} baseTexture - id of a BaseTexture to be removed, or a BaseTexture instance itself. + * @return {PIXI.BaseTexture|null} The BaseTexture that was removed. + */ + static removeFromCache(baseTexture) + { + if (typeof baseTexture === 'string') + { + const baseTextureFromCache = BaseTextureCache[baseTexture]; + + if (baseTextureFromCache) + { + const index = baseTextureFromCache.textureCacheIds.indexOf(baseTexture); + + if (index > -1) + { + baseTextureFromCache.textureCacheIds.splice(index, 1); + } + + delete BaseTextureCache[baseTexture]; + + return baseTextureFromCache; + } + } + else + { + for (let i = 0; i < baseTexture.textureCacheIds.length; ++i) + { + delete BaseTextureCache[baseTexture.textureCacheIds[i]]; + } + + baseTexture.textureCacheIds.length = 0; + + return baseTexture; + } + + return null; + } } BaseTexture.fromImage = BaseTexture.from; diff --git a/src/core/textures/Spritesheet.js b/src/core/textures/Spritesheet.js index 9b24f27..85abccf 100644 --- a/src/core/textures/Spritesheet.js +++ b/src/core/textures/Spritesheet.js @@ -1,5 +1,5 @@ import { Rectangle, Texture } from '../'; -import { getResolutionOfUrl, TextureCache } from '../utils'; +import { getResolutionOfUrl } from '../utils'; /** * Utility class for maintaining reference to a collection @@ -207,8 +207,7 @@ ); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions - TextureCache[i] = this.textures[i]; - this.textures[i].textureCacheId = i; + Texture.addToCache(this.textures[i], i); } frameIndex++; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 2213087..7f72349 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -6,7 +6,7 @@ import EventEmitter from 'eventemitter3'; import settings from '../settings'; import { Rectangle } from '../math'; -import { TextureCache, BaseTextureCache, getResolutionOfUrl, uid } from '../utils'; +import { TextureCache, getResolutionOfUrl } from '../utils'; /** * A texture stores the information that represents an image or part of an image. It cannot be added @@ -161,13 +161,13 @@ this.transform = null; /** - * The id under which this Texture has been added to the texture cache. This is - * automatically set in certain cases, but may not always be accurate, particularly if - * the texture is in the cache under multiple ids. + * The ids under which this Texture has been added to the texture cache. This is + * automatically set as long as Texture.addToCache is used, but may not be set if a + * Texture is added directly to the TextureCache array. * - * @member {string} + * @member {string[]} */ - this.textureCacheId = null; + this.textureCacheIds = []; } /** @@ -219,7 +219,7 @@ // this only needs to be removed if the base texture is actually destroyed too.. if (TextureCache[this.baseTexture.imageUrl]) { - delete TextureCache[this.baseTexture.imageUrl]; + Texture.removeFromCache(this.baseTexture.imageUrl); } this.baseTexture.destroy(); @@ -234,17 +234,11 @@ this._uvs = null; this.trim = null; this.orig = null; - this.textureCacheId = null; this.valid = false; - for (const prop in TextureCache) - { - if (TextureCache[prop] === this) - { - delete TextureCache[prop]; - } - } + Texture.removeFromCache(this); + this.textureCacheIds = null; } /** @@ -275,6 +269,95 @@ } /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param {string} imageUrl - The image url of the texture + * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images. + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode, sourceScale) + { + let texture = TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale)); + Texture.addToCache(texture, imageUrl); + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param {string} frameId - The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + const texture = TextureCache[frameId]; + + if (!texture) + { + throw new Error(`The frameId "${frameId}" does not exist in the texture cache`); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param {HTMLCanvasElement} canvas - The canvas element source of the texture + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @param {string} [origin='canvas'] - A string origin of who created the base texture + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode, origin = 'canvas') + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param {HTMLVideoElement|string} video - The URL or actual element of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param {string} videoUrl - URL of the video + * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** * Helper function that creates a new Texture based on the source you provide. * The source can be - frame id, image url, video url, canvas element, video element, base texture * @@ -338,8 +421,6 @@ settings.SCALE_MODE, getResolutionOfUrl(imageUrl)); - - /// console.log('base width ' + baseTexture.width); const texture = new Texture(baseTexture); // No name, use imageUrl instead @@ -349,51 +430,88 @@ } // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions - BaseTextureCache[name] = baseTexture; - TextureCache[name] = texture; - texture.textureCacheId = name; + BaseTexture.addToCache(texture.baseTexture, name); + Texture.addToCache(texture, name); // also add references by url if they are different. if (name !== imageUrl) { - BaseTextureCache[imageUrl] = baseTexture; - TextureCache[imageUrl] = texture; + BaseTexture.addToCache(texture.baseTexture, imageUrl); + Texture.addToCache(texture, imageUrl); } return texture; } /** - * Adds a texture to the global TextureCache. This cache is shared across the whole PIXI object. + * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object. * * @static * @param {PIXI.Texture} texture - The Texture to add to the cache. - * @param {string} id - The id that the texture will be stored against. + * @param {string} id - The id that the Texture will be stored against. */ - static addTextureToCache(texture, id) + static addToCache(texture, id) { - if (!texture.textureCacheId) + if (id) { - texture.textureCacheId = id; + if (texture.textureCacheIds.indexOf(id) === -1) + { + texture.textureCacheIds.push(id); + } + + // @if DEBUG + /* eslint-disable no-console */ + if (TextureCache[id]) + { + console.warn(`Texture added to the cache with an id [${id}] that already had an entry`); + } + /* eslint-enable no-console */ + // @endif + + TextureCache[id] = texture; } - TextureCache[id] = texture; } /** - * Remove a texture from the global TextureCache. + * Remove a Texture from the global TextureCache. * * @static - * @param {string} id - The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed + * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself + * @return {PIXI.Texture|null} The Texture that was removed */ - static removeTextureFromCache(id) + static removeFromCache(texture) { - const texture = TextureCache[id]; + if (typeof texture === 'string') + { + const textureFromCache = TextureCache[texture]; - delete TextureCache[id]; - delete BaseTextureCache[id]; + if (textureFromCache) + { + const index = textureFromCache.textureCacheIds.indexOf(texture); - return texture; + if (index > -1) + { + textureFromCache.textureCacheIds.splice(index, 1); + } + + delete TextureCache[texture]; + + return textureFromCache; + } + } + else + { + for (let i = 0; i < texture.textureCacheIds.length; ++i) + { + delete TextureCache[texture.textureCacheIds[i]]; + } + + texture.textureCacheIds.length = 0; + + return texture; + } + + return null; } /** diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index a9b9f51..65b74b9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -193,7 +193,7 @@ if (this.source && this.source._pixiId) { - delete BaseTextureCache[this.source._pixiId]; + BaseTexture.removeFromCache(this.source._pixiId); delete this.source._pixiId; } @@ -220,7 +220,7 @@ if (!baseTexture) { baseTexture = new VideoBaseTexture(video, scaleMode); - BaseTextureCache[video._pixiId] = baseTexture; + BaseTexture.addToCache(baseTexture, video._pixiId); } return baseTexture; diff --git a/src/core/utils/index.js b/src/core/utils/index.js index 7441f7b..3fdd996 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -368,7 +368,7 @@ * @memberof PIXI.utils * @private */ -export const TextureCache = {}; +export const TextureCache = Object.create(null); /** * @todo Describe property usage @@ -376,7 +376,7 @@ * @memberof PIXI.utils * @private */ -export const BaseTextureCache = {}; +export const BaseTextureCache = Object.create(null); /** * @todo Describe property usage diff --git a/src/deprecation.js b/src/deprecation.js index 33daeec..0a9a771 100644 --- a/src/deprecation.js +++ b/src/deprecation.js @@ -695,6 +695,49 @@ }; /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @name PIXI.Text.calculateFontProperties + * @see PIXI.TextMetrics.measureFont + * @deprecated since version 4.5.0 + * @param {string} font - String representing the style of the font + * @return {Object} Font properties object + */ +core.Text.calculateFontProperties = function calculateFontProperties(font) +{ + warn(`Text.calculateFontProperties is now deprecated, please use the TextMetrics.measureFont`); + + return core.TextMetrics.measureFont(font); +}; + +Object.defineProperties(core.Text, { + fontPropertiesCache: { + get() + { + warn(`Text.fontPropertiesCache is deprecated`); + + return core.TextMetrics._fonts; + }, + }, + fontPropertiesCanvas: { + get() + { + warn(`Text.fontPropertiesCanvas is deprecated`); + + return core.TextMetrics._canvas; + }, + }, + fontPropertiesContext: { + get() + { + warn(`Text.fontPropertiesContext is deprecated`); + + return core.TextMetrics._context; + }, + }, +}); + +/** * @method * @name PIXI.Text#setStyle * @see PIXI.Text#style @@ -710,7 +753,7 @@ /** * @method * @name PIXI.Text#determineFontProperties - * @see PIXI.Text#calculateFontProperties + * @see PIXI.Text#measureFontProperties * @deprecated since version 4.2.0 * @private * @param {string} fontStyle - String representing the style of the font @@ -718,10 +761,31 @@ */ core.Text.prototype.determineFontProperties = function determineFontProperties(fontStyle) { - warn('determineFontProperties is now deprecated, please use the static calculateFontProperties method, ' - + 'e.g : Text.calculateFontProperties(fontStyle);'); + warn('determineFontProperties is now deprecated, please use TextMetrics.measureFont method'); - return core.Text.calculateFontProperties(fontStyle); + return core.TextMetrics.measureFont(fontStyle); +}; + +/** + * @method + * @name PIXI.Text.getFontStyle + * @see PIXI.TextMetrics.getFontStyle + * @deprecated since version 4.5.0 + * @param {PIXI.TextStyle} style - The style to use. + * @return {string} Font string + */ +core.Text.getFontStyle = function getFontStyle(style) +{ + warn('getFontStyle is now deprecated, please use TextStyle.toFontString() instead'); + + style = style || {}; + + if (!(style instanceof core.TextStyle)) + { + style = new core.TextStyle(style); + } + + return style.toFontString(); }; Object.defineProperties(core.TextStyle.prototype, { @@ -830,6 +894,41 @@ warn('setFrame is now deprecated, please use the frame property, e.g: myTexture.frame = frame;'); }; +/** + * @static + * @function + * @name PIXI.Texture.addTextureToCache + * @see PIXI.Texture.addToCache + * @deprecated since 4.5.0 + * @param {PIXI.Texture} texture - The Texture to add to the cache. + * @param {string} id - The id that the texture will be stored against. + */ +core.Texture.addTextureToCache = function addTextureToCache(texture, id) +{ + core.Texture.addToCache(texture, id); + warn('Texture.addTextureToCache is deprecated, please use Texture.addToCache from now on.'); +}; + +/** + * @static + * @function + * @name PIXI.Texture.removeTextureFromCache + * @see PIXI.Texture.removeFromCache + * @deprecated since 4.5.0 + * @param {string} id - The id of the texture to be removed + * @return {PIXI.Texture|null} The texture that was removed + */ +core.Texture.removeTextureFromCache = function removeTextureFromCache(id) +{ + warn('Texture.removeTextureFromCache is deprecated, please use Texture.removeFromCache from now on. ' + + 'Be aware that Texture.removeFromCache does not automatically its BaseTexture from the BaseTextureCache. ' + + 'For that, use BaseTexture.removeFromCache'); + + core.BaseTexture.removeFromCache(id); + + return core.Texture.removeFromCache(id); +}; + Object.defineProperties(filters, { /** diff --git a/src/extras/cacheAsBitmap.js b/src/extras/cacheAsBitmap.js index aea06a8..4a165a3 100644 --- a/src/extras/cacheAsBitmap.js +++ b/src/extras/cacheAsBitmap.js @@ -1,4 +1,7 @@ import * as core from '../core'; +import Texture from '../core/textures/Texture'; +import BaseTexture from '../core/textures/BaseTexture'; +import { uid } from '../core/utils'; const DisplayObject = core.DisplayObject; const _tempMatrix = new core.Matrix(); @@ -20,6 +23,8 @@ */ constructor() { + this.textureCacheId = null; + this.originalRenderWebGL = null; this.originalRenderCanvas = null; this.originalCalculateBounds = null; @@ -185,6 +190,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -289,6 +301,13 @@ const renderTexture = core.RenderTexture.create(bounds.width | 0, bounds.height | 0); + const textureCacheId = `cacheAsBitmap_${uid()}`; + + this._cacheData.textureCacheId = textureCacheId; + + BaseTexture.addToCache(renderTexture.baseTexture, textureCacheId); + Texture.addToCache(renderTexture, textureCacheId); + // need to set // const m = _tempMatrix; @@ -371,6 +390,11 @@ { this._cacheData.sprite._texture.destroy(true); this._cacheData.sprite = null; + + BaseTexture.removeFromCache(this._cacheData.textureCacheId); + Texture.removeFromCache(this._cacheData.textureCacheId); + + this._cacheData.textureCacheId = null; }; /** diff --git a/src/interaction/InteractionEvent.js b/src/interaction/InteractionEvent.js index 75b2966..5dab953 100644 --- a/src/interaction/InteractionEvent.js +++ b/src/interaction/InteractionEvent.js @@ -33,14 +33,14 @@ */ this.currentTarget = null; - /* + /** * Type of the event * * @member {string} */ this.type = null; - /* + /** * InteractionData related to this event * * @member {PIXI.interaction.InteractionData} diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 2d75c7c..e1c50d7 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -257,7 +257,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -265,7 +265,7 @@ * on the display object. * * @event PIXI.interaction.InteractionManager#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -273,7 +273,7 @@ * object. * * @event PIXI.interaction.InteractionManager#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -281,7 +281,7 @@ * over the display object. * * @event PIXI.interaction.InteractionManager#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -289,7 +289,7 @@ * the display object. * * @event PIXI.interaction.InteractionManager#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -297,7 +297,7 @@ * and released on the display object. * * @event PIXI.interaction.InteractionManager#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -306,7 +306,7 @@ * [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. * * @event PIXI.interaction.InteractionManager#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -315,56 +315,56 @@ * [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. * * @event PIXI.interaction.InteractionManager#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved while over the display object * * @event PIXI.interaction.InteractionManager#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved onto the display object * * @event PIXI.interaction.InteractionManager#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device (usually a mouse) is moved off the display object * * @event PIXI.interaction.InteractionManager#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed on the display object. * * @event PIXI.interaction.InteractionManager#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is released over the display object. * * @event PIXI.interaction.InteractionManager#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a pointer event * * @event PIXI.interaction.InteractionManager#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device button is pressed and released on the display object. * * @event PIXI.interaction.InteractionManager#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -372,56 +372,56 @@ * registered a [pointerdown]{@link PIXI.interaction.InteractionManager#event:pointerdown}. * * @event PIXI.interaction.InteractionManager#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved while over the display object * * @event PIXI.interaction.InteractionManager#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved onto the display object * * @event PIXI.interaction.InteractionManager#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a pointer device is moved off the display object * * @event PIXI.interaction.InteractionManager#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed on the display object. * * @event PIXI.interaction.InteractionManager#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is removed from the display object. * * @event PIXI.interaction.InteractionManager#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when the operating system cancels a touch * * @event PIXI.interaction.InteractionManager#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is placed and removed from the display object. * * @event PIXI.interaction.InteractionManager#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -429,14 +429,14 @@ * registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. * * @event PIXI.interaction.InteractionManager#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** * Fired when a touch point is moved along the display object. * * @event PIXI.interaction.InteractionManager#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -444,7 +444,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousedown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -452,7 +452,7 @@ * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -460,7 +460,7 @@ * object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -468,7 +468,7 @@ * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -476,7 +476,7 @@ * the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#click - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -484,7 +484,7 @@ * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightclick - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -494,7 +494,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -504,7 +504,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#rightupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -512,7 +512,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mousemove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -520,7 +520,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -528,7 +528,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#mouseout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -536,7 +536,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerdown - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -544,7 +544,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerup - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -552,7 +552,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointercancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -560,7 +560,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointertap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -569,7 +569,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerupoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -577,7 +577,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointermove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -585,7 +585,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerover - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -593,7 +593,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#pointerout - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -601,7 +601,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchstart - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -609,7 +609,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchend - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -617,7 +617,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchcancel - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -625,7 +625,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#tap - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -634,7 +634,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchendoutside - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ /** @@ -642,7 +642,7 @@ * DisplayObject's `interactive` property must be set to `true` to fire event. * * @event PIXI.DisplayObject#touchmove - * @param {PIXI.interaction.InteractionData} event - Interaction data + * @param {PIXI.interaction.InteractionEvent} event - Interaction event */ } @@ -970,7 +970,7 @@ * testing the interactive objects and passes the hit across in the function. * * @private - * @param {InteractionEvent} interactionEvent - event containing the point that + * @param {PIXI.interaction.InteractionEvent} interactionEvent - event containing the point that * is tested for collision * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - the displayObject * that will be hit test (recursively crawls its children) @@ -1163,7 +1163,7 @@ * Processes the result of the pointer down check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1260,7 +1260,7 @@ * Processes the result of the pointer cancel check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested */ processPointerCancel(interactionEvent, displayObject) @@ -1296,7 +1296,7 @@ * Processes the result of the pointer up check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1434,7 +1434,7 @@ * Processes the result of the pointer move check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1503,7 +1503,7 @@ * Processes the result of the pointer over/out check and dispatches the event if need be * * @private - * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The interaction event wrapping the DOM event * @param {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} displayObject - The display object that was tested * @param {boolean} hit - the result of the hit test on the display object */ @@ -1596,7 +1596,7 @@ * * @private * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData - * @return {InteractionData} - Interaction data for the given pointer identifier + * @return {PIXI.interaction.InteractionData} - Interaction data for the given pointer identifier */ getInteractionDataForPointerId(event) { @@ -1640,10 +1640,11 @@ * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData * * @private - * @param {InteractionEvent} interactionEvent - The event to be configured + * @param {PIXI.interaction.InteractionEvent} interactionEvent - The event to be configured * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent - * @param {InteractionData} interactionData - The InteractionData that will be paired with the InteractionEvent - * @return {InteractionEvent} the interaction event that was passed in + * @param {PIXI.interaction.InteractionData} interactionData - The InteractionData that will be paired + * with the InteractionEvent + * @return {PIXI.interaction.InteractionEvent} the interaction event that was passed in */ configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) { diff --git a/src/mesh/NineSlicePlane.js b/src/mesh/NineSlicePlane.js index 08ef76e..c243246 100644 --- a/src/mesh/NineSlicePlane.js +++ b/src/mesh/NineSlicePlane.js @@ -52,8 +52,8 @@ uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; - this._origWidth = texture.width; - this._origHeight = texture.height; + this._origWidth = texture.orig.width; + this._origHeight = texture.orig.height; this._uvw = 1 / this._origWidth; this._uvh = 1 / this._origHeight; @@ -64,7 +64,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.width = texture.width; + this.width = this._origWidth; /** * The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane @@ -73,7 +73,7 @@ * @memberof PIXI.NineSlicePlane# * @override */ - this.height = texture.height; + this.height = this._origHeight; uvs[2] = uvs[10] = uvs[18] = uvs[26] = this._uvw * leftWidth; uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - (this._uvw * rightWidth); @@ -115,6 +115,8 @@ * @override */ this.bottomHeight = typeof bottomHeight !== 'undefined' ? bottomHeight : DEFAULT_BORDER_SIZE; + + this.refresh(true); } /** diff --git a/test/core/BaseTexture.js b/test/core/BaseTexture.js index bb65068..ac44c6a 100644 --- a/test/core/BaseTexture.js +++ b/test/core/BaseTexture.js @@ -1,11 +1,28 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('BaseTexture', function () { describe('updateImageType', function () { it('should allow no extension', function () { + cleanCache(); + const baseTexture = new PIXI.BaseTexture(); baseTexture.imageUrl = 'http://some.domain.org/100/100'; @@ -17,25 +34,71 @@ it('should remove Canvas BaseTexture from cache on destroy', function () { + cleanCache(); + const canvas = document.createElement('canvas'); - const texture = PIXI.BaseTexture.fromCanvas(canvas); + const baseTexture = PIXI.BaseTexture.fromCanvas(canvas); const _pixiId = canvas._pixiId; - expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(texture); - texture.destroy(); + expect(baseTexture.textureCacheIds.indexOf(_pixiId)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(baseTexture); + baseTexture.destroy(); + expect(baseTexture.textureCacheIds).to.equal(null); expect(PIXI.utils.BaseTextureCache[_pixiId]).to.equal(undefined); }); it('should remove Image BaseTexture from cache on destroy', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.baseTexture.textureCacheIds.indexOf(URL)).to.equal(1); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); texture.destroy(true); + expect(texture.baseTexture).to.equal(null); expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[URL]).to.equal(undefined); + }); + + it('should remove BaseTexture from entire cache using removeFromCache (by BaseTexture instance)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(baseTexture); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove BaseTexture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const baseTexture = new PIXI.BaseTexture(); + + PIXI.BaseTexture.addToCache(baseTexture, NAME); + PIXI.BaseTexture.addToCache(baseTexture, NAME2); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(baseTexture); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); + PIXI.BaseTexture.removeFromCache(NAME); + expect(baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(baseTexture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.BaseTextureCache[NAME2]).to.equal(baseTexture); }); }); diff --git a/test/core/Bounds.js b/test/core/Bounds.js index d2893a6..454a47c 100644 --- a/test/core/Bounds.js +++ b/test/core/Bounds.js @@ -350,7 +350,7 @@ expect(bounds.height).to.equal(20); }); - it('should register correct width/height with an a Text Object', function () + it('should register correct width/height with a Text Object', function () { const parent = new PIXI.Container(); diff --git a/test/core/Spritesheet.js b/test/core/Spritesheet.js index 7601a0c..7a3d6ac 100644 --- a/test/core/Spritesheet.js +++ b/test/core/Spritesheet.js @@ -18,7 +18,7 @@ expect(textures[id]).to.be.an.instanceof(PIXI.Texture); expect(textures[id].width).to.equal(spritesheet.data.frames[id].frame.w / spritesheet.resolution); expect(textures[id].height).to.equal(spritesheet.data.frames[id].frame.h / spritesheet.resolution); - expect(textures[id].textureCacheId).to.equal(id); + expect(textures[id].textureCacheIds.indexOf(id)).to.equal(0); spritesheet.destroy(true); expect(spritesheet.textures).to.be.null; expect(spritesheet.baseTexture).to.be.null; diff --git a/test/core/Text.js b/test/core/Text.js index 5799ad4..e678ba4 100644 --- a/test/core/Text.js +++ b/test/core/Text.js @@ -2,40 +2,6 @@ describe('PIXI.Text', function () { - describe('getFontStyle', function () - { - it('should be a valid API', function () - { - expect(PIXI.Text.getFontStyle).to.be.a.function; - }); - - it('should assume pixel fonts', function () - { - const style = PIXI.Text.getFontStyle({ fontSize: 72 }); - - expect(style).to.be.a.string; - expect(style).to.have.string(' 72px '); - }); - - it('should handle multiple fonts as array', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: ['Georgia', 'Arial', 'sans-serif'], - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - - it('should handle multiple fonts as string', function () - { - const style = PIXI.Text.getFontStyle({ - fontFamily: 'Georgia, "Arial", sans-serif', - }); - - expect(style).to.have.string('"Georgia","Arial","sans-serif"'); - }); - }); - describe('destroy', function () { it('should call through to Sprite.destroy', function () diff --git a/test/core/TextStyle.js b/test/core/TextStyle.js index b5761ad..49b99ef 100644 --- a/test/core/TextStyle.js +++ b/test/core/TextStyle.js @@ -23,4 +23,31 @@ expect(textStyle.fontSize).to.equal(1000); expect(clonedTextStyle.fontSize).to.equal(textStyle.fontSize); }); + + it('should assume pixel fonts', function () + { + const style = new PIXI.TextStyle({ fontSize: 72 }); + const font = style.toFontString(); + + expect(font).to.be.a.string; + expect(font).to.have.string(' 72px '); + }); + + it('should handle multiple fonts as array', function () + { + const style = new PIXI.TextStyle({ + fontFamily: ['Georgia', 'Arial', 'sans-serif'], + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); + + it('should handle multiple fonts as string', function () + { + const style = new PIXI.TextStyle({ + fontFamily: 'Georgia, "Arial", sans-serif', + }); + + expect(style.toFontString()).to.have.string('"Georgia","Arial","sans-serif"'); + }); }); diff --git a/test/core/Texture.js b/test/core/Texture.js index 241ec3e..995ed40 100644 --- a/test/core/Texture.js +++ b/test/core/Texture.js @@ -1,11 +1,26 @@ 'use strict'; +const URL = 'foo.png'; +const NAME = 'foo'; +const NAME2 = 'bar'; + +function cleanCache() +{ + delete PIXI.utils.BaseTextureCache[URL]; + delete PIXI.utils.BaseTextureCache[NAME]; + delete PIXI.utils.BaseTextureCache[NAME2]; + + delete PIXI.utils.TextureCache[URL]; + delete PIXI.utils.TextureCache[NAME]; + delete PIXI.utils.TextureCache[NAME2]; +} + describe('PIXI.Texture', function () { it('should register Texture from Loader', function () { - const URL = 'foo.png'; - const NAME = 'bar'; + cleanCache(); + const image = new Image(); const texture = PIXI.Texture.fromLoader(image, URL, NAME); @@ -19,17 +34,97 @@ it('should remove Texture from cache on destroy', function () { - const NAME = 'foo'; - const NAME2 = 'bar'; + cleanCache(); + const texture = new PIXI.Texture(new PIXI.BaseTexture()); - PIXI.Texture.addTextureToCache(texture, NAME); - PIXI.Texture.addTextureToCache(texture, NAME2); - expect(texture.textureCacheId).to.equal(NAME); + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); texture.destroy(); + expect(texture.textureCacheIds).to.equal(null); expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); }); + + it('should be added to the texture cache correctly, ' + + 'and should remove only itself, not effecting the base texture and its cache', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); + PIXI.Texture.addToCache(texture, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + PIXI.Texture.removeFromCache(NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + }); + + it('should be added to the texture cache correctly using legacy addTextureToCache, ' + + 'and should remove also remove the base texture from its cache with removeTextureFromCache', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.BaseTexture.addToCache(texture.baseTexture, NAME); + PIXI.Texture.addTextureToCache(texture, NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(texture.baseTexture); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + PIXI.Texture.removeTextureFromCache(NAME); + expect(texture.baseTexture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(PIXI.utils.BaseTextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + }); + + it('should remove Texture from entire cache using removeFromCache (by Texture instance)', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + PIXI.Texture.removeFromCache(texture); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(-1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(undefined); + }); + + it('should remove Texture from single cache entry using removeFromCache (by id)', function () + { + cleanCache(); + + const texture = new PIXI.Texture(new PIXI.BaseTexture()); + + PIXI.Texture.addToCache(texture, NAME); + PIXI.Texture.addToCache(texture, NAME2); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(0); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(1); + expect(PIXI.utils.TextureCache[NAME]).to.equal(texture); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + PIXI.Texture.removeFromCache(NAME); + expect(texture.textureCacheIds.indexOf(NAME)).to.equal(-1); + expect(texture.textureCacheIds.indexOf(NAME2)).to.equal(0); + expect(PIXI.utils.TextureCache[NAME]).to.equal(undefined); + expect(PIXI.utils.TextureCache[NAME2]).to.equal(texture); + }); }); diff --git a/test/core/index.js b/test/core/index.js index 58dc30e..28636ab 100755 --- a/test/core/index.js +++ b/test/core/index.js @@ -27,6 +27,7 @@ require('./SpriteRenderer'); require('./WebGLRenderer'); require('./Ellipse'); +require('./BaseTexture'); require('./Texture'); require('./Ticker'); require('./filters');