* @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\File\Dir; use Com\Tecnick\Pdf\Font\Exception as FontException; /** * Com\Tecnick\Pdf\Font\Load * * @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-type TFontDataCidInfo array{ * 'Ordering': string, * 'Registry': string, * 'Supplement': int, * 'uni2cid': array, * } * * @phpstan-type TFontDataDesc array{ * 'Ascent': int, * 'AvgWidth': int, * 'CapHeight': int, * 'Descent': int, * 'Flags': int, * 'FontBBox': string, * 'ItalicAngle': int, * 'Leading': int, * 'MaxWidth': int, * 'MissingWidth': int, * 'StemH': int, * 'StemV': int, * 'XHeight': int, * } * * @phpstan-type TFontDataEncTable array{ * 'encodingID': int, * 'offset': int, * 'platformID': int, * } * * @phpstan-type TFontDataMode array{ * 'bold': bool, * 'italic': bool, * 'linethrough': bool, * 'overline': bool, * 'underline': bool, * } * * @phpstan-type TFontDataTableItem array{ * 'checkSum': int, * 'data': string, * 'length': int, * 'offset': int, * } * * @phpstan-type TFontData array{ * 'Ascender': int, * 'Ascent': int, * 'AvgWidth': float, * 'CapHeight': int, * 'CharacterSet': string, * 'Descender': int, * 'Descent': int, * 'EncodingScheme': string, * 'FamilyName': string, * 'Flags': int, * 'FontBBox': array, * 'FontName': string, * 'FullName': string, * 'IsFixedPitch': bool, * 'ItalicAngle': int, * 'Leading': int, * 'MaxWidth': int, * 'MissingWidth': int, * 'StdHW': int, * 'StdVW': int, * 'StemH': int, * 'StemV': int, * 'UnderlinePosition': int, * 'UnderlineThickness': int, * 'Version': string, * 'Weight': string, * 'XHeight': int, * 'bbox': string, * 'cbbox': array>, * 'cidinfo': TFontDataCidInfo, * 'compress': bool, * 'ctg': string, * 'ctgdata': array, * 'cw': array, * 'datafile': string, * 'desc': TFontDataDesc, * 'diff': string, * 'diff_n': int, * 'dir': string, * 'dw': int, * 'enc': string, * 'enc_map': array, * 'encodingTables': array, * 'encoding_id': int, * 'encrypted': string, * 'fakestyle': bool, * 'family': string, * 'file': string, * 'file_n': int, * 'file_name': string, * 'i': int, * 'ifile': string, * 'indexToLoc': array, * 'input_file': string, * 'isUnicode': bool, * 'italicAngle': float, * 'key': string, * 'lenIV': int, * 'length1': int, * 'length2': int, * 'linked': bool, * 'mode': TFontDataMode, * 'n': int, * 'name': string, * 'numGlyphs': int, * 'numHMetrics': int, * 'originalsize': int, * 'pdfa': bool, * 'platform_id': int, * 'settype': string, * 'short_offset': bool, * 'size1': int, * 'size2': int, * 'style': string, * 'subset': bool, * 'subsetchars': array, * 'table': array, * 'tot_num_glyphs': int, * 'type': string, * 'underlinePosition': int, * 'underlineThickness': int, * 'unicode': bool, * 'unitsPerEm': int, * 'up': int, * 'urk': float, * 'ut': int, * 'weight': string, * } */ abstract class Load { /** * Valid Font types * * @var array Font types */ protected const FONTTYPES = [ 'Core' => true, 'TrueType' => true, 'TrueTypeUnicode' => true, 'Type1' => true, 'cidfont0' => true, ]; /** * Font data * * @var TFontData */ protected array $data = [ 'Ascender' => 0, 'Ascent' => 0, 'AvgWidth' => 0.0, 'CapHeight' => 0, 'CharacterSet' => '', 'Descender' => 0, 'Descent' => 0, 'EncodingScheme' => '', 'FamilyName' => '', 'Flags' => 0, 'FontBBox' => [], 'FontName' => '', 'FullName' => '', 'IsFixedPitch' => false, 'ItalicAngle' => 0, 'Leading' => 0, 'MaxWidth' => 0, 'MissingWidth' => 0, 'StdHW' => 0, 'StdVW' => 0, 'StemH' => 0, 'StemV' => 0, 'UnderlinePosition' => 0, 'UnderlineThickness' => 0, 'Version' => '', 'Weight' => '', 'XHeight' => 0, 'bbox' => '', 'cbbox' => [], 'cidinfo' => [ 'Ordering' => '', 'Registry' => '', 'Supplement' => 0, 'uni2cid' => [], ], 'compress' => false, 'ctg' => '', 'ctgdata' => [], 'cw' => [], 'datafile' => '', 'desc' => [ 'Ascent' => 0, 'AvgWidth' => 0, 'CapHeight' => 0, 'Descent' => 0, 'Flags' => 0, 'FontBBox' => '', 'ItalicAngle' => 0, 'Leading' => 0, 'MaxWidth' => 0, 'MissingWidth' => 0, 'StemH' => 0, 'StemV' => 0, 'XHeight' => 0, ], 'diff' => '', 'diff_n' => 0, 'dir' => '', 'dw' => 0, 'enc' => '', 'enc_map' => [], 'encodingTables' => [], 'encoding_id' => 0, 'encrypted' => '', 'fakestyle' => false, 'family' => '', 'file' => '', 'file_n' => 0, 'file_name' => '', 'i' => 0, 'ifile' => '', 'indexToLoc' => [], 'input_file' => '', 'isUnicode' => false, 'italicAngle' => 0, 'key' => '', 'lenIV' => 0, 'length1' => 0, 'length2' => 0, 'linked' => false, 'mode' => [ 'bold' => false, 'italic' => false, 'linethrough' => false, 'overline' => false, 'underline' => false, ], 'n' => 0, 'name' => '', 'numGlyphs' => 0, 'numHMetrics' => 0, 'originalsize' => 0, 'pdfa' => false, 'platform_id' => 0, 'settype' => '', 'short_offset' => false, 'size1' => 0, 'size2' => 0, 'style' => '', 'subset' => false, 'subsetchars' => [], 'table' => [], 'tot_num_glyphs' => 0, 'type' => '', 'underlinePosition' => 0, 'underlineThickness' => 0, 'unicode' => false, 'unitsPerEm' => 0, 'up' => 0, 'urk' => 0.0, 'ut' => 0, 'weight' => '', ]; /** * Load the font data * * @throws FontException in case of error */ public function load(): void { $this->getFontInfo(); $this->checkType(); $this->setName(); $this->setDefaultWidth(); if ($this->data['fakestyle']) { $this->setArtificialStyles(); } $this->setFileData(); } /** * Load the font data * * @throws FontException in case of error * * @SuppressWarnings("PHPMD.CyclomaticComplexity") */ protected function getFontInfo(): void { $this->findFontFile(); // read the font definition file if (! @is_readable($this->data['ifile'])) { throw new FontException('unable to read file: ' . $this->data['ifile']); } $fdt = @file_get_contents($this->data['ifile']); if ($fdt === false) { throw new FontException('unable to read file: ' . $this->data['ifile']); } $fdtdata = @json_decode($fdt, true, 5, JSON_OBJECT_AS_ARRAY); if ($fdtdata === null) { throw new FontException('JSON decoding error [' . json_last_error() . ']'); } if (! is_array($fdtdata) || (! isset($fdtdata['type']))) { throw new FontException('fhe font definition file has a bad format: ' . $this->data['ifile']); } $this->data = array_replace_recursive($this->data, $fdtdata); } /** * Returns a list of font directories * * @return array Font directories */ protected function findFontDirectories(): array { $dir = new Dir(); $dirs = ['']; if (defined('K_PATH_FONTS')) { $dirs[] = K_PATH_FONTS; $glb = glob(K_PATH_FONTS . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); if ($glb !== false) { $dirs = [...$dirs, ...$glb]; } } $parent_font_dir = $dir->findParentDir('fonts', __DIR__); if (($parent_font_dir !== '') && ($parent_font_dir !== '/')) { $dirs[] = $parent_font_dir; $glb = glob($parent_font_dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR); if ($glb !== false) { $dirs = array_merge($dirs, $glb); } } return array_unique($dirs); } /** * Load the font data * * @throws FontException in case of error */ protected function findFontFile(): void { if (! empty($this->data['ifile'])) { $this->data['dir'] = dirname($this->data['ifile']); return; } $this->data['ifile'] = strtolower($this->data['key']) . '.json'; // find font definition file names $files = array_unique( [strtolower($this->data['key']) . '.json', strtolower($this->data['family']) . '.json'] ); // directories where to search for the font definition file $dirs = $this->findFontDirectories(); foreach ($files as $file) { foreach ($dirs as $dir) { if (@is_readable($dir . DIRECTORY_SEPARATOR . $file)) { $this->data['ifile'] = $dir . DIRECTORY_SEPARATOR . $file; $this->data['dir'] = $dir; break 2; } } // we haven't found the version with style variations $this->data['fakestyle'] = true; } } protected function setDefaultWidth(): void { if (! empty($this->data['dw'])) { return; } if ($this->data['desc']['MissingWidth'] > 0) { $this->data['dw'] = $this->data['desc']['MissingWidth']; } elseif (! empty($this->data['cw'][32])) { $this->data['dw'] = $this->data['cw'][32]; } else { $this->data['dw'] = 600; } } /** * Check Font Type */ protected function checkType(): void { if (isset(self::FONTTYPES[$this->data['type']])) { return; } throw new FontException('Unknow font type: ' . $this->data['type']); } protected function setName(): void { if ($this->data['type'] == 'Core') { $this->data['name'] = (string) Core::FONT[$this->data['key']]; $this->data['subset'] = false; } elseif (($this->data['type'] == 'Type1') || ($this->data['type'] == 'TrueType')) { $this->data['subset'] = false; } elseif ($this->data['type'] == 'TrueTypeUnicode') { $this->data['enc'] = 'Identity-H'; } elseif (($this->data['type'] == 'cidfont0') && ($this->data['pdfa'])) { throw new FontException('CID0 fonts are not supported, all fonts must be embedded in PDF/A mode!'); } if (empty($this->data['name'])) { $this->data['name'] = (string) $this->data['key']; } } /** * Set artificial styles if the font variation file is missing */ protected function setArtificialStyles(): void { // artificial bold if ($this->data['mode']['bold']) { $this->data['name'] .= 'Bold'; $this->data['desc']['StemV'] = empty($this->data['desc']['StemV']) ? 123 : (int) round($this->data['desc']['StemV'] * 1.75); } // artificial italic if ($this->data['mode']['italic']) { $this->data['name'] .= 'Italic'; if (! empty($this->data['desc']['ItalicAngle'])) { $this->data['desc']['ItalicAngle'] -= 11; } else { $this->data['desc']['ItalicAngle'] = -11; } if (! empty($this->data['desc']['Flags'])) { $this->data['desc']['Flags'] |= 64; //bit 7 } else { $this->data['desc']['Flags'] = 64; } } } public function setFileData(): void { if (empty($this->data['file'])) { return; } if (str_contains($this->data['type'], 'TrueType')) { $this->data['length1'] = $this->data['originalsize']; $this->data['length2'] = 0; } elseif ($this->data['type'] != 'Core') { $this->data['length1'] = $this->data['size1']; $this->data['length2'] = $this->data['size2']; } } }