Ce tutoriel correspond à examples/embedded-font.php.
Il construit une police composite /Type0 utilisant /Identity-H, une fonte
descendante /CIDFontType2 et tous les glyphes TrueType de Noto Sans.
Le PDF présente du français et du grec ; le lecteur peut sélectionner et
extraire les caractères d'origine grâce au flux /ToUnicode.
Ce tutoriel utilise le module optionnel
pdftoolkit/pdf-fonts, avec le core, aux versions publiées sélectionnées par le manifeste de sources, pour analyser
TrueType. Le programme incorporé, les dictionnaires et les correspondances PDF
restent des objets du core. composer install dans le checkout du core installe le
module comme dépendance de développement ; une application l'installe depuis
Packagist avec composer require "pdftoolkit/pdf-fonts:^0.1.6".
Le guide des packages explique comment sélectionner aussi
la série du core.
La fonte de démonstration et sa licence SIL Open Font License sont conservées dans les ressources du module de fontes. Composer les installe avec le module ; le core ne distribue pas le fichier TrueType.
Exécuter l'exemple
Depuis la racine d'un checkout du core, après composer install :
php examples/embedded-font.php embedded-font.pdf
Le chemin de sortie est facultatif ; par défaut le script écrit dans
examples/embedded-font.pdf. La police Noto Sans et sa licence sont installées
dans vendor/pdftoolkit/pdf-fonts/examples/assets/fonts/. Le lecteur peut rechercher et copier le français
et le grec grâce aux correspondances Unicode construites par le script.
1. Le programme de fonte est un objet distinct
L'application lit le fichier et donne ses octets à
FontProgram.
Le modèle ne lit aucun chemin et ne contacte aucune URL. Le programme conserve
les octets complets, son nom PostScript, son cmap, ses métriques horizontales
et les boîtes de ses glyphes.
use PDF\Fonts\TrueType\FontProgram;
use PDF\Fonts\TrueType\UnicodeText;
$program = new FontProgram($fontBytes);
$heading = new UnicodeText('Une police incorporée, objet par objet.');
$body = new UnicodeText('Français : été, cœur, Noël. Ελληνικά : Αθήνα.');
Le budget d'entrée est un objet explicite
FontProgramLimits,
par défaut 32 MiB. L'application peut imposer une limite plus basse, par exemple :
$program = new FontProgram($fontBytes, new FontProgramLimits(maxBytes: 1048576));
UnicodeText valide UTF-8
et conserve une liste de valeurs scalaires Unicode. Il ne normalise pas le
texte et ne transforme pas sa direction ou sa composition typographique.
Les unités de la fonte ne sont pas directement les unités du dictionnaire PDF.
Une avance de 1 024 dans une fonte à 2 048 unités par em devient une largeur
PDF de 500. Les largeurs fractionnaires sont conservées. Le parseur respecte
aussi hmtx lorsqu'un dernier enregistrement d'avance s'applique à plusieurs
glyphes.
2. Déclarer le répertoire avant de construire le graphe
Le programme CID est d'abord préparé et incorporé dans un
FontFile2 du core. Le service
CharacterMapFactory
reçoit le programme CID puis produit un
CharacterMap du core à partir du
flux incorporé et de tous les caractères qui seront encodés avec cette instance.
Chaque caractère Unicode distinct obtient un CID distinct, à partir de 1 ;
le CID 0 est réservé au glyphe .notdef. Une répétition du même caractère
réutilise son CID. Deux caractères partageant le même glyphe gardent deux CID
pour conserver leur identité lors de l'extraction.
use PDF\Document\Fonts\FontFile2;
use PDF\Fonts\TrueType\CIDFontProgram;
use PDF\Fonts\TrueType\CharacterMapFactory;
use PDF\Fonts\TrueType\TextEncoder;
$cidProgram = new CIDFontProgram($program);
$fontFile = new FontFile2($cidProgram->bytes, new FlateDecode());
$map = (new CharacterMapFactory($cidProgram))->create(
$fontFile,
new UnicodeText($heading->utf8 . $body->utf8),
);
$encoder = new TextEncoder($map);
Un caractère absent de la fonte provoque une exception à la construction du
répertoire. Un caractère existant mais absent du répertoire provoque une
exception à l'encodage. Il n'y a ni ajout implicite, ni remplacement silencieux
par .notdef, ni état partagé entre les documents.
3. Assembler les objets définis par PDF
CIDFontProgram prépare
le programme destiné à une fonte CID. ISO 32000-1, 9.9, exige de retirer la table
cmap d'un programme TrueType incorporé par /FontFile2 pour une fonte CID :
la correspondance vers les glyphes vient de /CIDToGIDMap. Tous les glyphes
et toutes les autres tables sont conservés, à l'exception d'une éventuelle
signature DSIG devenue invalide après transformation. Le répertoire sfnt,
ses offsets, ses checksums et checksumAdjustment sont recalculés.
Ce programme est incorporé par
FontFile2, avec son /Length1
non compressé. Le filtre /FlateDecode compresse le flux sans modifier le
programme CID qui sera restitué après décodage.
use PDF\Fonts\TrueType\FontDescriptorFactory;
$descriptor = (new FontDescriptorFactory($cidProgram))->create(
$fontFile,
new FontDescriptorFlags(4),
new NumericType(80),
);
$glyphMap = new CIDToGIDMap($map, new FlateDecode());
$widths = new CIDWidths($map);
$descendant = new CIDFontType2($descriptor, $glyphMap, $widths);
$toUnicode = new ToUnicodeCMap($map, new FlateDecode());
$font = new Type0Font($descendant, $toUnicode);
Les responsabilités sont séparées :
| Objet | Entrée ou rôle PDF |
|---|---|
CIDFontProgram (module) |
Programme TrueType à tous ses glyphes, préparé pour une fonte CID |
FontFile2 |
Programme CID TrueType incorporé et /Length1 |
FontDescriptor |
Nom, boîte, ascension, descente, angle, flags et /FontFile2 |
CIDToGIDMap |
Correspondance CID → index de glyphe, en mots big-endian de deux octets |
CIDWidths |
/W, exprimé dans les 1 000 unités du système de glyphes PDF |
CIDFontType2 |
Fonte descendante, métriques et correspondance vers les glyphes |
ToUnicodeCMap |
Correspondance code de caractère → UTF-16BE pour l'extraction |
Type0Font |
Police composite /Identity-H qui relie la fonte descendante et /ToUnicode |
StemV est explicitement fourni : 80 unités PDF dans cet exemple. TrueType ne
définit pas de champ universel donnant l'épaisseur des fûts verticaux. La valeur
ne résulte donc pas d'une estimation cachée du constructeur. CapHeight vient
de OS/2 ou de la boîte du glyphe H ; lorsqu'elle n'est pas disponible, la
factory exige une valeur explicite. Le FontDescriptor du core reçoit directement
ses valeurs PDF : nom, boîte, angle et métriques. Il ne lit pas les tables TrueType.
Les flags sont aussi explicites :
4 correspond au bit Symbolic de la table 123, adapté à ce répertoire comprenant
du grec. Les bits Symbolic et Nonsymbolic ne peuvent pas être combinés.
Les objets du core vérifient la cohérence du même flux FontFile2 et du même
répertoire. Les services du module relient ces objets aux données du programme.
Les entrées qu'ils déterminent (/Encoding, /FontFile2, /W,
/ToUnicode, etc.) ne peuvent être remplacées ou supprimées avec with() ou
without(). Des entrées d'extension restent des objets explicites et immuables.
4. Encoder la chaîne présentée à Tj
/Identity-H prend un code de caractère sur deux octets. Ce code est ici le
CID attribué dans le répertoire, pas le point de code Unicode et pas le GID.
new ShowText($encoder->encode($heading));
TextEncoder retourne un
HexadecimalString. Les objets Tf, Td, Tj, /Resources, /Page, /Pages
et /Catalog s'assemblent ensuite comme dans les premiers tutoriels. Aucun
moteur de mise en page ne s'intercale dans ce graphe.
Le flux /ToUnicode convertit les codes vers UTF-16BE. Les caractères hors du
plan multilingue de base utilisent une paire de substituts. Les blocs
beginbfchar contiennent au plus 100 correspondances, conformément à la
syntaxe CMap utilisée par la norme.
5. Limites explicites de cette première incorporation
Le module incorpore tous les glyphes de la fonte ; il ne produit
pas de sous-ensemble. Le binaire sfnt est reconstruit pour les contraintes de
la fonte CID décrites ci-dessus. Elle accepte un programme sfnt TrueType statique avec contours
glyf, cmap Unicode format 4 ou 12 et métriques horizontales. Les fontes CFF,
CFF2, variables, les collections TTC, WOFF et WOFF2 sont refusées. Les séquences
de variation Unicode, le shaping OpenType, le crénage automatique, les
ligatures automatiques et la composition bidirectionnelle restent à construire.
Les caractères sont présentés dans l'ordre fourni à l'encodeur.
Les tables, segments, groupes, métriques, délimitations loca et structures
des contours sont bornés. Les références des glyphes composés sont vérifiées,
ainsi que leurs cycles et leur profondeur. Les limites sont par défaut 32 MiB par fonte,
256 tables, 65 535 références de glyphes composés, 1 024 composants par glyphe,
une profondeur de 64 et 1 MiB par objet UnicodeText. /Identity-H permet
65 535 caractères distincts dans le répertoire, en plus du CID 0.
Les lecteurs de tables utilisent des vues bornées sur les octets du programme,
sans copie de chaque table ou sous-table. Les groupes de cmap format 12 et le graphe des glyphes restent en stockage
binaire compact ; leur nombre ne multiplie pas les allocations de tableaux PHP.
La limite d'octets porte sur le programme d'entrée, pas sur le tas du processus :
l'application doit aussi prévoir son budget mémoire pour le graphe et l'écriture.
Les instructions TrueType sont conservées comme données ; le module ne les exécute pas et n'est pas un moteur de fontes ni un assainisseur de toutes les tables OpenType. Une application qui accepte des fontes externes doit choisir sa politique de confiance et, si nécessaire, un assainissement spécialisé avant de fournir les octets au convertisseur.
Le champ OS/2.fsType est contrôlé : les fontes à incorporation restreinte ou
réservées aux bitmaps sont refusées. L'interdiction de sous-ensemble est respectée
par l'incorporation complète. Les modes Preview & Print et Editable permettent
l'incorporation et restent exposés dans embeddingRights ; l'application doit
respecter les usages autorisés et la licence de la fonte. Ce champ ne remplace
pas la licence de redistribution des fichiers de fonte.
Références
- ISO 32000-1:2008, 9.7.4, 9.7.6, 9.8, 9.9 et 9.10.3 ; tables 117, 121–123, 126 et 127.
- Microsoft OpenType : cmap.
- Microsoft OpenType : métriques horizontales.
- Microsoft OpenType : OS/2 et droits d'incorporation.
- Microsoft OpenType : glyf.
← 08. Objets compressés · Tous les tutoriels · 10. Images JPEG et PNG →
Voir le résultat PDF
Ce fichier a été généré avec le code de cette documentation. Toutes ses pages sont reproduites ci-dessous ; ouvrir le PDF pour utiliser ses liens et ses signets.
Utiliser le lecteur PDF du navigateur
Ce lecteur dépend du navigateur. Si le document ne s’affiche pas, utiliser « Ouvrir le PDF » ou « Télécharger ».
Code source complet
Code de roukmoute/pdf · Consulter la source au commit documenté ↗
Lire le script commenté : embedded-font.php
<?php
declare(strict_types=1);
use PDF\Document\Fonts\CIDFontType2;
use PDF\Document\Fonts\FontDescriptorFlags;
use PDF\Fonts\TrueType\CIDFontProgram;
use PDF\Fonts\TrueType\CharacterMapFactory;
use PDF\Fonts\TrueType\FontDescriptorFactory;
use PDF\Document\Fonts\CIDToGIDMap;
use PDF\Document\Fonts\CIDWidths;
use PDF\Document\Fonts\FontFile2;
use PDF\Fonts\TrueType\FontProgram;
use PDF\Fonts\TrueType\TextEncoder;
use PDF\Document\Fonts\ToUnicodeCMap;
use PDF\Fonts\TrueType\UnicodeText;
use PDF\Document\Fonts\Type0Font;
use PDF\Document\ResourceDictionary;
use PDF\Document\Resources\Font;
use PDF\FileStructure;
use PDF\Syntax\ContentStreamsResources\ContentStreams;
use PDF\Syntax\ContentStreamsResources\Operators\BeginTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\EndTextObject;
use PDF\Syntax\ContentStreamsResources\Operators\MoveTextPosition;
use PDF\Syntax\ContentStreamsResources\Operators\SetTextFontAndSize;
use PDF\Syntax\ContentStreamsResources\Operators\ShowText;
use PDF\Syntax\DataStructure\Rectangle;
use PDF\Syntax\DocumentStructure\DocumentCatalog;
use PDF\Syntax\DocumentStructure\PageTree\PageObject;
use PDF\Syntax\DocumentStructure\PageTree\PageTreeNode;
use PDF\Syntax\FileStructure\FileBody;
use PDF\Syntax\FileStructure\FileHeader;
use PDF\Syntax\Filters\FlateDecode;
use PDF\Syntax\Object\DictionaryObject\Entry;
use PDF\Syntax\Object\DictionaryType;
use PDF\Syntax\Object\IndirectReference;
use PDF\Syntax\Object\NameType;
use PDF\Syntax\Object\NumericType;
use PDF\Syntax\Object\StreamObject;
require dirname(__DIR__) . '/vendor/autoload.php';
// Tutorial 09: docs/tutorials/09-embedded-font.md.
// The optional PDFToolkit/pdf-fonts package analyzes TrueType bytes.
// The core retains only PDF objects and their explicit values.
// File reading belongs to the application. FontProgram receives bytes
// without knowing paths, URLs or file-access mechanisms.
// Tutorial data are distributed by the module without copies in the core.
$fontBytes = file_get_contents(dirname(__DIR__) . '/vendor/pdftoolkit/pdf-fonts/examples/assets/fonts/NotoSans-Regular.ttf');
if ($fontBytes === false) {
throw new RuntimeException('Unable to read the bundled demonstration font.');
}
$program = new FontProgram($fontBytes);
$lines = [
new UnicodeText('Une police incorporée, objet par objet.'),
new UnicodeText('Français : été, cœur, Noël. Ελληνικά : Αθήνα.'),
new UnicodeText('Chaque caractère conserve son identité Unicode.'),
];
// The repertoire is known before writing: no mutable global registration.
// Two Unicode characters can have the same GID and different CIDs.
$repertoire = new UnicodeText(implode('', array_map(static fn (UnicodeText $text): string => $text->utf8, $lines)));
// The CID program retains every glyph. The specification requires removing cmap
// for FontFile2/CIDFontType2; mappings remain in our PDF objects.
// The sfnt directory and checksums are recalculated without glyph subsetting.
// Length1 measures embedded-program bytes after filter decoding.
// StemV is explicitly supplied (80 PDF units in this example), because
// TrueType has no universal field giving vertical stem thickness.
// The Symbolic flag suits this repertoire containing Greek (Table 123).
$cidProgram = new CIDFontProgram($program);
$fontFile = new FontFile2($cidProgram->bytes, new FlateDecode());
$map = (new CharacterMapFactory($cidProgram))->create($fontFile, $repertoire);
$encoder = new TextEncoder($map);
$descriptor = (new FontDescriptorFactory($cidProgram))->create($fontFile, new FontDescriptorFlags(4), new NumericType(80));
$glyphMap = new CIDToGIDMap($map, new FlateDecode());
$widths = new CIDWidths($map);
$descendant = new CIDFontType2($descriptor, $glyphMap, $widths);
$toUnicode = new ToUnicodeCMap($map, new FlateDecode());
$font = new Type0Font($descendant, $toUnicode);
$resources = new ResourceDictionary(new Font(new DictionaryType(
new Entry(new NameType('F1'), new IndirectReference($font)),
)));
// Identity-H expects two-byte codes, not the original UTF-8 string.
// The encoder supplies these codes; ToUnicode supplies the reverse mapping
// for selection, search and extraction in PDF readers.
$contents = new StreamObject(new ContentStreams(
new BeginTextObject(),
new SetTextFontAndSize(new NameType('F1'), new NumericType(22)),
new MoveTextPosition(new NumericType(48), new NumericType(748)),
new ShowText($encoder->encode($lines[0])),
new SetTextFontAndSize(new NameType('F1'), new NumericType(14)),
new MoveTextPosition(new NumericType(0), new NumericType(-42)),
new ShowText($encoder->encode($lines[1])),
new MoveTextPosition(new NumericType(0), new NumericType(-28)),
new ShowText($encoder->encode($lines[2])),
new EndTextObject(),
));
$page = new PageObject(
new Rectangle(new NumericType(0), new NumericType(0), new NumericType(595), new NumericType(842)),
$resources,
$contents,
);
$pages = (new PageTreeNode())->addKid($page);
$catalog = new DocumentCatalog($pages);
$file = new FileStructure(new FileHeader('1.7'), new FileBody($catalog), new IndirectReference($catalog));
$path = $argv[1] ?? __DIR__ . '/embedded-font.pdf';
$file->writeTo($path);
echo $path . PHP_EOL;
