font-editor.js (5129B)
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 getStr, 9 } = require("resource://devtools/client/inspector/fonts/utils/l10n.js"); 10 const { 11 parseFontVariationAxes, 12 } = require("resource://devtools/client/inspector/fonts/utils/font-utils.js"); 13 14 const { 15 APPLY_FONT_VARIATION_INSTANCE, 16 RESET_EDITOR, 17 SET_FONT_EDITOR_DISABLED, 18 UPDATE_AXIS_VALUE, 19 UPDATE_EDITOR_STATE, 20 UPDATE_PROPERTY_VALUE, 21 UPDATE_WARNING_MESSAGE, 22 } = require("resource://devtools/client/inspector/fonts/actions/index.js"); 23 24 const CUSTOM_INSTANCE_NAME = getStr("fontinspector.customInstanceName"); 25 26 const INITIAL_STATE = { 27 // Variable font axes. 28 axes: {}, 29 // Copy of the most recent axes values. Used to revert from a named instance. 30 customInstanceValues: [], 31 // When true, prevent users from interacting with inputs in the font editor. 32 disabled: false, 33 // Fonts used on the selected element. 34 fonts: [], 35 // Current selected font variation instance. 36 instance: { 37 name: CUSTOM_INSTANCE_NAME, 38 values: [], 39 }, 40 // CSS font properties defined on the selected rule. 41 properties: {}, 42 // Unique identifier for the selected element. 43 id: "", 44 // Warning message with the reason why the font editor cannot be shown. 45 warning: getStr("fontinspector.noFontsUsedOnCurrentElement"), 46 }; 47 48 const reducers = { 49 // Update font editor with the axes and values defined by a font variation instance. 50 [APPLY_FONT_VARIATION_INSTANCE](state, { name, values }) { 51 const newState = { ...state }; 52 newState.instance.name = name; 53 newState.instance.values = values; 54 55 if (Array.isArray(values) && values.length) { 56 newState.axes = values.reduce((acc, value) => { 57 acc[value.axis] = value.value; 58 return acc; 59 }, {}); 60 } 61 62 return newState; 63 }, 64 65 [RESET_EDITOR]() { 66 return { ...INITIAL_STATE }; 67 }, 68 69 [UPDATE_AXIS_VALUE](state, { axis, value }) { 70 const newState = { ...state }; 71 newState.axes[axis] = value; 72 73 // Cache the latest axes and their values to restore them when switching back from 74 // a named font variation instance to the custom font variation instance. 75 newState.customInstanceValues = Object.keys(state.axes).map(axisName => { 76 return { axis: [axisName], value: state.axes[axisName] }; 77 }); 78 79 // As soon as an axis value is manually updated, mark the custom font variation 80 // instance as selected. 81 newState.instance.name = CUSTOM_INSTANCE_NAME; 82 83 return newState; 84 }, 85 86 [SET_FONT_EDITOR_DISABLED](state, { disabled }) { 87 return { ...state, disabled }; 88 }, 89 90 [UPDATE_EDITOR_STATE](state, { fonts, properties, id }) { 91 const axes = parseFontVariationAxes(properties["font-variation-settings"]); 92 93 // If not defined in font-variation-settings, setup "wght" axis with the value of 94 // "font-weight" if it is numeric and not a keyword. 95 const weight = properties["font-weight"]; 96 if ( 97 axes.wght === undefined && 98 parseFloat(weight).toString() === weight.toString() 99 ) { 100 axes.wght = parseFloat(weight); 101 } 102 103 // If not defined in font-variation-settings, setup "wdth" axis with the percentage 104 // number from the value of "font-stretch" if it is not a keyword. 105 const stretch = properties["font-stretch"]; 106 // Match the number part from values like: 10%, 10.55%, 0.2% 107 // If there's a match, the number is the second item in the match array. 108 const match = stretch.trim().match(/^(\d+(.\d+)?)%$/); 109 if (axes.wdth === undefined && match && match[1]) { 110 axes.wdth = parseFloat(match[1]); 111 } 112 113 // If not defined in font-variation-settings, setup "slnt" axis with the negative 114 // of the "font-style: oblique" angle, if any. 115 const style = properties["font-style"]; 116 const obliqueMatch = style.trim().match(/^oblique(?:\s*(\d+(.\d+)?)deg)?$/); 117 if (axes.slnt === undefined && obliqueMatch) { 118 if (obliqueMatch[1]) { 119 // Negate the angle because CSS and OpenType measure in opposite directions. 120 axes.slnt = -parseFloat(obliqueMatch[1]); 121 } else { 122 // Lack of an <angle> for "font-style: oblique" represents "14deg". 123 axes.slnt = -14; 124 } 125 } 126 127 // If not defined in font-variation-settings, setup "ital" axis with 0 for 128 // "font-style: normal" or 1 for "font-style: italic". 129 if (axes.ital === undefined) { 130 if (style === "normal") { 131 axes.ital = 0; 132 } else if (style === "italic") { 133 axes.ital = 1; 134 } 135 } 136 137 return { ...state, axes, fonts, properties, id }; 138 }, 139 140 [UPDATE_PROPERTY_VALUE](state, { property, value }) { 141 const newState = { ...state }; 142 newState.properties[property] = value; 143 return newState; 144 }, 145 146 [UPDATE_WARNING_MESSAGE](state, { warning }) { 147 return { ...state, warning }; 148 }, 149 }; 150 151 module.exports = function (state = INITIAL_STATE, action) { 152 const reducer = reducers[action.type]; 153 if (!reducer) { 154 return state; 155 } 156 return reducer(state, action); 157 };