如何添加特定 Google 字体可用的所有字体?(在 JS 中)

问题描述 投票:0回答:1

我正在寻找一种方法来生成特定字体的所有可能的 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]}`]
}
javascript html fonts google-fonts
1个回答
1
投票

当前功能应该可以正常工作。
但有一些问题:

  • 静态字体将返回多个 URL - 所有粗细和样式都应该连接
  • 可变字体应返回有效的范围元组 - 不幸的是,fontsource API当前(2023)不包含任何轴数据。您将无法获得所有轴,例如包括
    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 部分贡献拉取请求或建议新功能。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.