Skip to content

Commit ec92e74

Browse files
committed
Add Chinese font support with Noto CJK fonts for matplotlib
- Add noto-fonts-cjk-sans and noto-fonts-cjk-serif packages to runtime environment - Configure fontconfig with Noto CJK font aliases for SC, TC, JP, and KR variants - Copy Noto CJK fonts to system font directory at build time - Create fontconfig configuration files for proper font discovery - Configure matplotlib to use Noto Sans CJK fonts by default - Add font setup script for runtime font cache initialization
1 parent d046912 commit ec92e74

1 file changed

Lines changed: 204 additions & 3 deletions

File tree

docker.nix

Lines changed: 204 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,11 @@ finally:
835835
pkgs.blas
836836
pkgs.gcc.cc.lib
837837
pkgs.stdenv.cc.cc.lib
838+
# Chinese font support for matplotlib
839+
# Note: noto-fonts-cjk has been renamed to noto-fonts-cjk-sans
840+
pkgs.noto-fonts-cjk-sans
841+
pkgs.noto-fonts-cjk-serif
842+
pkgs.fontconfig
838843
];
839844
};
840845

@@ -846,6 +851,31 @@ finally:
846851
mkdir -p $out/app $out/bin $out/tmp $out/etc/uv $out/home/python-user
847852
mkdir -p $out/etc/passwd.d $out/etc/group.d $out/etc/shadow.d
848853
mkdir -p $out/.local/lib/python3.12/site-packages
854+
mkdir -p $out/usr/share/fonts/truetype/noto-cjk
855+
mkdir -p $out/etc/fonts/conf.d
856+
857+
# Create fontconfig main configuration file
858+
# Fontconfig requires fonts.conf to exist, even if minimal
859+
cat > $out/etc/fonts/fonts.conf << 'FONTSCONF'
860+
<?xml version="1.0"?>
861+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
862+
<fontconfig>
863+
<!-- Font directory list -->
864+
<dir>/usr/share/fonts</dir>
865+
<dir>/usr/share/fonts/truetype</dir>
866+
<dir>/usr/share/fonts/truetype/noto-cjk</dir>
867+
<dir>/usr/share/fonts/opentype</dir>
868+
<dir>/usr/share/fonts/opentype/noto</dir>
869+
870+
<!-- Include conf.d directory for additional configurations -->
871+
<include ignore_missing="yes">conf.d</include>
872+
873+
<!-- Rescan interval (in seconds) -->
874+
<rescan>
875+
<int>30</int>
876+
</rescan>
877+
</fontconfig>
878+
FONTSCONF
849879
850880
# Create uv cache dir
851881
mkdir -p $out/tmp/.uv_cache
@@ -876,6 +906,151 @@ finally:
876906
# Copy MOSEK Python API from uv installation
877907
cp -r ${mosekPythonPackages}/site-packages/* $out/opt/mosek/lib/python3.12/site-packages/
878908
909+
# Copy Noto CJK fonts to system font directory at build time
910+
# Note: noto-fonts-cjk has been split into noto-fonts-cjk-sans and noto-fonts-cjk-serif
911+
# Font files are typically in share/fonts/opentype/noto-cjk/ or share/fonts/truetype/noto-cjk/
912+
echo "Copying Noto CJK fonts..."
913+
914+
# Copy all font files from both packages (sans and serif)
915+
for font_pkg in "${pkgs.noto-fonts-cjk-sans}" "${pkgs.noto-fonts-cjk-serif}"; do
916+
# Check opentype/noto-cjk directory (most common location)
917+
if [ -d "$font_pkg/share/fonts/opentype/noto-cjk" ]; then
918+
echo " Copying from $font_pkg/share/fonts/opentype/noto-cjk"
919+
find "$font_pkg/share/fonts/opentype/noto-cjk" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
920+
fi
921+
# Check truetype/noto-cjk directory
922+
if [ -d "$font_pkg/share/fonts/truetype/noto-cjk" ]; then
923+
echo " Copying from $font_pkg/share/fonts/truetype/noto-cjk"
924+
find "$font_pkg/share/fonts/truetype/noto-cjk" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
925+
fi
926+
# Also check top-level fonts directory (fallback)
927+
if [ -d "$font_pkg/share/fonts" ]; then
928+
echo " Copying from $font_pkg/share/fonts (recursive)"
929+
find "$font_pkg/share/fonts" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.ttc" -o -name "*.otf.ttc" \) -exec cp {} $out/usr/share/fonts/truetype/noto-cjk/ \; 2>/dev/null || true
930+
fi
931+
done
932+
933+
# Verify fonts were copied
934+
FONT_COUNT=$(find $out/usr/share/fonts/truetype/noto-cjk -type f 2>/dev/null | wc -l)
935+
if [ "$FONT_COUNT" -eq 0 ]; then
936+
echo " WARNING: No font files were copied! Check font package paths."
937+
else
938+
echo " Successfully copied $FONT_COUNT font files to $out/usr/share/fonts/truetype/noto-cjk"
939+
fi
940+
941+
# Create symbolic links in alternative paths for compatibility with user scripts
942+
# This allows scripts that check specific paths (like /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc) to find the fonts
943+
mkdir -p $out/usr/share/fonts/opentype/noto
944+
if [ -f $out/usr/share/fonts/truetype/noto-cjk/NotoSansCJK-VF.otf.ttc ]; then
945+
ln -sf ../../truetype/noto-cjk/NotoSansCJK-VF.otf.ttc $out/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc 2>/dev/null || true
946+
echo " Created symlink: /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc"
947+
fi
948+
949+
# Create fontconfig configuration
950+
# IMPORTANT: No leading spaces before XML declaration
951+
cat > $out/etc/fonts/conf.d/99-noto-cjk.conf << 'FONTCONF'
952+
<?xml version="1.0"?>
953+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
954+
<fontconfig>
955+
<dir>/usr/share/fonts/truetype/noto-cjk</dir>
956+
<alias>
957+
<family>sans-serif</family>
958+
<prefer>
959+
<family>Noto Sans CJK SC</family>
960+
<family>Noto Sans CJK TC</family>
961+
<family>Noto Sans CJK JP</family>
962+
<family>Noto Sans CJK KR</family>
963+
</prefer>
964+
</alias>
965+
<alias>
966+
<family>serif</family>
967+
<prefer>
968+
<family>Noto Serif CJK SC</family>
969+
<family>Noto Serif CJK TC</family>
970+
<family>Noto Serif CJK JP</family>
971+
<family>Noto Serif CJK KR</family>
972+
</prefer>
973+
</alias>
974+
</fontconfig>
975+
FONTCONF
976+
977+
# Create matplotlib configuration directory and file at build time
978+
mkdir -p $out/home/python-user/.matplotlib
979+
cat > $out/home/python-user/.matplotlib/matplotlibrc << 'MATPLOTLIBRC'
980+
font.family: sans-serif
981+
font.sans-serif: Noto Sans CJK JP, Noto Sans CJK TC, Noto Sans CJK SC, Noto Sans CJK KR, DejaVu Sans
982+
axes.unicode_minus: False
983+
MATPLOTLIBRC
984+
985+
# Create font setup script for runtime
986+
cat > $out/setup-fonts.sh << 'EOF'
987+
#!/bin/bash
988+
# Setup Chinese fonts for matplotlib
989+
echo "Setting up Chinese font support..."
990+
991+
# Ensure user cache directories exist (these can be created in tmpfs)
992+
mkdir -p /home/python-user/.cache/fontconfig 2>/dev/null || true
993+
mkdir -p /home/python-user/.matplotlib 2>/dev/null || true
994+
995+
# Copy matplotlibrc from Nix store to runtime directory (if it exists)
996+
# The runtime directory is tmpfs, so we need to copy the config file
997+
if [ -f /nix/store/*-docker-setup/home/python-user/.matplotlib/matplotlibrc ]; then
998+
MATPLOTLIBRC_SOURCE=$(find /nix/store -path "*/docker-setup/home/python-user/.matplotlib/matplotlibrc" 2>/dev/null | head -1)
999+
if [ -n "$MATPLOTLIBRC_SOURCE" ]; then
1000+
cp "$MATPLOTLIBRC_SOURCE" /home/python-user/.matplotlib/matplotlibrc 2>/dev/null || true
1001+
fi
1002+
fi
1003+
1004+
# Create/update matplotlibrc with correct font names
1005+
# Use actual available font names: Noto Sans CJK JP (available) instead of SC (may not be available)
1006+
cat > /home/python-user/.matplotlib/matplotlibrc << 'MATPLOTLIBRC'
1007+
font.family: sans-serif
1008+
font.sans-serif: Noto Sans CJK JP, Noto Sans CJK TC, Noto Sans CJK SC, Noto Sans CJK KR, DejaVu Sans
1009+
axes.unicode_minus: False
1010+
MATPLOTLIBRC
1011+
1012+
# Update font cache (if fontconfig is available)
1013+
# Note: Font files are already in /usr/share/fonts/truetype/noto-cjk (copied at build time)
1014+
if command -v fc-cache >/dev/null 2>&1; then
1015+
fc-cache -fv 2>/dev/null || true
1016+
fi
1017+
1018+
# Verify font files are accessible
1019+
if [ -f /usr/share/fonts/truetype/noto-cjk/NotoSansCJK-VF.otf.ttc ]; then
1020+
echo "✓ Noto CJK font files found in /usr/share/fonts/truetype/noto-cjk/"
1021+
else
1022+
echo "⚠ Warning: Noto CJK font files not found in expected location"
1023+
fi
1024+
1025+
# Set matplotlib font configuration environment variables
1026+
export MPLCONFIGDIR=/home/python-user/.matplotlib
1027+
export FONTCONFIG_PATH=/etc/fonts
1028+
1029+
# Ensure fonts.conf exists (fontconfig requires it)
1030+
if [ ! -f /etc/fonts/fonts.conf ]; then
1031+
mkdir -p /etc/fonts
1032+
cat > /etc/fonts/fonts.conf << 'FONTSCONF'
1033+
<?xml version="1.0"?>
1034+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
1035+
<fontconfig>
1036+
<dir>/usr/share/fonts</dir>
1037+
<dir>/usr/share/fonts/truetype</dir>
1038+
<dir>/usr/share/fonts/truetype/noto-cjk</dir>
1039+
<dir>/usr/share/fonts/opentype</dir>
1040+
<dir>/usr/share/fonts/opentype/noto</dir>
1041+
<include ignore_missing="yes">conf.d</include>
1042+
<rescan>
1043+
<int>30</int>
1044+
</rescan>
1045+
</fontconfig>
1046+
FONTSCONF
1047+
fi
1048+
1049+
echo "Chinese font support configured"
1050+
EOF
1051+
1052+
chmod +x $out/setup-fonts.sh
1053+
8791054
# Create uv configuration
8801055
cat > $out/etc/uv/uv.toml << 'EOFUV'
8811056
# Global uv configuration
@@ -1139,6 +1314,28 @@ in
11391314
11401315
# Ensure runtime directories exist in the layer
11411316
mkdir -p tmp tmp/.uv_cache .local/lib/python3.12/site-packages home/python-user app
1317+
mkdir -p usr/share/fonts/truetype/noto-cjk
1318+
mkdir -p usr/share/fonts/opentype/noto
1319+
mkdir -p etc/fonts/conf.d
1320+
1321+
# Ensure fonts.conf exists (fontconfig requires it)
1322+
if [ ! -f etc/fonts/fonts.conf ]; then
1323+
cat > etc/fonts/fonts.conf << 'FONTSCONF'
1324+
<?xml version="1.0"?>
1325+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
1326+
<fontconfig>
1327+
<dir>/usr/share/fonts</dir>
1328+
<dir>/usr/share/fonts/truetype</dir>
1329+
<dir>/usr/share/fonts/truetype/noto-cjk</dir>
1330+
<dir>/usr/share/fonts/opentype</dir>
1331+
<dir>/usr/share/fonts/opentype/noto</dir>
1332+
<include ignore_missing="yes">conf.d</include>
1333+
<rescan>
1334+
<int>30</int>
1335+
</rescan>
1336+
</fontconfig>
1337+
FONTSCONF
1338+
fi
11421339
11431340
# /tmp must be writable for non-root (cplex.log, matplotlib cache, tempfiles, uv cache)
11441341
chmod 1777 tmp
@@ -1187,8 +1384,9 @@ in
11871384
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
11881385
"CURL_CA_BUNDLE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
11891386
"REQUESTS_CA_BUNDLE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
1190-
# Matplotlib configuration directory
1191-
"MPLCONFIGDIR=/tmp/.matplotlib"
1387+
# Font configuration for matplotlib
1388+
"FONTCONFIG_PATH=/etc/fonts"
1389+
"MPLCONFIGDIR=/home/python-user/.matplotlib"
11921390
# Disable dangerous modules
11931391
"PYTHON_DISABLE_MODULES=os,subprocess,sys,importlib,exec,eval,compile,__import__,open,file,input,raw_input,urllib,requests,socket,ftplib,smtplib,poplib,imaplib,nntplib,telnetlib"
11941392
# Restrict network access
@@ -1203,7 +1401,10 @@ in
12031401
"MAIL="
12041402
];
12051403
# Use tail -f /dev/null for container management (keeps container running)
1206-
Cmd = [ "tail" "-f" "/dev/null" ];
1404+
# Note: setup-fonts.sh is best-effort (uses || true), so it won't fail the container startup
1405+
# The container will continue to work even if font setup fails
1406+
# Font files are already copied at build time, this just updates the font cache
1407+
Cmd = [ "bash" "-c" "cat /etc/passwd.d/python-user >> /etc/passwd 2>/dev/null || true && cat /etc/group.d/python-user >> /etc/group 2>/dev/null || true && cat /etc/shadow.d/python-user >> /etc/shadow 2>/dev/null || true && (/setup-fonts.sh || true) && tail -f /dev/null" ];
12071408
# Set security parameters - use non-root user
12081409
User = "1000:1000";
12091410
# Additional security settings

0 commit comments

Comments
 (0)