Skip to content

Latest commit

ย 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

php-ext-font-subset

PHP Version Platform License

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.


๐ŸŒŸ Features

  • 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.

๐Ÿ“ฆ Requirements

  • PHP >= 7.4 (Verified on PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4)
  • libharfbuzz and libharfbuzz-subset

Install System Dependencies

  • 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

๐Ÿš€ Installation & Compilation

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 install

Enable Extension in php.ini

Add the following line to your php.ini (or create a conf.d/ext-font_subset.ini):

extension=font_subset.so

Restart your PHP-FPM or web server:

sudo systemctl restart php-fpm

Verify the installation:

php -m | grep font_subset

๐Ÿ’ป Usage & API

1. Object-Oriented Style (FontSubset::reduce)

<?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!";
}

2. In-Memory Direct Buffer (FontSubset::reduceBuffer)

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;
}

3. Procedural Style (font_subset)

<?php

$success = font_subset('/path/to/source.ttf', 'ไฝ ๅฅฝไธ–็•Œ', '/path/to/output.ttf');

๐Ÿ”’ Security, Concurrency & Durability

1. Secure Temporary Files

  • Temporary files are created securely via mkstemp() with O_CREAT | O_EXCL in 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.

2. Atomic Replacement & Crash Durability

  • Calls fsync() on the file stream before fclose() 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.

3. File Permissions & Ownership

  • Permission Bits: When replacing an existing destination file, its original file mode bits (chmod permissions) are preserved. Newly created files respect the current process umask.
  • 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.

4. Concurrency & Caching Best Practice

  • 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';

๐Ÿ“Š Benchmark

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:

  1. 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.
  2. 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.
  3. 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

๐Ÿงช Testing

Run the standard PHPT test suite:

TEST_PHP_EXECUTABLE=$(which php) php run-tests.php -q tests/*.phpt

All 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)

๐Ÿ“„ License

MIT License. See LICENSE for details.

About

๐Ÿš€ Native PHP Extension for fast TTF/OTF font subsetting with HarfBuzz.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages