diff options
Diffstat (limited to 'devtools/client/shared/css-angle.js')
-rw-r--r-- | devtools/client/shared/css-angle.js | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/devtools/client/shared/css-angle.js b/devtools/client/shared/css-angle.js new file mode 100644 index 000000000..f3612ed84 --- /dev/null +++ b/devtools/client/shared/css-angle.js @@ -0,0 +1,345 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {CSS_ANGLEUNIT} = require("devtools/shared/css/properties-db"); + +const SPECIALVALUES = new Set([ + "initial", + "inherit", + "unset" +]); + +const {getCSSLexer} = require("devtools/shared/css/lexer"); + +/** + * This module is used to convert between various angle units. + * + * Usage: + * let {angleUtils} = require("devtools/client/shared/css-angle"); + * let angle = new angleUtils.CssAngle("180deg"); + * + * angle.authored === "180deg" + * angle.valid === true + * angle.rad === "3,14rad" + * angle.grad === "200grad" + * angle.turn === "0.5turn" + * + * angle.toString() === "180deg"; // Outputs the angle value and its unit + * // Angle objects can be reused + * angle.newAngle("-1TURN") === "-1TURN"; // true + */ + +function CssAngle(angleValue) { + this.newAngle(angleValue); +} + +module.exports.angleUtils = { + CssAngle: CssAngle, + classifyAngle: classifyAngle +}; + +CssAngle.ANGLEUNIT = CSS_ANGLEUNIT; + +CssAngle.prototype = { + _angleUnit: null, + _angleUnitUppercase: false, + + // The value as-authored. + authored: null, + // A lower-cased copy of |authored|. + lowerCased: null, + + get angleUnit() { + if (this._angleUnit === null) { + this._angleUnit = classifyAngle(this.authored); + } + return this._angleUnit; + }, + + set angleUnit(unit) { + this._angleUnit = unit; + }, + + get valid() { + let token = getCSSLexer(this.authored).nextToken(); + if (!token) { + return false; + } + return (token.tokenType === "dimension" + && token.text.toLowerCase() in CssAngle.ANGLEUNIT); + }, + + get specialValue() { + return SPECIALVALUES.has(this.lowerCased) ? this.authored : null; + }, + + get deg() { + let invalidOrSpecialValue = this._getInvalidOrSpecialValue(); + if (invalidOrSpecialValue !== false) { + return invalidOrSpecialValue; + } + + let angleUnit = classifyAngle(this.authored); + if (angleUnit === CssAngle.ANGLEUNIT.deg) { + // The angle is valid and is in degree. + return this.authored; + } + + let degValue; + if (angleUnit === CssAngle.ANGLEUNIT.rad) { + // The angle is valid and is in radian. + degValue = this.authoredAngleValue / (Math.PI / 180); + } + + if (angleUnit === CssAngle.ANGLEUNIT.grad) { + // The angle is valid and is in gradian. + degValue = this.authoredAngleValue * 0.9; + } + + if (angleUnit === CssAngle.ANGLEUNIT.turn) { + // The angle is valid and is in turn. + degValue = this.authoredAngleValue * 360; + } + + let unitStr = CssAngle.ANGLEUNIT.deg; + if (this._angleUnitUppercase === true) { + unitStr = unitStr.toUpperCase(); + } + return `${Math.round(degValue * 100) / 100}${unitStr}`; + }, + + get rad() { + let invalidOrSpecialValue = this._getInvalidOrSpecialValue(); + if (invalidOrSpecialValue !== false) { + return invalidOrSpecialValue; + } + + let unit = classifyAngle(this.authored); + if (unit === CssAngle.ANGLEUNIT.rad) { + // The angle is valid and is in radian. + return this.authored; + } + + let radValue; + if (unit === CssAngle.ANGLEUNIT.deg) { + // The angle is valid and is in degree. + radValue = this.authoredAngleValue * (Math.PI / 180); + } + + if (unit === CssAngle.ANGLEUNIT.grad) { + // The angle is valid and is in gradian. + radValue = this.authoredAngleValue * 0.9 * (Math.PI / 180); + } + + if (unit === CssAngle.ANGLEUNIT.turn) { + // The angle is valid and is in turn. + radValue = this.authoredAngleValue * 360 * (Math.PI / 180); + } + + let unitStr = CssAngle.ANGLEUNIT.rad; + if (this._angleUnitUppercase === true) { + unitStr = unitStr.toUpperCase(); + } + return `${Math.round(radValue * 10000) / 10000}${unitStr}`; + }, + + get grad() { + let invalidOrSpecialValue = this._getInvalidOrSpecialValue(); + if (invalidOrSpecialValue !== false) { + return invalidOrSpecialValue; + } + + let unit = classifyAngle(this.authored); + if (unit === CssAngle.ANGLEUNIT.grad) { + // The angle is valid and is in gradian + return this.authored; + } + + let gradValue; + if (unit === CssAngle.ANGLEUNIT.deg) { + // The angle is valid and is in degree + gradValue = this.authoredAngleValue / 0.9; + } + + if (unit === CssAngle.ANGLEUNIT.rad) { + // The angle is valid and is in radian + gradValue = this.authoredAngleValue / 0.9 / (Math.PI / 180); + } + + if (unit === CssAngle.ANGLEUNIT.turn) { + // The angle is valid and is in turn + gradValue = this.authoredAngleValue * 400; + } + + let unitStr = CssAngle.ANGLEUNIT.grad; + if (this._angleUnitUppercase === true) { + unitStr = unitStr.toUpperCase(); + } + return `${Math.round(gradValue * 100) / 100}${unitStr}`; + }, + + get turn() { + let invalidOrSpecialValue = this._getInvalidOrSpecialValue(); + if (invalidOrSpecialValue !== false) { + return invalidOrSpecialValue; + } + + let unit = classifyAngle(this.authored); + if (unit === CssAngle.ANGLEUNIT.turn) { + // The angle is valid and is in turn + return this.authored; + } + + let turnValue; + if (unit === CssAngle.ANGLEUNIT.deg) { + // The angle is valid and is in degree + turnValue = this.authoredAngleValue / 360; + } + + if (unit === CssAngle.ANGLEUNIT.rad) { + // The angle is valid and is in radian + turnValue = (this.authoredAngleValue / (Math.PI / 180)) / 360; + } + + if (unit === CssAngle.ANGLEUNIT.grad) { + // The angle is valid and is in gradian + turnValue = this.authoredAngleValue / 400; + } + + let unitStr = CssAngle.ANGLEUNIT.turn; + if (this._angleUnitUppercase === true) { + unitStr = unitStr.toUpperCase(); + } + return `${Math.round(turnValue * 100) / 100}${unitStr}`; + }, + + /** + * Check whether the angle value is in the special list e.g. + * inherit or invalid. + * + * @return {String|Boolean} + * - If the current angle is a special value e.g. "inherit" then + * return the angle. + * - If the angle is invalid return an empty string. + * - If the angle is a regular angle e.g. 90deg so we return false + * to indicate that the angle is neither invalid nor special. + */ + _getInvalidOrSpecialValue: function () { + if (this.specialValue) { + return this.specialValue; + } + if (!this.valid) { + return ""; + } + return false; + }, + + /** + * Change angle + * + * @param {String} angle + * Any valid angle value + unit string + */ + newAngle: function (angle) { + // Store a lower-cased version of the angle to help with format + // testing. The original text is kept as well so it can be + // returned when needed. + this.lowerCased = angle.toLowerCase(); + this._angleUnitUppercase = (angle === angle.toUpperCase()); + this.authored = angle; + + let reg = new RegExp( + `(${Object.keys(CssAngle.ANGLEUNIT).join("|")})$`, "i"); + let unitStartIdx = angle.search(reg); + this.authoredAngleValue = angle.substring(0, unitStartIdx); + this.authoredAngleUnit = angle.substring(unitStartIdx, angle.length); + + return this; + }, + + nextAngleUnit: function () { + // Get a reordered array from the formats object + // to have the current format at the front so we can cycle through. + let formats = Object.keys(CssAngle.ANGLEUNIT); + let putOnEnd = formats.splice(0, formats.indexOf(this.angleUnit)); + formats = formats.concat(putOnEnd); + let currentDisplayedValue = this[formats[0]]; + + for (let format of formats) { + if (this[format].toLowerCase() !== currentDisplayedValue.toLowerCase()) { + this.angleUnit = CssAngle.ANGLEUNIT[format]; + break; + } + } + return this.toString(); + }, + + /** + * Return a string representing a angle + */ + toString: function () { + let angle; + + switch (this.angleUnit) { + case CssAngle.ANGLEUNIT.deg: + angle = this.deg; + break; + case CssAngle.ANGLEUNIT.rad: + angle = this.rad; + break; + case CssAngle.ANGLEUNIT.grad: + angle = this.grad; + break; + case CssAngle.ANGLEUNIT.turn: + angle = this.turn; + break; + default: + angle = this.deg; + } + + if (this._angleUnitUppercase && + this.angleUnit != CssAngle.ANGLEUNIT.authored) { + angle = angle.toUpperCase(); + } + return angle; + }, + + /** + * This method allows comparison of CssAngle objects using ===. + */ + valueOf: function () { + return this.deg; + }, +}; + +/** + * Given a color, classify its type as one of the possible angle + * units, as known by |CssAngle.angleUnit|. + * + * @param {String} value + * The angle, in any form accepted by CSS. + * @return {String} + * The angle classification, one of "deg", "rad", "grad", or "turn". + */ +function classifyAngle(value) { + value = value.toLowerCase(); + if (value.endsWith("deg")) { + return CssAngle.ANGLEUNIT.deg; + } + + if (value.endsWith("grad")) { + return CssAngle.ANGLEUNIT.grad; + } + + if (value.endsWith("rad")) { + return CssAngle.ANGLEUNIT.rad; + } + if (value.endsWith("turn")) { + return CssAngle.ANGLEUNIT.turn; + } + + return CssAngle.ANGLEUNIT.deg; +} |