material.font.features
Version:
Material UI component for displaying opentype font features.
344 lines (343 loc) • 19.4 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SettingsFormat = void 0;
var jsx_runtime_1 = require("react/jsx-runtime");
var react_1 = __importDefault(require("react"));
var react_autobind_1 = __importDefault(require("react-autobind"));
var memoize_one_1 = __importDefault(require("memoize-one"));
var opentype_features_js_1 = __importStar(require("opentype.features.js"));
var Font_js_1 = __importDefault(require("opentype.features.js/lib/Font.js"));
var Select_1 = __importDefault(require("@mui/material/Select"));
var MenuItem_1 = __importDefault(require("@mui/material/MenuItem"));
var FormControlLabel_1 = __importDefault(require("@mui/material/FormControlLabel"));
var Checkbox_1 = __importDefault(require("@mui/material/Checkbox"));
var InputLabel_1 = __importDefault(require("@mui/material/InputLabel"));
var AlternateStylePicker_1 = __importDefault(require("./AlternateStylePicker"));
var SettingsFormat;
(function (SettingsFormat) {
SettingsFormat["CSS"] = "css";
SettingsFormat["LEGACY"] = "legacy";
})(SettingsFormat = exports.SettingsFormat || (exports.SettingsFormat = {}));
var FontFeatures = /** @class */ (function (_super) {
__extends(FontFeatures, _super);
function FontFeatures(props) {
var _this = _super.call(this, props) || this;
_this.settingsToArray = (0, memoize_one_1.default)(_this._settingsToArray);
(0, react_autobind_1.default)(_this);
_this.state = {
features: [],
fontFamily: '',
};
return _this;
}
FontFeatures.prototype.componentDidMount = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.setFeatures()];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
FontFeatures.prototype.componentDidUpdate = function (prevProps) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(this.props.fontFile !== prevProps.fontFile ||
this.props.fontUrl !== prevProps.fontUrl ||
this.props.fontData !== prevProps.fontData)) return [3 /*break*/, 2];
return [4 /*yield*/, this.setFeatures()];
case 1:
_a.sent();
_a.label = 2;
case 2: return [2 /*return*/];
}
});
});
};
FontFeatures.prototype.setFeatures = function () {
return __awaiter(this, void 0, void 0, function () {
var features, fontFamily;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
features = [];
if (!this.props.fontFile) return [3 /*break*/, 2];
return [4 /*yield*/, opentype_features_js_1.default.getFeaturesFromFile(this.props.fontFile)];
case 1:
features = _a.sent();
return [3 /*break*/, 7];
case 2:
if (!this.props.fontUrl) return [3 /*break*/, 4];
return [4 /*yield*/, opentype_features_js_1.default.getFeaturesFromUrl(this.props.fontUrl)];
case 3:
features = (_a.sent()) || [];
return [3 /*break*/, 7];
case 4:
if (!this.props.fontData) return [3 /*break*/, 6];
return [4 /*yield*/, opentype_features_js_1.default.getFeaturesFromArrayBuffer(this.props.fontData)];
case 5:
features = _a.sent();
return [3 /*break*/, 7];
case 6:
if (this.props.fontFeatures)
features = __spreadArray([], this.props.fontFeatures, true);
_a.label = 7;
case 7:
fontFamily = this.props.fontFamily;
if (!!fontFamily) return [3 /*break*/, 12];
if (!this.props.fontFile) return [3 /*break*/, 9];
return [4 /*yield*/, Font_js_1.default.getMetaDataFromFile(this.props.fontFile)];
case 8:
fontFamily = (_a.sent()).fontFamily;
return [3 /*break*/, 12];
case 9:
if (!this.props.fontUrl) return [3 /*break*/, 11];
return [4 /*yield*/, Font_js_1.default.getMetaDataFromUrl(this.props.fontUrl)];
case 10:
fontFamily = (_a.sent()).fontFamily;
return [3 /*break*/, 12];
case 11:
fontFamily = '';
_a.label = 12;
case 12:
this.setState({ features: features, fontFamily: fontFamily });
return [2 /*return*/];
}
});
});
};
// public helper function that converts from old style font feature definitions to the format needed for
// css property "font-feature-settings".
FontFeatures.legacySettingsToCss = function (settings) {
var _a;
if (!settings || ((_a = settings.trim()) === null || _a === void 0 ? void 0 : _a.length) <= 0)
return '';
var features = settings.split(',');
var array = features.map(function (feature) {
var parts = feature === null || feature === void 0 ? void 0 : feature.trim().split('=');
if (!parts || parts.length < 2)
return parts;
return "\"".concat(parts[0].trim(), "\" ").concat(FontFeatures._numberToValue(parts[1].trim()));
});
return array.filter(function (x) { return x; }).join(",");
};
FontFeatures._numberToValue = function (number) {
if (number === '0')
return 'off';
if (number === '1')
return 'on';
return number;
};
FontFeatures._valueToNumber = function (value) {
if (value === 'off')
return '0';
if (value === 'on')
return '1';
return value;
};
FontFeatures.prototype.settings = function () {
if (this.props.settingsFormat !== SettingsFormat.LEGACY)
return this.props.settings;
return FontFeatures.legacySettingsToCss(this.props.settings);
};
FontFeatures.prototype.parseFeatureTagValue = function (featureSettingString) {
var start = featureSettingString.indexOf('"');
var end = featureSettingString.lastIndexOf('"');
if (start === -1 || start === end)
return null;
var keyPart = featureSettingString.substring(start + 1, end);
var valuePart = featureSettingString.substring(end + 1);
keyPart = keyPart.trim();
valuePart = valuePart.trim();
if (valuePart.length <= 0)
valuePart = 'on';
return { feature: keyPart, value: valuePart };
};
FontFeatures.prototype._settingsToArray = function (settings) {
var _this = this;
if (!settings)
return [];
if (settings.trim().length <= 0)
return [];
if (settings.trim() === 'normal')
return [];
var parts = settings.split(',');
parts = parts.map(function (x) { return x.trim(); });
var featureSettingsArray = parts.map(function (x) { return _this.parseFeatureTagValue(x); }).filter(function (x) { return x; });
// Only return (last) unique features keys
return featureSettingsArray.reverse().filter(function (x, pos, array) { return array.findIndex(function (y) { return (y === null || y === void 0 ? void 0 : y.feature) === (x === null || x === void 0 ? void 0 : x.feature); }) === pos; }).reverse();
};
FontFeatures.prototype.settingsArrayToString = function (settings, format) {
if (format === void 0) { format = SettingsFormat.CSS; }
if (format !== SettingsFormat.LEGACY)
return settings.map(function (x) { return "\"".concat(x.feature, "\" ").concat(x.value); }).join(', ');
return settings.map(function (x) { return "".concat(x.feature, "=").concat(FontFeatures._valueToNumber(x.value)); }).join(',');
};
FontFeatures.prototype.handleCheckBoxChange = function (e, tag) {
var _a;
var enabled = (_a = e.target) === null || _a === void 0 ? void 0 : _a.checked;
var updatedValue = { feature: tag, value: enabled ? 'on' : 'off' };
this.emitSettingsChanged(updatedValue);
};
FontFeatures.prototype.emitSettingsChanged = function (updatedValue) {
var settingsArray = this.settingsToArray(this.settings()) || [];
var existingSettingIndex = settingsArray.findIndex(function (x) { return (x === null || x === void 0 ? void 0 : x.feature) === updatedValue.feature; });
if (existingSettingIndex !== -1)
settingsArray[existingSettingIndex] = updatedValue;
else
settingsArray.push(updatedValue);
var newSettingString = this.settingsArrayToString(settingsArray, this.props.settingsFormat);
this.props.onSettingsChanged(newSettingString);
};
FontFeatures.prototype.isCheckBoxEnabled = function (tag) {
var settingsArray = this.settingsToArray(this.settings());
var index = settingsArray.findIndex(function (x) { return (x === null || x === void 0 ? void 0 : x.feature) === tag; });
if (index === -1)
return false;
return !(settingsArray[index].value === 'off' || settingsArray[index].value === '0');
};
FontFeatures.prototype.getCurrentSelectValue = function (tag, options) {
var settingsArray = this.settingsToArray(this.settings());
var index = settingsArray.findIndex(function (x) { return (x === null || x === void 0 ? void 0 : x.feature) === tag; });
if (index === -1)
return 0;
var optionIndex = parseInt(FontFeatures._valueToNumber(settingsArray[index].value));
if (optionIndex >= options.length)
return 0;
return optionIndex;
};
FontFeatures.prototype.handleComboBoxChange = function (e, tag) {
var newValue = e.target.value;
var updatedValue = { feature: tag, value: newValue.toString() };
this.emitSettingsChanged(updatedValue);
};
FontFeatures.prototype.handleStylePickerChange = function (value, tag) {
var updatedValue = { feature: tag, value: value.toString() };
this.emitSettingsChanged(updatedValue);
};
FontFeatures.prototype.render = function () {
var _this = this;
return (0, jsx_runtime_1.jsx)("div", __assign({ className: 'font-features-container' }, { children: this.state.features.map(function (f) {
var _a, _b, _c;
if (!_this.props.showHidden && f.type && (f.type & opentype_features_js_1.FeatureType.Hidden))
return undefined;
var control = null;
if (((_a = f.alternateCharacters) === null || _a === void 0 ? void 0 : _a.length) > 0 && f.options.length > 1)
control = (0, jsx_runtime_1.jsx)(AlternateStylePicker_1.default, { fontFamily: _this.state.fontFamily, description: f.description, character: f.alternateCharacters[0], tag: f.tag, options: f.options, value: _this.getCurrentSelectValue(f.tag, f.options), onChange: function (value) { return _this.handleStylePickerChange(value === null || value === void 0 ? void 0 : value.toString(), f.tag); } });
else if (f.options.length > 1)
control = (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(InputLabel_1.default, { children: f.description }), (0, jsx_runtime_1.jsx)(Select_1.default, __assign({ label: f.description, value: _this.getCurrentSelectValue(f.tag, f.options), onChange: function (e) { return _this.handleComboBoxChange(e, f.tag); } }, { children: f.options.map(function (option, index) { return (0, jsx_runtime_1.jsx)(MenuItem_1.default, __assign({ value: index }, { children: option }), f.tag + '_' + "index"); }) }))] });
else if (((_b = f.alternateCharacters) === null || _b === void 0 ? void 0 : _b.length) > 0)
control = (0, jsx_runtime_1.jsx)(AlternateStylePicker_1.default, { fontFamily: _this.state.fontFamily, description: f.description, character: f.alternateCharacters[0], tag: f.tag, options: ['off', 'on'], value: _this.isCheckBoxEnabled(f.tag) ? 1 : 0, onChange: function (value) { return _this.handleStylePickerChange(value === null || value === void 0 ? void 0 : value.toString(), f.tag); } });
else
control = (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Checkbox_1.default, { checked: _this.isCheckBoxEnabled(f.tag), onChange: function (e) { return _this.handleCheckBoxChange(e, f.tag); } }), label: f.description || ((_c = f.options) === null || _c === void 0 ? void 0 : _c[0]) || f.tag });
return (0, jsx_runtime_1.jsx)("div", __assign({ className: 'font-features-entry' }, { children: control }), f.tag);
}) }));
};
FontFeatures.defaultProps = {
settingsFormat: SettingsFormat.CSS
};
return FontFeatures;
}(react_1.default.PureComponent));
exports.default = FontFeatures;