Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / devtools / client / shared / css-angle.js
blob296a87dab1f196398e0543754c29186cb44bfafe
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/. */
5 "use strict";
7 const SPECIALVALUES = new Set(["initial", "inherit", "unset"]);
9 const { getCSSLexer } = require("resource://devtools/shared/css/lexer.js");
11 loader.lazyRequireGetter(
12   this,
13   "CSS_ANGLEUNIT",
14   "resource://devtools/shared/css/constants.js",
15   true
18 /**
19  * This module is used to convert between various angle units.
20  *
21  * Usage:
22  *   let {angleUtils} = require("devtools/client/shared/css-angle");
23  *   let angle = new angleUtils.CssAngle("180deg");
24  *
25  *   angle.authored === "180deg"
26  *   angle.valid === true
27  *   angle.rad === "3,14rad"
28  *   angle.grad === "200grad"
29  *   angle.turn === "0.5turn"
30  *
31  *   angle.toString() === "180deg"; // Outputs the angle value and its unit
32  *   // Angle objects can be reused
33  *   angle.newAngle("-1TURN") === "-1TURN"; // true
34  */
36 function CssAngle(angleValue) {
37   this.newAngle(angleValue);
40 module.exports.angleUtils = {
41   CssAngle,
42   classifyAngle,
45 CssAngle.prototype = {
46   // Still keep trying to lazy load properties-db by lazily getting ANGLEUNIT
47   get ANGLEUNIT() {
48     return CSS_ANGLEUNIT;
49   },
51   _angleUnit: null,
52   _angleUnitUppercase: false,
54   // The value as-authored.
55   authored: null,
56   // A lower-cased copy of |authored|.
57   lowerCased: null,
59   get angleUnit() {
60     if (this._angleUnit === null) {
61       this._angleUnit = classifyAngle(this.authored);
62     }
63     return this._angleUnit;
64   },
66   set angleUnit(unit) {
67     this._angleUnit = unit;
68   },
70   get valid() {
71     const token = getCSSLexer(this.authored, true).nextToken();
72     if (!token) {
73       return false;
74     }
76     return (
77       token.tokenType === "Dimension" &&
78       token.unit.toLowerCase() in this.ANGLEUNIT
79     );
80   },
82   get specialValue() {
83     return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
84   },
86   get deg() {
87     const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
88     if (invalidOrSpecialValue !== false) {
89       return invalidOrSpecialValue;
90     }
92     const angleUnit = classifyAngle(this.authored);
93     if (angleUnit === this.ANGLEUNIT.deg) {
94       // The angle is valid and is in degree.
95       return this.authored;
96     }
98     let degValue;
99     if (angleUnit === this.ANGLEUNIT.rad) {
100       // The angle is valid and is in radian.
101       degValue = this.authoredAngleValue / (Math.PI / 180);
102     }
104     if (angleUnit === this.ANGLEUNIT.grad) {
105       // The angle is valid and is in gradian.
106       degValue = this.authoredAngleValue * 0.9;
107     }
109     if (angleUnit === this.ANGLEUNIT.turn) {
110       // The angle is valid and is in turn.
111       degValue = this.authoredAngleValue * 360;
112     }
114     let unitStr = this.ANGLEUNIT.deg;
115     if (this._angleUnitUppercase === true) {
116       unitStr = unitStr.toUpperCase();
117     }
118     return `${Math.round(degValue * 100) / 100}${unitStr}`;
119   },
121   get rad() {
122     const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
123     if (invalidOrSpecialValue !== false) {
124       return invalidOrSpecialValue;
125     }
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;
131     }
133     let radValue;
134     if (unit === this.ANGLEUNIT.deg) {
135       // The angle is valid and is in degree.
136       radValue = this.authoredAngleValue * (Math.PI / 180);
137     }
139     if (unit === this.ANGLEUNIT.grad) {
140       // The angle is valid and is in gradian.
141       radValue = this.authoredAngleValue * 0.9 * (Math.PI / 180);
142     }
144     if (unit === this.ANGLEUNIT.turn) {
145       // The angle is valid and is in turn.
146       radValue = this.authoredAngleValue * 360 * (Math.PI / 180);
147     }
149     let unitStr = this.ANGLEUNIT.rad;
150     if (this._angleUnitUppercase === true) {
151       unitStr = unitStr.toUpperCase();
152     }
153     return `${Math.round(radValue * 10000) / 10000}${unitStr}`;
154   },
156   get grad() {
157     const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
158     if (invalidOrSpecialValue !== false) {
159       return invalidOrSpecialValue;
160     }
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;
166     }
168     let gradValue;
169     if (unit === this.ANGLEUNIT.deg) {
170       // The angle is valid and is in degree
171       gradValue = this.authoredAngleValue / 0.9;
172     }
174     if (unit === this.ANGLEUNIT.rad) {
175       // The angle is valid and is in radian
176       gradValue = this.authoredAngleValue / 0.9 / (Math.PI / 180);
177     }
179     if (unit === this.ANGLEUNIT.turn) {
180       // The angle is valid and is in turn
181       gradValue = this.authoredAngleValue * 400;
182     }
184     let unitStr = this.ANGLEUNIT.grad;
185     if (this._angleUnitUppercase === true) {
186       unitStr = unitStr.toUpperCase();
187     }
188     return `${Math.round(gradValue * 100) / 100}${unitStr}`;
189   },
191   get turn() {
192     const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
193     if (invalidOrSpecialValue !== false) {
194       return invalidOrSpecialValue;
195     }
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;
201     }
203     let turnValue;
204     if (unit === this.ANGLEUNIT.deg) {
205       // The angle is valid and is in degree
206       turnValue = this.authoredAngleValue / 360;
207     }
209     if (unit === this.ANGLEUNIT.rad) {
210       // The angle is valid and is in radian
211       turnValue = this.authoredAngleValue / (Math.PI / 180) / 360;
212     }
214     if (unit === this.ANGLEUNIT.grad) {
215       // The angle is valid and is in gradian
216       turnValue = this.authoredAngleValue / 400;
217     }
219     let unitStr = this.ANGLEUNIT.turn;
220     if (this._angleUnitUppercase === true) {
221       unitStr = unitStr.toUpperCase();
222     }
223     return `${Math.round(turnValue * 100) / 100}${unitStr}`;
224   },
226   /**
227    * Check whether the angle value is in the special list e.g.
228    * inherit or invalid.
229    *
230    * @return {String|Boolean}
231    *         - If the current angle is a special value e.g. "inherit" then
232    *           return the angle.
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.
236    */
237   _getInvalidOrSpecialValue() {
238     if (this.specialValue) {
239       return this.specialValue;
240     }
241     if (!this.valid) {
242       return "";
243     }
244     return false;
245   },
247   /**
248    * Change angle
249    *
250    * @param  {String} angle
251    *         Any valid angle value + unit string
252    */
253   newAngle(angle) {
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);
266     return this;
267   },
269   nextAngleUnit() {
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];
280         break;
281       }
282     }
283     return this.toString();
284   },
286   /**
287    * Return a string representing a angle
288    */
289   toString() {
290     let angle;
292     switch (this.angleUnit) {
293       case this.ANGLEUNIT.deg:
294         angle = this.deg;
295         break;
296       case this.ANGLEUNIT.rad:
297         angle = this.rad;
298         break;
299       case this.ANGLEUNIT.grad:
300         angle = this.grad;
301         break;
302       case this.ANGLEUNIT.turn:
303         angle = this.turn;
304         break;
305       default:
306         angle = this.deg;
307     }
309     if (this._angleUnitUppercase && this.angleUnit != this.ANGLEUNIT.authored) {
310       angle = angle.toUpperCase();
311     }
312     return angle;
313   },
315   /**
316    * This method allows comparison of CssAngle objects using ===.
317    */
318   valueOf() {
319     return this.deg;
320   },
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.
329  * @return {String}
330  *         The angle classification, one of "deg", "rad", "grad", or "turn".
331  */
332 function classifyAngle(value) {
333   value = value.toLowerCase();
334   if (value.endsWith("deg")) {
335     return CSS_ANGLEUNIT.deg;
336   }
338   if (value.endsWith("grad")) {
339     return CSS_ANGLEUNIT.grad;
340   }
342   if (value.endsWith("rad")) {
343     return CSS_ANGLEUNIT.rad;
344   }
345   if (value.endsWith("turn")) {
346     return CSS_ANGLEUNIT.turn;
347   }
349   return CSS_ANGLEUNIT.deg;