forked from samuelmeuli/font-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFontManager.ts
194 lines (171 loc) · 5.95 KB
/
FontManager.ts
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import "./picker-styles/styles.scss";
import getFontList from "./google-fonts/fontList";
import { loadActiveFont, loadFontPreviews } from "./loadFonts";
import { Font, FONT_FAMILY_DEFAULT, FontList, Options, OPTIONS_DEFAULTS } from "./types";
import { getFontId, validatePickerId } from "./utils/ids";
/**
* Class for managing the list of fonts for the font picker, keeping track of the active font and
* downloading/activating Google Fonts
*/
export default class FontManager {
// Parameters
private readonly apiKey: string;
private readonly options: Options;
private onChange: (font: Font) => void;
// Other class variables
// Name of currently applied font
private activeFontFamily: string = '';
// Map from font families to font objects
private fonts: FontList = new Map<string, Font>();
// Suffix appended to CSS selectors which would have name clashes if multiple font pickers are
// used on the same site (e.g. "-test" if the picker has pickerId "test" or "" if the picker
// doesn't have an ID)
public selectorSuffix: string;
/**
* Save relevant options, download the default font, add it to the font list and apply it
*/
constructor(
apiKey: string,
defaultFamily: string = FONT_FAMILY_DEFAULT,
{
pickerId = OPTIONS_DEFAULTS.pickerId,
families = OPTIONS_DEFAULTS.families,
categories = OPTIONS_DEFAULTS.categories,
scripts = OPTIONS_DEFAULTS.scripts,
variants = OPTIONS_DEFAULTS.variants,
filter = OPTIONS_DEFAULTS.filter,
limit = OPTIONS_DEFAULTS.limit,
sort = OPTIONS_DEFAULTS.sort,
}: Options,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange: (font: Font) => void = (): void => {},
) {
// Validate pickerId parameter
validatePickerId(pickerId);
this.selectorSuffix = pickerId ? `-${pickerId}` : "";
// Save parameters as class variables
this.apiKey = apiKey;
this.options = {
pickerId,
families,
categories,
scripts,
variants,
filter,
limit,
sort,
};
this.onChange = onChange;
// Download default font and add it to the empty font list
this.addFont(defaultFamily, false);
this.setActiveFont(defaultFamily, false);
}
/**
* Fetch list of all fonts from Google Fonts API, filter it according to the class parameters and
* save them to the font map
*/
public async init(): Promise<FontList> {
// Get list of all fonts
const fonts = await getFontList(this.apiKey);
// Save desired fonts in the font map
for (let i = 0; i < fonts.length; i += 1) {
const font = fonts[i];
// Exit once specified limit of number of fonts is reached
if (this.fonts.size >= this.options.limit) {
break;
}
if (
// Skip default font if it is also contained in the list
!this.fonts.has(font.family) &&
// `families` parameter: Only keep fonts whose names are included in the provided array
(this.options.families.length === 0 || this.options.families.includes(font.family)) &&
// `categories` parameter: only keep fonts in categories from the provided array
(this.options.categories.length === 0 || this.options.categories.includes(font.category)) &&
// `scripts` parameter: Only keep fonts which are available in all specified scripts
this.options.scripts.every((script): boolean => font.scripts.includes(script)) &&
// `variants` parameter: Only keep fonts which contain all specified variants
this.options.variants.every((variant): boolean => font.variants.includes(variant)) &&
// `filter` parameter: Only keep fonts for which the `filter` function evaluates to `true`
this.options.filter(font) === true
) {
// Font fulfils all requirements: Add it to font map
this.fonts.set(font.family, font);
}
}
// Download previews for all fonts in list except for default font (its full font has already
// been downloaded)
const fontsToLoad = new Map(this.fonts);
fontsToLoad.delete(this.activeFontFamily);
loadFontPreviews(fontsToLoad, this.options.scripts, this.options.variants, this.selectorSuffix);
return this.fonts;
}
/**
* Return font map
*/
public getFonts(): FontList {
return this.fonts;
}
/**
* Add a new font to the font map and download its preview characters
*/
public addFont(fontFamily: string, downloadPreview = true): void {
// @ts-ignore: Custom font does not need `categories`, `scripts` and `variants` attributes
const font: Font = {
family: fontFamily,
id: getFontId(fontFamily),
};
this.fonts.set(fontFamily, font);
// Download font preview unless specified not to
if (downloadPreview) {
const fontMap: FontList = new Map<string, Font>();
fontMap.set(fontFamily, font);
loadFontPreviews(fontMap, this.options.scripts, this.options.variants, this.selectorSuffix);
}
}
/**
* Remove the specified font from the font map
*/
public removeFont(fontFamily: string): void {
this.fonts.delete(fontFamily);
}
/**
* Return the font object of the currently active font
*/
public getActiveFont(): Font {
const activeFont = this.fonts.get(this.activeFontFamily);
if (!activeFont) {
throw Error(`Cannot get active font: "${this.activeFontFamily}" is not in the font list`);
} else {
return activeFont;
}
}
/**
* Set the specified font as the active font and download it
*/
public setActiveFont(fontFamily: string, runOnChange = true): void {
const previousFontFamily = this.activeFontFamily;
const activeFont = this.fonts.get(fontFamily);
if (!activeFont) {
// Font is not in fontList: Keep current activeFont and log error
throw Error(`Cannot update active font: "${fontFamily}" is not in the font list`);
}
this.activeFontFamily = fontFamily;
loadActiveFont(
activeFont,
previousFontFamily,
this.options.scripts,
this.options.variants,
this.selectorSuffix,
).then((): void => {
if (runOnChange) {
this.onChange(activeFont);
}
});
}
/**
* Update the onChange function (executed when changing the active font)
*/
public setOnChange(onChange: (font: Font) => void): void {
this.onChange = onChange;
}
}