tecnickcom/tc-lib-pdf-font
Font import, metrics, and runtime font-stack utilities for PDF generation in PHP. Supports core, Type1, and TrueType fonts, glyph width and bounding-box helpers, character fallback, and composite font encoding.
Font import, metrics, and stack management utilities for PDF generation.
💖 Part of the tc-lib-pdf / TCPDF ecosystem (100M+ installs). Sponsor its maintenance →
tc-lib-pdf-font provides font import and runtime font-stack services used by PDF composition engines.
It bridges static font assets and runtime document composition by handling metrics, encodings, and font program references in a PDF-friendly way. This modular design lets applications evolve font workflows independently from the rest of the rendering stack.
| Namespace | \Com\Tecnick\Pdf\Font |
| Author | Nicola Asuni [email protected] |
| License | GNU LGPL v3 - see LICENSE |
| API docs | https://tcpdf.org/docs/srcdoc/tc-lib-pdf-font |
| Packagist | https://packagist.org/packages/tecnickcom/tc-lib-pdf-font |
json, zlibcomposer require tecnickcom/tc-lib-pdf-font
<?php
require_once __DIR__ . '/vendor/autoload.php';
$font = new \Com\Tecnick\Pdf\Font\Import('/path/to/font.ttf');
$metrics = $font->getFontMetrics();
var_dump($font->getFontName(), $metrics['type']);
For larger examples, refer to test/OutputTest.php and the conversion tooling in this repository.
A TrueTypeUnicode font is emitted as a composite (Type0) font with the Identity-H encoding, so every character code in a content stream is 2 bytes wide.
Since version 4.0 the character code is the glyph index of the font (CID == GID): the font dictionary declares /CIDToGIDMap /Identity, the /W array is keyed by glyph index, and a /ToUnicode CMap is generated for the glyphs used by the document so that the text stays searchable and extractable. Earlier versions used the Unicode codepoint as the character code and embedded a 131072-byte CIDToGIDMap stream, which limited the addressable characters to the Basic Multilingual Plane.
The consumer of this library must encode composite text through the font stack, so that the glyphs used are recorded for the /W array and the /ToUnicode CMap:
$stack = new \Com\Tecnick\Pdf\Font\Stack(1.0);
$stack->insert($objnum, 'dejavusans', '', 12);
if ($stack->isCurrentGidEncoded()) {
// 2-byte big-endian glyph indices, and the glyphs are recorded on the font
$codes = $stack->ordArrToGidStr([0x41, 0x20, 0x1D703]);
} else {
// CID-0 fonts keep the UTF-16BE encoding expected by their predefined CMap
$codes = $uniconv->toUTF16BE($str);
}
The text object written to the page must also select the font the string was encoded with, otherwise the glyph indices are resolved against a different font. This does not apply to the fonts that are not GID encoded, whose character codes mean the same thing in every font.
Relevant methods:
Stack::isCurrentGidEncoded(): true when the current font encodes text as glyph indices.Stack::ordArrToGidStr(array $uniarr): encodes codepoints as 2-byte glyph indices and records them.Stack::getGidForOrd(int $ord): glyph index of a codepoint, 0 when the font has no glyph for it.Buffer::addUsedGid(string $key, int $gid, int $ord): records a glyph and the codepoint it was encoded from.The glyph index of a codepoint is read at runtime from the .ctg.z artifact of the font, so definition files generated by earlier versions keep working for the whole Basic Multilingual Plane without being regenerated. The file must remain next to the .json definition file.
Codepoints above U+FFFF do not fit that table and are stored in the definition file under the ctgu key:
"ctgu": {"120579": 1588, "119886": 941}
That key is only written when the font is converted with --encoding_id=10, which selects the format 12 cmap subtable. A definition file carrying ctgu is still readable by older versions of the library, which ignore it.
TrueType font subsetting is computational and memory intensive. When the same
fonts and character sets are embedded repeatedly (for example across many
generated documents), you can inject an optional cache into
\Com\Tecnick\Pdf\Font\Output to reuse previously computed subset programs.
Provide any object implementing
\Com\Tecnick\Pdf\Font\FontSubsetCacheInterface:
interface FontSubsetCacheInterface
{
public function get(string $key): ?string; // null on cache miss
public function set(string $key, string $subsetFont): void;
}
Pass it as the last constructor argument:
$output = new \Com\Tecnick\Pdf\Font\Output(
$fonts,
$objectNumber,
$encrypt,
$fileHelper, // or null
$subsetCache, // your FontSubsetCacheInterface implementation
);
When no cache is supplied (the default), behavior is unchanged and every subset is recomputed. The library never evicts entries — the injected backend owns expiration and size limits. The cache key already accounts for the font program bytes, the cmap-selection metrics, and the requested subset characters, so distinct inputs never collide.
No PSR-16 adapter is bundled, because the interface above is intentionally trivial to wrap around any backend (PSR-16/PSR-6, Symfony Cache, Laravel, Redis, APCu, …). A correct PSR-16 wrapper is a few lines:
use Com\Tecnick\Pdf\Font\FontSubsetCacheInterface;
use Psr\SimpleCache\CacheInterface;
final class Psr16SubsetCache implements FontSubsetCacheInterface
{
public function __construct(private CacheInterface $cache) {}
public function get(string $key): ?string
{
$value = $this->cache->get($this->normalize($key));
return \is_string($value) ? $value : null;
}
public function set(string $key, string $subsetFont): void
{
$this->cache->set($this->normalize($key), $subsetFont);
}
/**
* PSR-16 only guarantees keys matching [A-Za-z0-9_.] up to 64 chars and
* reserves {}()/\@: — the library's raw key uses ':' and '-' and is longer,
* so hash it into a guaranteed-legal, fixed-length key.
*/
private function normalize(string $key): string
{
return 'tclpf_' . \sha1($key);
}
}
The normalize() step matters: the library's raw key is human-readable and
works with permissive backends, but stricter PSR-16 implementations may reject
or mangle it. Hashing it keeps the wrapper portable. Note that hashing discards
the readable namespace, so scope the underlying PSR-16 pool to this library if
you share it with other consumers.
Use the CLI utilities in util/ to convert existing font files into the JSON/Z format consumed by this library.
Run util/convert.php and pass one or more input files with --fonts:
php util/convert.php \
--outpath=./target/fonts/custom/ \
--type=TrueTypeUnicode \
--flags=32 \
--encoding_id=10 \
--fonts=/path/to/MyFont-Regular.ttf,/path/to/MyFont-Bold.ttf
The command writes generated font definition files to --outpath.
Common options:
--type: Explicit font type (TrueTypeUnicode, TrueType, Type1, CID0JP, CID0KR, CID0CS, CID0CT). Leave empty for autodetect.--encoding: Encoding table (for example cp1252 for many non-Unicode Type1/Core cases). Omit for Unicode and symbolic fonts.--flags: PDF descriptor flags. Default is 32 (non-symbolic).--platform_id and --encoding_id: CMAP selection for TrueType Unicode imports (defaults: 3 and 1). Use --encoding_id=10 to read the format 12 subtable, which is required for the characters above U+FFFF. It is a safe default: a font without that subtable falls back to the BMP one.--linked: Link to system font file instead of embedding/copying it (not transportable).To see full usage help:
php util/convert.php --help
For batch generation from the mirrored font set:
cd util
make build
This installs util dependencies and runs bulk_convert.php, which scans the mirror package and writes converted fonts under target/fonts/.
Notes:
bulk_convert.php also attempts OTF conversion via FontForge (fontforge -script otf2ttf.ff ...) before import.php util/bulk_convert.php --outpath=/your/path/.Version 4.0 changes the character codes emitted for TrueTypeUnicode fonts from Unicode codepoints to glyph indices, as described in Character Encoding of Composite Fonts.
A consumer of this library must be updated together with it:
Stack::ordArrToGidStr() instead of writing UTF-16BE codepoints, and make the text object select the font the string was encoded with. A consumer that keeps writing codepoints produces PDFs whose text renders as unrelated glyphs, with no error raised.TrueTypeUnicode font emits one PDF object less, since the CIDToGIDMap stream is not embedded any more. Baselines that compare PDF output byte by byte, or that assert on object numbers, must be regenerated.TFontData shape gained the ctgu, gidenc and usedgid keys. Code that builds that array must add them.Unchanged:
.ctg.z artifacts generated by 3.x keep working for the whole Basic Multilingual Plane. Regenerate a font only to gain the characters above U+FFFF, with --encoding_id=10.Core, TrueType, Type1 and cidfont0 fonts is unchanged.make deps
make help
make qa
Font generation helpers are also available through Make targets such as fonts.
make rpm
make deb
For system packages, bootstrap with:
require_once '/usr/share/php/Com/Tecnick/Pdf/Font/autoload.php';
Contributions are welcome. Please review CONTRIBUTING.md, CODE_OF_CONDUCT.md, and SECURITY.md.
How can I help you explore Laravel packages today?