🐛 fix: remove flashing from header text (Firefox)
Loads a subset of the sans-serif font for the header. Fixes #75
This commit is contained in:
parent
1c06c99047
commit
a5981e6fdd
@ -62,8 +62,15 @@ socials = [
|
|||||||
# Default config, allows for https remote images and embedding YouTube and Vimeo content.
|
# Default config, allows for https remote images and embedding YouTube and Vimeo content.
|
||||||
# This configuration (along with the right webserver settings) gets an A+ in Mozilla's Observatory: https://observatory.mozilla.org
|
# This configuration (along with the right webserver settings) gets an A+ in Mozilla's Observatory: https://observatory.mozilla.org
|
||||||
allowed_domains = [
|
allowed_domains = [
|
||||||
|
{ directive = "font-src", domains = ["'self'", "data:"] },
|
||||||
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
|
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
|
||||||
{ directive = "script-src", domains = ["'self'"] },
|
{ directive = "script-src", domains = ["'self'"] },
|
||||||
{ directive = "style-src", domains = ["'self'"] },
|
{ directive = "style-src", domains = ["'self'"] },
|
||||||
{ directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] },
|
{ directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Custom subset of characters for the header.
|
||||||
|
# If set to true, the `static/custom_subset.css` file will be loaded first.
|
||||||
|
# This avoids a flashing text issue in Firefox.
|
||||||
|
# Please see https://welpo.github.io/tabi/blog/custom-font-subset/ to learn how to create this file.
|
||||||
|
custom_subset = true
|
||||||
|
181
content/blog/custom-font-subset.md
Normal file
181
content/blog/custom-font-subset.md
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
+++
|
||||||
|
title = "Optimise loading times with a custom font subset"
|
||||||
|
date = 2023-04-29
|
||||||
|
description = "Learn how to create a custom subset that only includes the necessary glyphs."
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tags = ["showcase", "tutorial"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
## The problem
|
||||||
|
|
||||||
|
Custom fonts cause flashing text in Firefox. For a gif and more details, see [this issue](https://github.com/welpo/tabi/issues/75).
|
||||||
|
|
||||||
|
## The solution
|
||||||
|
|
||||||
|
To fix this, tabi loads a subset of glyphs for the header. Since this (slightly) increases the initial load time, it's a good idea to try and minimise the size of this subset.
|
||||||
|
|
||||||
|
By default, there are subset files for English and Spanish characters (with a few symbols). These files are loaded when the Zola page/site is set to that language.
|
||||||
|
|
||||||
|
For further optimisation, you can create a custom font subset that only includes the characters used in your header.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Install these tools:
|
||||||
|
|
||||||
|
- [fonttools](https://github.com/fonttools/fonttools)
|
||||||
|
|
||||||
|
- [brotli](https://github.com/google/brotli)
|
||||||
|
|
||||||
|
Run `pip install fonttools brotli` to install both.
|
||||||
|
|
||||||
|
## The script
|
||||||
|
|
||||||
|
The script below takes a `config.toml` file and a font file as input, extracts the necessary characters, creates a subset of the font, and generates a CSS file containing the base64 encoded subset.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 [--config | -c CONFIG_FILE] [--font | -f FONT_FILE] [--output | -o OUTPUT_PATH]"
|
||||||
|
echo
|
||||||
|
echo "Options:"
|
||||||
|
echo " --config, -c Path to the config.toml file."
|
||||||
|
echo " --font, -f Path to the font file."
|
||||||
|
echo " --output, -o Output path for the generated custom_subset.css file (default: current directory)"
|
||||||
|
echo " --help, -h Show this help message and exit"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default output is current directory.
|
||||||
|
output_path="."
|
||||||
|
|
||||||
|
# Parse command line options
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--config|-c)
|
||||||
|
config_file="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--font|-f)
|
||||||
|
font_file="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--output|-o)
|
||||||
|
output_path="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if -c and -f options are provided
|
||||||
|
if [ -z "$config_file" ]; then
|
||||||
|
echo "Error: --config|-c option is required."
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$font_file" ]; then
|
||||||
|
echo "Error: --font|-f option is required."
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if config and font files exist.
|
||||||
|
if [ ! -f "$config_file" ]; then
|
||||||
|
echo "Error: Config file '$config_file' not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$font_file" ]; then
|
||||||
|
echo "Error: Font file '$font_file' not found."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract the title and menu names from the config file.
|
||||||
|
title=$(awk -F' = ' '/title/{print $2}' "$config_file" | tr -d '"' | grep -v "atom feed")
|
||||||
|
menu_names=$(awk '/menu/{f=1;next} /^\S/{f=0} f{print}' "$config_file" | awk -F' = ' '/name/{print $2}' | tr -d '"' )
|
||||||
|
|
||||||
|
# Combine the extracted strings.
|
||||||
|
combined="$title$menu_names"
|
||||||
|
|
||||||
|
# Get unique characters.
|
||||||
|
unique_chars=$(echo "$combined" | grep -o . | sort -u | tr -d '\n')
|
||||||
|
|
||||||
|
# Create a temporary file for subset.woff2.
|
||||||
|
temp_subset=$(mktemp)
|
||||||
|
|
||||||
|
# Create the subset.
|
||||||
|
pyftsubset "$font_file" \
|
||||||
|
--text="$unique_chars" \
|
||||||
|
--layout-features="" --flavor="woff2" --output-file="$temp_subset" --with-zopfli
|
||||||
|
|
||||||
|
# Remove trailing slash from output path, if present.
|
||||||
|
output_path=${output_path%/}
|
||||||
|
|
||||||
|
# Base64 encode the temporary subset.woff2 file and create the CSS file.
|
||||||
|
base64_encoded_font=$(base64 -i "$temp_subset")
|
||||||
|
echo "@font-face{font-family:\"Inter Subset\";src:url(data:application/font-woff2;base64,$base64_encoded_font);}" > "$output_path/custom_subset.css"
|
||||||
|
|
||||||
|
# Remove the temporary subset.woff2 file.
|
||||||
|
rm "$temp_subset"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Save the script somewhere like `~/bin/subset_font`. Make it executable with `chmod +x ~/bin/subset_font`.
|
||||||
|
|
||||||
|
Now you can run it with the required `--config` and `--font` options:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/bin/subset_font --config path/to/config.toml --font path/to/font.woff2
|
||||||
|
```
|
||||||
|
By default, this generates a `custom_subset.css` file in the current directory. Use `-o` or `--output` to specify a different path:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/bin/subset_font -c path/to/config.toml -f path/to/font.woff2 -o path/to/output
|
||||||
|
```
|
||||||
|
|
||||||
|
You should place this `custom_subset.css` file inside the `static/` directory.
|
||||||
|
|
||||||
|
|
||||||
|
## Automating with Pre-commit Hook
|
||||||
|
|
||||||
|
You might change the title or menu options of your site, making the custom subset no longer useful.
|
||||||
|
|
||||||
|
To automate the process of creating this file, you can integrate the script into a Git pre-commit hook that checks for changes in the `config.toml` file, runs the script, and stores the resulting CSS file in the `static/` directory of your site.
|
||||||
|
|
||||||
|
1. Create a `.git/hooks/pre-commit` file in your Git project, if it doesn't already exist.
|
||||||
|
|
||||||
|
2. Make it executable with `chmod +x .git/hooks/pre-commit`.
|
||||||
|
|
||||||
|
3. Add the following code to the file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if config.toml has been modified.
|
||||||
|
if git diff --cached --name-only | grep -q "config.toml"; then
|
||||||
|
echo "config.toml modified. Running subset_font…"
|
||||||
|
|
||||||
|
# Call the subset_font script.
|
||||||
|
~/bin/subset_font -c config.toml -f static/fonts/Inter4.woff2 -o static/
|
||||||
|
|
||||||
|
# Add the generated subset.css file to the commit.
|
||||||
|
git add static/custom_subset.css
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to modify the script to match the path where you stored the `subset_font` script. The config and font paths should work fine with tabi's default setup.
|
||||||
|
|
||||||
|
Now, every time you commit changes to your Git project, the pre-commit hook will check for modifications in the `config.toml` file and automatically run the``subset_font` script to update the `custom_subset.css` file.
|
||||||
|
|
||||||
|
By the way, if you're interested in a way to automatically update the date of your Zola posts or compress your PNG files, check out [this post](https://welpo.ooo/blog/zola-date-git-hook/).
|
||||||
|
|
||||||
|
If you want to use all scripts at once (compressing PNG files, updating the date, and creating the font subset), combine their code into a single `.git/hooks/pre-commit` file.
|
@ -1,9 +0,0 @@
|
|||||||
+++
|
|
||||||
title = "One more post"
|
|
||||||
date = 2021-05-30
|
|
||||||
description = "The only reason this post exists is to show the pagination in the blog section."
|
|
||||||
+++
|
|
||||||
|
|
||||||
> ‘Look at you,’ she said, not without affection. ‘Sitting there like an owl. Melancholy bloody gargoyle. You mawkish bugger. You don’t get any insight, you know, just because it’s night. Just because some buildings have their lights on.’
|
|
||||||
>
|
|
||||||
> — China Miéville, The City & the City
|
|
@ -1,7 +1,7 @@
|
|||||||
+++
|
+++
|
||||||
title = "Secure by default"
|
title = "Secure by default"
|
||||||
date = 2023-02-22
|
date = 2023-02-22
|
||||||
updated = 2023-04-14
|
updated = 2023-04-29
|
||||||
description = "tabi has an easily customizable Content Security Policy (CSP) with safe defaults. Get peace of mind and an A+ on Mozilla Observatory."
|
description = "tabi has an easily customizable Content Security Policy (CSP) with safe defaults. Get peace of mind and an A+ on Mozilla Observatory."
|
||||||
|
|
||||||
[taxonomies]
|
[taxonomies]
|
||||||
@ -10,19 +10,20 @@ tags = ["security", "showcase"]
|
|||||||
|
|
||||||
The default configuration of the theme gets an A+ score on [Mozilla Observatory](https://observatory.mozilla.org).[^1]
|
The default configuration of the theme gets an A+ score on [Mozilla Observatory](https://observatory.mozilla.org).[^1]
|
||||||
|
|
||||||
This is accomplished by programatically configuring Content Security Policy (CSP) headers based on a user-defined list of allowed domains in the theme's `config.toml` file. Here's the default and recommended setup (you could remove the last directive if you don't want to embed videos):
|
This is accomplished by programatically configuring Content Security Policy (CSP) headers based on a user-defined list of allowed domains in the `config.toml` file. Here's the default and recommended setup (you could remove the last directive if you don't want to embed YouTube videos):
|
||||||
|
|
||||||
```
|
```
|
||||||
[extra]
|
[extra]
|
||||||
allowed_domains = [
|
allowed_domains = [
|
||||||
{ directive = "img-src", domains = ["'self'", "https://*"] },
|
{ directive = "font-src", domains = ["'self'", "data:"] },
|
||||||
|
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
|
||||||
{ directive = "script-src", domains = ["'self'"] },
|
{ directive = "script-src", domains = ["'self'"] },
|
||||||
{ directive = "style-src", domains = ["'self'"] },
|
{ directive = "style-src", domains = ["'self'"] },
|
||||||
{ directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] },
|
{ directive = "frame-src", domains = ["https://www.youtube-nocookie.com"] },
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
The allowed_domains list specifies the URLs that the website should be able to connect to, and each domain in the list is associated with a CSP directive such as `frame-src`, `connect-src`, or `script-src`. The `templates/partials/header.html` file dynamically generates the CSP header based on this list.
|
The `allowed_domains` list specifies the URLs that the website should be able to connect to, and each domain in the list is associated with a CSP directive such as `frame-src`, `connect-src`, or `script-src`. The `templates/partials/header.html` file dynamically generates the CSP header based on this list.
|
||||||
|
|
||||||
This feature allows you to easily customize the website's security headers to allow for specific use cases, such as embedding YouTube videos, loading remote fonts ([not recommended](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)) or scripts.
|
This feature allows you to easily customize the website's security headers to allow for specific use cases, such as embedding YouTube videos, loading remote fonts ([not recommended](https://www.albertovarela.net/blog/2022/11/stop-using-google-fonts/)) or scripts.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
/* Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter). Licensed under the SIL Open Font License, Version 1.1. More information available at: http://scripts.sil.org/OFL */
|
/* Copyright 2016 The Inter Project Authors (https://github.com/rsms/inter). Licensed under the SIL Open Font License, Version 1.1. More information available at: http://scripts.sil.org/OFL */
|
||||||
font-family: 'Inter';
|
font-family: 'Inter';
|
||||||
src: local('Inter'),
|
src: local('Inter'),
|
||||||
url('fonts/Inter4.0-beta8.woff2') format("woff2");
|
url('fonts/Inter4.woff2') format("woff2");
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
header {
|
header {
|
||||||
|
font-family: 'Inter Subset', var(--sans-serif-font);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
1
static/custom_subset.css
Normal file
1
static/custom_subset.css
Normal file
File diff suppressed because one or more lines are too long
1
static/inter_subset_en.css
Normal file
1
static/inter_subset_en.css
Normal file
File diff suppressed because one or more lines are too long
1
static/inter_subset_es.css
Normal file
1
static/inter_subset_es.css
Normal file
File diff suppressed because one or more lines are too long
@ -8,7 +8,7 @@
|
|||||||
{% import "macros/format_date.html" as macros_format_date %}
|
{% import "macros/format_date.html" as macros_format_date %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang = "en">
|
<html lang="{{ lang }}">
|
||||||
{% include "partials/header.html" %}
|
{% include "partials/header.html" %}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -14,6 +14,17 @@
|
|||||||
{# RSS #}
|
{# RSS #}
|
||||||
<link rel="alternate" type="application/atom+xml" title="{{ config.title }}" href="{{ get_url(path="atom.xml",
|
<link rel="alternate" type="application/atom+xml" title="{{ config.title }}" href="{{ get_url(path="atom.xml",
|
||||||
trailing_slash=false) }}">
|
trailing_slash=false) }}">
|
||||||
|
|
||||||
|
{# CSS #}
|
||||||
|
{# Load subset of glyphs for header. Avoids flashing issue in Firefox #}
|
||||||
|
{% if config.extra.custom_subset == true %}
|
||||||
|
<link rel="stylesheet" href={{ get_url(path="custom_subset.css" ) }}>
|
||||||
|
{% elif lang == 'en' %}
|
||||||
|
<link rel="stylesheet" href={{ get_url(path="inter_subset_en.css" ) }}>
|
||||||
|
{% elif lang == 'es' %}
|
||||||
|
<link rel="stylesheet" href={{ get_url(path="inter_subset_es.css" ) }}>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href={{ get_url(path="main.css" ) }} />
|
<link rel="stylesheet" type="text/css" media="screen" href={{ get_url(path="main.css" ) }} />
|
||||||
|
|
||||||
{% if config.extra.stylesheets %}
|
{% if config.extra.stylesheets %}
|
||||||
|
@ -44,8 +44,15 @@ menu = [
|
|||||||
# Default config, allows for https remote images and embedding YouTube and Vimeo content.
|
# Default config, allows for https remote images and embedding YouTube and Vimeo content.
|
||||||
# This configuration (along with the right webserver settings) gets an A+ in Mozilla's Observatory: https://observatory.mozilla.org
|
# This configuration (along with the right webserver settings) gets an A+ in Mozilla's Observatory: https://observatory.mozilla.org
|
||||||
allowed_domains = [
|
allowed_domains = [
|
||||||
|
{ directive = "font-src", domains = ["'self'", "data:"] },
|
||||||
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
|
{ directive = "img-src", domains = ["'self'", "https://*", "data:"] },
|
||||||
{ directive = "script-src", domains = ["'self'"] },
|
{ directive = "script-src", domains = ["'self'"] },
|
||||||
{ directive = "style-src", domains = ["'self'"] },
|
{ directive = "style-src", domains = ["'self'"] },
|
||||||
{ directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] },
|
{ directive = "frame-src", domains = ["player.vimeo.com", "https://www.youtube-nocookie.com"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Custom subset of characters for the header.
|
||||||
|
# If set to true, the `static/custom_subset.css` file will be loaded first.
|
||||||
|
# This avoids a flashing text issue in Firefox.
|
||||||
|
# Please see https://welpo.github.io/tabi/blog/custom-font-subset/ to learn how to create this file.
|
||||||
|
custom_subset = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user