Le script complet affiche ABCDEF éΩ fi avec
un encodage d'un octet dans une CMap incorporée. La police provient du module
optionnel de conversion ; les objets PDF et les CMaps restent dans le core.
php examples/cmap-font.php cmap-font.pdf
La police Type0 relie les codes à des CID par /Encoding. Sa descendante relie
les CID aux glyphes et métriques. /ToUnicode associe les codes originaux au
texte Unicode pour la recherche et l'extraction. Ces trois liens sont distincts.
La CMap de base définit un codespace, une plage CID et des correspondances
notdef. La CMap étendue la référence par /UseCMap puis ajoute des codes.
Les bornes du codespace sont contrôlées octet par octet ; un intervalle entier
ordinaire ne représente pas correctement toutes les plages multioctets.
La CMap Unicode montre bfchar, une plage bfrange séquentielle et une plage
avec tableau de destinations. Le code de fi produit deux caractères Unicode :
le texte extrait peut différer du nombre de glyphes. UTF-16BE et les paires de
substituts sont vérifiés explicitement.
Modifier les codes exige des changements cohérents dans le contenu et dans
ToUnicode. La CMap ne réécrit pas le programme de police. Le mode vertical
et ses métriques constituent également un choix explicite.
La CI vérifie le texte extrait avec pypdf et Poppler.
Référence : ISO 9.7 et 9.10, pages physiques 268–292 et 299–302.
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é : cmap-font.php
<?php
declare(strict_types=1);
use PDF\Document\Fonts\CIDFontType2;
use PDF\Document\Fonts\CIDToGIDMap;
use PDF\Document\Fonts\CIDWidths;
use PDF\Document\Fonts\CMaps\CMapCode;
use PDF\Document\Fonts\CMaps\CMapCodeRange;
use PDF\Document\Fonts\CMaps\CMapCodeSequence;
use PDF\Document\Fonts\CMaps\CMapName;
use PDF\Document\Fonts\CMaps\CMapSystemInfo;
use PDF\Document\Fonts\CMaps\CodeSpaceRange;
use PDF\Document\Fonts\CMaps\CodeSpaces;
use PDF\Document\Fonts\CMaps\CidChar;
use PDF\Document\Fonts\CMaps\CidMappings;
use PDF\Document\Fonts\CMaps\CidRange;
use PDF\Document\Fonts\CMaps\EncodingCMap;
use PDF\Document\Fonts\CMaps\NotdefRange;
use PDF\Document\Fonts\CMaps\UnicodeArrayRange;
use PDF\Document\Fonts\CMaps\UnicodeChar;
use PDF\Document\Fonts\CMaps\UnicodeCMap;
use PDF\Document\Fonts\CMaps\UnicodeMappings;
use PDF\Document\Fonts\CMaps\UnicodeRange;
use PDF\Document\Fonts\CMaps\UnicodeScalar;
use PDF\Document\Fonts\CMaps\UnicodeScalars;
use PDF\Document\Fonts\FontDescriptorFlags;
use PDF\Document\Fonts\FontFile2;
use PDF\Document\Fonts\StandardFont;
use PDF\Document\Fonts\ToUnicodeCMap;
use PDF\Document\Fonts\Type0Font;
use PDF\Document\Fonts\Type1Font;
use PDF\Document\ResourceDictionary;
use PDF\Document\Resources\Font;
use PDF\FileStructure;
use PDF\Fonts\TrueType\CIDFontProgram;
use PDF\Fonts\TrueType\CharacterMapFactory;
use PDF\Fonts\TrueType\FontDescriptorFactory;
use PDF\Fonts\TrueType\FontProgram;
use PDF\Fonts\TrueType\UnicodeText;
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\IntegerType;
use PDF\Syntax\Object\NameType;
use PDF\Syntax\Object\NumericType;
use PDF\Syntax\Object\StreamObject;
use PDF\Syntax\Object\StringObject\HexadecimalString;
use PDF\Syntax\Object\StringObject\LiteralString;
require dirname(__DIR__) . '/vendor/autoload.php';
// The optional module analyzes the TrueType program supplied by the application.
// The core then receives explicit CIDs, GIDs and metrics without reading a font.
$bytes = file_get_contents(dirname(__DIR__) . '/vendor/pdftoolkit/pdf-fonts/examples/assets/fonts/NotoSans-Regular.ttf');
if ($bytes === false) {
throw new RuntimeException('Cannot read the demonstration font supplied by pdf-fonts.');
}
$program = new CIDFontProgram(new FontProgram($bytes));
$fontFile = new FontFile2($program->bytes, new FlateDecode());
$map = (new CharacterMapFactory($program))->create($fontFile, new UnicodeText('ABCDEF éΩfi'));
$descriptor = (new FontDescriptorFactory($program))->create($fontFile, new FontDescriptorFlags(4), new NumericType(80));
$descendant = new CIDFontType2($descriptor, new CIDToGIDMap($map, new FlateDecode()), new CIDWidths($map));
// This CMap uses one byte per code, so it is not Identity-H. The first
// dictionary describes the codespace and six letters. The second references
// this CMap through /UseCMap and adds three distinct codes A4, A5 and A6.
$code = static fn (string $bytes): CMapCode => new CMapCode(new HexadecimalString($bytes));
$spaces = new CodeSpaces(new CodeSpaceRange($code("\0"), $code("\xff")));
$collection = new CMapSystemInfo(new LiteralString('Adobe'), new LiteralString('Identity'), new IntegerType(0));
$base = new EncodingCMap(
new CMapName(new NameType('PDFToolkit-OneByte-Base')),
$collection,
$spaces,
new CidMappings(
new CidRange(new CMapCodeRange($code('A'), $code('F')), new IntegerType($map->character(0x41)->cid)),
new CidChar($code(' '), new IntegerType($map->character(0x20)->cid)),
new NotdefRange(new CMapCodeRange($code("\0"), $code("\x1f")), new IntegerType(0)),
),
);
$encoding = new EncodingCMap(
new CMapName(new NameType('PDFToolkit-OneByte-Extended')),
$collection,
new CodeSpaces(),
new CidMappings(new CidRange(new CMapCodeRange($code("\xa4"), $code("\xa6")), new IntegerType($map->character(0xe9)->cid))),
base: $base,
);
// /Encoding selects glyphs; /ToUnicode explains their textual content.
// A..F uses incremental bfrange; A4..A6 uses distinct sequences.
// The ligature fi is one glyph whose extracted Unicode sequence is "fi".
$unicode = new ToUnicodeCMap(new UnicodeCMap(
new CMapName(new NameType('PDFToolkit-OneByte-UCS')),
$spaces,
new UnicodeMappings(
new UnicodeRange(new CMapCodeRange($code('A'), $code('F')), (new UnicodeScalar(new IntegerType(0x41)))->sequence()),
new UnicodeChar($code(' '), (new UnicodeScalar(new IntegerType(0x20)))->sequence()),
new UnicodeArrayRange(
new CMapCodeRange($code("\xa4"), $code("\xa6")),
(new UnicodeScalar(new IntegerType(0xe9)))->sequence(),
(new UnicodeScalar(new IntegerType(0x3a9)))->sequence(),
new UnicodeScalars(new UnicodeScalar(new IntegerType(0x66)), new UnicodeScalar(new IntegerType(0x69))),
),
),
), new FlateDecode());
$font = new Type0Font($descendant, $unicode, encoding: $encoding);
$resources = new ResourceDictionary(new Font(new DictionaryType(new Entry(new NameType('F1'), new IndirectReference(new Type1Font(StandardFont::Helvetica))), new Entry(new NameType('F2'), new IndirectReference($font)))));
// Each displayed code remains an object. Concatenation is checked against
// the CMap codespace; no implicit UTF-8 -> code -> CID conversion occurs here.
$characters = new CMapCodeSequence($encoding->codeSpaces(), $code('A'), $code('B'), $code('C'), $code('D'), $code('E'), $code('F'), $code(' '), $code("\xa4"), $code("\xa5"), $code(' '), $code("\xa6"));
$contents = new ContentStreams(new BeginTextObject(), new SetTextFontAndSize(new NameType('F1'), new NumericType(24)), new MoveTextPosition(new NumericType(54), new NumericType(738)), new ShowText(new LiteralString('PDFToolkit - CMaps explicites')), new EndTextObject());
foreach ([['Un encodage incorpore avec des codes sur un octet.', 684], ['Le second CMap herite du premier par UseCMap.', 658], ['CID : selection des glyphes dans la police incorporee.', 612], ['ToUnicode : extraction du sens de chaque code.', 586], ['Resultat ci-dessous : six lettres, accent, grec, ligature.', 540]] as [$text, $y]) {
$contents = $contents->with(new BeginTextObject(), new SetTextFontAndSize(new NameType('F1'), new NumericType(13)), new MoveTextPosition(new NumericType(54), new NumericType($y)), new ShowText(new LiteralString($text)), new EndTextObject());
}
$contents = $contents->with(new BeginTextObject(), new SetTextFontAndSize(new NameType('F2'), new NumericType(34)), new MoveTextPosition(new NumericType(54), new NumericType(466)), new ShowText($characters->string()), new EndTextObject());
foreach ([['A4 = e accentue ; A5 = Omega ; A6 = ligature fi.', 402], ['Les trois destinations viennent d\'un bfrange avec tableau.', 376], ['Le bfrange de A..F incremente seulement le dernier octet.', 350], ['La police et le repertoire restent des objets PDF independants.', 292], ['Source : ISO 32000-1, pages PDF 285-289 et 301-303.', 266]] as [$text, $y]) {
$contents = $contents->with(new BeginTextObject(), new SetTextFontAndSize(new NameType('F1'), new NumericType(12)), new MoveTextPosition(new NumericType(54), new NumericType($y)), new ShowText(new LiteralString($text)), new EndTextObject());
}
$page = new PageObject(new Rectangle(new NumericType(0), new NumericType(0), new NumericType(612), new NumericType(792)), $resources, new StreamObject($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__ . '/cmap-font.pdf';
$file->writeTo($path);
echo 'PDF written to ' . $path . PHP_EOL;
