gecko-dev/devtools/client/shared/css-angle.js

349 строки
9.1 KiB
JavaScript

/* 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 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 = {
"deg": "deg",
"rad": "rad",
"grad": "grad",
"turn": "turn"
};
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;
}