Pick the matching Noto CJK variant for language-tagged text
The generic assigns swap sans-serif, serif and monospace for fonts with no CJK glyphs, so tagged Chinese text ends up on whatever Noto CJK variant the charset scan finds first, usually the Japanese one. Fold the many Chinese tags down to one per variant and prepend the matching family ahead of the assigns.
This commit is contained in:
2 files changed
+425
No files matched your search
@@ -1,6 +1,298 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- CJK: fold the language tag onto one tag per Noto CJK variant -->
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="eq">
|
||||
<string>zh</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-cn</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="eq">
|
||||
<string>zh-hant</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-tw</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hant-hk</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-hk</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hant-mo</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-hk</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hant</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-tw</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hans</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-cn</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-sg</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-cn</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-mo</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-hk</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="eq">
|
||||
<string>yue</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-hk</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>yue-hans</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-cn</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>yue-cn</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-cn</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>yue</string>
|
||||
</test>
|
||||
<edit name="lang" mode="assign">
|
||||
<string>zh-hk</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<!-- Then append that variant after the generic family it falls back from -->
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-cn</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans CJK SC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-cn</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Serif CJK SC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-cn</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans Mono CJK SC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hk</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans CJK HK</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hk</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Serif CJK HK</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-hk</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans Mono CJK HK</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-tw</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans CJK TC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-tw</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Serif CJK TC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<string>zh-tw</string>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans Mono CJK TC</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ja</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans CJK JP</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ja</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Serif CJK JP</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ja</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans Mono CJK JP</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ko</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans CJK KR</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ko</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Serif CJK KR</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="lang" qual="first" compare="contains">
|
||||
<langset><string>ko</string></langset>
|
||||
</test>
|
||||
<test name="family">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="strong">
|
||||
<string>Noto Sans Mono CJK KR</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="pattern">
|
||||
<test name="family" qual="any">
|
||||
<string>sans-serif</string>
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
if ! command -v fc-match >/dev/null || [[ $(fc-list : file) != *NotoSansCJK-Regular.ttc* ]]; then
|
||||
skip "fontconfig CJK variants need fc-match and noto-fonts-cjk"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
candidate="$ROOT/default/fontconfig/conf.avail/50-omarchy.conf"
|
||||
{
|
||||
echo '<?xml version="1.0"?><!DOCTYPE fontconfig SYSTEM "fonts.dtd"><fontconfig>'
|
||||
echo "<dir>/usr/share/fonts</dir><cachedir>$test_tmp/cache</cachedir>"
|
||||
placed=false
|
||||
for conf in /etc/fonts/conf.d/*.conf; do
|
||||
name=${conf##*/}
|
||||
[[ $name == "50-omarchy.conf" ]] && continue
|
||||
if ! $placed && [[ $name > "50-omarchy.conf" ]]; then
|
||||
echo "<include>$candidate</include>"
|
||||
placed=true
|
||||
fi
|
||||
echo "<include>$conf</include>"
|
||||
done
|
||||
$placed || echo "<include>$candidate</include>"
|
||||
echo '</fontconfig>'
|
||||
} >"$test_tmp/fonts.conf"
|
||||
export FONTCONFIG_FILE="$test_tmp/fonts.conf"
|
||||
fc-cache >/dev/null
|
||||
|
||||
# Pango, Qt and Skia add lang as a string, which fc-match never does.
|
||||
cat >"$test_tmp/string-lang.py" <<'PY'
|
||||
import ctypes, sys
|
||||
fc = ctypes.CDLL("libfontconfig.so.1")
|
||||
fc.FcPatternCreate.restype = ctypes.c_void_p
|
||||
fc.FcPatternAddString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p]
|
||||
fc.FcConfigSubstitute.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
|
||||
fc.FcDefaultSubstitute.argtypes = [ctypes.c_void_p]
|
||||
fc.FcPatternFormat.restype = ctypes.c_char_p
|
||||
fc.FcPatternFormat.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
|
||||
fc.FcCharSetCreate.restype = ctypes.c_void_p
|
||||
fc.FcCharSetAddChar.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
||||
fc.FcPatternAddCharSet.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p]
|
||||
fc.FcFontMatch.restype = ctypes.c_void_p
|
||||
fc.FcFontMatch.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)]
|
||||
for line in sys.stdin:
|
||||
family, lang, char = line.split()
|
||||
pattern = fc.FcPatternCreate()
|
||||
fc.FcPatternAddString(pattern, b"family", family.encode())
|
||||
if lang != "-":
|
||||
fc.FcPatternAddString(pattern, b"lang", lang.encode())
|
||||
if char == "families":
|
||||
fc.FcConfigSubstitute(None, pattern, 0)
|
||||
print(fc.FcPatternFormat(pattern, b"%{family}").decode())
|
||||
continue
|
||||
if char != "-":
|
||||
charset = fc.FcCharSetCreate()
|
||||
fc.FcCharSetAddChar(charset, int(char, 16))
|
||||
fc.FcPatternAddCharSet(pattern, b"charset", charset)
|
||||
fc.FcConfigSubstitute(None, pattern, 0)
|
||||
fc.FcDefaultSubstitute(pattern)
|
||||
result = ctypes.c_int()
|
||||
print(fc.FcPatternFormat(fc.FcFontMatch(None, pattern, ctypes.byref(result)), b"%{family[0]}").decode())
|
||||
PY
|
||||
|
||||
# resolve <locale> <string|langset> reads "family lang char" lines
|
||||
resolve() {
|
||||
if [[ $2 == "string" ]]; then
|
||||
LANG=$1 python3 "$test_tmp/string-lang.py"
|
||||
else
|
||||
local family lang char query
|
||||
while read -r family lang char; do
|
||||
query=$family
|
||||
[[ $lang == "-" ]] || query+=":lang=$lang"
|
||||
[[ $char == "-" ]] || query+=":charset=$char"
|
||||
LANG=$1 fc-match -f '%{family[0]}\n' "$query"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
declare -A latin=([sans-serif]="Liberation Sans" [serif]="Liberation Serif" [monospace]="JetBrainsMono Nerd Font")
|
||||
declare -A cjk=([sans-serif]="Noto Sans CJK" [serif]="Noto Serif CJK" [monospace]="Noto Sans Mono CJK")
|
||||
declare -A variant=(
|
||||
[zh]=SC [zh-cn]=SC [zh-sg]=SC [zh-hans]=SC [zh-hans-cn]=SC [zh-hans-hk]=SC
|
||||
[zh-tw]=TC [zh-hant]=TC [zh-hant-tw]=TC
|
||||
[zh-hk]=HK [zh-mo]=HK [zh-hant-hk]=HK [zh-hant-mo]=HK
|
||||
[yue]=HK [yue-hk]=HK [yue-hant]=HK [yue-hant-hk]=HK [yue-hant-cn]=HK
|
||||
[yue-hans]=SC [yue-hans-cn]=SC [yue-hans-hk]=SC [yue-cn]=SC
|
||||
[ja]=JP [ja-jp]=JP [ko]=KR [ko-kr]=KR
|
||||
)
|
||||
|
||||
queries=() expected=()
|
||||
for tag in "${!variant[@]}"; do
|
||||
for generic in sans-serif serif monospace; do
|
||||
queries+=("$generic $tag -") expected+=("${latin[$generic]}")
|
||||
queries+=("$generic $tag 9aa8") expected+=("${cjk[$generic]} ${variant[$tag]}")
|
||||
done
|
||||
done
|
||||
for generic in sans-serif serif monospace; do
|
||||
queries+=("$generic - -") expected+=("${latin[$generic]}")
|
||||
done
|
||||
|
||||
for locale in C.UTF-8 zh_CN.UTF-8; do
|
||||
for path in string langset; do
|
||||
mapfile -t actual < <(printf '%s\n' "${queries[@]}" | resolve "$locale" "$path")
|
||||
for i in "${!queries[@]}"; do
|
||||
[[ ${actual[i]} == "${expected[i]}" ]] ||
|
||||
fail "$locale $path lang: ${queries[i]} resolves to ${expected[i]}" "${actual[i]}"
|
||||
done
|
||||
done
|
||||
pass "$locale: tagged CJK keeps Omarchy's Latin families and sets Han in the tag's variant"
|
||||
done
|
||||
|
||||
for path in string langset; do
|
||||
for generic in sans-serif serif monospace; do
|
||||
actual=$(echo "$generic - 9aa8" | resolve zh_CN.UTF-8 "$path")
|
||||
[[ $actual == "${cjk[$generic]} SC" ]] || fail "zh_CN $path: untagged Han in $generic is SC" "$actual"
|
||||
done
|
||||
done
|
||||
pass "a Chinese locale sets untagged Han in SC"
|
||||
|
||||
untouched=$(echo "sans-serif en families" | resolve C.UTF-8 string)
|
||||
[[ $(echo "sans-serif ja families" | resolve C.UTF-8 string) != "$untouched" ]] ||
|
||||
fail "lang=ja changes the family list, so the prefix check below can see a match"
|
||||
for tag in jam jav kok koi; do
|
||||
actual=$(echo "sans-serif $tag families" | resolve C.UTF-8 string)
|
||||
[[ $actual == "$untouched" ]] || fail "string lang=$tag is not taken for Japanese or Korean" "$actual"
|
||||
done
|
||||
pass "languages that only share a prefix with ja or ko get no CJK variant"
|
||||
Reference in new issue
Block a user