summaryrefslogtreecommitdiffstats
path: root/devtools/client/inspector/fonts/reducers/font-editor.js
blob: fbfee2d6663e02bec28c7202ed5eabbefb9e05cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* 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 {
  getStr,
} = require("resource://devtools/client/inspector/fonts/utils/l10n.js");
const {
  parseFontVariationAxes,
} = require("resource://devtools/client/inspector/fonts/utils/font-utils.js");

const {
  APPLY_FONT_VARIATION_INSTANCE,
  RESET_EDITOR,
  SET_FONT_EDITOR_DISABLED,
  UPDATE_AXIS_VALUE,
  UPDATE_EDITOR_STATE,
  UPDATE_PROPERTY_VALUE,
  UPDATE_WARNING_MESSAGE,
} = require("resource://devtools/client/inspector/fonts/actions/index.js");

const CUSTOM_INSTANCE_NAME = getStr("fontinspector.customInstanceName");

const INITIAL_STATE = {
  // Variable font axes.
  axes: {},
  // Copy of the most recent axes values. Used to revert from a named instance.
  customInstanceValues: [],
  // When true, prevent users from interacting with inputs in the font editor.
  disabled: false,
  // Fonts used on the selected element.
  fonts: [],
  // Current selected font variation instance.
  instance: {
    name: CUSTOM_INSTANCE_NAME,
    values: [],
  },
  // CSS font properties defined on the selected rule.
  properties: {},
  // Unique identifier for the selected element.
  id: "",
  // Warning message with the reason why the font editor cannot be shown.
  warning: getStr("fontinspector.noFontsUsedOnCurrentElement"),
};

const reducers = {
  // Update font editor with the axes and values defined by a font variation instance.
  [APPLY_FONT_VARIATION_INSTANCE](state, { name, values }) {
    const newState = { ...state };
    newState.instance.name = name;
    newState.instance.values = values;

    if (Array.isArray(values) && values.length) {
      newState.axes = values.reduce((acc, value) => {
        acc[value.axis] = value.value;
        return acc;
      }, {});
    }

    return newState;
  },

  [RESET_EDITOR]() {
    return { ...INITIAL_STATE };
  },

  [UPDATE_AXIS_VALUE](state, { axis, value }) {
    const newState = { ...state };
    newState.axes[axis] = value;

    // Cache the latest axes and their values to restore them when switching back from
    // a named font variation instance to the custom font variation instance.
    newState.customInstanceValues = Object.keys(state.axes).map(axisName => {
      return { axis: [axisName], value: state.axes[axisName] };
    });

    // As soon as an axis value is manually updated, mark the custom font variation
    // instance as selected.
    newState.instance.name = CUSTOM_INSTANCE_NAME;

    return newState;
  },

  [SET_FONT_EDITOR_DISABLED](state, { disabled }) {
    return { ...state, disabled };
  },

  [UPDATE_EDITOR_STATE](state, { fonts, properties, id }) {
    const axes = parseFontVariationAxes(properties["font-variation-settings"]);

    // If not defined in font-variation-settings, setup "wght" axis with the value of
    // "font-weight" if it is numeric and not a keyword.
    const weight = properties["font-weight"];
    if (
      axes.wght === undefined &&
      parseFloat(weight).toString() === weight.toString()
    ) {
      axes.wght = parseFloat(weight);
    }

    // If not defined in font-variation-settings, setup "wdth" axis with the percentage
    // number from the value of "font-stretch" if it is not a keyword.
    const stretch = properties["font-stretch"];
    // Match the number part from values like: 10%, 10.55%, 0.2%
    // If there's a match, the number is the second item in the match array.
    const match = stretch.trim().match(/^(\d+(.\d+)?)%$/);
    if (axes.wdth === undefined && match && match[1]) {
      axes.wdth = parseFloat(match[1]);
    }

    // If not defined in font-variation-settings, setup "slnt" axis with the negative
    // of the "font-style: oblique" angle, if any.
    const style = properties["font-style"];
    const obliqueMatch = style.trim().match(/^oblique(?:\s*(\d+(.\d+)?)deg)?$/);
    if (axes.slnt === undefined && obliqueMatch) {
      if (obliqueMatch[1]) {
        // Negate the angle because CSS and OpenType measure in opposite directions.
        axes.slnt = -parseFloat(obliqueMatch[1]);
      } else {
        // Lack of an <angle> for "font-style: oblique" represents "14deg".
        axes.slnt = -14;
      }
    }

    // If not defined in font-variation-settings, setup "ital" axis with 0 for
    // "font-style: normal" or 1 for "font-style: italic".
    if (axes.ital === undefined) {
      if (style === "normal") {
        axes.ital = 0;
      } else if (style === "italic") {
        axes.ital = 1;
      }
    }

    return { ...state, axes, fonts, properties, id };
  },

  [UPDATE_PROPERTY_VALUE](state, { property, value }) {
    const newState = { ...state };
    newState.properties[property] = value;
    return newState;
  },

  [UPDATE_WARNING_MESSAGE](state, { warning }) {
    return { ...state, warning };
  },
};

module.exports = function (state = INITIAL_STATE, action) {
  const reducer = reducers[action.type];
  if (!reducer) {
    return state;
  }
  return reducer(state, action);
};