generated from jric11/baseProject
Initial commit
This commit is contained in:
@@ -0,0 +1,622 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Core.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @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\Import;
|
||||
|
||||
use Com\Tecnick\File\File as ObjFile;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\Unicode\Data\Encoding;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Import\Core
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-font
|
||||
*
|
||||
* @phpstan-import-type TFontData from \Com\Tecnick\Pdf\Font\Load
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
/**
|
||||
* Adobe Glyph List subset covering all Core14 AFM glyph names.
|
||||
* Maps AFM glyph names to Unicode codepoints.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private const GLYPH_UNICODE = [
|
||||
'A' => 0x0041,
|
||||
'AE' => 0x00C6,
|
||||
'Aacute' => 0x00C1,
|
||||
'Abreve' => 0x0102,
|
||||
'Acircumflex' => 0x00C2,
|
||||
'Adieresis' => 0x00C4,
|
||||
'Agrave' => 0x00C0,
|
||||
'Amacron' => 0x0100,
|
||||
'Aogonek' => 0x0104,
|
||||
'Aring' => 0x00C5,
|
||||
'Atilde' => 0x00C3,
|
||||
'B' => 0x0042,
|
||||
'C' => 0x0043,
|
||||
'Cacute' => 0x0106,
|
||||
'Ccaron' => 0x010C,
|
||||
'Ccedilla' => 0x00C7,
|
||||
'D' => 0x0044,
|
||||
'Dcaron' => 0x010E,
|
||||
'Dcroat' => 0x0110,
|
||||
'Delta' => 0x2206,
|
||||
'E' => 0x0045,
|
||||
'Eacute' => 0x00C9,
|
||||
'Ecaron' => 0x011A,
|
||||
'Ecircumflex' => 0x00CA,
|
||||
'Edieresis' => 0x00CB,
|
||||
'Edotaccent' => 0x0116,
|
||||
'Egrave' => 0x00C8,
|
||||
'Emacron' => 0x0112,
|
||||
'Eogonek' => 0x0118,
|
||||
'Eth' => 0x00D0,
|
||||
'Euro' => 0x20AC,
|
||||
'F' => 0x0046,
|
||||
'G' => 0x0047,
|
||||
'Gbreve' => 0x011E,
|
||||
'Gcommaaccent' => 0x0122,
|
||||
'H' => 0x0048,
|
||||
'I' => 0x0049,
|
||||
'Iacute' => 0x00CD,
|
||||
'Icircumflex' => 0x00CE,
|
||||
'Idieresis' => 0x00CF,
|
||||
'Idotaccent' => 0x0130,
|
||||
'Igrave' => 0x00CC,
|
||||
'Imacron' => 0x012A,
|
||||
'Iogonek' => 0x012E,
|
||||
'J' => 0x004A,
|
||||
'K' => 0x004B,
|
||||
'Kcommaaccent' => 0x0136,
|
||||
'L' => 0x004C,
|
||||
'Lacute' => 0x0139,
|
||||
'Lcaron' => 0x013D,
|
||||
'Lcommaaccent' => 0x013B,
|
||||
'Lslash' => 0x0141,
|
||||
'M' => 0x004D,
|
||||
'N' => 0x004E,
|
||||
'Nacute' => 0x0143,
|
||||
'Ncaron' => 0x0147,
|
||||
'Ncommaaccent' => 0x0145,
|
||||
'Ntilde' => 0x00D1,
|
||||
'O' => 0x004F,
|
||||
'OE' => 0x0152,
|
||||
'Oacute' => 0x00D3,
|
||||
'Ocircumflex' => 0x00D4,
|
||||
'Odieresis' => 0x00D6,
|
||||
'Ograve' => 0x00D2,
|
||||
'Ohungarumlaut' => 0x0150,
|
||||
'Omacron' => 0x014C,
|
||||
'Oslash' => 0x00D8,
|
||||
'Otilde' => 0x00D5,
|
||||
'P' => 0x0050,
|
||||
'Q' => 0x0051,
|
||||
'R' => 0x0052,
|
||||
'Racute' => 0x0154,
|
||||
'Rcaron' => 0x0158,
|
||||
'Rcommaaccent' => 0x0156,
|
||||
'S' => 0x0053,
|
||||
'Sacute' => 0x015A,
|
||||
'Scaron' => 0x0160,
|
||||
'Scedilla' => 0x015E,
|
||||
'Scommaaccent' => 0x0218,
|
||||
'T' => 0x0054,
|
||||
'Tcaron' => 0x0164,
|
||||
'Tcommaaccent' => 0x0162,
|
||||
'Thorn' => 0x00DE,
|
||||
'U' => 0x0055,
|
||||
'Uacute' => 0x00DA,
|
||||
'Ucircumflex' => 0x00DB,
|
||||
'Udieresis' => 0x00DC,
|
||||
'Ugrave' => 0x00D9,
|
||||
'Uhungarumlaut' => 0x0170,
|
||||
'Umacron' => 0x016A,
|
||||
'Uogonek' => 0x0172,
|
||||
'Uring' => 0x016E,
|
||||
'V' => 0x0056,
|
||||
'W' => 0x0057,
|
||||
'X' => 0x0058,
|
||||
'Y' => 0x0059,
|
||||
'Yacute' => 0x00DD,
|
||||
'Ydieresis' => 0x0178,
|
||||
'Z' => 0x005A,
|
||||
'Zacute' => 0x0179,
|
||||
'Zcaron' => 0x017D,
|
||||
'Zdotaccent' => 0x017B,
|
||||
'a' => 0x0061,
|
||||
'aacute' => 0x00E1,
|
||||
'abreve' => 0x0103,
|
||||
'acircumflex' => 0x00E2,
|
||||
'acute' => 0x00B4,
|
||||
'adieresis' => 0x00E4,
|
||||
'ae' => 0x00E6,
|
||||
'agrave' => 0x00E0,
|
||||
'amacron' => 0x0101,
|
||||
'ampersand' => 0x0026,
|
||||
'aogonek' => 0x0105,
|
||||
'aring' => 0x00E5,
|
||||
'asciicircum' => 0x005E,
|
||||
'asciitilde' => 0x007E,
|
||||
'asterisk' => 0x002A,
|
||||
'at' => 0x0040,
|
||||
'atilde' => 0x00E3,
|
||||
'b' => 0x0062,
|
||||
'backslash' => 0x005C,
|
||||
'bar' => 0x007C,
|
||||
'braceleft' => 0x007B,
|
||||
'braceright' => 0x007D,
|
||||
'bracketleft' => 0x005B,
|
||||
'bracketright' => 0x005D,
|
||||
'breve' => 0x02D8,
|
||||
'brokenbar' => 0x00A6,
|
||||
'bullet' => 0x2022,
|
||||
'c' => 0x0063,
|
||||
'cacute' => 0x0107,
|
||||
'caron' => 0x02C7,
|
||||
'ccaron' => 0x010D,
|
||||
'ccedilla' => 0x00E7,
|
||||
'cedilla' => 0x00B8,
|
||||
'cent' => 0x00A2,
|
||||
'circumflex' => 0x02C6,
|
||||
'colon' => 0x003A,
|
||||
'comma' => 0x002C,
|
||||
'commaaccent' => 0x0326,
|
||||
'copyright' => 0x00A9,
|
||||
'currency' => 0x00A4,
|
||||
'd' => 0x0064,
|
||||
'dagger' => 0x2020,
|
||||
'daggerdbl' => 0x2021,
|
||||
'dcaron' => 0x010F,
|
||||
'dcroat' => 0x0111,
|
||||
'degree' => 0x00B0,
|
||||
'dieresis' => 0x00A8,
|
||||
'divide' => 0x00F7,
|
||||
'dollar' => 0x0024,
|
||||
'dotaccent' => 0x02D9,
|
||||
'dotlessi' => 0x0131,
|
||||
'e' => 0x0065,
|
||||
'eacute' => 0x00E9,
|
||||
'ecaron' => 0x011B,
|
||||
'ecircumflex' => 0x00EA,
|
||||
'edieresis' => 0x00EB,
|
||||
'edotaccent' => 0x0117,
|
||||
'egrave' => 0x00E8,
|
||||
'eight' => 0x0038,
|
||||
'ellipsis' => 0x2026,
|
||||
'emacron' => 0x0113,
|
||||
'emdash' => 0x2014,
|
||||
'endash' => 0x2013,
|
||||
'eogonek' => 0x0119,
|
||||
'equal' => 0x003D,
|
||||
'eth' => 0x00F0,
|
||||
'exclam' => 0x0021,
|
||||
'exclamdown' => 0x00A1,
|
||||
'f' => 0x0066,
|
||||
'fi' => 0xFB01,
|
||||
'five' => 0x0035,
|
||||
'fl' => 0xFB02,
|
||||
'florin' => 0x0192,
|
||||
'four' => 0x0034,
|
||||
'fraction' => 0x2044,
|
||||
'g' => 0x0067,
|
||||
'gbreve' => 0x011F,
|
||||
'gcommaaccent' => 0x0123,
|
||||
'germandbls' => 0x00DF,
|
||||
'grave' => 0x0060,
|
||||
'greater' => 0x003E,
|
||||
'greaterequal' => 0x2265,
|
||||
'guillemotleft' => 0x00AB,
|
||||
'guillemotright' => 0x00BB,
|
||||
'guilsinglleft' => 0x2039,
|
||||
'guilsinglright' => 0x203A,
|
||||
'h' => 0x0068,
|
||||
'hungarumlaut' => 0x02DD,
|
||||
'hyphen' => 0x002D,
|
||||
'i' => 0x0069,
|
||||
'iacute' => 0x00ED,
|
||||
'icircumflex' => 0x00EE,
|
||||
'idieresis' => 0x00EF,
|
||||
'igrave' => 0x00EC,
|
||||
'imacron' => 0x012B,
|
||||
'iogonek' => 0x012F,
|
||||
'j' => 0x006A,
|
||||
'k' => 0x006B,
|
||||
'kcommaaccent' => 0x0137,
|
||||
'l' => 0x006C,
|
||||
'lacute' => 0x013A,
|
||||
'lcaron' => 0x013E,
|
||||
'lcommaaccent' => 0x013C,
|
||||
'less' => 0x003C,
|
||||
'lessequal' => 0x2264,
|
||||
'logicalnot' => 0x00AC,
|
||||
'lozenge' => 0x25CA,
|
||||
'lslash' => 0x0142,
|
||||
'm' => 0x006D,
|
||||
'macron' => 0x00AF,
|
||||
'minus' => 0x2212,
|
||||
'mu' => 0x00B5,
|
||||
'multiply' => 0x00D7,
|
||||
'n' => 0x006E,
|
||||
'nacute' => 0x0144,
|
||||
'ncaron' => 0x0148,
|
||||
'ncommaaccent' => 0x0146,
|
||||
'nine' => 0x0039,
|
||||
'notequal' => 0x2260,
|
||||
'ntilde' => 0x00F1,
|
||||
'numbersign' => 0x0023,
|
||||
'o' => 0x006F,
|
||||
'oacute' => 0x00F3,
|
||||
'ocircumflex' => 0x00F4,
|
||||
'odieresis' => 0x00F6,
|
||||
'oe' => 0x0153,
|
||||
'ogonek' => 0x02DB,
|
||||
'ograve' => 0x00F2,
|
||||
'ohungarumlaut' => 0x0151,
|
||||
'omacron' => 0x014D,
|
||||
'one' => 0x0031,
|
||||
'onehalf' => 0x00BD,
|
||||
'onequarter' => 0x00BC,
|
||||
'onesuperior' => 0x00B9,
|
||||
'ordfeminine' => 0x00AA,
|
||||
'ordmasculine' => 0x00BA,
|
||||
'oslash' => 0x00F8,
|
||||
'otilde' => 0x00F5,
|
||||
'p' => 0x0070,
|
||||
'paragraph' => 0x00B6,
|
||||
'parenleft' => 0x0028,
|
||||
'parenright' => 0x0029,
|
||||
'partialdiff' => 0x2202,
|
||||
'percent' => 0x0025,
|
||||
'period' => 0x002E,
|
||||
'periodcentered' => 0x00B7,
|
||||
'perthousand' => 0x2030,
|
||||
'plus' => 0x002B,
|
||||
'plusminus' => 0x00B1,
|
||||
'q' => 0x0071,
|
||||
'question' => 0x003F,
|
||||
'questiondown' => 0x00BF,
|
||||
'quotedbl' => 0x0022,
|
||||
'quotedblbase' => 0x201E,
|
||||
'quotedblleft' => 0x201C,
|
||||
'quotedblright' => 0x201D,
|
||||
'quoteleft' => 0x2018,
|
||||
'quoteright' => 0x2019,
|
||||
'quotesinglbase' => 0x201A,
|
||||
'quotesingle' => 0x0027,
|
||||
'r' => 0x0072,
|
||||
'racute' => 0x0155,
|
||||
'radical' => 0x221A,
|
||||
'rcaron' => 0x0159,
|
||||
'rcommaaccent' => 0x0157,
|
||||
'registered' => 0x00AE,
|
||||
'ring' => 0x02DA,
|
||||
's' => 0x0073,
|
||||
'sacute' => 0x015B,
|
||||
'scaron' => 0x0161,
|
||||
'scedilla' => 0x015F,
|
||||
'scommaaccent' => 0x0219,
|
||||
'section' => 0x00A7,
|
||||
'semicolon' => 0x003B,
|
||||
'seven' => 0x0037,
|
||||
'six' => 0x0036,
|
||||
'slash' => 0x002F,
|
||||
'space' => 0x0020,
|
||||
'sterling' => 0x00A3,
|
||||
'summation' => 0x2211,
|
||||
't' => 0x0074,
|
||||
'tcaron' => 0x0165,
|
||||
'tcommaaccent' => 0x0163,
|
||||
'thorn' => 0x00FE,
|
||||
'three' => 0x0033,
|
||||
'threequarters' => 0x00BE,
|
||||
'threesuperior' => 0x00B3,
|
||||
'tilde' => 0x02DC,
|
||||
'trademark' => 0x2122,
|
||||
'two' => 0x0032,
|
||||
'twosuperior' => 0x00B2,
|
||||
'u' => 0x0075,
|
||||
'uacute' => 0x00FA,
|
||||
'ucircumflex' => 0x00FB,
|
||||
'udieresis' => 0x00FC,
|
||||
'ugrave' => 0x00F9,
|
||||
'uhungarumlaut' => 0x0171,
|
||||
'umacron' => 0x016B,
|
||||
'underscore' => 0x005F,
|
||||
'uogonek' => 0x0173,
|
||||
'uring' => 0x016F,
|
||||
'v' => 0x0076,
|
||||
'w' => 0x0077,
|
||||
'x' => 0x0078,
|
||||
'y' => 0x0079,
|
||||
'yacute' => 0x00FD,
|
||||
'ydieresis' => 0x00FF,
|
||||
'yen' => 0x00A5,
|
||||
'z' => 0x007A,
|
||||
'zacute' => 0x017A,
|
||||
'zcaron' => 0x017E,
|
||||
'zdotaccent' => 0x017C,
|
||||
'zero' => 0x0030,
|
||||
];
|
||||
|
||||
/**
|
||||
* WinAnsi (cp1252) glyph-name → byte index (inverse of ENCMAP['cp1252']).
|
||||
* Built once on first use.
|
||||
*
|
||||
* @var array<string, int>|null
|
||||
*/
|
||||
private static ?array $winAnsiByName = null;
|
||||
|
||||
/**
|
||||
* Unicode-keyed widths accumulated during AFM parsing.
|
||||
*
|
||||
* @var array<int, int>
|
||||
*/
|
||||
private array $cwu = [];
|
||||
|
||||
/**
|
||||
* File helper used to load font definition files.
|
||||
*/
|
||||
protected ObjFile $fileHelper;
|
||||
|
||||
/**
|
||||
* @param string $font Content of the input font file
|
||||
* @param TFontData $fdt Extracted font metrics
|
||||
* @param ObjFile $fileHelper File helper for font loading.
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $font,
|
||||
protected array $fdt,
|
||||
ObjFile $fileHelper,
|
||||
) {
|
||||
$this->fileHelper = $fileHelper;
|
||||
$this->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the extracted font metrics
|
||||
*
|
||||
* @return TFontData
|
||||
*/
|
||||
public function getFontMetrics(): array
|
||||
{
|
||||
return $this->fdt;
|
||||
}
|
||||
|
||||
protected function setFlags(): void
|
||||
{
|
||||
if ($this->fdt['FontName'] === 'Symbol' || $this->fdt['FontName'] === 'ZapfDingbats') {
|
||||
$this->fdt['Flags'] |= 4;
|
||||
} else {
|
||||
$this->fdt['Flags'] |= 32;
|
||||
}
|
||||
|
||||
if ($this->fdt['IsFixedPitch']) {
|
||||
$this->fdt['Flags'] = (int) $this->fdt['Flags'] | 1;
|
||||
}
|
||||
|
||||
if ((int) $this->fdt['ItalicAngle'] !== 0) {
|
||||
$this->fdt['Flags'] = (int) $this->fdt['Flags'] | 64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Char widths
|
||||
*
|
||||
* @param array<int, int> $cwidths Extracted widths
|
||||
*/
|
||||
protected function setCharWidths(array $cwidths): void
|
||||
{
|
||||
$this->fdt['MissingWidth'] = 600;
|
||||
if (isset($cwidths[32]) && $cwidths[32] !== 0) {
|
||||
$this->fdt['MissingWidth'] = $cwidths[32];
|
||||
}
|
||||
|
||||
$this->fdt['MaxWidth'] = (int) $this->fdt['MissingWidth'];
|
||||
$this->fdt['AvgWidth'] = 0;
|
||||
$this->fdt['cw'] = [];
|
||||
for ($cid = 0; $cid <= 255; ++$cid) {
|
||||
if (isset($cwidths[$cid])) {
|
||||
if ($cwidths[$cid] > $this->fdt['MaxWidth']) {
|
||||
$this->fdt['MaxWidth'] = $cwidths[$cid];
|
||||
}
|
||||
|
||||
$this->fdt['AvgWidth'] += $cwidths[$cid];
|
||||
$this->fdt['cw'][$cid] = $cwidths[$cid];
|
||||
} else {
|
||||
$this->fdt['cw'][$cid] = (int) $this->fdt['MissingWidth'];
|
||||
}
|
||||
}
|
||||
|
||||
$numWidths = \count($cwidths);
|
||||
$this->fdt['AvgWidth'] = $numWidths > 0 ? (int) \round($this->fdt['AvgWidth'] / $numWidths) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build (once) the WinAnsi glyph-name → byte-index reverse map.
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
private static function getWinAnsiByName(): array
|
||||
{
|
||||
if (self::$winAnsiByName === null) {
|
||||
self::$winAnsiByName = [];
|
||||
// Iterate high-to-low so the lowest byte wins for duplicate names
|
||||
// (e.g. 'space' appears at bytes 32 and 160 — we want 32).
|
||||
for ($cid = 255; $cid >= 0; --$cid) {
|
||||
$name = Encoding::MAP['cp1252'][$cid];
|
||||
if ($name !== '.notdef') {
|
||||
self::$winAnsiByName[$name] = $cid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$winAnsiByName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Metrics
|
||||
*/
|
||||
protected function extractMetrics(): void
|
||||
{
|
||||
$cwd = [];
|
||||
$this->cwu = [];
|
||||
$this->fdt['cbbox'] = [];
|
||||
$lines = \explode("\n", \str_replace("\r", '', $this->font));
|
||||
// process each row
|
||||
foreach ($lines as $line) {
|
||||
$col = \explode(' ', \rtrim($line));
|
||||
if (\count($col) > 1) {
|
||||
$this->processMetricRow($col, $cwd);
|
||||
}
|
||||
}
|
||||
|
||||
$this->fdt['Leading'] = 0;
|
||||
$this->fdt['cwu'] = $this->cwu;
|
||||
$this->setCharWidths($cwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Metrics
|
||||
*
|
||||
* @param array<int, string> $col Array containing row elements to process
|
||||
* @param array<int, int> $cwd Array containing cid widths
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
*/
|
||||
protected function processMetricRow(array $col, array &$cwd): void
|
||||
{
|
||||
switch ($col[0]) {
|
||||
case 'IsFixedPitch':
|
||||
$this->fdt['IsFixedPitch'] = $col[1] === 'true';
|
||||
break;
|
||||
case 'FontBBox':
|
||||
$this->fdt['FontBBox'] = [(int) $col[1], (int) $col[2], (int) $col[3], (int) $col[4]];
|
||||
break;
|
||||
case 'C':
|
||||
$name = $col[7] ?? '';
|
||||
if ($name === '' || $name === '.notdef') {
|
||||
break;
|
||||
}
|
||||
|
||||
$width = (int) $col[4];
|
||||
// Map glyph name to the WinAnsi byte actually used in the PDF stream.
|
||||
$winAnsi = self::getWinAnsiByName();
|
||||
$winansiCid = $winAnsi[$name] ?? null;
|
||||
if ($winansiCid !== null) {
|
||||
$cwd[$winansiCid] = $width;
|
||||
if (isset($col[14]) && $col[14] !== '') {
|
||||
$this->fdt['cbbox'][$winansiCid] = [
|
||||
(int) $col[10],
|
||||
(int) $col[11],
|
||||
(int) $col[12],
|
||||
(int) $col[13],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Also store under the Unicode codepoint for Stack::getCharWidth().
|
||||
$unicode = self::GLYPH_UNICODE[$name] ?? null;
|
||||
if ($unicode !== null) {
|
||||
$this->cwu[$unicode] = $width;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'FontName':
|
||||
case 'FullName':
|
||||
case 'FamilyName':
|
||||
case 'Weight':
|
||||
case 'CharacterSet':
|
||||
case 'Version':
|
||||
case 'EncodingScheme':
|
||||
$this->fdt[$col[0]] = $col[1];
|
||||
break;
|
||||
case 'ItalicAngle':
|
||||
case 'UnderlinePosition':
|
||||
case 'UnderlineThickness':
|
||||
case 'CapHeight':
|
||||
case 'XHeight':
|
||||
case 'Ascender':
|
||||
case 'Descender':
|
||||
case 'StdHW':
|
||||
case 'StdVW':
|
||||
$this->fdt[$col[0]] = (int) $col[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map values to the correct key name
|
||||
*
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function remapValues(): void
|
||||
{
|
||||
// rename properties
|
||||
$this->fdt['name'] = $this->fdt['FullName'];
|
||||
$this->fdt['underlinePosition'] = $this->fdt['UnderlinePosition'];
|
||||
$this->fdt['underlineThickness'] = $this->fdt['UnderlineThickness'];
|
||||
$this->fdt['italicAngle'] = $this->fdt['ItalicAngle'];
|
||||
$this->fdt['Ascent'] = $this->fdt['Ascender'];
|
||||
$this->fdt['Descent'] = $this->fdt['Descender'];
|
||||
$this->fdt['StemV'] = $this->fdt['StdVW'];
|
||||
$this->fdt['StemH'] = $this->fdt['StdHW'];
|
||||
|
||||
$name = \preg_replace('/[^a-zA-Z0-9_\-]/', '', $this->fdt['name']);
|
||||
if ($name === null) {
|
||||
throw new FontException('Invalid font name');
|
||||
}
|
||||
|
||||
$this->fdt['name'] = $name;
|
||||
$this->fdt['bbox'] = \implode(' ', $this->fdt['FontBBox']);
|
||||
}
|
||||
|
||||
protected function setMissingValues(): void
|
||||
{
|
||||
$this->fdt['Descender'] = $this->fdt['FontBBox'][1];
|
||||
|
||||
$this->fdt['Ascender'] = $this->fdt['FontBBox'][3];
|
||||
|
||||
if ($this->fdt['CapHeight'] === 0) {
|
||||
$this->fdt['CapHeight'] = $this->fdt['Ascender'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Core font
|
||||
*
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
$this->extractMetrics();
|
||||
$this->setFlags();
|
||||
$this->setMissingValues();
|
||||
$this->remapValues();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* TypeOne.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @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\Import;
|
||||
|
||||
use Com\Tecnick\File\Exception as FileException;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\Unicode\Data\Encoding;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Import\TypeOne
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||||
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||||
* @link https://github.com/tecnickcom/tc-lib-pdf-font
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
class TypeOne extends \Com\Tecnick\Pdf\Font\Import\Core
|
||||
{
|
||||
/**
|
||||
* Store font data
|
||||
*
|
||||
* @throws FileException
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function storeFontData(): void
|
||||
{
|
||||
// read first segment
|
||||
$dat = \unpack('Cmarker/Ctype/Vsize', \substr($this->font, 0, 6));
|
||||
if ($dat === false || $dat['marker'] !== 128) {
|
||||
throw new FontException('Font file is not a valid binary Type1');
|
||||
}
|
||||
|
||||
$this->fdt['size1'] = $dat['size'];
|
||||
$fontlen = \strlen($this->font);
|
||||
// the first segment plus the 6-byte header of the second segment must fit in the file
|
||||
if ((6 + $this->fdt['size1'] + 6) > $fontlen) {
|
||||
throw new FontException('Type1 font segment 1 length exceeds the file size');
|
||||
}
|
||||
|
||||
$data = \substr($this->font, 6, $this->fdt['size1']);
|
||||
// read second segment
|
||||
$dat = \unpack('Cmarker/Ctype/Vsize', \substr($this->font, 6 + $this->fdt['size1'], 6));
|
||||
if ($dat === false || $dat['marker'] !== 128) {
|
||||
throw new FontException('Font file is not a valid binary Type1');
|
||||
}
|
||||
|
||||
$this->fdt['size2'] = $dat['size'];
|
||||
if ((12 + $this->fdt['size1'] + $this->fdt['size2']) > $fontlen) {
|
||||
throw new FontException('Type1 font segment 2 length exceeds the file size');
|
||||
}
|
||||
|
||||
$this->fdt['encrypted'] = \substr($this->font, 12 + $this->fdt['size1'], $this->fdt['size2']);
|
||||
$data .= $this->fdt['encrypted'];
|
||||
// store compressed font
|
||||
$this->fdt['file'] = $this->fdt['file_name'] . '.z';
|
||||
$fpt = $this->fileHelper->fopenLocal($this->fdt['dir'] . $this->fdt['file'], 'wb');
|
||||
|
||||
$cmpr = \gzcompress($data);
|
||||
if ($cmpr === false) {
|
||||
throw new FontException('Unable to compress font data');
|
||||
}
|
||||
|
||||
\fwrite($fpt, $cmpr);
|
||||
\fclose($fpt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Font information
|
||||
*
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function extractFontInfo(): void
|
||||
{
|
||||
$matches = [];
|
||||
if (
|
||||
\preg_match('#/FontName[\s]*+\/([^\s]*+)#', $this->font, $matches) !== 1
|
||||
&& \preg_match('#/FullName[\s]*+\(([^\)]*+)#', $this->font, $matches) !== 1
|
||||
) {
|
||||
throw new FontException('Unable to extract font name');
|
||||
}
|
||||
|
||||
$name = \preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
|
||||
if ($name === null) {
|
||||
throw new FontException('Unable to extract font name');
|
||||
}
|
||||
|
||||
$this->fdt['name'] = $name;
|
||||
|
||||
$bvl = [0, 0, 0, 0];
|
||||
if (\preg_match('#/FontBBox[\s]*+{([^}]*+)#', $this->font, $matches) === 1) {
|
||||
$rawbvl = \explode(' ', \trim($matches[1]));
|
||||
$bvl = [
|
||||
(int) $rawbvl[0],
|
||||
(int) ($rawbvl[1] ?? 0),
|
||||
(int) ($rawbvl[2] ?? 0),
|
||||
(int) ($rawbvl[3] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
$this->fdt['bbox'] = \implode(' ', $bvl);
|
||||
$this->fdt['Ascent'] = $bvl[3];
|
||||
$this->fdt['Descent'] = $bvl[1];
|
||||
|
||||
$this->fdt['italicAngle'] = \preg_match('#/ItalicAngle[\s]*+([0-9\+\-]*+)#', $this->font, $matches) === 1
|
||||
? (int) $matches[1]
|
||||
: 0;
|
||||
|
||||
if ($this->fdt['italicAngle'] !== 0) {
|
||||
$this->fdt['Flags'] |= 64;
|
||||
}
|
||||
|
||||
$this->fdt['underlinePosition'] = \preg_match('#/UnderlinePosition[\s]*+([0-9\+\-]*+)#', $this->font, $matches)
|
||||
=== 1
|
||||
? (int) $matches[1]
|
||||
: 0;
|
||||
$this->fdt['underlineThickness'] = \preg_match(
|
||||
'#/UnderlineThickness[\s]*+([0-9\+\-]*+)#',
|
||||
$this->font,
|
||||
$matches,
|
||||
) === 1
|
||||
? (int) $matches[1]
|
||||
: 0;
|
||||
|
||||
if (\preg_match('#/isFixedPitch[\s]*+([^\s]*+)#', $this->font, $matches) === 1 && $matches[1] === 'true') {
|
||||
$this->fdt['Flags'] = (int) $this->fdt['Flags'] | 1;
|
||||
}
|
||||
|
||||
$this->fdt['weight'] = 'Book';
|
||||
if (\preg_match('#/Weight[\s]*+\(([^\)]*+)#', $this->font, $matches) === 1 && $matches[1] !== '') {
|
||||
$this->fdt['weight'] = \strtolower($matches[1]);
|
||||
}
|
||||
|
||||
$this->fdt['Leading'] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Font information
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
protected function getInternalMap(): array
|
||||
{
|
||||
$imap = [];
|
||||
$fmap = [];
|
||||
$matches = \preg_match_all('#dup[\s]([0-9]+)[\s]*+/([^\s]*+)[\s]put#sU', $this->font, $fmap, PREG_SET_ORDER);
|
||||
if ($matches !== false && $matches >= 1) {
|
||||
foreach ($fmap as $val) {
|
||||
$imap[$val[2]] = (int) $val[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $imap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt eexec encrypted part
|
||||
*/
|
||||
protected function getEplain(): string
|
||||
{
|
||||
$csr = 55_665; // eexec encryption constant
|
||||
$cc1 = 52_845;
|
||||
$cc2 = 22_719;
|
||||
$elen = \strlen($this->fdt['encrypted']);
|
||||
$eplain = '';
|
||||
for ($idx = 0; $idx < $elen; ++$idx) {
|
||||
$chr = \ord($this->fdt['encrypted'][$idx]);
|
||||
$eplain .= \chr($chr ^ ($csr >> 8));
|
||||
$csr = ((($chr + $csr) * $cc1) + $cc2) % 65_536;
|
||||
}
|
||||
|
||||
return $eplain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract eexec info
|
||||
*
|
||||
* @return array<int, array<int, string>>
|
||||
*/
|
||||
protected function extractEplainInfo(): array
|
||||
{
|
||||
$eplain = $this->getEplain();
|
||||
$matches = [];
|
||||
if (\preg_match('#/ForceBold[\s]*+([^\s]*+)#', $eplain, $matches) === 1 && $matches[1] === 'true') {
|
||||
$this->fdt['Flags'] |= 0x4_0000;
|
||||
}
|
||||
|
||||
$this->extractStem($eplain);
|
||||
if (\preg_match('#/BlueValues[\s]*+\[([^\]]*+)#', $eplain, $matches) === 1) {
|
||||
$bvl = \explode(' ', $matches[1]);
|
||||
if (\count($bvl) >= 6) {
|
||||
$vl1 = (int) $bvl[2];
|
||||
$vl2 = (int) $bvl[4];
|
||||
$this->fdt['XHeight'] = \min($vl1, $vl2);
|
||||
$this->fdt['CapHeight'] = \max($vl1, $vl2);
|
||||
}
|
||||
}
|
||||
|
||||
$this->getRandomBytes($eplain);
|
||||
return $this->getCharstringData($eplain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract eexec info
|
||||
*
|
||||
* @param string $eplain Decoded eexec encrypted part
|
||||
*/
|
||||
protected function extractStem(string $eplain): void
|
||||
{
|
||||
$matches = [];
|
||||
if (\preg_match('#/StdVW[\s]*+\[([^\]]*+)#', $eplain, $matches) === 1) {
|
||||
$this->fdt['StemV'] = (int) $matches[1];
|
||||
} elseif ($this->fdt['weight'] === 'bold' || $this->fdt['weight'] === 'black') {
|
||||
$this->fdt['StemV'] = 123;
|
||||
} else {
|
||||
$this->fdt['StemV'] = 70;
|
||||
}
|
||||
|
||||
$this->fdt['StemH'] = \preg_match('#/StdHW[\s]*+\[([^\]]*+)#', $eplain, $matches) === 1
|
||||
? (int) $matches[1]
|
||||
: 30;
|
||||
|
||||
if (\preg_match('#/Cap[X]?Height[\s]*+\[([^\]]*+)#', $eplain, $matches) === 1) {
|
||||
$this->fdt['CapHeight'] = (int) $matches[1];
|
||||
} else {
|
||||
$this->fdt['CapHeight'] = (int) $this->fdt['Ascent'];
|
||||
}
|
||||
|
||||
$this->fdt['XHeight'] = (int) $this->fdt['Ascent'] + (int) $this->fdt['Descent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of random bytes at the beginning of charstrings
|
||||
*/
|
||||
protected function getRandomBytes(string $eplain): void
|
||||
{
|
||||
$this->fdt['lenIV'] = 4;
|
||||
$matches = [];
|
||||
if (\preg_match('#/lenIV[\s]*+([\d]*+)#', $eplain, $matches) === 1) {
|
||||
$this->fdt['lenIV'] = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, string>>
|
||||
*/
|
||||
protected function getCharstringData(string $eplain): array
|
||||
{
|
||||
$this->fdt['enc_map'] = [];
|
||||
$charstringsPos = \strpos($eplain, '/CharStrings');
|
||||
if ($charstringsPos === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$eplain = \substr($eplain, $charstringsPos + 1);
|
||||
$matches = [];
|
||||
\preg_match_all('#/([A-Za-z0-9\.]*+)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
|
||||
/** @var array<int, array<int, string>> $matches */
|
||||
if ($this->fdt['enc'] === '') {
|
||||
return $matches;
|
||||
}
|
||||
|
||||
if (!isset(Encoding::MAP[$this->fdt['enc']])) {
|
||||
return $matches;
|
||||
}
|
||||
|
||||
$this->fdt['enc_map'] = Encoding::MAP[$this->fdt['enc']];
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* get CID
|
||||
*
|
||||
* @param array<string, int> $imap
|
||||
* @param array<int, string> $val
|
||||
*/
|
||||
protected function getCid(array $imap, array $val): int
|
||||
{
|
||||
if (isset($imap[$val[1]])) {
|
||||
return $imap[$val[1]];
|
||||
}
|
||||
|
||||
$cid = \array_search($val[1], $this->fdt['enc_map'], true);
|
||||
|
||||
if ($cid === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($cid > 1000) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
return (int) $cid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode number
|
||||
*
|
||||
* @param array<int, int> $ccom
|
||||
* @param array<int, int> $cdec
|
||||
* @param array<int, int> $cwidths
|
||||
*
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function decodeNumber(int $idx, int &$cck, int &$cid, array &$ccom, array &$cdec, array &$cwidths): int
|
||||
{
|
||||
if ($ccom[$idx] === 255) {
|
||||
if (!isset($ccom[$idx + 4])) {
|
||||
throw new FontException('Truncated Type1 charstring number operand');
|
||||
}
|
||||
|
||||
$sval = \chr($ccom[$idx + 1]) . \chr($ccom[$idx + 2]) . \chr($ccom[$idx + 3]) . \chr($ccom[$idx + 4]);
|
||||
$vsval = \unpack('li', $sval);
|
||||
if ($vsval === false) {
|
||||
throw new FontException('Unable to unpack number');
|
||||
}
|
||||
|
||||
$cdec[$cck] = (int) $vsval['i'];
|
||||
return $idx + 5;
|
||||
}
|
||||
|
||||
if ($ccom[$idx] >= 251) {
|
||||
if (!isset($ccom[$idx + 1])) {
|
||||
throw new FontException('Truncated Type1 charstring number operand');
|
||||
}
|
||||
|
||||
$cdec[$cck] = (-($ccom[$idx] - 251) * 256) - $ccom[$idx + 1] - 108;
|
||||
return $idx + 2;
|
||||
}
|
||||
|
||||
if ($ccom[$idx] >= 247) {
|
||||
if (!isset($ccom[$idx + 1])) {
|
||||
throw new FontException('Truncated Type1 charstring number operand');
|
||||
}
|
||||
|
||||
$cdec[$cck] = (($ccom[$idx] - 247) * 256) + $ccom[$idx + 1] + 108;
|
||||
return $idx + 2;
|
||||
}
|
||||
|
||||
if ($ccom[$idx] >= 32) {
|
||||
$cdec[$cck] = $ccom[$idx] - 139;
|
||||
return ++$idx;
|
||||
}
|
||||
|
||||
$cdec[$cck] = $ccom[$idx];
|
||||
if ($cck <= 0) {
|
||||
return ++$idx;
|
||||
}
|
||||
|
||||
if ($cdec[$cck] !== 13) {
|
||||
return ++$idx;
|
||||
}
|
||||
|
||||
// hsbw command: update width
|
||||
$cwidths[$cid] = $cdec[$cck - 1];
|
||||
return ++$idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Type1 font
|
||||
*
|
||||
* @throws FileException
|
||||
* @throws FontException
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
$this->storeFontData();
|
||||
$this->extractFontInfo();
|
||||
$imap = $this->getInternalMap();
|
||||
$matches = $this->extractEplainInfo();
|
||||
$cwidths = [];
|
||||
$cc1 = 52_845;
|
||||
$cc2 = 22_719;
|
||||
foreach ($matches as $match) {
|
||||
$cid = $this->getCid($imap, $match);
|
||||
// decrypt charstring encrypted part
|
||||
$csr = 4330; // charstring encryption constant
|
||||
$ccd = $match[2];
|
||||
$clen = \strlen($ccd);
|
||||
$ccom = [];
|
||||
for ($idx = 0; $idx < $clen; ++$idx) {
|
||||
$chr = \ord($ccd[$idx]);
|
||||
$ccom[] = $chr ^ ($csr >> 8);
|
||||
$csr = ((($chr + $csr) * $cc1) + $cc2) % 65_536;
|
||||
}
|
||||
|
||||
// decode numbers
|
||||
$cdec = [];
|
||||
$cck = 0;
|
||||
$idx = $this->fdt['lenIV'];
|
||||
while ($idx < $clen) {
|
||||
$idx = $this->decodeNumber($idx, $cck, $cid, $ccom, $cdec, $cwidths);
|
||||
++$cck;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setCharWidths($cwidths);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user