feat: use variable fonts with subsetting #2817

Merged
konrad merged 1 commits from dpschen/frontend:feature/variable-fonts-with-subsetting into main 2022-12-15 21:37:03 +00:00
30 changed files with 331 additions and 71 deletions

View File

@ -9,13 +9,9 @@
<link rel="icon" href="/favicon.ico">
<link rel="apple-touch-icon" href="/images/icons/apple-touch-icon-180x180.png"/>
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-italic.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-500.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-regular.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/open-sans-v15-latin-700.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/fonts/quicksand-v7-latin-regular.woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/OpenSans[wght].woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/OpenSans-Italic[wght].woff2" as="font">
<link rel="preload" crossorigin="anonymous" href="/src/assets/fonts/Quicksand[wght].woff2" as="font">
</head>
<body>
<noscript>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -15,7 +15,10 @@
"test:unit-watch": "vitest watch",
"test:frontend": "cypress run",
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"browserslist:update": "npx browserslist@latest --update-db"
"browserslist:update": "npx browserslist@latest --update-db",
"fonts:update": "pnpm run fonts:download && pnpm run fonts:subset",
"fonts:download": "./scripts/fonts-download.sh",
"fonts:subset": "./scripts/fonts-subset.sh"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "6.2.1",

56
scripts/fonts-download.sh Executable file
View File

@ -0,0 +1,56 @@
#!/bin/sh
set -e
#
# This script downloads our original font files from their source repos
# and puts them in our originalMedia folder.
#
err_report() {
echo "Error on line $(caller)" >&2
}
trap err_report ERR
ORIGINAL_FONTS_DIR="./originalMedia/fonts"
# update these if there is a new version
FONT_URLS=(
"https://github.com/googlefonts/opensans/blob/27d060e1aad6886daeda67629ee28189f795f534/fonts/variable/OpenSans%5Bwdth%2Cwght%5D.ttf?raw=true"
"https://github.com/googlefonts/opensans/blob/27d060e1aad6886daeda67629ee28189f795f534/fonts/variable/OpenSans-Italic%5Bwdth%2Cwght%5D.ttf?raw=true"
"https://github.com/andrew-paglinawan/QuicksandFamily/blob/db6de44878582966f45a0debaef10d57108d93a7/fonts/Quicksand%5Bwght%5D.ttf?raw=true"
)
echo ""
echo "###################################################"
echo "# Download font files"
echo "###################################################"
echo ""
mkdir -p $ORIGINAL_FONTS_DIR
for URL in ${FONT_URLS[@]}; do
wget -L $URL \
--directory-prefix=$ORIGINAL_FONTS_DIR \
--quiet \
--timestamping \
--show-progress
done
echo ""
echo "###################################################"
echo "# Remove '?raw=true' filename suffix"
echo "###################################################"
echo ""
# Iterate over all files in directory with filetype ending in "?raw=true"
for file in $ORIGINAL_FONTS_DIR/*?raw=true; do
# Remove "?raw=true" from file name and store in variable
new_name=$(echo $file | sed 's/?raw=true//')
# Overwrite existing file with new name
mv -v $file $new_name
done
echo "Renaming files complete"

161
scripts/fonts-subset.sh Executable file
View File

@ -0,0 +1,161 @@
#!/bin/sh
set -e
#
# This script subsets our variable fonts,
# converts them to woff2 files and puts them in the
# fonts folder.
#
# We do have to update the font paths in the @font-face
# definitions manually since we use a checksum to make
#
# We use fonttools to create a partial instance of the
# variable font where we keep only our needed features.
# See more at:
# https://fonttools.readthedocs.io/en/latest/varLib/instancer.html
#
# fonttools requires python > 3.7. For up-to-date
# instructions see https://github.com/fonttools/fonttools#installation
#
# Lot's of info was gathered from:
# https://markoskon.com/creating-font-subsets/
# https://barrd.dev/article/create-a-variable-font-subset-for-smaller-file-size/
#
ORIGINAL_FONTS="./originalMedia/fonts"
TEMP_FOLDER="./.subset-fonts-temp"
FONT_FOLDER="./src/assets/fonts"
err_report() {
echo "Error on line $(caller)" >&2
}
trap err_report ERR
mkdir -p $TEMP_FOLDER
# the latin subset that google uses on GoogleFonts
# this is the same as the latin subset range that google uses on GoogleFonts
# see for examle the unicode-range definition here:
# https://fonts.googleapis.com/css2?family=Open+Sans
UNICODE_LATIN_SUBSET="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"
get_filename_without_type() {
filename=$1
dirname=$(dirname $filename)
# Extract the file type using parameter expansion
filetype=${filename##*.}
basename=$(basename $filename .$filetype)
echo $basename
}
# This function takes a font file and creates a subset of it using a specified set of unicode characters.
instance_and_subset () {
# Define default arguments for the subsetter.
DEFAULT_SUBSETTER_ARGS="--layout-features=* --unicodes=${UNICODE_LATIN_SUBSET}"
# Assign function arguments to variables with more descriptive names.
INPUT_FONT_FILE=$1
INSTANCER_ARGS=$2
OUTPUT_FONT_BASENAME=$3
OUTPUT_FOLDER=$FONT_FOLDER
# If the output font basename is not provided, use the input font file's basename as the output font basename.
if [ -z "$OUTPUT_FONT_BASENAME" ]; then
INPUT_FONT_BASENAME=$(get_filename_without_type $INPUT_FONT_FILE)
OUTPUT_FONT_BASENAME=$INPUT_FONT_BASENAME
fi
# Use the default subsetter arguments if no custom arguments are provided.
SUBSETTER_ARGS="${4:-$DEFAULT_SUBSETTER_ARGS}"
CHECKSUM=$(
# Concatenate the contents of the input font file, the instancer arguments, and the subsetter arguments
printf "%s%s" "$(cat $INPUT_FONT_FILE)" "$INSTANCER_ARGS" "$SUBSETTER_ARGS" |
# Calculate the Blake2b checksum of the concatenated string
b2sum |
# Extract the checksum from the output of b2sum (it's the first field)
awk '{print $1}'
)
# Limit the checksum to 8 characters.
CHECKSUM=$(echo "${CHECKSUM:0:8}")
# Construct the output font's filename
OUTPUT_FONT_BASENAME="${OUTPUT_FONT_BASENAME}_${CHECKSUM}"
OUTPUT_FONT_FILE="${OUTPUT_FOLDER}/${OUTPUT_FONT_BASENAME}.woff2"
# Check if the output font file already exists
if test -f $OUTPUT_FONT_FILE; then
echo "${OUTPUT_FONT_FILE} exists"
return 0
fi
FONT_INSTANCE="${TEMP_FOLDER}/${OUTPUT_FONT_BASENAME}.ttf"
if [ -n "$INSTANCER_ARGS" ]; then
# If the INSTANCER_ARGS variable is set, use fonttools to create a font instance
fonttools varLib.instancer --output $FONT_INSTANCE $INPUT_FONT_FILE $INSTANCER_ARGS
else
# Otherwise, just copy the input font file to the font instance file
cp $INPUT_FONT_FILE $FONT_INSTANCE
fi
# Use pyftsubset to create a subset of the font instance and save it to the output font file
pyftsubset $FONT_INSTANCE --output-file=$OUTPUT_FONT_FILE --flavor=woff2 $SUBSETTER_ARGS
echo "${OUTPUT_FONT_BASENAME} subsetted."
}
echo ""
echo "###################################################"
echo "# Install required libs"
echo "###################################################"
echo ""
pip install fonttools brotli
echo ""
echo "###################################################"
echo "# Create a partial instance of the variable font"
echo "# where we keep only our needed features and then"
echo "# subset fonts with latin unicode range and export"
echo "# as woff2 file"
echo "###################################################"
echo ""
mkdir -p $TEMP_FOLDER
echo "\nOpen Sans"
# we drop the wdth axis for all
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans[wght]"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-Regular"
instance_and_subset "${ORIGINAL_FONTS}/OpenSans[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-Bold"
echo "\nOpen Sans Italic"
# we drop the wdth axis for all
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400:700" "OpenSans-Italic[wght]"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=400" "OpenSans-RegularItalic"
instance_and_subset "${ORIGINAL_FONTS}/OpenSans-Italic[wdth,wght].ttf" "wdth=drop wght=700" "OpenSans-BoldItalic"
echo "\nQuicksand"
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400:700"
# we restrict the wght range
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=400" "Quicksand-Regular"
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=600" "Quicksand-SemiBold"
instance_and_subset "${ORIGINAL_FONTS}/Quicksand[wght].ttf" "wght=700" "Quicksand-Bold"
echo "\nSubsetting files complete"
# remove temp folder
rm -r $TEMP_FOLDER

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -435,7 +435,7 @@ $vikunja-nav-selected-width: 0.4rem;
.menu-list {
li {
font-weight: 500;

It seems like our 500 file was actually a semi-bold, meaning 600.
When I changed to the variable font everything was a bit to thin.
Also the exported 500. That's the reason for this change.
One disadvantage of this: if there is no variable font loaded the browser might round this up to bold for the fallback font. Since we preload the font the probability should be very small that this happens.

It seems like our 500 file was actually a semi-bold, meaning 600. When I changed to the variable font everything was a bit to thin. Also the exported 500. That's the reason for this change. One disadvantage of this: if there is no variable font loaded the browser might round this up to bold for the fallback font. Since we preload the font the probability should be very small that this happens.
font-weight: 600;
font-family: $vikunja-font;
}
@ -460,7 +460,7 @@ $vikunja-nav-selected-width: 0.4rem;
font-weight: bold;
font-family: $vikunja-font;
color: $vikunja-nav-color;
font-weight: 500;
font-weight: 600;
min-height: 2.5rem;
padding-top: 0;
padding-left: $navbar-padding;
@ -479,6 +479,8 @@ $vikunja-nav-selected-width: 0.4rem;
.count {
color: var(--grey-500);
margin-right: .5rem;
// align brackets with number
font-feature-settings: "case";
dpschen marked this conversation as resolved Outdated

This aligns the brackets with the number.

This aligns the brackets with the number.
}
}

View File

@ -98,5 +98,6 @@ function setSubscriptionInStore(sub: ISubscription) {
<style scoped lang="scss">
.dropdown-trigger {
padding: 0.5rem;
display: flex;
dpschen marked this conversation as resolved Outdated

Aligns the three dots with the rest of the line

Aligns the three dots with the rest of the line
}
</style>

View File

@ -38,7 +38,7 @@
<template v-for="(pt, i) in task.relatedTasks.parenttask">
{{ pt.title }}<template v-if="(i + 1) < task.relatedTasks.parenttask.length">,&nbsp;</template>
</template>
>
&rsaquo;
dpschen marked this conversation as resolved Outdated

More fitting sign for the parent child relation indicator

More fitting sign for the parent child relation indicator
</span>
{{ task.title }}
</span>
@ -71,7 +71,7 @@
class="is-italic"
:aria-expanded="showDefer ? 'true' : 'false'"
>
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
{{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
dpschen marked this conversation as resolved Outdated

Longer sign.

Longer sign.
</time>
</BaseButton>
<CustomTransition name="fade">

View File

@ -10,13 +10,14 @@
// are defined here.
//
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
dpschen marked this conversation as resolved Outdated

This was the actual issue why Open Sans wasn't used.

This was the actual issue why Open Sans wasn't used.

Because the variable was defined after the bulma import?

Because the variable was defined after the bulma import?

yes. by setting the variable before the variables in bulma dont fall back to their default

yes. by setting the variable before the variables in bulma dont fall back to their default
// the default values get overwritten by the definitions above
@import "bulma-css-variables/sass/utilities/_all";
// since $tablet is defined by bulma we can just define it after importing the utilities
$mobile: math.div($tablet, 2);
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
$vikunja-font: 'Quicksand', sans-serif;
$pagination-current-border: var(--primary);

View File

@ -1,65 +1,105 @@
$font-files-path: '/fonts/';
$font-files-path: '@/assets/fonts/';
dpschen marked this conversation as resolved Outdated

By not using the public folder for the font files, a hash gets added to the final font file.
This should simplify caching, since we could cache forever.

This additional hash is needed in case we change something inside the instance_and_subset function that has nothing to do with the input arguments (original font file / parameters).

By not using the public folder for the font files, a hash gets added to the final font file. This should simplify caching, since we could cache forever. This additional hash is needed in case we change something inside the `instance_and_subset` function that has nothing to do with the input arguments (original font file / parameters).
// quicksand-regular - latin
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url($font-files-path + 'quicksand-v7-latin-regular.woff2') format('woff2');
// this is the same as the latin range that google uses.
// see for examle the unicode-range definition here:
// https://fonts.googleapis.com/css2?family=Open+Sans
$unicode-range: "U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,\
dpschen marked this conversation as resolved Outdated

The current unicode range only supports (what google defines as) 'latin'. We could extend that in the future and create more additional subsets.

The current unicode range only supports (what google defines as) 'latin'. We could extend that in the future and create more additional subsets.
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";
@supports (font-variation-settings: normal) {
@font-face {
font-family: 'Quicksand';
src: url($font-files-path + 'Quicksand[wght]_87bdcc7f.woff2') format('woff2-variations');
src: url($font-files-path + 'Quicksand[wght]_87bdcc7f.woff2') format('woff2') tech('variations');
dpschen marked this conversation as resolved Outdated

This is the upcoming syntax. Recommended to use both currently

This is the upcoming syntax. Recommended to use both currently
font-style: normal;
font-weight: 400 700;
font-display: swap;
unicode-range: $unicode-range;
dpschen marked this conversation as resolved Outdated

If I remember correctly the font files before were already subsetted. So this unicode-range definition basically just tells what's written in the file. It gets useful in the moment that we add multiple ranges.

If I remember correctly the font files before were already subsetted. So this unicode-range definition basically just tells what's written in the file. It gets useful in the moment that we add multiple ranges.
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans[wght]_54a65da5.woff2') format('woff2-variations');
src: url($font-files-path + 'OpenSans[wght]_54a65da5.woff2') format('woff2') tech('variations');
dpschen marked this conversation as resolved Outdated

Every time we update the font we need to change the file name here. I don't have a better solution for this as of now. For sure I could have created this file via a script, but that seemed more complex than simply updating the hash. It doesn't happen that often that a new font version is released. But it's good that in that case we are prepared.

Every time we update the font we need to change the file name here. I don't have a better solution for this as of now. For sure I could have created this file via a script, but that seemed more complex than simply updating the hash. It doesn't happen that often that a new font version is released. But it's good that in that case we are prepared.
font-weight: 400 700;
font-display: swap;
unicode-range: $unicode-range;
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans-Italic[wght]_c9a8fe68.woff2') format('woff2-variations');
src: url($font-files-path + 'OpenSans-Italic[wght]_c9a8fe68.woff2') format('woff2') tech('variations');
font-weight: 400 700;
font-style: italic;
font-display: swap;
unicode-range: $unicode-range;
}
}
// quicksand-500 - latin
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url($font-files-path + 'quicksand-v7-latin-500.woff2') format('woff2');
}
/* Set up for old browsers */
@supports not (font-variation-settings: normal) {
@font-face {
font-family: 'Quicksand';
src: url($font-files-path + 'Quicksand-Regular_3e913e7e.woff2') format('woff2');
font-style: normal;
font-weight: 400;
font-display: swap;
unicode-range: $unicode-range;
}
// quicksand-700 - latin
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url($font-files-path + 'quicksand-v7-latin-700.woff2') format('woff2');
}
@font-face {
font-family: 'Quicksand';
src: url($font-files-path + 'Quicksand-SemiBold_be48a442.woff2') format('woff2');
font-style: normal;
font-weight: 600;
font-display: swap;
unicode-range: $unicode-range;
}
// open-sans-regular - latin
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url($font-files-path + 'open-sans-v15-latin-regular.woff2') format('woff2');
}
@font-face {
font-family: 'Quicksand';
src: url($font-files-path + 'Quicksand-Bold_20b26f76.woff2') format('woff2');
font-style: normal;
font-weight: 700;
font-display: swap;
unicode-range: $unicode-range;
}
// open-sans-italic - latin
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url($font-files-path + 'open-sans-v15-latin-italic.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans-Regular_d0acb717.woff2') format('woff2');
font-style: normal;
font-weight: 400;
font-display: swap;
unicode-range: $unicode-range;
}
// open-sans-700 - latin
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url($font-files-path + 'open-sans-v15-latin-700.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans-RegularItalic_48244a7a.woff2') format('woff2');
font-style: italic;
font-weight: 400;
font-display: swap;
unicode-range: $unicode-range;
}
// open-sans-700italic - latin
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url($font-files-path + 'open-sans-v15-latin-700italic.woff2') format('woff2');
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans-Bold_eb52363b.woff2') format('woff2');
font-style: normal;
font-weight: 700;
font-display: swap;
unicode-range: $unicode-range;
}
@font-face {
font-family: 'Open Sans';
src: url($font-files-path + 'OpenSans-BoldItalic_3ff98862.woff2') format('woff2');
font-style: italic;
font-weight: 700;
font-display: swap;
unicode-range: $unicode-range;
}
}

View File

@ -14,7 +14,7 @@
ref="heading"
/>
<h6 class="subtitle" v-if="parent && parent.namespace && parent.list">
{{ getNamespaceTitle(parent.namespace) }} >
{{ getNamespaceTitle(parent.namespace) }} &rsaquo;
<router-link :to="{ name: 'list.index', params: { listId: parent.list.id } }">
{{ getListTitle(parent.list) }}
</router-link>