Reputation: 6970
I want to defer font loading on my site inspired by deferred font loading logic for Smashing Magazine.
Main part of this is converting fonts to base64 and preparing your CSS file. My steps so far:
CSS snippet for Open Sans Bold:
@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;
}
The problem is, that converted fonts look a lot different. Take a look at Open Sans Bold:
Especially notice accents being way off and absolutely horrible letter a
. Other font families and variants look very noticeably different as well (size and shape distortions, etc.).
So the question is: How do you properly encode TTF files from Google Web Fonts (or other source) to base64 format and use it in a way that the result is identical to the original file?
Upvotes: 120
Views: 154830
Reputation: 139
This bash script effortlessly converts .otf, .ttf, .woff, and .woff2 font files to its css-base64 @font-face version, ensuring they stay as gorgeous in your CSS as they do in the wild.
Why It's Awesome:
Quick Start:
Code:
#!/bin/bash
# Initialize a variable to hold all @font-face codes, used when choosing the clipboard
all_font_face_codes=""
FONT_DIR="."
OUTPUT_DIR="./css"
SUPPORTED_EXTENSIONS=("otf" "ttf" "woff" "woff2")
usage() {
echo "This script is designed to generate CSS files or copy @font-face code to the clipboard for font files in a specified directory."
echo "Usage: $0 -i path/to/fonts -o path/to/output/css"
exit 1
}
# Parse command-line arguments
while getopts "i:o:h" opt; do
case ${opt} in
i)
FONT_DIR="${OPTARG}"
;;
o)
OUTPUT_DIR="${OPTARG}"
;;
h|*)
usage
;;
esac
done
# Prompt user for action: create CSS files or copy to clipboard
options=("Create CSS files" "Copy the @font-face code to the clipboard")
select opt in "${options[@]}"; do
case $opt in
"Create CSS files")
user_choice=1
break
;;
"Copy the @font-face code to the clipboard")
user_choice=2
break
;;
*)
echo "Invalid choice. Please enter 1 or 2."
;;
esac
done
mkdir -p $OUTPUT_DIR
# Function to generate @font-face code
generate_font_face_code() {
local font_filename="$1"
local font_name
font_name="${font##*/}" # Extract filename from path
font_name="${font_name%.*}" # Remove extension
local font_format
# Check if base64 supports the -w or -b options
if base64 -w 0 < /dev/null &>/dev/null; then
base64_string=$(base64 -w 0 < "$font_filename")
elif base64 -b 0 < /dev/null &>/dev/null; then
base64_string=$(base64 -b 0 < "$font_filename")
else
echo "The base64 command on your system does not support the required options."
exit 1
fi
font_filename_lowercase=$(echo "$font_filename" | tr '[:upper:]' '[:lower:]')
case "$font_filename_lowercase" in
*.ttf)
font_format='truetype'
;;
*.otf)
font_format='opentype'
;;
*.woff)
font_format='woff'
;;
*.woff2)
font_format='woff2'
;;
esac
echo "@font-face {
font-family: '$font_name';
/* Remember to add font-weight, font-style, etc. if needed */
src: url(data:application/$font_format;charset=utf-8;base64,$base64_string) format('$font_format');
}"
}
# Function to copy to clipboard
copy_to_clipboard() {
local data=$1
if command -v pbcopy &>/dev/null; then
echo "$data" | pbcopy
elif command -v xclip &>/dev/null; then
echo "$data" | xclip -selection clipboard
else
echo "Clipboard support is not available on your OS."
exit 1
fi
}
# Loop through each file in the directory and target supported font extensions
for file in "$FONT_DIR"/*; do
# Extract the file extension in lowercase
ext=$(echo "${file##*.}" | tr '[:upper:]' '[:lower:]')
# Check if the file extension is supported
if [[ " ${SUPPORTED_EXTENSIONS[@]} " =~ " ${ext} " ]]; then
font="$file"
font_face_code=$(generate_font_face_code "$font")
case $user_choice in
1)
# User chose to create CSS files
# Remove extension from filename and append .css
css_filename="${OUTPUT_DIR}/${file%.*}.css"
echo "$font_face_code" >> "$css_filename"
echo "Generated CSS for ${file%.*}"
;;
2)
# User chose to copy to clipboard
all_font_face_codes+="$font_face_code"$'\n'
echo "Generated CSS source for ${file%.*}"
;;
esac
fi
done
# Output message based on user choice
if [ $user_choice -eq 1 ]; then
echo "All fonts processed."
echo "CSS files generated in $OUTPUT_DIR"
elif [ $user_choice -eq 2 ]; then
copy_to_clipboard "$all_font_face_codes"
echo "All @font-face codes copied to clipboard."
fi
Example usage
$ ./font_encoder.sh # [-h -i -o]
Try it out and watch your fonts transform smoothly, happy coding! 🚀
Upvotes: 1
Reputation: 17115
In addition to the previous answers – some warnings:
Generators/Converters like fontquirrel or transfonter will actually parse and rebuild your font file.
This process might also introduce changes due to optimization settings like hinting data that impacts font rendering.
2023: Many converters like fontsquirrel and transfonter don't support variable fonts
When using these tools to get a base64 data URL you might lose variable font features (design axes related data is stripped) when generating a data URL.
That doesn't mean, you shouldn't use these converters at all - they mostly work well if
As demonstrated by Ilyich: You can use any base64 encoder. (E.g browserlings converter.
Here's another JS helper example based on these steps:
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>
For testing: Codepen example
Upvotes: 5
Reputation: 1
Simple Nodejs Script That works for .woff fonts. Just change the extension to your font files extension and it will work with other extensions as well
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");
Upvotes: 0
Reputation: 5680
In the Font Squirrel Expert options, make sure to set the 'TrueType Hinting' option to 'Keep Existing'. Either of the other options will cause the TrueType instructions (hints) to be modified, which will in turn affect the rendering of the font.
Alternatively, if you're happy with the rendering of the font directly from GWF, you can just take that file and do the base64 encoding yourself. In OS X or Linux, use the built-in base64 command in Terminal/shell:
$ base64 -i myfont.ttf -o fontbase64.txt
For Windows, you'll need to download a program to encode in base64 (there are several free/Open Source tools available). Copy the contents of that file, then use in your CSS as:
@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;
}
(Note that you may need to make some adjustments to the various @font-face info to match your particular font data; this is just an example template)
Upvotes: 167
Reputation: 5776
Use this code snippet to base64 encode your font directly in the browser (OS independent, no need to install anything)
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)">
Then copy the output and paste it into your CSS:
@font-face {
font-family: 'myfont';
src: url("<<copied base64 string>>");
}
Upvotes: 110