我正在寻找一种方法来生成特定字体的所有可能的 Google 字体 URL。我想包括所有可用的样式( 斜体,正常)和权重(例如 400–800)。
我正在开发图像编辑器,当用户选择字体时,我需要加载字体系列中的每种字体。
目前,我正在使用 fontsource API 检索可用字体的数据,并尝试从该数据生成 google CSS 链接,
例如
https://fonts.googleapis.com/css2?family=Open%20Sans:ital,wght@0,300..800;1,300..800
但是我编写的函数有时会生成无效链接,而且我不确定它是否包含所有可能的样式/权重。
API 结果示例:
{
"id": "open-sans",
"family": "Open Sans",
"subsets": [
"cyrillic",
"cyrillic-ext",
"greek",
"greek-ext",
"hebrew",
"latin",
"latin-ext",
"vietnamese"
],
"weights": [
300,
400,
500,
600,
700,
800
],
"styles": [
"italic",
"normal"
],
"defSubset": "latin",
"variable": true,
"lastModified": "2022-09-22",
"category": "sans-serif",
"license": "OFL-1.1",
"type": "google"
}
这是我当前的代码,您知道更好的解决方案吗?谢谢
function generateGoogleFontURL(font) {
const formattedFontFamily = font.family.replace(/\s+/g, "+")
if (font.weights.length == 0) {
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}`]
}
if (!font.variable) {
if (font.styles.includes("italic")) {
return font.weights.map(x => `https://fonts.googleapis.com/css2?family=${formattedFontFamily}:ital,wght@0,${x};1,${x}`)
}
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}:wght@${font.weights.join(';')}`]
}
if (font.weights.length > 1) {
const minWeight = font.weights[0]
const maxWeight = font.weights[font.weights.length - 1]
if (font.styles.includes("italic")) {
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}:ital,wght@0,${minWeight}..${maxWeight};1,${minWeight}..${maxWeight}`]
}
else {
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}:wght@${minWeight}..${maxWeight}`]
}
}
if (font.styles.includes("italic")) {
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}:ital,wght@0,${font.weights[0]};1,${font.weights[0]}`]
}
return [`https://fonts.googleapis.com/css2?family=${formattedFontFamily}:wght@${font.weights[0]}`]
}
当前功能应该可以正常工作。
但有一些问题:
wdth
(宽度)轴。这是一个修订版本,将所有样式和权重连接到一个 URL 中
let fontAPiJsonFS = "https://api.fontsource.org/v1/fonts";
let fontFamily = "Open Sans";
fontFamily = "Roboto";
(async() => {
let fetched = await fetch(fontAPiJsonFS);
let fontItems = await (await await fetch(fontAPiJsonFS)).json();
// filter family name and google fonts
let fontItem = fontItems.filter(
(item) => item.family === fontFamily && item.type === "google"
)[0];
let url = generateGoogleFontURL(fontItem);
a_css.href = url;
a_css.textContent = url;
})();
function generateGoogleFontURL(font) {
let styles = font.styles;
let weights = font.weights;
let hasItalics = styles.includes("italic");
// very uncommon but might be used for handwriting fonts
let italicOnly = styles.length === 1 && styles[0] === "italic";
const formattedFontFamily = font.family.replace(/\s+/g, "+");
const baseUrl = `https://fonts.googleapis.com/css2?family=`;
let url = baseUrl + formattedFontFamily;
// concatenate prefix like: ":ital,wght@"
let query_prefix = "";
/**
* 1. static fonts
*/
if (!font.variable) {
let tuples = [];
if (hasItalics) {
query_prefix += ":ital";
tuples.push(weights.map((x) => `0,${x}`));
}
// italics and regular
if (!italicOnly) {
query_prefix += ",wght";
tuples.push(weights.map((x) => `1,${x}`));
}
// only regular
else {
query_prefix = ":wght";
}
// concatenate URL
url += query_prefix + "@" + tuples.flat().join(";");
return url;
} else {
/**
* 2. variable fonts
*/
if (font.weights.length > 1) {
const minWeight = font.weights[0];
const maxWeight = font.weights[font.weights.length - 1];
if (hasItalics) {
return `https://fonts.googleapis.com/css2?family=${formattedFontFamily}:ital,wght@0,${minWeight}..${maxWeight};1,${minWeight}..${maxWeight}`;
} else {
return `https://fonts.googleapis.com/css2?family=${formattedFontFamily}:wght@${minWeight}..${maxWeight}`;
}
}
}
}
<p>
<strong>Google font URL: </strong>
<a id="a_css" href=""></a>
</p>
正如 Mike 'Pomax' Kamermans 所评论的:google 的开发者 API 目前是更好的选择 – 至少在可变字体方面是这样。
您需要一个 API 密钥。
API 调用看起来像这样:
https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&capability=WOFF2&sort=style&key=${apiKey}
将 capability 参数
capability=VF
添加到查询中至关重要 - 否则响应将省略轴数据。
此参数不会仅将输出过滤为可变字体!
因此,您可能需要包含一个额外的过滤器,仅返回具有axes属性的项目。
Google 的开放字体 API 对于元组的顺序(简化:查询块)是无情的。
谷歌错误消息
轴必须按字母顺序列出(例如 a、b、c、A、B、C)
它们必须按字母和区分大小写顺序
排序let axes = [
{ tag: "wght", start: 300, end: 800 },
{ tag: "wdth", start: 75, end: 100 },
{ tag: "GRAD", start: -200, end: 150 },
{ tag: "slnt", start: -10, end: 0 }
];
axes = alphanumeric_sort(axes, 'tag');
console.log(axes);
function alphanumeric_sort(object, key) {
// sort keys alphabetically
object = object.sort((a, b) => a[key].localeCompare(b[key]));
// sort keys case sensitive
object = [
object.filter((item) => item[key].toLowerCase() === item[key]),
object.filter((item) => item[key].toUpperCase() === item[key])
].flat();
return object;
}
现在,我们可以组装有效的 CSS URL,包括所有轴:
let baseUrl = `https://fonts.googleapis.com/css2?family=`;
let fontAPiJson =
"https://raw.githubusercontent.com/herrstrietzel/fonthelpers/main/json/gfontsAPI.json";
let fontFamily = "Roboto Flex";
// init
(async () => {
let fetched = await fetch(fontAPiJson);
let fontItems = await (await fetched.json()).items;
let fontItem = fontItems.filter((item) => item.family === fontFamily)[0];
//console.log(fontItem)
let url = baseUrl + getGoogleFontQueryAPI(fontItem, true);
a_var.href=url;
a_var.textContent=url;
})();
/**
* parse API Json
*/
function getGoogleFontQueryAPI(fontItem, variable = true) {
let fontFamily = fontItem.family;
// sanitize whitespace in font family name
let fontfamilyQuery = fontFamily.replaceAll(" ", "+");
let axes = fontItem.axes;
let isVF = axes && axes.length ? true : false;
// prepended tuple keys like ital, wght etc
let queryPre = [];
let query = "";
// count weights in variants
let styles = fontItem.variants;
// sanitize styles
styles = styles.map((style) => {
style = style === "italic" ? "400i" : style;
return style.replaceAll("italic", "i").replaceAll("regular", "400");
});
let weightsItalic = [];
let weightsRegular = [];
styles.forEach((style) => {
if (style.includes("i")) {
weightsItalic.push(parseFloat(style));
} else {
weightsRegular.push(parseFloat(style));
}
});
// italic and regular
if (weightsItalic.length) {
queryPre.push("ital");
}
if (weightsRegular.length && !isVF) {
queryPre.push("wght");
}
// is variable
if (isVF) {
// sort axes alphabetically - case sensitive ([a-z],[A-Z])!!!
axes = alphanumeric_sort(axes, 'tag');
let ranges = axes.map((val) => {
return val.start + ".." + val.end;
});
// italic and regular
if (weightsItalic.length && weightsRegular.length) {
//queryPre.push("ital");
rangeArr = [];
for (let i = 0; i < 2; i++) {
rangeArr.push(`${i},${ranges.join(",")}`);
}
}
// only italic
else if (weightsItalic.length && !weightsRegular.length) {
//queryPre.push("ital");
rangeArr = [];
rangeArr.push(`1,${ranges.join(",")}`);
}
// only regular
else {
rangeArr = [];
rangeArr.push(`${ranges.join(",")}`);
}
// add axes tags to pre query string
axes.map((val) => {
return queryPre.push(val.tag);
});
query =
fontfamilyQuery +
":" +
queryPre.join(",") +
"@" +
rangeArr.join(";") +
"&display=swap";
return query;
}
/**
* 2. get static
*/
query = fontfamilyQuery + ":" + queryPre.join(",") + "@";
// italic and regular
if (weightsItalic.length && weightsRegular.length) {
query +=
weightsRegular
.map((val) => {
return "0," + val;
})
.join(";") +
";" +
weightsItalic
.map((val) => {
return "1," + val;
})
.join(";");
}
// only italic
else if (weightsItalic.length && !weightsRegular.length) {
query += weightsItalic
.map((val) => {
return "1," + val;
})
.join(";");
}
// only regular
else {
query += weightsRegular
.map((val) => {
return val;
})
.join(";");
}
return query;
}
function alphanumeric_sort(object, key) {
// sort keys alphabetically
object = object.sort((a, b) => a[key].localeCompare(b[key]));
// sort keys case sensitive
object = [
object.filter((item) => item[key].toLowerCase() === item[key]),
object.filter((item) => item[key].toUpperCase() === item[key])
].flat();
return object;
}
<p><strong>Variable font URL: </strong><a id="a_var" href=""></a></p>
在上面的示例中,我使用输出 JSON 的静态副本。某种形式的缓存可能是防止过多 API 请求的好主意。
但是,您应该偶尔更新此静态副本以包含最近添加的字体。
由于 fontsource 仍然很新(状态 2023),您应该考虑在 discussion 部分贡献拉取请求或建议新功能。