将Web字体转换和呈现为base64格式 - 保持原始外观

106

我想推迟我的网站上的字体加载,受到Smashing Magazine延迟字体加载逻辑的启发。

其中主要部分是将字体转换为base64并准备好您的CSS文件。 我迄今为止的步骤:

  1. Google Web Fonts上选择字体并下载它们。
  2. 使用Font Squirrel Webfont Generator将下载的TTF文件转换为带有嵌入WOFF字体的CSS文件(专家选项 -> CSS -> Base64编码)。
  3. 异步加载CSS文件(这里不重要)。

Open Sans Bold的CSS片段:

@font-face {
  font-family: 'Open Sans';
  src: url(data:application/x-font-woff;charset=utf-8;base64,<base64_encoded>) format('woff');
  font-weight: 700;
  font-style: normal;
}

问题在于,转换后的字体看起来非常不同。看看Open Sans Bold: GWF vs base64 rendering comparison

特别注意重音符号偏离和绝对可怕的字母a。其他字体系列和变体也明显不同(大小和形状扭曲等)。


所以问题是:如何正确地将来自Google Web字体(或其他来源)的TTF文件编码为base64格式,并以使结果与原始文件相同的方式使用它?

1
发现了一个在线工具,可以将Google字体转换为Base64。 - vsync
5个回答

150
在Font Squirrel专家选项中,请确保将"TrueType Hinting"选项设置为"Keep Existing"。其他选项会导致TrueType指令(提示)被修改,进而影响字体的渲染。
或者,如果您对直接从GWF渲染的字体满意,可以自己进行base64编码。在OS X或Linux中,请使用终端/Shell中内置的base64命令: $ base64 -i myfont.ttf -o fontbase64.txt
对于Windows,您需要下载一个用于base64编码的程序(有几个免费/开源工具可用)。复制该文件的内容,然后在CSS中使用如下方式:
@font-face {
    font-family: 'myfont';
    src: url(data:font/truetype;charset=utf-8;base64,<<copied base64 string>>) format('truetype');
    font-weight: normal;
    font-style: normal;
}

(请注意,您可能需要对各种@font-face信息进行一些调整,以匹配您特定的字体数据;这只是一个示例模板)

1
我试图做完全相同的事情。我按照您的说明操作,但仍然无法工作。字体比Google提供的WOFF呈现更糟糕。我也尝试了其他不同的选项。还有其他办法吗? - Sebastian Starke
1
回答已更新,附有如何自己进行base64编码并在CSS中使用的解释。 - djangodude
你说得对,RFC 2397规定在“base64”指示符后应该加上逗号。我已经更新了我的答案以反映这一点。 - djangodude
29
我注意到保持src:行的完整性很重要,包括base64字符串在内,必须写在一行上。由于base64命令可能默认插入换行符,因此可以使用-w参数来禁用该功能: base64 -w 0 font.ttf > font_base64.txt。(适用于GNU coreutils中的base64工具) - zpea
1
感谢您的精彩回答。 - AmerllicA
显示剩余4条评论

104

使用此代码片段可以在浏览器中直接对字体进行Base64编码(与操作系统无关,无需安装任何软件)。

function base64convert (files) {
  console.clear()
  const reader = new FileReader()
  reader.onload = (e) => {
    console.log(e.target.result)
  }
  reader.readAsDataURL(files[0])
}
<input type="file" onchange="base64convert(this.files)">

然后将输出内容复制并粘贴到你的CSS文件中:

@font-face {
    font-family: 'myfont';
    src: url("<<copied base64 string>>");
}

3
哇,这真的很令人印象深刻。而且它有效。 :-) - mrmut
可以运行,但在我的HTML文件中感觉不好。 - Shaun Whyte
另请参阅FileReader.readAsDataURL - djvg

3

1
除了之前的答案之外,还有一些警告:

专用字体转换器可能会丢失功能或更改数据

像fontquirrel或transfonter这样的生成器/转换器实际上会解析和重建您的字体文件。
这个过程可能会由于优化设置(如hinting数据)而引入变化,从而影响字体渲染。 2023年:许多转换器(如fontsquirrel和transfonter)不支持可变字体 当使用这些工具获取base64数据URL时,生成数据URL时,您可能会丢失可变字体功能(设计轴相关数据被剥离)。
这并不意味着您完全不应该使用这些转换器-如果满足以下条件,它们通常可以很好地工作:
  • 仔细检查所有转换预设
  • 您需要静态字体支持,并且需要对字形范围进行子集化

通用的base64转换器不会操作任何字体数据

Ilyich所示:您可以使用任何base64编码器。(例如browserlings转换器)。
以下是基于这些步骤的另一个JS助手示例:
  • 获取外部CSS URL
  • 通过正则表达式查找字体URL
  • 将所有URL作为blob()获取
  • 将blob转换为base64数据URL
  • 用base64数据URL替换外部URL

inputUrl.addEventListener("input", async(e) => {
  let url = e.currentTarget.value;
  let css = await getdataUrlsFromCss(url)
  // output and download button
  fontCss.value = css;
  btnDownload.href = URL.createObjectURL(new Blob([css]));
});

// init
inputUrl.dispatchEvent(new Event("input"));


async function getdataUrlsFromCss(url) {
  // fetch external css
  let css = await (await fetch(url)).text();

  // find external urls in css via regex
  let urls = css.match(/https:\/\/[^)]+/g);

  for (let i = 0; i < urls.length; i++) {
    let url = urls[i];

    // fetch font file
    let blob = await (await await fetch(url)).blob();

    // create base64 string
    let base64 = await blobToBase64(blob);

    //replace urls with data url
    css = css.replaceAll(url, base64);
  }

  return css;
}


/**
 * fetched blob to base64
 */
function blobToBase64(blob) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  });
}
body {
  font-family: sans-serif
}

legend {
  font-weight: bold;
}

fieldset {
  margin-bottom: 1em;
}

fieldset input,
fieldset textarea {
  border: none
}

input {
  width: 100%;
  display: 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;
}
<h1>Fonts to base64</h1>
<fieldset>
  <legend>Enter CSS Url</legend>
  <input type="text" id="inputUrl" value="https://fonts.googleapis.com/css2?family=Open+Sans:ital@0;1&display=swap">
</fieldset>

<fieldset>
  <legend>New Css</legend>
  <textarea id="fontCss"></textarea>
  <p><a class="btn-default" id="btnDownload" href="#" download="fontface.css">Download css</a></p>
</fieldset>

进行测试: Codepen示例


0
简单的Nodejs脚本,适用于.woff字体文件。只需将扩展名更改为您的字体文件的扩展名,它也可以与其他扩展名一起使用。
const { readdirSync, mkdir, existsSync, readFileSync, writeFileSync } = require("fs")
const { resolve } = require("path")

const woffDirPath=resolve(".", "public", "assets", "fonts", "Typold", "woff")
const files = readdirSync(woffDirPath)
const base64Path = resolve(".", "public", "assets", "fonts", "Typold", "base64")
if (!existsSync(base64Path)) mkdir(base64Path, (err) => {
    console.log("Error on dir creattion", err);
});

for (let file of files) {
    if (file.includes(".woff")) {
        const fileData = readFileSync(resolve(woffDirPath, file), { encoding: "base64" })
        writeFileSync(resolve(base64Path, file.replace(".woff", ".txt")), fileData)
    }
}
console.log("done");

感谢您为Stack Overflow社区做出的贡献。这可能是一个正确的答案,但如果您能提供代码的额外解释,让开发人员能够理解您的推理过程,那将非常有用。对于不太熟悉语法或难以理解概念的新开发人员来说,这尤其有帮助。您是否可以编辑您的答案,以包含更多细节,造福社区? - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接