* @copyright 2011-2024 Nicola Asuni - Tecnick.com LTD * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * @link https://github.com/tecnickcom/tc-lib-pdf-font * * This file is part of tc-lib-pdf-font software library. */ namespace Com\Tecnick\Pdf\Font; use Com\Tecnick\Pdf\Encrypt\Encrypt; use Com\Tecnick\Pdf\Font\Exception as FontException; use Com\Tecnick\Unicode\Data\Identity; /** * Com\Tecnick\Pdf\Font\OutFont * * @since 2011-05-23 * @category Library * @package PdfFont * @author Nicola Asuni * @copyright 2011-2024 Nicola Asuni - Tecnick.com LTD * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) * @link https://github.com/tecnickcom/tc-lib-pdf-font * * @phpstan-import-type TFontDataCidInfo from Load * @phpstan-import-type TFontDataDesc from Load */ abstract class OutFont extends \Com\Tecnick\Pdf\Font\OutUtil { /** * Current PDF object number */ protected int $pon; /** * Encrypt object */ protected Encrypt $enc; /** * Get the PDF output string for a CID-0 font. * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format * * @param array{ * 'cidinfo': TFontDataCidInfo, * 'cw': array, * 'desc': TFontDataDesc, * 'dw': int, * 'enc': string, * 'i': int, * 'n': int, * 'name': string, * 'subset': bool, * 'subsetchars': array, * } $font Font to process * * return string */ protected function getCid0(array $font): string { $cidoffset = 0; if (! isset($font['cw'][1])) { $cidoffset = 31; } $this->uniToCid($font, $cidoffset); $name = $font['name']; $longname = $name; if (! empty($font['enc'])) { $longname .= '-' . $font['enc']; } // obj 1 $out = $font['n'] . ' 0 obj' . "\n" . '<pon + 1) . ' 0 R]' . ' >>' . "\n" . 'endobj' . "\n"; // obj 2 $out .= (++$this->pon) . ' 0 obj' . "\n" . '<enc->escapeDataString($font['cidinfo']['Registry'], $this->pon) . ' /Ordering ' . $this->enc->escapeDataString($font['cidinfo']['Ordering'], $this->pon) . ' /Supplement ' . $font['cidinfo']['Supplement']; $out .= ' /CIDSystemInfo <<' . $cidinfo . '>>' . ' /FontDescriptor ' . ($this->pon + 1) . ' 0 R' . ' /DW ' . $font['dw'] . "\n" . $this->getCharWidths($font, $cidoffset) . ' >>' . "\n" . 'endobj' . "\n"; // obj 3 $out .= (++$this->pon) . ' 0 obj' . "\n" . '< $val) { if ($key !== 'Style') { // @phpstan-ignore-line $out .= $this->getKeyValOut($key, $val); } } return $out . ('>>' . "\n" . 'endobj' . "\n"); } /** * Convert Unicode to CID * * @param array{ * 'cidinfo': TFontDataCidInfo, * 'cw': array, * 'desc': TFontDataDesc, * 'dw': int, * 'enc': string, * 'i': int, * 'n': int, * 'name': string, * 'subset': bool, * 'subsetchars': array, * } $font Font to process * @param int $cidoffset Offset for CID values */ protected function uniToCid(array &$font, int $cidoffset): void { // convert unicode to cid. $uni2cid = $font['cidinfo']['uni2cid']; $chw = []; foreach ($font['cw'] as $uni => $width) { if (isset($uni2cid[$uni])) { $chw[($uni2cid[$uni] + $cidoffset)] = $width; } elseif ($uni < 256) { $chw[$uni] = $width; } // else unknown character } $font['cw'] = array_merge($font['cw'], $chw); } /** * Get the PDF output string for a TrueTypeUnicode font. * Based on PDF Reference 1.3 (section 5) * * @param array{ * 'cidinfo': TFontDataCidInfo, * 'compress': bool, * 'ctg': string, * 'cw': array, * 'desc': TFontDataDesc, * 'dir': string, * 'dw': int, * 'enc': string, * 'file_n': int, * 'i': int, * 'n': int, * 'name': string, * 'subset': bool, * 'subsetchars': array, * } $font Font to process * * return string * * @SuppressWarnings("PHPMD.ExcessiveMethodLength") * @SuppressWarnings("PHPMD.CyclomaticComplexity") * @SuppressWarnings("PHPMD.NPathComplexity") */ protected function getTrueTypeUnicode(array $font): string { $fontname = ''; if ($font['subset']) { // change name for font subsetting $subtag = sprintf('%06u', $font['i']); $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ'); $fontname .= $subtag . '+'; } $fontname .= $font['name']; // Type0 Font // A composite font composed of other fonts, organized hierarchically // obj 1 $out = $font['n'] . ' 0 obj' . "\n" . '<< /Type /Font' . ' /Subtype /Type0' . ' /BaseFont /' . $fontname . ' /Name /F' . $font['i'] . ' /Encoding /' . $font['enc'] . ' /ToUnicode ' . ($this->pon + 1) . ' 0 R' . ' /DescendantFonts [' . ($this->pon + 2) . ' 0 R]' . ' >>' . "\n" . 'endobj' . "\n"; // ToUnicode Object $out .= (++$this->pon) . ' 0 obj' . "\n" . '<<'; $cidhmap = Identity::CIDHMAP; if ($font['compress']) { $out .= ' /Filter /FlateDecode'; $cidhmap = gzcompress($cidhmap); if ($cidhmap === false) { throw new \RuntimeException('Unable to compress CIDHMAP'); } } $stream = $this->enc->encryptString($cidhmap, $this->pon); // ToUnicode map for Identity-H $out .= ' /Length ' . strlen($stream) . ' >>' . ' stream' . "\n" . $stream . "\n" . 'endstream' . "\n" . 'endobj' . "\n"; // CIDFontType2 // A CIDFont whose glyph descriptions are based on TrueType font technology $out .= (++$this->pon) . ' 0 obj' . "\n" . '<< /Type /Font' . ' /Subtype /CIDFontType2' . ' /BaseFont /' . $fontname; // A dictionary containing entries that define the character collection of the CIDFont. $cidinfo = '/Registry ' . $this->enc->escapeDataString($font['cidinfo']['Registry'], $this->pon) . ' /Ordering ' . $this->enc->escapeDataString($font['cidinfo']['Ordering'], $this->pon) . ' /Supplement ' . $font['cidinfo']['Supplement']; $out .= ' /CIDSystemInfo << ' . $cidinfo . ' >>' . ' /FontDescriptor ' . ($this->pon + 1) . ' 0 R' . ' /DW ' . $font['dw'] . "\n" . $this->getCharWidths($font, 0); if (! empty($font['ctg'])) { $out .= "\n" . '/CIDToGIDMap ' . ($this->pon + 2) . ' 0 R'; } $out .= ' >>' . "\n" . 'endobj' . "\n"; // Font descriptor // A font descriptor describing the CIDFont default metrics other than its glyph widths $out .= (++$this->pon) . ' 0 obj' . "\n" . '<< /Type /FontDescriptor' . ' /FontName /' . $fontname; foreach ($font['desc'] as $key => $val) { $out .= $this->getKeyValOut($key, $val); } if (! empty($font['file_n'])) { // A stream containing a TrueType font $out .= ' /FontFile2 ' . $font['file_n'] . ' 0 R'; } $out .= ' >>' . "\n" . 'endobj' . "\n"; if (! empty($font['ctg'])) { $out .= (++$this->pon) . ' 0 obj' . "\n"; // Embed CIDToGIDMap // A specification of the mapping from CIDs to glyph indices // search and get CTG font file to embedd $ctgfile = strtolower($font['ctg']); // search and get ctg font file to embedd $fontfile = $this->getFontFullPath($font['dir'], $ctgfile); $content = file_get_contents($fontfile); if ($content === false) { throw new FontException('Unable to read font file: ' . $fontfile); } $stream = $this->enc->encryptString($content, $this->pon); $out .= '<< /Length ' . strlen($stream) . ''; if (str_ends_with($fontfile, '.z')) { // check file extension // Decompresses data encoded using the public-domain // zlib/deflate compression method, reproducing the // original text or binary data $out .= ' /Filter /FlateDecode'; } $out .= ' >> stream' . "\n" . $stream . "\n" . 'endstream' . "\n" . 'endobj' . "\n"; } return $out; } /** * Get the PDF output string for a Core font. * * @param array{ * 'family': string, * 'i': int, * 'n': int, * 'name': string, * } $font Font to process * * return string */ protected function getCore(array $font): string { $out = $font['n'] . ' 0 obj' . "\n" . '<>' . "\n" . 'endobj' . "\n"); } /** * Get the PDF output string for a Core font. * * @param array{ * 'cw': array, * 'desc': TFontDataDesc, * 'diff_n': int, * 'dw': int, * 'enc': string, * 'file': string, * 'file_n': int, * 'i': int, * 'n': int, * 'name': string, * 'type': string, * } $font Font to process * * return string */ protected function getTrueType(array $font): string { // obj 1 $out = $font['n'] . ' 0 obj' . "\n" . '<pon + 1) . ' 0 R' . ' /FontDescriptor ' . ($this->pon + 2) . ' 0 R'; if (! empty($font['enc'])) { if (isset($font['diff_n']) && $font['diff_n'] !== 0) { $out .= ' /Encoding ' . $font['diff_n'] . ' 0 R'; } else { $out .= ' /Encoding /WinAnsiEncoding'; } } $out .= ' >>' . "\n" . 'endobj' . "\n"; // obj 2 - Widths $out .= (++$this->pon) . ' 0 obj' . "\n" . '['; for ($idx = 32; $idx < 256; ++$idx) { if (isset($font['cw'][$idx])) { $out .= $font['cw'][$idx] . ' '; } else { $out .= $font['dw'] . ' '; } } $out .= ']' . "\n" . 'endobj' . "\n"; // obj 3 - Descriptor $out .= (++$this->pon) . ' 0 obj' . "\n" . '< $fdv) { $out .= $this->getKeyValOut($fdk, $fdv); } if (! empty($font['file'])) { $out .= ' /FontFile' . ($font['type'] == 'Type1' ? '' : '2') . ' ' . $font['file_n'] . ' 0 R'; } return $out . ('>>' . "\n" . 'endobj' . "\n"); } /** * Returns the formatted key/value PDF string * * @param string $key Key name * @param mixed $val Value */ protected function getKeyValOut(string $key, mixed $val): string { if (is_float($val)) { $val = sprintf('%F', $val); } return ' /' . $key . ' ' . $val . ''; } }