tor-browser

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

FontEditor.js (10158B)


      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 {
      8  createFactory,
      9  PureComponent,
     10 } = require("resource://devtools/client/shared/vendor/react.mjs");
     11 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
     12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs");
     13 
     14 const FontAxis = createFactory(
     15  require("resource://devtools/client/inspector/fonts/components/FontAxis.js")
     16 );
     17 const FontName = createFactory(
     18  require("resource://devtools/client/inspector/fonts/components/FontName.js")
     19 );
     20 const FontSize = createFactory(
     21  require("resource://devtools/client/inspector/fonts/components/FontSize.js")
     22 );
     23 const FontStyle = createFactory(
     24  require("resource://devtools/client/inspector/fonts/components/FontStyle.js")
     25 );
     26 const FontWeight = createFactory(
     27  require("resource://devtools/client/inspector/fonts/components/FontWeight.js")
     28 );
     29 const LetterSpacing = createFactory(
     30  require("resource://devtools/client/inspector/fonts/components/LetterSpacing.js")
     31 );
     32 const LineHeight = createFactory(
     33  require("resource://devtools/client/inspector/fonts/components/LineHeight.js")
     34 );
     35 
     36 const {
     37  getStr,
     38 } = require("resource://devtools/client/inspector/fonts/utils/l10n.js");
     39 const Types = require("resource://devtools/client/inspector/fonts/types.js");
     40 
     41 // Maximum number of font families to be shown by default. Any others will be hidden
     42 // under a collapsed <details> element with a toggle to reveal them.
     43 const MAX_FONTS = 3;
     44 
     45 class FontEditor extends PureComponent {
     46  static get propTypes() {
     47    return {
     48      fontEditor: PropTypes.shape(Types.fontEditor).isRequired,
     49      onInstanceChange: PropTypes.func.isRequired,
     50      onPropertyChange: PropTypes.func.isRequired,
     51      onToggleFontHighlight: PropTypes.func.isRequired,
     52    };
     53  }
     54 
     55  /**
     56   * Get an array of FontAxis components with editing controls for of the given variable
     57   * font axes. If no axes were given, return null.
     58   * If an axis' value was declared on the font-variation-settings CSS property or was
     59   * changed using the font editor, use that value, otherwise use the axis default.
     60   *
     61   * @param  {Array} fontAxes
     62   *         Array of font axis instances
     63   * @param  {object} editedAxes
     64   *         Object with axes and values edited by the user or defined in the CSS
     65   *         declaration for font-variation-settings.
     66   * @return {Array|null}
     67   */
     68  renderAxes(fontAxes = [], editedAxes) {
     69    if (!fontAxes.length) {
     70      return null;
     71    }
     72 
     73    return fontAxes.map(axis => {
     74      return FontAxis({
     75        key: axis.tag,
     76        axis,
     77        disabled: this.props.fontEditor.disabled,
     78        onChange: this.props.onPropertyChange,
     79        minLabel: true,
     80        maxLabel: true,
     81        value: editedAxes[axis.tag] || axis.defaultValue,
     82      });
     83    });
     84  }
     85 
     86  /**
     87   * Render fonts used on the selected node grouped by font-family.
     88   *
     89   * @param {Array} fonts
     90   *        Fonts used on selected node.
     91   * @return {DOMNode}
     92   */
     93  renderUsedFonts(fonts) {
     94    if (!fonts.length) {
     95      return null;
     96    }
     97 
     98    // Group fonts by family name.
     99    const fontGroups = fonts.reduce((acc, font) => {
    100      const family = font.CSSFamilyName.toString();
    101      acc[family] = acc[family] || [];
    102      acc[family].push(font);
    103      return acc;
    104    }, {});
    105 
    106    const renderedFontGroups = Object.keys(fontGroups).map(family => {
    107      return this.renderFontGroup(family, fontGroups[family]);
    108    });
    109 
    110    const topFontsList = renderedFontGroups.slice(0, MAX_FONTS);
    111    const moreFontsList = renderedFontGroups.slice(
    112      MAX_FONTS,
    113      renderedFontGroups.length
    114    );
    115 
    116    const moreFonts = !moreFontsList.length
    117      ? null
    118      : dom.details(
    119          {},
    120          dom.summary(
    121            {},
    122            dom.span(
    123              { className: "label-open" },
    124              getStr("fontinspector.showMore")
    125            ),
    126            dom.span(
    127              { className: "label-close" },
    128              getStr("fontinspector.showLess")
    129            )
    130          ),
    131          moreFontsList
    132        );
    133 
    134    return dom.label(
    135      {
    136        className: "font-control font-control-used-fonts",
    137      },
    138      dom.span(
    139        {
    140          className: "font-control-label",
    141        },
    142        getStr("fontinspector.fontsUsedLabel")
    143      ),
    144      dom.div(
    145        {
    146          className: "font-control-box",
    147        },
    148        topFontsList,
    149        moreFonts
    150      )
    151    );
    152  }
    153 
    154  renderFontGroup(family, fonts = []) {
    155    const group = fonts.map((font, i) => {
    156      return FontName({
    157        key: font.name + ":" + i,
    158        font,
    159        onToggleFontHighlight: this.props.onToggleFontHighlight,
    160      });
    161    });
    162 
    163    return dom.div(
    164      {
    165        key: family,
    166        className: "font-group",
    167      },
    168      dom.div(
    169        {
    170          className: "font-family-name",
    171        },
    172        family
    173      ),
    174      group
    175    );
    176  }
    177 
    178  renderFontSize(value) {
    179    return (
    180      value !== null &&
    181      FontSize({
    182        key: `${this.props.fontEditor.id}:font-size`,
    183        disabled: this.props.fontEditor.disabled,
    184        onChange: this.props.onPropertyChange,
    185        value,
    186      })
    187    );
    188  }
    189 
    190  renderLineHeight(value) {
    191    return (
    192      value !== null &&
    193      LineHeight({
    194        key: `${this.props.fontEditor.id}:line-height`,
    195        disabled: this.props.fontEditor.disabled,
    196        onChange: this.props.onPropertyChange,
    197        value,
    198      })
    199    );
    200  }
    201 
    202  renderLetterSpacing(value) {
    203    return (
    204      value !== null &&
    205      LetterSpacing({
    206        key: `${this.props.fontEditor.id}:letter-spacing`,
    207        disabled: this.props.fontEditor.disabled,
    208        onChange: this.props.onPropertyChange,
    209        value,
    210      })
    211    );
    212  }
    213 
    214  renderFontStyle(value) {
    215    return (
    216      value &&
    217      FontStyle({
    218        onChange: this.props.onPropertyChange,
    219        disabled: this.props.fontEditor.disabled,
    220        value,
    221      })
    222    );
    223  }
    224 
    225  renderFontWeight(value) {
    226    return (
    227      value !== null &&
    228      FontWeight({
    229        onChange: this.props.onPropertyChange,
    230        disabled: this.props.fontEditor.disabled,
    231        value,
    232      })
    233    );
    234  }
    235 
    236  /**
    237   * Get a dropdown which allows selecting between variation instances defined by a font.
    238   *
    239   * @param {Array} fontInstances
    240   *        Named variation instances as provided with the font file.
    241   * @param {object} selectedInstance
    242   *        Object with information about the currently selected variation instance.
    243   *        Example:
    244   *        {
    245   *          name: "Custom",
    246   *          values: []
    247   *        }
    248   * @return {DOMNode}
    249   */
    250  renderInstances(fontInstances = [], selectedInstance = {}) {
    251    // Append a "Custom" instance entry which represents the latest manual axes changes.
    252    const customInstance = {
    253      name: getStr("fontinspector.customInstanceName"),
    254      values: this.props.fontEditor.customInstanceValues,
    255    };
    256    fontInstances = [...fontInstances, customInstance];
    257 
    258    // Generate the <option> elements for the dropdown.
    259    const instanceOptions = fontInstances.map(instance =>
    260      dom.option(
    261        {
    262          key: instance.name,
    263          value: instance.name,
    264        },
    265        instance.name
    266      )
    267    );
    268 
    269    // Generate the dropdown.
    270    const instanceSelect = dom.select(
    271      {
    272        className: "font-control-input font-value-select",
    273        value: selectedInstance.name || customInstance.name,
    274        onChange: e => {
    275          const instance = fontInstances.find(
    276            inst => e.target.value === inst.name
    277          );
    278          instance &&
    279            this.props.onInstanceChange(instance.name, instance.values);
    280        },
    281      },
    282      instanceOptions
    283    );
    284 
    285    return dom.label(
    286      {
    287        className: "font-control",
    288      },
    289      dom.span(
    290        {
    291          className: "font-control-label",
    292        },
    293        getStr("fontinspector.fontInstanceLabel")
    294      ),
    295      instanceSelect
    296    );
    297  }
    298 
    299  renderWarning(warning) {
    300    return dom.div(
    301      {
    302        id: "font-editor",
    303      },
    304      dom.div(
    305        {
    306          className: "devtools-sidepanel-no-result",
    307        },
    308        warning
    309      )
    310    );
    311  }
    312 
    313  render() {
    314    const { fontEditor } = this.props;
    315    const { fonts, axes, instance, properties, warning } = fontEditor;
    316    // Pick the first font to show editor controls regardless of how many fonts are used.
    317    const font = fonts[0];
    318    const hasFontAxes = font?.variationAxes;
    319    const hasFontInstances = font?.variationInstances?.length > 0;
    320    const hasSlantOrItalicAxis = font?.variationAxes?.find(axis => {
    321      return axis.tag === "slnt" || axis.tag === "ital";
    322    });
    323    const hasWeightAxis = font?.variationAxes?.find(axis => {
    324      return axis.tag === "wght";
    325    });
    326 
    327    // Show the empty state with a warning message when a used font was not found.
    328    if (!font) {
    329      return this.renderWarning(warning);
    330    }
    331 
    332    return dom.div(
    333      {
    334        id: "font-editor",
    335      },
    336      // Always render UI for used fonts.
    337      this.renderUsedFonts(fonts),
    338      // Render UI for font variation instances if they are defined.
    339      hasFontInstances &&
    340        this.renderInstances(font.variationInstances, instance),
    341      // Always render UI for font size.
    342      this.renderFontSize(properties["font-size"]),
    343      // Always render UI for line height.
    344      this.renderLineHeight(properties["line-height"]),
    345      // Always render UI for letter spacing.
    346      this.renderLetterSpacing(properties["letter-spacing"]),
    347      // Render UI for font weight if no "wght" registered axis is defined.
    348      !hasWeightAxis && this.renderFontWeight(properties["font-weight"]),
    349      // Render UI for font style if no "slnt" or "ital" registered axis is defined.
    350      !hasSlantOrItalicAxis && this.renderFontStyle(properties["font-style"]),
    351      // Render UI for each variable font axis if any are defined.
    352      hasFontAxes && this.renderAxes(font.variationAxes, axes)
    353    );
    354  }
    355 }
    356 
    357 module.exports = FontEditor;