Оптимизация количества глифов шрифта для применения в 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