Оптимізація кількості гліфів шрифту для використання у web

Вибираємо потрібні гліфи 1590x814 PTSansNarrowRegular.png
fontforge glyphs

Необхідні пакети apt install fontforge и pip install fonttools.

Визначимося з необхідними гліфами, потрібна латиниця, кирилиця, спецсимволи, включаючи деякі грецькі символи (ΣΩαβγμ), аналоги грецького, схожі в накресленні, але такі, що мають інші коди (∆∑µ), і ще трохи, які можуть десь використовуватися в тексті або як елементи, що замінюють графіку (♪♫♬).

Чому декілька варіантів гліфів з різними кодами?

Тому що символ µ (mu U+00b5) та грецька літера μ (mu U+03bc) у розширених шрифтах можуть мати свій гліф з різним накресленням, або навпаки, загальний гліф, де схожі символи з начерку посилаються на один гліф.

Або, наприклад, грецька Σ (Sigma U+03a3) не те саме, що символ ∑ (summation U+2211).
Але можливо, у вас на екрані ці символи виглядають однаково.

Варіант з підготовленими наборами символів у unicode

nano make_menu.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/bin/bash

OPTS=`getopt -o I:O:S:L: --long in:,out:,subset:,layout: -n 'parse-options' -- "$@"` ; EC="$?"
eval set -- "$OPTS"
while true; do
  case "$1" in
    -I | --in )                   in=$2;                      shift 2 ;;
    -O | --out)                   out=$2;                     shift 2 ;;
    -S | --subset)                subset=$2;                  shift 2 ;;
    -L | --layout)                layout=$2;                  shift 2 ;;
    -- ) shift; break ;;
    *  ) break ;;
  esac
done

printf 'Read:   %s\n' "${in}"
printf 'Write:  %s\n' "${out}"

base="U+20-7E,U+A1-B7,U+B9-BE,U+C0,U+C1,U+C8,U+C9,\
U+CC,U+CD,U+D2,U+D3,U+D7,U+D9,U+DA,U+E0,U+E1,U+E8,U+E9,U+EB-ED,\
U+EF,U+F2,U+F3,U+F7-FA,U+131,U+2BB,U+2BC,U+2C6,U+2DA,U+2DC,U+400,U+401,\
U+403-408,U+410-451,U+453-458,U+45C-45F,U+490,U+491,U+4B0,U+4B1,\
U+2011,U+2018-201F,U+2022-2026,U+2030-203A,U+2041,U+2043,U+2044,\
U+204B,U+2052,U+2053,U+2074,U+20AC,U+2116,U+2122,U+2191,U+2193,U+2212,U+2215"

base_ext="U+20-7E,U+A1-B7,U+B9-BE,U+C0,U+C1,U+C8,U+C9,\
U+CC,U+CD,U+D2,U+D3,U+D7,U+D9,U+DA,U+E0,U+E1,U+E8,U+E9,\
U+EB-ED,U+EF,U+F2,U+F3,U+F7-FA,U+131,U+192,U+2BB,U+2BC,U+2C6,U+2C7,\
U+2D9,U+2DA,U+2DC,U+394,U+3A9,U+3C0,U+400,U+401,U+403-408,U+410-451,\
U+453-458,U+45C-45F,U+462,U+463,U+490,U+491,U+4B0,U+4B1,U+2011,\
U+2018-201F,U+2022-2026,U+2030-203A,U+2041,U+2043,U+2044,U+204B,\
U+2052,U+2053,U+2074,U+2081-2084,U+20AC,U+20B4,U+20BD,U+20BF,\
U+2116,U+2122,U+212E,U+2191,U+2193,U+2211,U+2212,U+2215,U+221A,U+221E,\
U+222B,U+2248,U+2260,U+2264,U+2265"

menu="U+20-7E,U+A3,U+A5,U+A7,U+A9,U+AB,U+AD,U+AE,U+B0,U+B4,U+B5,U+BB,\
U+C0,U+C1,U+C8,U+C9,U+CC,U+CD,U+D2,U+D3,U+D7,U+D9,U+DA,U+E0,U+E1,\
U+E8,U+E9,U+EB-ED,U+EF,U+F2,U+F3,U+F7-FA,U+131,U+192,\
U+2BB,U+2BC,U+2C6,U+2C7,U+2D9,U+2DA,\U+2DC,U+400,U+401,U+403-408,U+410-451,\
U+453-458,U+45C-45F,U+462,U+463,U+490,U+491,U+4B0,U+4B1,U+2011,U+2018-201F,\
U+2022-2026,U+2030-2037,U+2039,U+203A,U+2041,U+2043,U+2052,U+2053,U+20AC,U+20B4,\
U+20BD,U+20BF,U+2116,U+2122,U+2191,U+2193,U+221A,U+221E,U+222B,U+2248,U+2260,U+2264,U+2265"

menu_textonly="U+20-7E,U+A9,U+AB,U+AD,U+AE,U+B0,U+B4,U+BB,U+C0,U+C1,U+C8,U+C9,U+CC,U+CD,\
U+D2,U+D3,U+D9,U+DA,U+E0,U+E1,U+E8,U+E9,U+EB-ED,U+EF,U+F2,U+F3,U+F9,U+FA,U+131,U+400,U+401,U+403-408,U+410-451,\
U+453-458,U+45C-45E,U+490,U+491,U+2024-2026,U+2039,U+203A,U+20AC,U+20B4,U+20BD,U+20BF"

if [[ "$subset" == "base" ]]; then
    subset_list=$base
fi
if [[ "$subset" == "base_ext" ]]; then
    subset_list=$base_ext
fi
if [[ "$subset" == "menu" ]]; then
    subset_list=$menu
fi
if [[ "$subset" == "menu_textonly" ]]; then
    subset_list=$menu_textonly
fi
layout_add="kern,liga,clig,calt,ccmp,locl,mark,mkmk,onum,pnum,smcp,c2sc,lnum,tnum,subs,sups,cswh,dlig,ss01,ss03,zero"
layout_rem="dnom,numr"
if [[ "$layout" == "web" ]]; then
    layout_add="kern,liga,clig,calt,ccmp,locl,mark,mkmk,lnum,tnum,cswh,dlig,ss01,ss03,zero"
    layout_rem="dnom,numr,frac,onum,pnum,smcp,c2sc,subs,sups"
fi

/usr/bin/pyftsubset "${in}" --output-file="${out}" \
    --unicodes="${subset_list}" \
    --layout-features+="${layout_add}" \
    --layout-features-="${layout_rem}"

#     --flavor=woff2 \
#    --no-hinting \
#    --desubroutinize \

nano ff-conv.sh

1
2
3
4
5
6
7
8
#!/usr/bin/fontforge
Open($1)
Generate($1:r + ".otf")
Generate($1:r + ".svg")
Generate($1:r + ".woff")
Generate($1:r + ".woff2")
Generate($1:r + ".eot")
Generate($1:r + ".ttf")

На цьому сайті використовується латиниця, але не з усіма символами, наприклад діакритичними, а ще кирилиця, і деякі спеціальні символи.

Символьне представлення юнікод-символів

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# base_ext
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¹
º»¼½¾ÀÁÈÉÌÍÒÓ×ÙÚàáèéëìíïòó÷øùúıƒʻʼˆˇ˙˚˜ΔΩπЀЁЃЄЅІЇЈАБВГДЕЖЗИ
ЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёѓє
ѕіїјќѝўџѢѣҐґҰұ‑‘’‚‛“”„‟•‣․‥…‰‱′″‴‵‶‷‸‹›⁁⁃⁄⁋⁒⁓⁴₁₂₃₄€₴₽₿№™℮
↑↓∑−∕√∞∫≈≠≤≥

# base_unicode
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¹
º»¼½¾ÀÁÈÉÌÍÒÓ×ÙÚàáèéëìíïòó÷øùúıʻʼˆ˚˜ЀЁЃЄЅІЇЈАБВГДЕЖЗИЙКЛМНО
ПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёѓєѕіїјќѝ
ўџҐґҰұ‑‘’‚‛“”„‟•‣․‥…‰‱′″‴‵‶‷‸‹›⁁⁃⁄⁋⁒⁓⁴€№™↑↓−∕

# menu
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~£¥§©«­®°´µ»ÀÁÈÉÌÍÒÓ×ÙÚàá
èéëìíïòó÷øùúıƒʻʼˆˇ˙˚˜ЀЁЃЄЅІЇЈАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭ
ЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяѐёѓєѕіїјќѝўџѢѣҐґҰұ‑‘’‚‛“”
„‟•‣․‥…‰‱′″‴‵‶‷‹›⁁⁃⁒⁓€₴₽₿№™↑↓√∞∫≈≠≤≥

# menu_textonly
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ
[\]^_`abcdefghijklmnopqrstuvwxyz{|}~©«­®°´»ÀÁÈÉÌÍÒÓÙÚàáèéëìí
ïòóùúıЀЁЃЄЅІЇЈАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклм
нопрстуфхцчшщъыьэюяѐёѓєѕіїјќѝўҐґ․‥…‹›€₴₽₿

Генеруємо шрифти

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
./make_menu.sh --subset "base_ext" \
    --in PTSansNarrowRegular_original.ttf \
    --out original/PTSansNarrowRegular_subset.ttf
./ff-conv.sh original/PTSansNarrowRegular_subset.ttf

./make_menu.sh --subset "base_ext" \
    --in FiraSansExtraCondensedLight_original.ttf \
    --out original/FiraSansExtraCondensedLight_subset.ttf
./ff-conv.sh original/FiraSansExtraCondensedLight_subset.ttf

./make_menu.sh --subset "base_ext" \
    --in LiberationMonoRegular_original.ttf \
    --out original/LiberationMonoRegular_subset.ttf
./ff-conv.sh original/LiberationMonoRegular_subset.ttf

./make_menu.sh --subset "menu_textonly" \
    --in ShantellSansNormalRegular_original.otf \
    --out original/ShantellSansNormalRegular_subset.otf
./ff-conv.sh original/ShantellSansNormalRegular_subset.otf

Оригінальний шрифт PTSansNarrowRegular_original.ttf 230K

1
2
3
4
5
6
7
  87K PTSansNarrowRegular_subset.afm
  44K PTSansNarrowRegular_subset.eot
  71K PTSansNarrowRegular_subset.otf
 129K PTSansNarrowRegular_subset.svg
 101K PTSansNarrowRegular_subset.ttf
  52K PTSansNarrowRegular_subset.woff
  40K PTSansNarrowRegular_subset.woff2

Деякі шрифти можуть використовувати один гліф для певних комбінацій символів,
для латиниці це часто комбінації fi fl ff ft.
Найчастіше веб-сервіси генерації шрифтів видаляють ці гліфи.
Якщо у вас така проблема, її можна вирішити просто додавши CSS правило
letter-spacing:-0.1pt;

fl-fi-ft
fl-fi-ft 1590x814
font-glyph-fl-fi-ft.png
fl-fi-ft error
fl-fi-ft error 400x110
font-fl-fi-ft-errore.png

Варіант із вибором кодів вручну

Відкриваємо .ttf у fontforge і починаємо вибирати та копіювати потрібні числові коди гліфів у файл.

Вибираємо потрібні гліфи 1290x753 fontforge.png
fontforge glyphs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# latin range
awk 'BEGIN{for(i=32;i<127;i++)printf "%c",i}' > glyph-symbols

# cyrillic range
awk 'BEGIN{for(i=1040;i<1106;i++)printf "%c",i}' >> glyph-symbols

# special range
awk 'BEGIN{for(i=8216;i<8223;i++)printf "%c",i}' >> glyph-symbols

# special
A="160 161 166 167 169 171 173 174 176 177 178 179 180 181 183 185 187 \
188 189 190 198 215 216 230 247 248 402 729 730 732 733 916 931 937 \
945 946 947 948 949 955 956 957 960 966 968 969 1025 1027 1028 1030 \
1031 1037 1107 1108 1110 1111 1122 1123 1168 1169 8240 8226 8230 8240 \
8242 8243 8249 8250 8356 8364 8372 8381 8470 8482 8486 8494 8592 8593 \
8594 8595 8596 8597 8710 8721 8722 8725 8730 8734 8747 8776 8800 8804 \
8805 9834 9835 9836" ; awk -v var="${A[*]}" 'BEGIN{split(var,list," "); \
    for (i=1;i<=length(list);i++) printf "%c", list[i]}' \
        >> glyph-symbols

pyftsubset PTSansNarrow-Regular.ttf --output-file=PTSans-sub.ttf --text-file=glyph-symbols

Універсальний css стиль для шрифту та fallback з коригуванням розмірів

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@font-face {
        font-family: "ShantellSansNormalRegular";
        src:  url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.eot");                                               /* IE9 Compat Modes */
        src:  url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.eot?#iefix") format("embedded-opentype"),            /* IE6-IE8 */
              url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.woff2") format("woff2"),                             /* Modern Browsers */
              url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.woff") format("woff"),                               /* Modern Browsers */
              url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.ttf") format("truetype"),                            /* Safari, Android, iOS */
              url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.svg#ShantellSansNormal-Regular") format("svg");      /* Legacy iOS */
        src:  url("/assets/css/fonts/original/ShantellSansNormalRegular_subset.otf") format(opentype) tech(incremental-range);      /* Open Type Font */
        font-weight:normal;
        font-style:normal;
        font-display:block;
        unicode-range:U+20-7E,U+A9,U+AB,U+AD,U+AE,U+B0,U+B4,U+BB,U+C0,U+C1,U+C8,U+C9,U+CC,U+CD,U+D2,U+D3,U+D9,U+DA,U+E0,U+E1,U+E8,U+E9,U+EB-ED,U+EF,U+F2,U+F3,U+F9,U+FA,U+131,U+400,U+401,U+403-408,U+410-451,U+453-458,U+45C-45E,U+490,U+491,U+2024-2026,U+2039,U+203A,U+20AC,U+20B4,U+20BD,U+20BF;}

@font-face {
        font-family: "fallbackShantellComic";
        src: local("Comic Sans MS");
        size-adjust: 98%;
        ascent-override: 107%;
        descent-override: 25%;
        line-gap-override: 5%;
}
@font-face {
        font-family: "fallbackShantellArial";
        src: local("Arial");
        size-adjust: 105%;
        ascent-override: 100%;
        descent-override: 29%;
        line-gap-override: 5%;
}

p {font-family:"ShantellSansNormalRegular", "fallbackShantellComic", "fallbackShantellArial", "Comic Sans MS", sans-serif;}

І ще трошки

Підраховуємо кількість гліфів в оригінальному файлі та зробленому самостійно.

1
2
3
4
5
6
7
8
9
10
11
fontforge -lang=ff -c 'Open($1); SelectWorthOutputting(); Print($selection)' \
    "PTSansNarrowRegular_original.ttf" 2>/dev/null | \
    tr -d '][' | tr , '\n' | grep -c 1

723

fontforge -lang=ff -c 'Open($1); SelectWorthOutputting(); Print($selection)' \
    "PTSansNarrowRegular_subset.ttf" 2>/dev/null | \
    tr -d '][' | tr , '\n' | grep -c 1

292

Для svg це можна зробити наприклад так:

cat PTSansNarrow.svg | grep "glyph-name" | wc -l
723
cat PTSansNarrow_subset.svg | grep "glyph-name" | wc -l
292

Слід зазначити, що гліфів в отриманих шрифтах може бути менше, ніж символів для вибірки, тому що в конкретному шрифті може бути не гліфів для деяких символів, скрипт містить розширений набір символів (кодів) для вибірки з різних шрифтів, але головне, що він не містить не потрібних символів. Проте, все ж таки правильніше буде відкривати конкретний шрифт у редакторі і вибирати потрібні з наявних гліфів.

Якщо планується використовувати шрифт тільки для тексту, який іноді навіть не передбачає додаткових символів до alphanumeric, крім крапки та коми, то кількість гліфів можна скоротити ще більше.

1
2
3
4
5
6
7
8
9
# latin
awk 'BEGIN{for(i=32;i<127;i++)printf "%c",i}' > short-glyphs
# кирилиця
awk 'BEGIN{for(i=1040;i<1106;i++)printf "%c",i}' >> short-glyphs
A="1025 1028 1030 1031 1108 1110 1111 1168 1169" ; awk -v var="${A[*]}" 'BEGIN{split(var,list," "); for (i=1;i<=length(list);i++) printf "%c", list[i]}'  >> short-glyphs

pyftsubset Caveat-Regular.ttf \
    --output-file=Caveat-Regular-short.ttf \
    --text-file=short-glyphs