123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- /* 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;
- }
|