High-performance PHP C extension for TrueType (.ttf) and OpenType (.otf) font subsetting, powered by the HarfBuzz (hb-subset) C library.
Designed specifically for Chinese / CJK fonts to extract only needed glyphs, reducing multi-megabyte font files down to a few kilobytes in sub-millisecond time.
- High Performance: Subsetting takes < 1 millisecond for in-memory extraction and atomic disk writes.
- Strict RFC 3629 UTF-8 Validation: Built-in strict validator detects and rejects overlong sequences, UTF-16 surrogates (
U+D800..U+DFFF), and out-of-range codepoints (> U+10FFFF). Full support for 4-byte UTF-8 and Emojis. - Atomic File Writing: Writes to an isolated temporary file first, followed by POSIX atomic
rename(). Never corrupts or truncates existing destination files on write failure. - Zero CLI / Shell Dependency: Runs entirely in PHP process memory via HarfBuzz C API. Completely bypasses
disable_functions(exec,shell_exec,proc_open). - In-Memory Buffer Support: Generates binary font streams directly in memory (
FontSubset::reduceBuffer) without disk I/O, ideal for direct HTTP responses or Redis caching. - IDE & Static Analysis Support: Includes IDE stubs (
stubs/FontSubset.php) with guards to avoid redeclaration fatal errors.
- PHP >= 7.4 (Verified on PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4)
libharfbuzzandlibharfbuzz-subset
-
macOS (Homebrew):
brew install harfbuzz pkg-config
-
Ubuntu / Debian:
sudo apt-get update sudo apt-get install -y libharfbuzz-dev pkg-config php-dev
-
CentOS / RHEL / RockyLinux:
sudo yum install -y harfbuzz-devel pkgconfig php-devel
cd /path/to/php-ext-font-subset
phpize
./configure
make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)
sudo make installAdd the following line to your php.ini (or create a conf.d/ext-font_subset.ini):
extension=font_subset.soRestart your PHP-FPM or web server:
sudo systemctl restart php-fpmVerify the installation:
php -m | grep font_subset<?php
$sourceFont = '/data/fonts/AlibabaPuHuiTi-2-55-Regular.ttf';
$outputPath = '/data/temp/subset_font.ttf';
$text = 'ๅพฎไฟกๅฐ็จๅบPDF็ๆๆต่ฏ๏ผไฝ ๅฅฝไธ็๏ผ123456';
$success = FontSubset::reduce($sourceFont, $text, $outputPath);
if ($success) {
echo "Subset created successfully: " . $outputPath;
} else {
echo "Subsetting failed!";
}Generate subset font data directly in memory (ideal for direct HTTP response streaming or object storage upload):
<?php
$sourceFont = '/data/fonts/AlibabaPuHuiTi-2-55-Regular.ttf';
$text = '็จๆท่พๅ
ฅ็ๆๅญ';
$binaryBuffer = FontSubset::reduceBuffer($sourceFont, $text);
if ($binaryBuffer !== false) {
header('Content-Type: font/ttf');
header('Content-Length: ' . strlen($binaryBuffer));
echo $binaryBuffer;
}<?php
$success = font_subset('/path/to/source.ttf', 'ไฝ ๅฅฝไธ็', '/path/to/output.ttf');- Temporary files are created securely via
mkstemp()withO_CREAT | O_EXCLin the destination directory. - Strictly checks
fchmod()return values, immediately aborting on failure. - Prevents predictable temp name collision and symlink race attacks in shared/world-writable directories.
- Calls
fsync()on the file stream beforefclose()to ensure file data is physically flushed to media. - Replaces the target file via POSIX atomic
rename(). Readers never observe partially written or truncated files. - Attempts best-effort
fsync()on the parent directory to persist the directory entry update across sudden power failures (some filesystems or network mounts do not support directory syncing and are handled gracefully without error). - If subsetting fails, temporary files are immediately unlinked and any existing destination file remains intact.
- Permission Bits: When replacing an existing destination file, its original file mode bits (
chmodpermissions) are preserved. Newly created files respect the current processumask. - Ownership / ACLs: The resulting file will be owned by the active PHP-FPM / CLI process user (
uid/gid). Extended filesystem ACLs or SELinux labels are not copied across inode replacements.
- Because replacement is atomic, if multiple PHP workers generate the exact same destination file concurrently, the last writer wins cleanly without file corruption.
- Recommended Caching Patterns:
- Option A: Fast Metadata Hash (recommended for local trusted storage):
$cacheKey = md5(realpath($fontPath) . '_' . filemtime($fontPath) . '_' . filesize($fontPath) . '_' . $text) . '.ttf';
- Option B: True Content Hash (highest reliability for clustered / containerized environments):
$cacheKey = md5(md5_file($fontPath) . '_' . $text) . '.ttf';
- Option A: Fast Metadata Hash (recommended for local trusted storage):
Benchmark executed on Apple M1 Pro (macOS 15.6, APFS on NVMe SSD, PHP 7.4.33, HarfBuzz 12.2.0):
- Test Font: 307.28 KB TrueType Font
- Text Input: 40 UTF-8 characters
- Iterations: 1,000 runs (after 50 warm-up runs)
| Method | Output Size | Mean Latency | P50 Latency | P95 Latency | P99 Latency | Throughput | PHP Managed Memory Delta |
|---|---|---|---|---|---|---|---|
FontSubset::reduceBuffer() (In-Memory) |
12.5 KB | ~0.043 ms | 0.042 ms | 0.052 ms | 0.076 ms | ~23,000 ops/sec | ~52 KB |
FontSubset::reduce() (Atomic Disk Write) |
12.5 KB | ~0.222 ms | 0.211 ms | 0.318 ms | 0.393 ms | ~4,500 ops/sec | ~36 KB |
Important Benchmark Disclaimers:
- Hardware & Filesystem Dependency: Disk write latency is heavily influenced by underlying storage hardware (local NVMe SSD vs cloud block storage like AWS EBS vs Docker volume mounts) and filesystem driver
fsync()characteristics.- Memory Metrics: "PHP Managed Memory Delta" measures PHP engine heap difference before and after execution. HarfBuzz C library allocations are transient and freed within the C call.
- Font Scale: For large CJK fonts (15~20MB with 30,000+ glyphs), processing time typically scales to ~5-15ms.
Run the benchmark yourself:
php benchmark.php 1000 50
Run the standard PHPT test suite:
TEST_PHP_EXECUTABLE=$(which php) php run-tests.php -q tests/*.phptAll 8 tests cover:
- Extension loading and reflection info
- Procedural
font_subset()API - OOP
FontSubset::reduce()API - In-memory
FontSubset::reduceBuffer()API - Parameter boundary and same-path overwrite protection
- Strict RFC 3629 UTF-8 validation (overlong, surrogates, out-of-range)
- SFNT header & glyph integrity verification
- Atomic write safety (destination preservation on failure)
MIT License. See LICENSE for details.