Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query local fonts to show a font list #860

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions src/ui/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
context: string,
Expand Down Expand Up @@ -175,6 +176,7 @@ export class JsonSettingValueFactory implements SettingValueFactory<ExtendedSett
}
}


export class SettingsComponent<T> extends React.Component<Props<T>> {
public render() {
const settingsRows: JSX.Element[] = [];
Expand Down Expand Up @@ -908,21 +910,55 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
];

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(
<select
style={{
fontFamily: family,
}}
value={family}
onChange={(e) => {
this.props.setValue(
valueIndex,
expect(
factory.fromFont(e.target.value, style, weight, stretch),
"Unexpected Font",
),
);
}}
>
<option value=""></option>
{
FontList.knownFamilies.map((n) =>
<option value={n} style={{ fontFamily: n }}>{n}</option>
)
}
</select>
);
} else {
children.push(
<input
value={family}
onChange={(e) => {
this.props.setValue(
valueIndex,
expect(
factory.fromFont(e.target.value, style, weight, stretch),
"Unexpected Font",
),
);
}}
/>
);
}

children.push(
<input
value={family}
onChange={(e) => {
this.props.setValue(
valueIndex,
expect(
factory.fromFont(e.target.value, style, weight, stretch),
"Unexpected Font",
),
);
}}
/>,
<>Style</>,
<select
value={style}
Expand Down Expand Up @@ -952,17 +988,13 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
);
}}
>
<option value="thin">Thin</option>
<option value="extra-light">Extra Light</option>
<option value="light">Light</option>
<option value="semi-light">Semi Light</option>
<option value="normal">Normal</option>
<option value="medium">Medium</option>
<option value="semi-bold">Semi Bold</option>
<option value="bold">Bold</option>
<option value="extra-bold">Extra Bold</option>
<option value="black">Black</option>
<option value="extra-black">Extra Black</option>
{
FontList.FONT_WEIGHTS.map(([_, value, name]) => (
<option style={{
color: styles?.has(value) ? "white" : "grey"
}} value={value}>{name}</option>
))
}
</select>,
<>Stretch</>,
<select
Expand All @@ -977,15 +1009,13 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
);
}}
>
<option value="ultra-condensed">Ultra Condensed</option>
<option value="extra-condensed">Extra Condensed</option>
<option value="condensed">Condensed</option>
<option value="semi-condensed">Semi Condensed</option>
<option value="normal">Normal</option>
<option value="semi-expanded">Semi Expanded</option>
<option value="expanded">Expanded</option>
<option value="extra-expanded">Extra Expanded</option>
<option value="ultra-expanded">Ultra Expanded</option>
{
FontList.FONT_STRETCHES.map(([_, value, name]) => (
<option style={{
color: styles?.has(value) ? "white" : "grey"
}} value={value}>{name}</option>
))
}
</select>,
);
}
Expand Down
74 changes: 74 additions & 0 deletions src/util/FontList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
let queriedKnownFonts = false;
export let knownFamilies: string[] = [];
export const knownStyles = new Map<string, Set<string>>();

// 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.
}
});
}
}
}
Loading