可变字体托管、语法以及为什么 Google 不提供用于自托管可变字体的 WOFF2 下载?

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

我在这里不知所措。尝试将 google 变量 webfont (open sans) 添加到我的网站。

  1. 选择字体时,谷歌只会为
    static
    CSS字体创建<link>为什么?(分号,没有“300..700”)

在网络上使用

要嵌入字体,请将代码复制到 html 中

[x] <link>    [ ] @import
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">

指定族的 CSS 规则

font-family: 'Open Sans', sans-serif;
  1. 整个页面都没有 woff2 的下载(仅来自 API)。 D/L 按钮仅适用于 .ttf。 讽刺的是在关于自托管的文章中,他们使用

    woff2
    作为示例,即使他们不提供它。此外,甚至该字体的官方 GITHUB 页面也仅提供 .ttf。 为什么?

  2. 其他来源提供了各种格式的静态文件(但我在那里没有看到可变的文件),另一个线程中的 ppl 甚至发布了他们自己的工具,例如:

  1. 折腾了一整天,终于找到了这个。还提到了另一个(官方)工具,用于将 ttf 转换为 woff2,这对于可变字体来说似乎不容易实现。 SRSLY?这是唯一的方法吗?为什么没有任何文档? (好吧,也许我应该从 API 中获取 woff2,但我注意到浏览器之间的差异,我认为例如 Opera gets 仅提供静态类型而不是变量类型。)

  2. “好”的 API 就是为此服务的。但它只使用

    format('woff2')
    :

但我读过,对于可变字体,语法应该更像这样,使用

format('woff2 supports variations')
format('woff2-variations')
@supports (font-variation-settings: normal)
。为什么谷歌不使用这种语法?现在哪个更好?

谷歌:

/* latin */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300 800;
  font-stretch: 100%;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/opensans/v34/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

应该如何完成:

@font-face {
    font-family: Asap;
    src: url('/fonts/Asap-VariableFont_wght.woff2') format('woff2 supports variations'),
         url('/fonts/Asap-VariableFont_wght.woff2') format('woff2-variations');
    font-weight: 400 700;
    font-display: swap;
    font-style: normal;
}

旁注: 从谷歌页面我需要手动更改

    https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..0,800;1,300..1,800&display=swap

https://fonts.googleapis.com/css2?family=Open+Sans:[email protected]&display=swap

甚至获得可变字体。

css fonts google-webfonts
1个回答
0
投票

2024 年更新:谷歌字体 UI 终于提供了可变字体 CSS URL

不幸的是你仍然无法直接下载woff2文件。

format('woff2-variations')
这样的格式标识符可以追溯到可变字体支持还处于实验阶段的时代——所以任何像样的“现代”浏览器都不应该需要它们。但是,可能会有例外,例如非常旧的浏览器版本 - 请参阅 caniuse 报告。

通过JS fetch下载字体

由于 google-webfont-helper 仍然不支持可变字体,我想出了一个自定义字体加载器。

async function getAllVariableFonts(
  apiKey = "",
  format = "woff2",
  apiUrlStatic = ""
) {


  let apiURL = `https://www.googleapis.com/webfonts/v1/webfonts?capability=VF&capability=${format}&sort=style&key=${apiKey}`;

  // switch between API and static src
  let listUrl = apiKey ? apiURL : apiUrlStatic;

  // fetch font JSON
  let listObj = await (await fetch(listUrl)).json();

  // get only VF items
  let items = listObj.items;
  items = items.filter((item) => item.axes && item.axes.length);
  return items;
}

async function getGoogleFontUrl(font) {
  // replace whitespace
  let familyQuery = font.family.replaceAll(" ", "+");
  let gfontBase = `https://fonts.googleapis.com/css2?family=`;

  // check variants
  let variants = [...new Set(font.variants.filter(Boolean))];
  let stylesItalic = variants.filter((variant) => variant.includes("italic"));
  let stylesRegular = variants.filter((variant) => !variant.includes("italic"));

  // sort axes alphabetically - case sensitive ([a-z],[A-Z])
  let axes = font.axes;
  axes = [
    axes.filter((item) => item.tag.toLowerCase() === item.tag),
    axes.filter((item) => item.tag.toUpperCase() === item.tag)
  ].flat();
  let ranges = axes.map((val) => {
    return val.start + ".." + val.end;
  });
  let tuples = [];

  //  italic and regular
  if (stylesItalic.length && stylesRegular.length) {
    tuples.push("ital");
    rangeArr = [];
    for (let i = 0; i < 2; i++) {
      rangeArr.push(`${i},${ranges.join(",")}`);
    }
  }
  // only italic
  else if (stylesItalic.length && !stylesRegular.length) {
    tuples.push("ital");
    rangeArr = [];
    rangeArr.push(`${1},${ranges.join(",")}`);
  }

  // only regular
  else {
    rangeArr = [];
    rangeArr.push(`${ranges.join(",")}`);
  }

  // add axes tags to tuples
  axes.map((val) => {
    return tuples.push(val.tag);
  });
  query = tuples.join(",") + "@" + rangeArr.join(";") + "&display=swap";

  let url = `${gfontBase}${familyQuery}:${query}`;
  return url;
}

function updatePreview(item, googleFontUrl) {
  legend.textContent = `Preview: ${item.family}`;

  // add css
  let im = `@impo` + `rt`;
  gfCss.textContent = `
  ${im} '${googleFontUrl}';
    .preview{
      font-family: "${item.family}";
      font-size: 12vmin;
    }`;

  let axes = item.axes;
  styleSelect.innerHTML = "";
  let fontVariationSettings = {};

  let hasItalic = item.variants.includes("italic");
  if (hasItalic) {
    let checkbox = document.createElement("input");
    let checkboxLabel = document.createElement("label");
    checkboxLabel.textContent = "Italic ";
    checkbox.type = "checkbox";
    checkboxLabel.append(checkbox);
    styleSelect.append(checkboxLabel);

    checkbox.addEventListener("click", (e) => {
      preview.style.fontStyle = checkbox.checked ? "italic" : "normal";
    });
  }

  axes.forEach((axis) => {
    let label = document.createElement("label");
    let input = document.createElement("input");
    input.type = "range";
    input.min = axis.start;
    input.max = axis.end;
    input.value = axis.start;

    fontVariationSettings[axis.tag] = axis.start;
    label.textContent = `${axis.tag}: ${axis.start}–${axis.end} `;
    styleSelect.append(label, input);

    // apply style
    input.addEventListener("input", (e) => {
      let val = e.currentTarget.value;
      fontVariationSettings[axis.tag] = val;
      let cssVar = [];
      for (tag in fontVariationSettings) {
        cssVar.push(`"${tag}" ${fontVariationSettings[tag]}`);
      }
      preview.style.fontVariationSettings = cssVar.join(", ");
    });
  });
}

function showLink(target, url) {
  target.innerHTML = "";
  let link = `<a href="${url}">${url}</a>`;
  target.insertAdjacentHTML("beforeend", link);
}

function populateDatalist(target, list) {
  let fonts = list;
  let datalistOptions = "";
  fonts.forEach((font) => {
    //only VF
    if (font.axes) {
      datalistOptions += `<option >${font.family}</option>`;
    }
  });
  target.insertAdjacentHTML("beforeend", datalistOptions);
}

/**
 * fetch
 */

async function fetchFontsFromCssAndZip(css) {
  // find subset identifiers by comments
  let regexComments = /\/\*\s*([^*]*(?:\*(?!\/)[^*]*)*)\*\//g;
  let subsets = css.match(regexComments).map((sub) => {
    return sub.replace(/(\/\*|\*\/)/g, "").trim();
  });

  //create and parse temporary stylesheet object
  let cssSheet = new CSSStyleSheet();
  cssSheet.replaceSync(css);

  // filter font-face rules
  let rules = [...cssSheet.cssRules].filter((rule) => {
    return rule.type === 5;
  });

  // sanitize font-family name
  let fontFamily = rules[0].style
    .getPropertyValue("font-family")
    .replaceAll('"', "");
  let fontFamilyFilename = fontFamily.replaceAll(" ", "-");

  // create zip object
  let zip = new JSZip();

  // loop through all rules/fonts
  for (let i = 0; i < rules.length; i++) {
    // get properties
    let fontWeight = rules[i].style.getPropertyValue("font-weight");
    let fontStyle = rules[i].style.getPropertyValue("font-style");
    let fontStretch = rules[i].style.getPropertyValue("font-stretch");
    fontStretch = fontStretch === "normal" ? "" : fontStretch;
    let src = rules[i].style.getPropertyValue("src");
    src = src.match(/\(([^)]+)\)/)[1].replaceAll('"', "");

    //replace cryptic file names with readable local names
    let fontName = [fontFamilyFilename, subsets[i], fontWeight, fontStyle, fontStretch]
      .filter(Boolean)
      .join("_") + ".woff2";
    css = css.replaceAll(src, `"${fontFamilyFilename}/${fontName}"`);

    // add data to zip
    let fontData = await (await fetch(src)).arrayBuffer();
    zip.file(`${fontFamilyFilename}/${fontName}`, fontData, {
      type: "uint8array"
    });
  }

  // add simple example HTML
  let htmlDoc = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">
  <link rel="stylesheet" href="${fontFamilyFilename}.css">
  <body style="font-family:'${fontFamily}\'">
  <h1>Sample font</h1>
  <p>One morning, when <em>Gregor Samsa</em> woke from <strong>troubled dreams</strong>, he found himself transformed in his bed into a horrible vermin.</p>
  <p>He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment.</p>
  </body></html>`;
  zip.file("index.html", htmlDoc);

  // add css
  fontCss.value = css;
  zip.file(`${fontFamilyFilename}.css`, css);

  // create object url
  let blob = await zip.generateAsync({
    type: "blob"
  });

  blob.name = fontFamilyFilename + ".zip";
  return blob;
}
:root {
  --loadingImg: url("data:image/svg+xml,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><path d='M12 1A11 11 0 1 0 23 12 11 11 0 0 0 12 1Zm0 19a8 8 0 1 1 8-8A8 8 0 0 1 12 20Z' opacity='.25'/><path d='M10.14 1.16a11 11 0 0 0-9 8.92A1.59 1.59 0 0 0 2.46 12 1.52 1.52 0 0 0 4.11 10.7a8 8 0 0 1 6.66-6.61A1.42 1.42 0 0 0 12 2.69h0A1.57 1.57 0 0 0 10.14 1.16Z'><animateTransform attributeName='transform' type='rotate' dur='0.75s' values='0 12 12;360 12 12' repeatCount='indefinite'/></path></svg>")
}

body {
  font-family: sans-serif
}

legend {
  font-weight: bold;
}

fieldset {
  margin-bottom: 1em;
}

fieldset input,
fieldset textarea {
  border: none
}

input[type="text"] {
  width: 100%;
  display: block;
  margin-bottom: 1em;
}

#inputFonts,
.inputUrl,
input[type="search"] {
  font-size: 2em;
  margin-bottom: 1em;
  border: 1px solid #000;
  border-radius: 0.3em;
}

input[type="checkbox"] {
  width: auto;
  display: inline-block;
  margin-bottom: 1em;
}

textarea {
  width: 100%;
  min-height: 20em;
}

.btn-default {
  text-decoration: none;
  border: 1px solid #000;
  background: #ccc;
  color: #000;
  font-weight: bold;
  padding: 0.3em;
  font-family: inherit;
  font-size: 1em;
  margin-right: 0.3em;
  cursor: pointer;
}

.inactive {
  pointer-events: none;
  opacity: 0;
}

.btn-load .icn-loading {
  opacity: 0;
}

.btn-load.loading .icn-loading {
  opacity: 1;
}


/*
.btn-load.active
.icn-loading{
  width:0px;
}
*/

.icn-loading {
  transition: 0.3s;
  transform: translateY(0.15em);
  display: inline-block;
  position: relative;
  overflow: hidden;
  width: 1em;
  height: 1em;
  background-image: var(--loadingImg);
  background-repeat: no-repeat;
  background-position: 0%;
  color: transparent;
  border-color: transparent;
}
<h1>Fetch variable fonts from google</h1>
<style id="gfCss"></style>

<p><button class="btn-default btn-fetch" id="fetchData">Fetch font files</button><a class="btn-default btn-load inactive" id="btnDownload" href="#" download="fontface.css">Download fontkit <span class="icn-loading"></span></a> </p>

<fieldset>
  <legend>Search font by name</legend>
  <input type="text" list="datalistFonts" id="inputFonts" placeholder="Enter font-family name">

  <!-- autocomplete -->
  <datalist id="datalistFonts">
  </datalist>

  <label for="">Or enter CSS URL</label>
  <p><input type="text" class="inputUrl" id="inputUrl" value="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900"></p>
</fieldset>

<fieldset>
  <legend id="legend">Preview</legend>
  <div id="preview" class="preview">
    Hamburglefonstiv
  </div>
  <div id="styleSelect"></div>
</fieldset>

<fieldset>
  <legend>New Css</legend>
  <textarea id="fontCss"></textarea>
</fieldset>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js"></script>

<script>
  window.addEventListener('DOMContentLoaded', async(e) => {

    // enter you own API key
    let apiKey = '';

    // static copy of developer API response
    let apiUrlStatic = "https://cdn.jsdelivr.net/gh/herrstrietzel/fonthelpers@main/json/gfontsAPI.json";
    // inputs
    const inputUrl = document.getElementById('inputUrl');
    const btnDownload = document.getElementById('btnDownload');
    
    // init example
    (async() => {
      /**
       * get all google fonts from API
       * filter only variable fonts
       */
      let googleFontList = await getAllVariableFonts(apiKey, 'woff2', apiUrlStatic);

      // generate autofill
      populateDatalist(datalistFonts, googleFontList)

      // show first
      let item = googleFontList.filter(item => item.family === 'Open Sans')[0];
      inputFonts.value = item.family;
      //console.log(item);
      googleFontUrl = await getGoogleFontUrl(item);
      inputUrl.value = googleFontUrl;
      //update css
      updatePreview(item, googleFontUrl)
      // filter fonts
      inputFonts.addEventListener('change', async e => {
        let family = e.currentTarget.value;
        let familyQuery = family.replaceAll(' ', '+');
        // filter current family
        let item = googleFontList.filter(item => item.family === family)[0];
        // update links
        googleFontUrl = await getGoogleFontUrl(item);
        inputUrl.value = googleFontUrl;
        //showLink(cssUrls, [googleFontUrl])
        updatePreview(item, googleFontUrl)
      });
      //updateGoogleCssUrl();
    })();

    inputUrl.addEventListener("change", async(e) => {
      updateGoogleCssUrl()
    });

    fetchData.addEventListener("click", async(e) => {
      btnDownload.classList.remove('inactive');
      btnDownload.classList.add('active');
      btnDownload.classList.add('loading');
      updateGoogleCssUrl()
    });
    // fetch 
    async function updateGoogleCssUrl() {
      // fetch css content as text
      let url = inputUrl.value;
      let css = await (await fetch(url)).text();

      // fetch font files and zip
      let blob = await fetchFontsFromCssAndZip(css);
      let objectUrl = URL.createObjectURL(blob);
      // update download link
      btnDownload.href = objectUrl;
      btnDownload.download = blob.name;
      btnDownload.classList.replace('inactive', 'active');
      btnDownload.classList.remove('loading');
    }
  })
</script>

下载功能在 SO 片段中不起作用。请参阅工作 codepen 示例

如何运作

  1. 通过 dev API 或字体列表 JSON 响应的静态副本获取字体列表
  2. 按字体系列名称过滤列表
  3. 根据可用样式和轴生成查询 URL,例如
https://fonts.googleapis.com/css2?family=Open+Sans:ital,wdth,wght@0,75..100,300..800;1,75..100,300..800&display=swap
  1. 获取并解析包含所有
    @font-face
    规则的CSS
  2. 将远程字体 URL 替换为本地使用的新字体 URL
  3. 获取字体文件,创建
    arrayBuffer()
    并将数据添加到 JSZIP 对象

浏览器嗅探

不幸的是,谷歌部署使用代理检测,可能无法向某些浏览器(如 Opera)提供可变字体(尽管支持可变字体)

© www.soinside.com 2019 - 2024. All rights reserved.