Files
EpiWebview/vendor/tecnickcom/tc-lib-pdf-font/src/Import.php

599 lines
19 KiB
PHP

<?php
/**
* Import.php
*
* @since 2011-05-23
* @category Library
* @package PdfFont
* @author Nicola Asuni <info@tecnick.com>
* @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\Byte;
use Com\Tecnick\File\Dir;
use Com\Tecnick\File\File;
use Com\Tecnick\Pdf\Font\Exception as FontException;
use Com\Tecnick\Pdf\Font\Import\Core;
use Com\Tecnick\Pdf\Font\Import\TrueType;
use Com\Tecnick\Pdf\Font\Import\TypeOne;
use Com\Tecnick\Unicode\Data\Encoding;
/**
* Com\Tecnick\Pdf\Font\Import
*
* @since 2011-05-23
* @category Library
* @package PdfFont
* @author Nicola Asuni <info@tecnick.com>
* @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 TFontData from Load
*
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
*/
class Import
{
/**
* Content of the input font file
*/
protected string $font = '';
/**
* Object used to read font bytes
*/
protected Byte $fbyte;
/**
* Extracted font metrics
*
* @var TFontData
*/
protected array $fdt = [
'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' => '',
];
/**
* Import the specified font and create output files.
*
* @param string $file Font file to process
* @param string $output_path Output path for generated font files (must be writeable by the web server).
* Leave null for default font folder.
* @param string $type Font type. Leave empty for autodetect mode. Valid values are:
* Core (AFM - Adobe Font Metrics) TrueTypeUnicode TrueType
* Type1 CID0JP (CID-0 Japanese) CID0KR (CID-0 Korean) CID0CS
* (CID-0 Chinese Simplified) CID0CT (CID-0 Chinese Traditional)
* @param string $encoding Name of the encoding table to use. Leave empty for default mode.
* Omit this parameter for TrueType Unicode and symbolic fonts like
* Symbol or ZapfDingBats.
* @param int $flags Unsigned 32-bit integer containing flags specifying various characteristics
* of the font as described in "PDF32000:2008 - 9.8.2 Font Descriptor Flags":
* +1 for fixed width font +4 for symbol or +32 for non-symbol +64 for italic
* Note: Fixed and Italic mode are generally autodetected, so you have to set
* it to 32 = non-symbolic font (default) or 4 = symbolic font.
* @param int $platform_id Platform ID for CMAP table to extract.
* For a Unicode font for Windows this
* value should be 3, for Macintosh
* should be 1.
* @param int $encoding_id Encoding ID for CMAP table to extract.
* For a Unicode font for Windows this
* value should be 1, for Macintosh
* should be 0. When Platform ID is 3,
* legal values for Encoding ID are: 0 =
* Symbol, 1 = Unicode, 2 = ShiftJIS, 3 =
* PRC, 4 = Big5, 5 = Wansung, 6 = Johab,
* 7 = Reserved, 8 = Reserved, 9 =
* Reserved, 10 = UCS-4.
* @param bool $linked If true, links the font file to system font instead of copying the font data
* (not transportable). Note: this option do not work with Type1 fonts.
*
* @throws FontException in case of error
*/
public function __construct(
string $file,
string $output_path = '',
string $type = '',
string $encoding = '',
int $flags = 32,
int $platform_id = 3,
int $encoding_id = 1,
bool $linked = false
) {
if (FILE::hasDoubleDots($file) || FILE::hasForbiddenProtocol($file)) {
throw new FontException('Invalid font file name: ' . $file);
}
$this->fdt['input_file'] = $file;
$this->fdt['file_name'] = $this->makeFontName($file);
if (empty($this->fdt['file_name'])) {
throw new FontException('the font name is empty');
}
$this->fdt['dir'] = $this->findOutputPath($output_path);
$this->fdt['datafile'] = $this->fdt['dir'] . $this->fdt['file_name'] . '.json';
if (@file_exists($this->fdt['datafile'])) {
throw new FontException('this font has been already imported: ' . $this->fdt['datafile']);
}
// get font data
if (! is_file($file)) {
throw new FontException('invalid font file: ' . $file);
}
if (($font = @file_get_contents($file)) === false) {
throw new FontException('unable to read the input font file: ' . $file);
}
$this->font = $font;
$this->fbyte = new Byte($this->font);
$this->fdt['settype'] = $type;
$this->fdt['type'] = $this->getFontType($type);
$this->fdt['isUnicode'] = (($this->fdt['type'] == 'TrueTypeUnicode') || ($this->fdt['type'] == 'cidfont0'));
$this->fdt['Flags'] = $flags;
$this->initFlags();
$this->fdt['enc'] = $this->getEncodingTable($encoding);
$this->fdt['diff'] = $this->getEncodingDiff();
$this->fdt['originalsize'] = strlen($this->font);
$this->fdt['ctg'] = $this->fdt['file_name'] . '.ctg.z';
$this->fdt['platform_id'] = $platform_id;
$this->fdt['encoding_id'] = $encoding_id;
$this->fdt['linked'] = $linked;
$processor = match ($this->fdt['type']) {
'Core' => new Core($this->font, $this->fdt),
'Type1' => new TypeOne($this->font, $this->fdt),
default => new TrueType($this->font, $this->fdt, $this->fbyte),
};
;
$this->fdt = $processor->getFontMetrics();
$this->saveFontData();
}
/**
* Get all the extracted font metrics
*
* @return TFontData
*/
public function getFontMetrics(): array
{
return $this->fdt;
}
/**
* Get the output font name
*/
public function getFontName(): string
{
return $this->fdt['file_name'];
}
/**
* Initialize font flags from font name
*/
protected function initFlags(): void
{
$filename = strtolower(basename($this->fdt['input_file']));
if (
(str_contains($filename, 'mono'))
|| (str_contains($filename, 'courier'))
|| (str_contains($filename, 'fixed'))
) {
$this->fdt['Flags'] |= 1;
}
if (
(str_contains($filename, 'symbol'))
|| (str_contains($filename, 'zapfdingbats'))
) {
$this->fdt['Flags'] |= 4;
}
if (
(str_contains($filename, 'italic'))
|| (str_contains($filename, 'oblique'))
) {
$this->fdt['Flags'] |= 64;
}
}
/**
* Save the eported metadata font file
*
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
* @SuppressWarnings("PHPMD.NPathComplexity")
*/
protected function saveFontData(): void
{
$pfile = '{"type":"' . $this->fdt['type'] . '"'
. ',"name":"' . $this->fdt['name'] . '"'
. ',"up":' . $this->fdt['underlinePosition']
. ',"ut":' . $this->fdt['underlineThickness']
. ',"dw":' . (($this->fdt['MissingWidth'] > 0) ? $this->fdt['MissingWidth'] : $this->fdt['AvgWidth'])
. ',"diff":"' . $this->fdt['diff'] . '"'
. ',"platform_id":' . $this->fdt['platform_id']
. ',"encoding_id":' . $this->fdt['encoding_id'];
if ($this->fdt['type'] == 'Core') {
// Core
$pfile .= ',"enc":""';
} elseif ($this->fdt['type'] == 'Type1') {
// Type 1
$pfile .= ',"enc":"' . $this->fdt['enc'] . '"'
. ',"file":"' . $this->fdt['file'] . '"'
. ',"size1":' . $this->fdt['size1']
. ',"size2":' . $this->fdt['size2'];
} else {
$pfile .= ',"originalsize":' . $this->fdt['originalsize'];
if ($this->fdt['type'] == 'cidfont0') {
$pfile .= ',' . UniToCid::TYPE[$this->fdt['settype']];
} else {
// TrueType
$pfile .= ',"enc":"' . $this->fdt['enc'] . '"'
. ',"file":"' . $this->fdt['file'] . '"'
. ',"ctg":"' . $this->fdt['ctg'] . '"';
// create CIDToGIDMap
$cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072
foreach ($this->fdt['ctgdata'] as $cid => $gid) {
$cidtogidmap = $this->updateCIDtoGIDmap($cidtogidmap, (int) $cid, (int) $gid);
}
// store compressed CIDToGIDMap
$file = new File();
$fpt = $file->fopenLocal($this->fdt['dir'] . $this->fdt['ctg'], 'wb');
$cmpr = gzcompress($cidtogidmap);
if ($cmpr === false) {
throw new FontException('unable to compress CIDToGIDMap');
}
fwrite($fpt, $cmpr);
fclose($fpt);
}
}
if ($this->fdt['isUnicode']) {
$pfile .= ',"isUnicode":true';
} else {
$pfile .= ',"isUnicode":false';
}
$pfile .= ',"desc":{"Flags":' . $this->fdt['Flags']
. ',"FontBBox":"[' . $this->fdt['bbox'] . ']"'
. ',"ItalicAngle":' . $this->fdt['italicAngle']
. ',"Ascent":' . $this->fdt['Ascent']
. ',"Descent":' . $this->fdt['Descent']
. ',"Leading":' . $this->fdt['Leading']
. ',"CapHeight":' . $this->fdt['CapHeight']
. ',"XHeight":' . $this->fdt['XHeight']
. ',"StemV":' . $this->fdt['StemV']
. ',"StemH":' . $this->fdt['StemH']
. ',"AvgWidth":' . $this->fdt['AvgWidth']
. ',"MaxWidth":' . $this->fdt['MaxWidth']
. ',"MissingWidth":' . $this->fdt['MissingWidth']
. '}';
if (! empty($this->fdt['cbbox'])) {
$ccboxstr = '';
foreach ($this->fdt['cbbox'] as $cid => $bbox) {
$ccboxstr .= ',"' . $cid . '":[' . $bbox[0] . ',' . $bbox[1] . ',' . $bbox[2] . ',' . $bbox[3] . ']';
}
$pfile .= ',"cbbox":{' . substr($ccboxstr, 1) . '}';
}
if (! empty($this->fdt['cw'])) {
$cwstr = '';
foreach ($this->fdt['cw'] as $cid => $width) {
$cwstr .= ',"' . $cid . '":' . $width;
}
$pfile .= ',"cw":{' . substr($cwstr, 1) . '}';
}
$pfile .= '}' . "\n";
// store file
$file = new File();
$fpt = $file->fopenLocal($this->fdt['datafile'], 'wb');
fwrite($fpt, $pfile);
fclose($fpt);
}
/**
* Make the output font name
*
* @param string $font_file Input font file
*/
protected function makeFontName(string $font_file): string
{
$font_path_parts = pathinfo($font_file);
if (empty($font_path_parts['filename'])) {
throw new FontException('Invalid font file name: ' . $font_file);
}
$fname = preg_replace('/[^a-z0-9_]/', '', strtolower($font_path_parts['filename']));
if ($fname === null) {
throw new FontException('Invalid font file name: ' . $font_file);
}
return str_replace(
['bold', 'oblique', 'italic', 'regular'],
['b', 'i', 'i', ''],
$fname
);
}
/**
* Find the path where to store the processed font.
*
* @param string $output_path Output path for generated font files (must be writeable by the web server).
* Leave null for default font folder (K_PATH_FONTS).
*/
protected function findOutputPath(string $output_path = ''): string
{
if (
$output_path !== ''
&& (strpos($output_path, '://') === false)
&& !FILE::hasDoubleDots($output_path)
&& is_writable($output_path)
) {
return $output_path;
}
if (defined('K_PATH_FONTS') && is_writable(K_PATH_FONTS)) {
return K_PATH_FONTS;
}
$dirobj = new Dir();
$dir = $dirobj->findParentDir('fonts', __DIR__);
if ($dir == '/') {
$dir = sys_get_temp_dir();
}
if (! str_ends_with($dir, '/')) {
$dir .= '/';
}
return $dir;
}
/**
* Get the font type
*
* @param string $font_type Font type. Leave empty for autodetect mode.
*/
protected function getFontType(string $font_type): string
{
// autodetect font type
if ($font_type === '') {
if (str_starts_with($this->font, 'StartFontMetrics')) {
// AFM type - we use this type only for the 14 Core fonts
return 'Core';
}
if (str_starts_with($this->font, 'OTTO')) {
throw new FontException('Unsupported font format: OpenType with CFF data');
}
if ($this->fbyte->getULong(0) == 0x10000) {
return 'TrueTypeUnicode';
}
return 'Type1';
}
if (str_starts_with($font_type, 'CID0')) {
return 'cidfont0';
}
if (in_array($font_type, ['Core', 'Type1', 'TrueType', 'TrueTypeUnicode'])) {
return $font_type;
}
throw new FontException('unknown or unsupported font type: ' . $font_type);
}
/**
* Get the encoding table
*
* @param string $encoding Name of the encoding table to use. Leave empty for default mode.
* Omit this parameter for TrueType Unicode and symbolic fonts like
* Symbol or ZapfDingBats.
*/
protected function getEncodingTable(string $encoding = ''): string
{
if ($encoding === '') {
if (($this->fdt['type'] == 'Type1') && (($this->fdt['Flags'] & 4) == 0)) {
return 'cp1252';
}
return '';
}
$enc = preg_replace('/[^A-Za-z0-9_\-]/', '', $encoding);
if ($enc === null) {
throw new FontException('Invalid encoding name: ' . $encoding);
}
return $enc;
}
/**
* If required, get differences between the reference encoding (cp1252) and the current encoding
*
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
*/
protected function getEncodingDiff(): string
{
$diff = '';
if (
(($this->fdt['type'] == 'TrueType') || ($this->fdt['type'] == 'Type1'))
&& (! empty($this->fdt['enc'])
&& (is_string($this->fdt['enc']))
&& ($this->fdt['enc'] != 'cp1252')
&& isset(Encoding::MAP[$this->fdt['enc']]))
) {
// build differences from reference encoding
$enc_ref = Encoding::MAP['cp1252'];
$enc_target = Encoding::MAP[$this->fdt['enc']];
$last = 0;
for ($idx = 32; $idx <= 255; ++$idx) {
if ($enc_target[$idx] != $enc_ref[$idx]) {
if ($idx != $last + 1) {
$diff .= $idx . ' ';
}
$last = $idx;
$diff .= '/' . $enc_target[$idx] . ' ';
}
}
}
return $diff;
}
/**
* Update the CIDToGIDMap string with a new value
*
* @param string $map CIDToGIDMap.
* @param int $cid CID value.
* @param int $gid GID value.
*/
protected function updateCIDtoGIDmap(string $map, int $cid, int $gid): string
{
if (($cid >= 0) && ($cid <= 0xFFFF) && ($gid >= 0)) {
if ($gid > 0xFFFF) {
$gid -= 0x10000;
}
$map[($cid * 2)] = chr($gid >> 8);
$map[(($cid * 2) + 1)] = chr($gid & 0xFF);
}
return $map;
}
}