tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

css-angle.js (9303B)


      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/. */
      4 
      5 "use strict";
      6 
      7 const SPECIALVALUES = new Set(["initial", "inherit", "unset"]);
      8 
      9 const {
     10  InspectorCSSParserWrapper,
     11 } = require("resource://devtools/shared/css/lexer.js");
     12 
     13 loader.lazyRequireGetter(
     14  this,
     15  "CSS_ANGLEUNIT",
     16  "resource://devtools/shared/css/constants.js",
     17  true
     18 );
     19 
     20 /**
     21 * This module is used to convert between various angle units.
     22 *
     23 * Usage:
     24 *   let {angleUtils} = require("devtools/client/shared/css-angle");
     25 *   let angle = new angleUtils.CssAngle("180deg");
     26 *
     27 *   angle.authored === "180deg"
     28 *   angle.valid === true
     29 *   angle.rad === "3,14rad"
     30 *   angle.grad === "200grad"
     31 *   angle.turn === "0.5turn"
     32 *
     33 *   angle.toString() === "180deg"; // Outputs the angle value and its unit
     34 *   // Angle objects can be reused
     35 *   angle.newAngle("-1TURN") === "-1TURN"; // true
     36 */
     37 
     38 class CssAngle {
     39  constructor(angleValue) {
     40    this.newAngle(angleValue);
     41  }
     42 
     43  // Still keep trying to lazy load properties-db by lazily getting ANGLEUNIT
     44  get ANGLEUNIT() {
     45    return CSS_ANGLEUNIT;
     46  }
     47 
     48  _angleUnit = null;
     49  _angleUnitUppercase = false;
     50 
     51  // The value as-authored.
     52  authored = null;
     53  // A lower-cased copy of |authored|.
     54  lowerCased = null;
     55 
     56  get angleUnit() {
     57    if (this._angleUnit === null) {
     58      this._angleUnit = classifyAngle(this.authored);
     59    }
     60    return this._angleUnit;
     61  }
     62 
     63  set angleUnit(unit) {
     64    this._angleUnit = unit;
     65  }
     66 
     67  get valid() {
     68    const token = new InspectorCSSParserWrapper(this.authored).nextToken();
     69    if (!token) {
     70      return false;
     71    }
     72 
     73    return (
     74      token.tokenType === "Dimension" &&
     75      token.unit.toLowerCase() in this.ANGLEUNIT
     76    );
     77  }
     78 
     79  get specialValue() {
     80    return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
     81  }
     82 
     83  get deg() {
     84    const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
     85    if (invalidOrSpecialValue !== false) {
     86      return invalidOrSpecialValue;
     87    }
     88 
     89    const angleUnit = classifyAngle(this.authored);
     90    if (angleUnit === this.ANGLEUNIT.deg) {
     91      // The angle is valid and is in degree.
     92      return this.authored;
     93    }
     94 
     95    let degValue;
     96    if (angleUnit === this.ANGLEUNIT.rad) {
     97      // The angle is valid and is in radian.
     98      degValue = this.authoredAngleValue / (Math.PI / 180);
     99    }
    100 
    101    if (angleUnit === this.ANGLEUNIT.grad) {
    102      // The angle is valid and is in gradian.
    103      degValue = this.authoredAngleValue * 0.9;
    104    }
    105 
    106    if (angleUnit === this.ANGLEUNIT.turn) {
    107      // The angle is valid and is in turn.
    108      degValue = this.authoredAngleValue * 360;
    109    }
    110 
    111    let unitStr = this.ANGLEUNIT.deg;
    112    if (this._angleUnitUppercase === true) {
    113      unitStr = unitStr.toUpperCase();
    114    }
    115    return `${Math.round(degValue * 100) / 100}${unitStr}`;
    116  }
    117 
    118  get rad() {
    119    const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
    120    if (invalidOrSpecialValue !== false) {
    121      return invalidOrSpecialValue;
    122    }
    123 
    124    const unit = classifyAngle(this.authored);
    125    if (unit === this.ANGLEUNIT.rad) {
    126      // The angle is valid and is in radian.
    127      return this.authored;
    128    }
    129 
    130    let radValue;
    131    if (unit === this.ANGLEUNIT.deg) {
    132      // The angle is valid and is in degree.
    133      radValue = this.authoredAngleValue * (Math.PI / 180);
    134    }
    135 
    136    if (unit === this.ANGLEUNIT.grad) {
    137      // The angle is valid and is in gradian.
    138      radValue = this.authoredAngleValue * 0.9 * (Math.PI / 180);
    139    }
    140 
    141    if (unit === this.ANGLEUNIT.turn) {
    142      // The angle is valid and is in turn.
    143      radValue = this.authoredAngleValue * 360 * (Math.PI / 180);
    144    }
    145 
    146    let unitStr = this.ANGLEUNIT.rad;
    147    if (this._angleUnitUppercase === true) {
    148      unitStr = unitStr.toUpperCase();
    149    }
    150    return `${Math.round(radValue * 10000) / 10000}${unitStr}`;
    151  }
    152 
    153  get grad() {
    154    const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
    155    if (invalidOrSpecialValue !== false) {
    156      return invalidOrSpecialValue;
    157    }
    158 
    159    const unit = classifyAngle(this.authored);
    160    if (unit === this.ANGLEUNIT.grad) {
    161      // The angle is valid and is in gradian
    162      return this.authored;
    163    }
    164 
    165    let gradValue;
    166    if (unit === this.ANGLEUNIT.deg) {
    167      // The angle is valid and is in degree
    168      gradValue = this.authoredAngleValue / 0.9;
    169    }
    170 
    171    if (unit === this.ANGLEUNIT.rad) {
    172      // The angle is valid and is in radian
    173      gradValue = this.authoredAngleValue / 0.9 / (Math.PI / 180);
    174    }
    175 
    176    if (unit === this.ANGLEUNIT.turn) {
    177      // The angle is valid and is in turn
    178      gradValue = this.authoredAngleValue * 400;
    179    }
    180 
    181    let unitStr = this.ANGLEUNIT.grad;
    182    if (this._angleUnitUppercase === true) {
    183      unitStr = unitStr.toUpperCase();
    184    }
    185    return `${Math.round(gradValue * 100) / 100}${unitStr}`;
    186  }
    187 
    188  get turn() {
    189    const invalidOrSpecialValue = this._getInvalidOrSpecialValue();
    190    if (invalidOrSpecialValue !== false) {
    191      return invalidOrSpecialValue;
    192    }
    193 
    194    const unit = classifyAngle(this.authored);
    195    if (unit === this.ANGLEUNIT.turn) {
    196      // The angle is valid and is in turn
    197      return this.authored;
    198    }
    199 
    200    let turnValue;
    201    if (unit === this.ANGLEUNIT.deg) {
    202      // The angle is valid and is in degree
    203      turnValue = this.authoredAngleValue / 360;
    204    }
    205 
    206    if (unit === this.ANGLEUNIT.rad) {
    207      // The angle is valid and is in radian
    208      turnValue = this.authoredAngleValue / (Math.PI / 180) / 360;
    209    }
    210 
    211    if (unit === this.ANGLEUNIT.grad) {
    212      // The angle is valid and is in gradian
    213      turnValue = this.authoredAngleValue / 400;
    214    }
    215 
    216    let unitStr = this.ANGLEUNIT.turn;
    217    if (this._angleUnitUppercase === true) {
    218      unitStr = unitStr.toUpperCase();
    219    }
    220    return `${Math.round(turnValue * 100) / 100}${unitStr}`;
    221  }
    222 
    223  /**
    224   * Check whether the angle value is in the special list e.g.
    225   * inherit or invalid.
    226   *
    227   * @return {string | boolean}
    228   *         - If the current angle is a special value e.g. "inherit" then
    229   *           return the angle.
    230   *         - If the angle is invalid return an empty string.
    231   *         - If the angle is a regular angle e.g. 90deg so we return false
    232   *           to indicate that the angle is neither invalid nor special.
    233   */
    234  _getInvalidOrSpecialValue() {
    235    if (this.specialValue) {
    236      return this.specialValue;
    237    }
    238    if (!this.valid) {
    239      return "";
    240    }
    241    return false;
    242  }
    243 
    244  /**
    245   * Change angle
    246   *
    247   * @param  {string} angle
    248   *         Any valid angle value + unit string
    249   */
    250  newAngle(angle) {
    251    // Store a lower-cased version of the angle to help with format
    252    // testing.  The original text is kept as well so it can be
    253    // returned when needed.
    254    this.lowerCased = angle.toLowerCase();
    255    this._angleUnitUppercase = angle === angle.toUpperCase();
    256    this.authored = angle;
    257 
    258    const reg = new RegExp(`(${Object.keys(this.ANGLEUNIT).join("|")})$`, "i");
    259    const unitStartIdx = angle.search(reg);
    260    this.authoredAngleValue = angle.substring(0, unitStartIdx);
    261    this.authoredAngleUnit = angle.substring(unitStartIdx, angle.length);
    262 
    263    return this;
    264  }
    265 
    266  nextAngleUnit() {
    267    // Get a reordered array from the formats object
    268    // to have the current format at the front so we can cycle through.
    269    let formats = Object.keys(this.ANGLEUNIT);
    270    const putOnEnd = formats.splice(0, formats.indexOf(this.angleUnit));
    271    formats = formats.concat(putOnEnd);
    272    const currentDisplayedValue = this[formats[0]];
    273 
    274    for (const format of formats) {
    275      if (this[format].toLowerCase() !== currentDisplayedValue.toLowerCase()) {
    276        this.angleUnit = this.ANGLEUNIT[format];
    277        break;
    278      }
    279    }
    280    return this.toString();
    281  }
    282 
    283  /**
    284   * Return a string representing a angle
    285   */
    286  toString() {
    287    let angle;
    288 
    289    switch (this.angleUnit) {
    290      case this.ANGLEUNIT.deg:
    291        angle = this.deg;
    292        break;
    293      case this.ANGLEUNIT.rad:
    294        angle = this.rad;
    295        break;
    296      case this.ANGLEUNIT.grad:
    297        angle = this.grad;
    298        break;
    299      case this.ANGLEUNIT.turn:
    300        angle = this.turn;
    301        break;
    302      default:
    303        angle = this.deg;
    304    }
    305 
    306    if (this._angleUnitUppercase && this.angleUnit != this.ANGLEUNIT.authored) {
    307      angle = angle.toUpperCase();
    308    }
    309    return angle;
    310  }
    311 
    312  /**
    313   * This method allows comparison of CssAngle objects using ===.
    314   */
    315  valueOf() {
    316    return this.deg;
    317  }
    318 }
    319 
    320 /**
    321 * Given a color, classify its type as one of the possible angle
    322 * units, as known by |CssAngle.angleUnit|.
    323 *
    324 * @param  {string} value
    325 *         The angle, in any form accepted by CSS.
    326 * @return {string}
    327 *         The angle classification, one of "deg", "rad", "grad", or "turn".
    328 */
    329 function classifyAngle(value) {
    330  value = value.toLowerCase();
    331  if (value.endsWith("deg")) {
    332    return CSS_ANGLEUNIT.deg;
    333  }
    334 
    335  if (value.endsWith("grad")) {
    336    return CSS_ANGLEUNIT.grad;
    337  }
    338 
    339  if (value.endsWith("rad")) {
    340    return CSS_ANGLEUNIT.rad;
    341  }
    342  if (value.endsWith("turn")) {
    343    return CSS_ANGLEUNIT.turn;
    344  }
    345 
    346  return CSS_ANGLEUNIT.deg;
    347 }
    348 
    349 module.exports.angleUtils = {
    350  CssAngle,
    351  classifyAngle,
    352 };