feat: use variable fonts with subsetting (#2817)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#2817
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
Dominik Pschenitschni 2022-12-15 21:37:02 +00:00 committed by konrad
parent a3978bb359
commit b6a89a0cde
30 changed files with 331 additions and 71 deletions

@ -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.

@ -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

@ -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

@ -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.

@ -435,7 +435,7 @@ $vikunja-nav-selected-width: 0.4rem;
.menu-list {
li {
font-weight: 500;
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";
}
}

@ -98,5 +98,6 @@ function setSubscriptionInStore(sub: ISubscription) {
<style scoped lang="scss">
.dropdown-trigger {
padding: 0.5rem;
display: flex;
}
</style>

@ -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;
</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)}) }}
</time>
</BaseButton>
<CustomTransition name="fade">

@ -10,13 +10,14 @@
// are defined here.
//
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
// 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);

@ -1,65 +1,105 @@
$font-files-path: '/fonts/';
$font-files-path: '@/assets/fonts/';
// 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,\
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');
font-style: normal;
font-weight: 400 700;
font-display: swap;
unicode-range: $unicode-range;
}
@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');
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;
}
}

@ -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>