1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 const SPECIALVALUES = new Set(["initial", "inherit", "unset"]);
9 const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js");
11 loader.lazyRequireGetter(
14 "resource://devtools/shared/css/constants.js",
19 * This module is used to convert between various angle units.
22 * let {angleUtils} = require("devtools/client/shared/css-angle");
23 * let angle = new angleUtils.CssAngle("180deg");
25 * angle.authored === "180deg"
26 * angle.valid === true
27 * angle.rad === "3,14rad"
28 * angle.grad === "200grad"
29 * angle.turn === "0.5turn"
31 * angle.toString() === "180deg"; // Outputs the angle value and its unit
32 * // Angle objects can be reused
33 * angle.newAngle("-1TURN") === "-1TURN"; // true
36 function CssAngle(angleValue) {
37 this.newAngle(angleValue);
40 module.exports.angleUtils = {
45 CssAngle.prototype = {
46 // Still keep trying to lazy load properties-db by lazily getting ANGLEUNIT
52 _angleUnitUppercase: false,
54 // The value as-authored.
56 // A lower-cased copy of |authored|.
60 if (this._angleUnit === null) {
61 this._angleUnit = classifyAngle(this.authored);
63 return this._angleUnit;
67 this._angleUnit = unit;
71 const token = getCSSLexer(this.authored, true).nextToken();
77 token.tokenType === "Dimension" &&
78 token.unit.toLowerCase() in this.ANGLEUNIT
83 return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
87 const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
88 if (invalidOrSpecialValue !== false) {
89 return invalidOrSpecialValue;
92 const angleUnit = classifyAngle(this.authored);
93 if (angleUnit === this.ANGLEUNIT.deg) {
94 // The angle is valid and is in degree.
99 if (angleUnit === this.ANGLEUNIT.rad) {
100 // The angle is valid and is in radian.
101 degValue = this.authoredAngleValue / (Math.PI / 180);
104 if (angleUnit === this.ANGLEUNIT.grad) {
105 // The angle is valid and is in gradian.
106 degValue = this.authoredAngleValue * 0.9;
109 if (angleUnit === this.ANGLEUNIT.turn) {
110 // The angle is valid and is in turn.
111 degValue = this.authoredAngleValue * 360;
114 let unitStr = this.ANGLEUNIT.deg;
115 if (this._angleUnitUppercase === true) {
116 unitStr = unitStr.toUpperCase();
118 return `${Math.round(degValue * 100) / 100}${unitStr}`;
122 const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
123 if (invalidOrSpecialValue !== false) {
124 return invalidOrSpecialValue;
127 const unit = classifyAngle(this.authored);
128 if (unit === this.ANGLEUNIT.rad) {
129 // The angle is valid and is in radian.
130 return this.authored;
134 if (unit === this.ANGLEUNIT.deg) {
135 // The angle is valid and is in degree.
136 radValue = this.authoredAngleValue * (Math.PI / 180);
139 if (unit === this.ANGLEUNIT.grad) {
140 // The angle is valid and is in gradian.
141 radValue = this.authoredAngleValue * 0.9 * (Math.PI / 180);
144 if (unit === this.ANGLEUNIT.turn) {
145 // The angle is valid and is in turn.
146 radValue = this.authoredAngleValue * 360 * (Math.PI / 180);
149 let unitStr = this.ANGLEUNIT.rad;
150 if (this._angleUnitUppercase === true) {
151 unitStr = unitStr.toUpperCase();
153 return `${Math.round(radValue * 10000) / 10000}${unitStr}`;
157 const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
158 if (invalidOrSpecialValue !== false) {
159 return invalidOrSpecialValue;
162 const unit = classifyAngle(this.authored);
163 if (unit === this.ANGLEUNIT.grad) {
164 // The angle is valid and is in gradian
165 return this.authored;
169 if (unit === this.ANGLEUNIT.deg) {
170 // The angle is valid and is in degree
171 gradValue = this.authoredAngleValue / 0.9;
174 if (unit === this.ANGLEUNIT.rad) {
175 // The angle is valid and is in radian
176 gradValue = this.authoredAngleValue / 0.9 / (Math.PI / 180);
179 if (unit === this.ANGLEUNIT.turn) {
180 // The angle is valid and is in turn
181 gradValue = this.authoredAngleValue * 400;
184 let unitStr = this.ANGLEUNIT.grad;
185 if (this._angleUnitUppercase === true) {
186 unitStr = unitStr.toUpperCase();
188 return `${Math.round(gradValue * 100) / 100}${unitStr}`;
192 const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
193 if (invalidOrSpecialValue !== false) {
194 return invalidOrSpecialValue;
197 const unit = classifyAngle(this.authored);
198 if (unit === this.ANGLEUNIT.turn) {
199 // The angle is valid and is in turn
200 return this.authored;
204 if (unit === this.ANGLEUNIT.deg) {
205 // The angle is valid and is in degree
206 turnValue = this.authoredAngleValue / 360;
209 if (unit === this.ANGLEUNIT.rad) {
210 // The angle is valid and is in radian
211 turnValue = this.authoredAngleValue / (Math.PI / 180) / 360;
214 if (unit === this.ANGLEUNIT.grad) {
215 // The angle is valid and is in gradian
216 turnValue = this.authoredAngleValue / 400;
219 let unitStr = this.ANGLEUNIT.turn;
220 if (this._angleUnitUppercase === true) {
221 unitStr = unitStr.toUpperCase();
223 return `${Math.round(turnValue * 100) / 100}${unitStr}`;
227 * Check whether the angle value is in the special list e.g.
228 * inherit or invalid.
230 * @return {String|Boolean}
231 * - If the current angle is a special value e.g. "inherit" then
233 * - If the angle is invalid return an empty string.
234 * - If the angle is a regular angle e.g. 90deg so we return false
235 * to indicate that the angle is neither invalid nor special.
237 _getInvalidOrSpecialValue() {
238 if (this.specialValue) {
239 return this.specialValue;
250 * @param {String} angle
251 * Any valid angle value + unit string
254 // Store a lower-cased version of the angle to help with format
255 // testing. The original text is kept as well so it can be
256 // returned when needed.
257 this.lowerCased = angle.toLowerCase();
258 this._angleUnitUppercase = angle === angle.toUpperCase();
259 this.authored = angle;
261 const reg = new RegExp(`(${Object.keys(this.ANGLEUNIT).join("|")})$`, "i");
262 const unitStartIdx = angle.search(reg);
263 this.authoredAngleValue = angle.substring(0, unitStartIdx);
264 this.authoredAngleUnit = angle.substring(unitStartIdx, angle.length);
270 // Get a reordered array from the formats object
271 // to have the current format at the front so we can cycle through.
272 let formats = Object.keys(this.ANGLEUNIT);
273 const putOnEnd = formats.splice(0, formats.indexOf(this.angleUnit));
274 formats = formats.concat(putOnEnd);
275 const currentDisplayedValue = this[formats[0]];
277 for (const format of formats) {
278 if (this[format].toLowerCase() !== currentDisplayedValue.toLowerCase()) {
279 this.angleUnit = this.ANGLEUNIT[format];
283 return this.toString();
287 * Return a string representing a angle
292 switch (this.angleUnit) {
293 case this.ANGLEUNIT.deg:
296 case this.ANGLEUNIT.rad:
299 case this.ANGLEUNIT.grad:
302 case this.ANGLEUNIT.turn:
309 if (this._angleUnitUppercase && this.angleUnit != this.ANGLEUNIT.authored) {
310 angle = angle.toUpperCase();
316 * This method allows comparison of CssAngle objects using ===.
324 * Given a color, classify its type as one of the possible angle
325 * units, as known by |CssAngle.angleUnit|.
327 * @param {String} value
328 * The angle, in any form accepted by CSS.
330 * The angle classification, one of "deg", "rad", "grad", or "turn".
332 function classifyAngle(value) {
333 value = value.toLowerCase();
334 if (value.endsWith("deg")) {
335 return CSS_ANGLEUNIT.deg;
338 if (value.endsWith("grad")) {
339 return CSS_ANGLEUNIT.grad;
342 if (value.endsWith("rad")) {
343 return CSS_ANGLEUNIT.rad;
345 if (value.endsWith("turn")) {
346 return CSS_ANGLEUNIT.turn;
349 return CSS_ANGLEUNIT.deg;