From 600734790c83dd492e7f3e2676dc339166a7eff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 23:56:40 -0400 Subject: [PATCH] Match ja and ko by subtag and test the string lang path apps use --- default/fontconfig/conf.avail/50-omarchy.conf | 12 +- test/shell.d/fontconfig-cjk-test.sh | 109 ++++++++++++++---- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/default/fontconfig/conf.avail/50-omarchy.conf b/default/fontconfig/conf.avail/50-omarchy.conf index 31f4bb37..78ba9d95 100644 --- a/default/fontconfig/conf.avail/50-omarchy.conf +++ b/default/fontconfig/conf.avail/50-omarchy.conf @@ -223,7 +223,7 @@ - ja + ja sans-serif @@ -235,7 +235,7 @@ - ja + ja serif @@ -247,7 +247,7 @@ - ja + ja monospace @@ -259,7 +259,7 @@ - ko + ko sans-serif @@ -271,7 +271,7 @@ - ko + ko serif @@ -283,7 +283,7 @@ - ko + ko monospace diff --git a/test/shell.d/fontconfig-cjk-test.sh b/test/shell.d/fontconfig-cjk-test.sh index 493bd8dd..c04be559 100755 --- a/test/shell.d/fontconfig-cjk-test.sh +++ b/test/shell.d/fontconfig-cjk-test.sh @@ -12,21 +12,76 @@ fi test_tmp=$(mktemp -d) trap 'rm -rf "$test_tmp"' EXIT +candidate="$ROOT/default/fontconfig/conf.avail/50-omarchy.conf" { echo '' echo "/usr/share/fonts$test_tmp/cache" + placed=false for conf in /etc/fonts/conf.d/*.conf; do - if [[ ${conf##*/} == "50-omarchy.conf" ]]; then - echo "$ROOT/default/fontconfig/conf.avail/50-omarchy.conf" - else - echo "$conf" + name=${conf##*/} + [[ $name == "50-omarchy.conf" ]] && continue + if ! $placed && [[ $name > "50-omarchy.conf" ]]; then + echo "$candidate" + placed=true fi + echo "$conf" done + $placed || echo "$candidate" echo '' } >"$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 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=( @@ -38,33 +93,41 @@ declare -A variant=( [ja]=JP [ja-jp]=JP [ko]=KR [ko-kr]=KR ) -family() { LANG=$1 fc-match -f '%{family[0]}' "$2"; } +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 tag in "${!variant[@]}"; do - for generic in sans-serif serif monospace; do - actual=$(family "$locale" "$generic:lang=$tag") - [[ $actual == "${latin[$generic]}" ]] || fail "$locale $generic:lang=$tag keeps Latin in ${latin[$generic]}" "$actual" - actual=$(family "$locale" "$generic:lang=$tag:charset=9aa8") - expected="${cjk[$generic]} ${variant[$tag]}" - [[ $actual == "$expected" ]] || fail "$locale $generic:lang=$tag sets Han in $expected" "$actual" + 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 generic in sans-serif serif monospace; do - actual=$(family zh_CN.UTF-8 "$generic") - [[ $actual == "${latin[$generic]}" ]] || fail "zh_CN untagged $generic keeps ${latin[$generic]}" "$actual" - actual=$(family zh_CN.UTF-8 "$generic:charset=9aa8") - [[ $actual == "${cjk[$generic]} SC" ]] || fail "zh_CN untagged $generic sets Han in SC" "$actual" +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 keeps Omarchy's Latin families and sets untagged Han in SC" +pass "a Chinese locale sets untagged Han in SC" -[[ $(LANG=C.UTF-8 fc-pattern --config "sans-serif:lang=ja" family) == *'"Noto Sans CJK JP"(s)'* ]] || - fail "lang=ja binds its variant strongly, so the prefix check below can see one" -for tag in jam kok; do - actual=$(LANG=C.UTF-8 fc-pattern --config "sans-serif:lang=$tag" family) - [[ ! $actual =~ CJK\ [A-Z]+\"\(s\) ]] || fail "lang=$tag is not taken for Japanese or Korean" "$actual" +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"