221 lines
7.8 KiB
Bash
Executable File
221 lines
7.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Portable across bash 3.2+ (macOS stock /bin/bash) and bash 4+ (Linux, Homebrew).
|
|
# Avoids `declare -A` so the script runs on a fresh macOS without `brew install bash`.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_FONT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/assets/fonts"
|
|
|
|
# Download target lives OUTSIDE the skill directory on purpose.
|
|
#
|
|
# Claude Desktop skill ZIPs exclude the large bundled fonts (TsangerJinKai TTFs,
|
|
# Source Han Serif K OTFs). The old code downloaded them back into the skill's
|
|
# own assets/fonts, which pushed the installed skill past Claude Desktop's size
|
|
# limit ("upload/execution too big"). We instead drop them in the XDG user font
|
|
# dir, which fontconfig scans by default on both macOS (Homebrew) and Linux, yet
|
|
# does NOT show up in macOS Font Book. WeasyPrint then resolves "TsangerJinKai02"
|
|
# / "Source Han Serif K" from here when the template's relative @font-face path
|
|
# is absent; online renders still fall back to the jsDelivr URL baked alongside
|
|
# each @font-face declaration.
|
|
FONT_DIR="${KAMI_FONT_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/fonts/kami}"
|
|
|
|
# Partial downloads from an interrupted run must not linger in FONT_DIR, and
|
|
# two concurrent runs must not fight over one temp path: temp files carry this
|
|
# run's PID and are swept on exit.
|
|
TMP_SUFFIX="tmp.$$"
|
|
cleanup_tmp() { rm -f "$FONT_DIR"/*."$TMP_SUFFIX" 2>/dev/null || true; }
|
|
trap cleanup_tmp EXIT
|
|
|
|
MIN_SIZE_CN=10000000 # 10MB for TsangerJinKai (large CJK glyph set)
|
|
MIN_SIZE_KO=6500000 # 6.5MB for Source Han Serif K (Adobe full subset)
|
|
|
|
# TsangerJinKai (CN): index N pairs CN_NAMES[N] with CN_LOCAL_NAMES[N].
|
|
CN_NAMES=("仓耳今楷02-W04.ttf" "仓耳今楷02-W05.ttf")
|
|
CN_LOCAL_NAMES=("TsangerJinKai02-W04.ttf" "TsangerJinKai02-W05.ttf")
|
|
|
|
# Source Han Serif K (KO): mirror filenames match the repo filenames, so there
|
|
# is no rename step (unlike Tsanger's Chinese-named official downloads).
|
|
KO_NAMES=("SourceHanSerifKR-Regular.otf" "SourceHanSerifKR-Medium.otf")
|
|
|
|
# Mirror order is intentionally jsdmirror-first here, opposite of the
|
|
# templates' @font-face fallback (which lists jsdelivr first). Reasoning:
|
|
# this script runs interactively when fonts are missing locally, often from
|
|
# China where jsdmirror is reachable and faster than jsdelivr; templates run
|
|
# anywhere and prioritize jsdelivr's broader global coverage.
|
|
MIRROR_SOURCES=(
|
|
"https://cdn.jsdmirror.com/gh/tw93/Kami@main/assets/fonts"
|
|
"https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts"
|
|
)
|
|
|
|
check_size() {
|
|
local file="$1"
|
|
local min_size="$2"
|
|
[[ -f "$file" ]] || return 1
|
|
local size
|
|
size=$(wc -c < "$file" | tr -d ' ')
|
|
[[ "$size" -ge "$min_size" ]]
|
|
}
|
|
|
|
cn_present_in() {
|
|
local dir="$1" name
|
|
for name in "${CN_LOCAL_NAMES[@]}"; do
|
|
check_size "$dir/$name" "$MIN_SIZE_CN" || return 1
|
|
done
|
|
return 0
|
|
}
|
|
|
|
ko_present_in() {
|
|
local dir="$1" name
|
|
for name in "${KO_NAMES[@]}"; do
|
|
check_size "$dir/$name" "$MIN_SIZE_KO" || return 1
|
|
done
|
|
return 0
|
|
}
|
|
|
|
refresh_fontconfig() {
|
|
# The XDG font dir is already on fontconfig's default scan path, so a cache
|
|
# refresh is all that is needed for WeasyPrint to pick the fonts up. Optional:
|
|
# absence of fc-cache (e.g. minimal sandbox) is non-fatal, fontconfig rescans
|
|
# the directory lazily on next use.
|
|
if command -v fc-cache >/dev/null 2>&1; then
|
|
fc-cache -f "$FONT_DIR" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
download_tsanger() {
|
|
local cn_name="$1"
|
|
local local_name="$2"
|
|
local target="$FONT_DIR/$local_name"
|
|
|
|
# Source 1: official tsanger.cn
|
|
local official_url="https://tsanger.cn/download/${cn_name}"
|
|
echo " Trying: tsanger.cn (official)"
|
|
if curl --retry 2 --connect-timeout 15 --max-time 300 -fSL "$official_url" -o "$target.$TMP_SUFFIX" 2>/dev/null; then
|
|
if check_size "$target.$TMP_SUFFIX" "$MIN_SIZE_CN"; then
|
|
mv "$target.$TMP_SUFFIX" "$target"
|
|
echo " OK: $local_name downloaded ($(du -h "$target" | cut -f1))"
|
|
return 0
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
|
|
# Source 2+: CDN mirrors (already named TsangerJinKai02-W0x.ttf)
|
|
for src in "${MIRROR_SOURCES[@]}"; do
|
|
local url="$src/$local_name"
|
|
echo " Trying: $url"
|
|
if curl --retry 2 --connect-timeout 15 --max-time 300 -fSL "$url" -o "$target.$TMP_SUFFIX" 2>/dev/null; then
|
|
if check_size "$target.$TMP_SUFFIX" "$MIN_SIZE_CN"; then
|
|
mv "$target.$TMP_SUFFIX" "$target"
|
|
echo " OK: $local_name downloaded ($(du -h "$target" | cut -f1))"
|
|
return 0
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
done
|
|
|
|
echo " ERROR: all sources failed for $local_name"
|
|
return 1
|
|
}
|
|
|
|
download_ko_serif() {
|
|
local local_name="$1"
|
|
local target="$FONT_DIR/$local_name"
|
|
|
|
# CDN mirrors only: Source Han Serif K has no single official direct-download
|
|
# URL, so we serve the committed OTFs from the same jsDelivr/jsdmirror gh path.
|
|
for src in "${MIRROR_SOURCES[@]}"; do
|
|
local url="$src/$local_name"
|
|
echo " Trying: $url"
|
|
if curl --retry 2 --connect-timeout 15 --max-time 300 -fSL "$url" -o "$target.$TMP_SUFFIX" 2>/dev/null; then
|
|
if check_size "$target.$TMP_SUFFIX" "$MIN_SIZE_KO"; then
|
|
mv "$target.$TMP_SUFFIX" "$target"
|
|
echo " OK: $local_name downloaded ($(du -h "$target" | cut -f1))"
|
|
return 0
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
else
|
|
rm -f "$target.$TMP_SUFFIX"
|
|
fi
|
|
done
|
|
|
|
echo " ERROR: all sources failed for $local_name"
|
|
return 1
|
|
}
|
|
|
|
# A repo checkout ships the committed font files. Templates resolve their
|
|
# relative `../fonts/*` @font-face path against them directly, so there is
|
|
# nothing to download or register. These branches are skipped inside a Claude
|
|
# Desktop skill, whose assets/fonts has the large fonts stripped out.
|
|
|
|
cn_failed=0
|
|
if cn_present_in "$REPO_FONT_DIR"; then
|
|
echo "OK: TsangerJinKai fonts present in repo checkout ($REPO_FONT_DIR)"
|
|
else
|
|
mkdir -p "$FONT_DIR"
|
|
if cn_present_in "$FONT_DIR"; then
|
|
echo "OK: TsangerJinKai fonts present ($FONT_DIR)"
|
|
else
|
|
echo "Downloading TsangerJinKai fonts to $FONT_DIR ..."
|
|
for i in "${!CN_NAMES[@]}"; do
|
|
cn_name="${CN_NAMES[$i]}"
|
|
local_name="${CN_LOCAL_NAMES[$i]}"
|
|
if check_size "$FONT_DIR/$local_name" "$MIN_SIZE_CN"; then
|
|
echo " OK: $local_name already present"
|
|
continue
|
|
fi
|
|
if ! download_tsanger "$cn_name" "$local_name"; then
|
|
cn_failed=$((cn_failed + 1))
|
|
fi
|
|
done
|
|
if [[ "$cn_failed" -gt 0 ]]; then
|
|
echo ""
|
|
echo "Some TsangerJinKai files could not be downloaded. Alternatives:"
|
|
echo " 1. Install Source Han Serif SC: brew install --cask font-source-han-serif-sc"
|
|
echo " 2. Copy TsangerJinKai02-W04.ttf and W05.ttf manually into $FONT_DIR"
|
|
# Don't exit yet, try the KO recovery too so a Korean-only user still gets KO fonts.
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
ko_failed=0
|
|
if ko_present_in "$REPO_FONT_DIR"; then
|
|
echo "OK: Source Han Serif K fonts present in repo checkout ($REPO_FONT_DIR)"
|
|
else
|
|
mkdir -p "$FONT_DIR"
|
|
if ko_present_in "$FONT_DIR"; then
|
|
echo "OK: Source Han Serif K fonts present ($FONT_DIR)"
|
|
else
|
|
echo "Downloading Source Han Serif K fonts to $FONT_DIR ..."
|
|
for local_name in "${KO_NAMES[@]}"; do
|
|
if check_size "$FONT_DIR/$local_name" "$MIN_SIZE_KO"; then
|
|
echo " OK: $local_name already present"
|
|
continue
|
|
fi
|
|
if ! download_ko_serif "$local_name"; then
|
|
ko_failed=$((ko_failed + 1))
|
|
fi
|
|
done
|
|
if [[ "$ko_failed" -gt 0 ]]; then
|
|
echo ""
|
|
echo "Some Source Han Serif K files could not be downloaded. Alternatives:"
|
|
echo " 1. Download from https://github.com/adobe-fonts/source-han-serif/releases"
|
|
echo " 2. Copy SourceHanSerifKR-Regular.otf and -Medium.otf manually into $FONT_DIR"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "$cn_failed" -gt 0 || "$ko_failed" -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
|
|
refresh_fontconfig
|
|
echo "OK: all fonts ready"
|