diff --git a/src/ui/Settings.tsx b/src/ui/Settings.tsx index ef2e3360..abba6e84 100644 --- a/src/ui/Settings.tsx +++ b/src/ui/Settings.tsx @@ -7,6 +7,7 @@ import HotkeyButton from "./HotkeyButton"; import ToggleCheckbox from "./ToggleCheckbox"; import { UrlCache } from "../util/UrlCache"; import { openFileAsArrayBuffer } from "../util/FileUtil"; +import * as FontList from "../util/FontList"; export interface Props { context: string, @@ -175,6 +176,7 @@ export class JsonSettingValueFactory implements SettingValueFactory extends React.Component> { public render() { const settingsRows: JSX.Element[] = []; @@ -908,21 +910,55 @@ export class SettingsComponent extends React.Component> { ]; if (value.Font !== null) { + FontList.load(() => this.setState({})); + const { family, style, weight, stretch } = value.Font; + const styles = FontList.knownStyles.get(family); + + if (FontList.knownFamilies.length > 0) { + children.push( + + ); + } else { + children.push( + { + this.props.setValue( + valueIndex, + expect( + factory.fromFont(e.target.value, style, weight, stretch), + "Unexpected Font", + ), + ); + }} + /> + ); + } + children.push( - { - this.props.setValue( - valueIndex, - expect( - factory.fromFont(e.target.value, style, weight, stretch), - "Unexpected Font", - ), - ); - }} - />, <>Style, , <>Stretch, , ); } diff --git a/src/util/FontList.ts b/src/util/FontList.ts new file mode 100644 index 00000000..eb6f0526 --- /dev/null +++ b/src/util/FontList.ts @@ -0,0 +1,74 @@ +let queriedKnownFonts = false; +export let knownFamilies: string[] = []; +export const knownStyles = new Map>(); + +// There's three values per row: +// 1. What to look for in the style specifier of the loaded font list. +// 2. The value to tell livesplit-core. +// 3. The label to display in the UI. +export const FONT_WEIGHTS = [ + ["thin", "thin", "Thin"], + ["extralight", "extra-light", "Extra Light"], + ["light", "light", "Light"], + ["semilight", "semi-light", "Semi Light"], + ["normal", "normal", "Normal"], + ["medium", "medium", "Medium"], + ["semibold", "semi-bold", "Semi Bold"], + ["bold", "bold", "Bold"], + ["extrabold", "extra-bold", "Extra Bold"], + ["black", "black", "Black"], + ["extrablack", "extra-black", "Extra Black"], +]; + +export const FONT_STRETCHES = [ + ["ultracondensed", "ultra-condensed", "Ultra Condensed"], + ["extracondensed", "extra-condensed", "Extra Condensed"], + ["condensed", "condensed", "Condensed"], + ["semicondensed", "semi-condensed", "Semi Condensed"], + ["normal", "normal", "Normal"], + ["semiexpanded", "semi-expanded", "Semi Expanded"], + ["expanded", "expanded", "Expanded"], + ["extraexpanded", "extra-expanded", "Extra Expanded"], + ["ultraexpanded", "ultra-expanded", "Ultra Expanded"], +]; + +// Italics are always supported. + +export function load(loadedCallback: () => void) { + if (!queriedKnownFonts) { + queriedKnownFonts = true; + if ("queryLocalFonts" in window) { + (window as any).queryLocalFonts().then((availableFonts: any) => { + try { + for (const font of availableFonts) { + if (!knownStyles.has(font.family)) { + knownStyles.set(font.family, new Set(["normal", "bold"])); + } + + const set = knownStyles.get(font.family)!; + const styles = (font.style as string).toLowerCase().split(" "); + + for (const [keyword, value] of FONT_STRETCHES) { + if (styles.includes(keyword)) { + set.add(value); + } + } + + for (const [keyword, value] of FONT_WEIGHTS) { + if (styles.includes(keyword)) { + set.add(value); + } + } + } + + knownFamilies = Array.from(knownStyles.keys()); + + loadedCallback(); + } catch { + // It's fine if it fails, it's an experimental web API, and + // the user may reject it. + } + }); + } + } +}