Feature: Labelprint für Kistenetiketten hinzugefügt
This commit is contained in:
227
vendor/tecnickcom/tc-lib-pdf-font/src/Import/Core.php
vendored
Normal file
227
vendor/tecnickcom/tc-lib-pdf-font/src/Import/Core.php
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Core.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\Import;
|
||||
|
||||
use Com\Tecnick\File\File;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Import\Core
|
||||
*
|
||||
* @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 \Com\Tecnick\Pdf\Font\Load
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
/**
|
||||
* @param string $font Content of the input font file
|
||||
* @param TFontData $fdt Extracted font metrics
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $font,
|
||||
protected array $fdt
|
||||
) {
|
||||
$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 (! empty($cwidths[32])) {
|
||||
$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'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->fdt['AvgWidth'] = (int) round($this->fdt['AvgWidth'] / count($cwidths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Metrics
|
||||
*/
|
||||
protected function extractMetrics(): void
|
||||
{
|
||||
$cwd = [];
|
||||
$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->setCharWidths($cwd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Metrics
|
||||
*
|
||||
* @param array<int, string> $col Array containing row elements to process
|
||||
* @param array<int, int> $cwd Array contianing 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':
|
||||
$cid = (int) $col[1];
|
||||
if ($cid >= 0) {
|
||||
$cwd[$cid] = (int) $col[4];
|
||||
if (! empty($col[14])) {
|
||||
$this->fdt['cbbox'][$cid] = [(int) $col[10], (int) $col[11], (int) $col[12], (int) $col[13]];
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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']);
|
||||
|
||||
if (empty($this->fdt['XHeight'])) {
|
||||
$this->fdt['XHeight'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setMissingValues(): void
|
||||
{
|
||||
$this->fdt['Descender'] = $this->fdt['FontBBox'][1];
|
||||
|
||||
$this->fdt['Ascender'] = $this->fdt['FontBBox'][3];
|
||||
|
||||
if (empty($this->fdt['CapHeight'])) {
|
||||
$this->fdt['CapHeight'] = $this->fdt['Ascender'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Core font
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
$this->extractMetrics();
|
||||
$this->setFlags();
|
||||
$this->setMissingValues();
|
||||
$this->remapValues();
|
||||
}
|
||||
}
|
||||
811
vendor/tecnickcom/tc-lib-pdf-font/src/Import/TrueType.php
vendored
Normal file
811
vendor/tecnickcom/tc-lib-pdf-font/src/Import/TrueType.php
vendored
Normal file
@@ -0,0 +1,811 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TrueType.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\Import;
|
||||
|
||||
use Com\Tecnick\File\Byte;
|
||||
use Com\Tecnick\File\File;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\Unicode\Data\Encoding;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Import\TrueType
|
||||
*
|
||||
* @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 \Com\Tecnick\Pdf\Font\Load
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassLength")
|
||||
*/
|
||||
class TrueType
|
||||
{
|
||||
/**
|
||||
* Array containing subset chars
|
||||
*
|
||||
* @var array<int, bool>
|
||||
*/
|
||||
protected array $subchars = [];
|
||||
|
||||
/**
|
||||
* Array containing subset glyphs indexes of chars from cmap table
|
||||
*
|
||||
* @var array<int, bool>
|
||||
*/
|
||||
protected array $subglyphs = [
|
||||
0 => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Pointer position on the original font data
|
||||
*/
|
||||
protected int $offset = 0;
|
||||
|
||||
/**
|
||||
* Process TrueType font
|
||||
*
|
||||
* @param string $font Content of the input font file
|
||||
* @param TFontData $fdt Extracted font metrics
|
||||
* @param Byte $fbyte Object used to read font bytes
|
||||
* @param array<int, bool> $subchars Array containing subset chars
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $font,
|
||||
protected array $fdt,
|
||||
protected Byte $fbyte,
|
||||
array $subchars = []
|
||||
) {
|
||||
ksort($subchars);
|
||||
$this->subchars = $subchars;
|
||||
$this->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the extracted font metrics
|
||||
*
|
||||
* @return TFontData
|
||||
*/
|
||||
public function getFontMetrics(): array
|
||||
{
|
||||
return $this->fdt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get glyphs in the subset
|
||||
*
|
||||
* @return array<int, bool>
|
||||
*/
|
||||
public function getSubGlyphs(): array
|
||||
{
|
||||
return $this->subglyphs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process TrueType font
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
$this->isValidType();
|
||||
$this->setFontFile();
|
||||
$this->getTables();
|
||||
$this->checkMagickNumber();
|
||||
$this->offset += 2; // skip flags
|
||||
$this->getBbox();
|
||||
$this->getIndexToLoc();
|
||||
$this->getEncodingTables();
|
||||
$this->getOS2Metrics();
|
||||
$this->getFontName();
|
||||
$this->getPostData();
|
||||
$this->getHheaData();
|
||||
$this->getMaxpData();
|
||||
$this->getCIDToGIDMap();
|
||||
$this->getHeights();
|
||||
$this->getWidths();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the font is a valid type
|
||||
*
|
||||
* @throws FontException if the font is invalid
|
||||
*/
|
||||
protected function isValidType(): void
|
||||
{
|
||||
if ($this->fbyte->getULong($this->offset) != 0x10000) {
|
||||
throw new FontException('sfnt version must be 0x00010000 for TrueType version 1.0.');
|
||||
}
|
||||
|
||||
$this->offset += 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy or link the original font file
|
||||
*/
|
||||
protected function setFontFile(): void
|
||||
{
|
||||
if (! empty($this->fdt['desc']['MaxWidth'])) {
|
||||
// subsetting mode
|
||||
$this->fdt['Flags'] = $this->fdt['desc']['Flags'];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fdt['type'] == 'cidfont0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->fdt['linked']) {
|
||||
// creates a symbolic link to the existing font
|
||||
symlink($this->fdt['input_file'], $this->fdt['dir'] . $this->fdt['file_name']);
|
||||
return;
|
||||
}
|
||||
|
||||
// store compressed font
|
||||
$this->fdt['file'] = $this->fdt['file_name'] . '.z';
|
||||
$file = new File();
|
||||
$fpt = $file->fopenLocal($this->fdt['dir'] . $this->fdt['file'], 'wb');
|
||||
|
||||
$cmpr = gzcompress($this->font);
|
||||
if ($cmpr === false) {
|
||||
throw new FontException('Error compressing font file.');
|
||||
}
|
||||
|
||||
fwrite($fpt, $cmpr);
|
||||
fclose($fpt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font tables
|
||||
*/
|
||||
protected function getTables(): void
|
||||
{
|
||||
// get number of tables
|
||||
$numTables = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
// skip searchRange, entrySelector and rangeShift
|
||||
$this->offset += 6;
|
||||
// tables array
|
||||
$this->fdt['table'] = [];
|
||||
// ---------- get tables ----------
|
||||
for ($idx = 0; $idx < $numTables; ++$idx) {
|
||||
// get table info
|
||||
$tag = substr($this->font, $this->offset, 4);
|
||||
$this->offset += 4;
|
||||
$this->fdt['table'][$tag] = [
|
||||
'checkSum' => 0,
|
||||
'data' => '',
|
||||
'length' => 0,
|
||||
'offset' => 0,
|
||||
];
|
||||
$this->fdt['table'][$tag]['checkSum'] = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$this->fdt['table'][$tag]['offset'] = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$this->fdt['table'][$tag]['length'] = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the font is a valid type
|
||||
*
|
||||
* @throws FontException if the font is invalid
|
||||
*/
|
||||
protected function checkMagickNumber(): void
|
||||
{
|
||||
$this->offset = ($this->fdt['table']['head']['offset'] + 12);
|
||||
if ($this->fbyte->getULong($this->offset) != 0x5F0F3CF5) {
|
||||
// magicNumber must be 0x5F0F3CF5
|
||||
throw new FontException('magicNumber must be 0x5F0F3CF5');
|
||||
}
|
||||
|
||||
$this->offset += 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get BBox, units and flags
|
||||
*/
|
||||
protected function getBbox(): void
|
||||
{
|
||||
$this->fdt['unitsPerEm'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
// units ratio constant
|
||||
$this->fdt['urk'] = (1000 / $this->fdt['unitsPerEm']);
|
||||
$this->offset += 16; // skip created, modified
|
||||
$xMin = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$yMin = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$xMax = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$yMax = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$this->fdt['bbox'] = $xMin . ' ' . $yMin . ' ' . $xMax . ' ' . $yMax;
|
||||
$macStyle = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
// PDF font flags
|
||||
if (($macStyle & 2) == 2) {
|
||||
// italic flag
|
||||
$this->fdt['Flags'] |= 64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get index to loc map
|
||||
*/
|
||||
protected function getIndexToLoc(): void
|
||||
{
|
||||
// get offset mode (indexToLocFormat : 0 = short, 1 = long)
|
||||
$this->offset = ($this->fdt['table']['head']['offset'] + 50);
|
||||
$this->fdt['short_offset'] = ($this->fbyte->getShort($this->offset) == 0);
|
||||
$this->offset += 2;
|
||||
// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
|
||||
$this->fdt['indexToLoc'] = [];
|
||||
$this->offset = $this->fdt['table']['loca']['offset'];
|
||||
if ($this->fdt['short_offset']) {
|
||||
// short version
|
||||
$this->fdt['tot_num_glyphs'] = (int) floor($this->fdt['table']['loca']['length'] / 2); // numGlyphs + 1
|
||||
for ($idx = 0; $idx < $this->fdt['tot_num_glyphs']; ++$idx) {
|
||||
$this->fdt['indexToLoc'][$idx] = $this->fbyte->getUShort($this->offset) * 2;
|
||||
if (
|
||||
isset($this->fdt['indexToLoc'][($idx - 1)])
|
||||
&& ($this->fdt['indexToLoc'][$idx] === $this->fdt['indexToLoc'][($idx - 1)])
|
||||
) {
|
||||
// the last glyph didn't have an outline
|
||||
unset($this->fdt['indexToLoc'][($idx - 1)]);
|
||||
}
|
||||
|
||||
$this->offset += 2;
|
||||
}
|
||||
} else {
|
||||
// long version
|
||||
$this->fdt['tot_num_glyphs'] = (int) floor($this->fdt['table']['loca']['length'] / 4); // numGlyphs + 1
|
||||
for ($idx = 0; $idx < $this->fdt['tot_num_glyphs']; ++$idx) {
|
||||
$this->fdt['indexToLoc'][$idx] = $this->fbyte->getULong($this->offset);
|
||||
if (
|
||||
isset($this->fdt['indexToLoc'][($idx - 1)])
|
||||
&& ($this->fdt['indexToLoc'][$idx] === $this->fdt['indexToLoc'][($idx - 1)])
|
||||
) {
|
||||
// the last glyph didn't have an outline
|
||||
unset($this->fdt['indexToLoc'][($idx - 1)]);
|
||||
}
|
||||
|
||||
$this->offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getEncodingTables(): void
|
||||
{
|
||||
// get glyphs indexes of chars from cmap table
|
||||
$this->offset = $this->fdt['table']['cmap']['offset'] + 2;
|
||||
$numEncodingTables = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->fdt['encodingTables'] = [];
|
||||
for ($idx = 0; $idx < $numEncodingTables; ++$idx) {
|
||||
$this->fdt['encodingTables'][$idx]['platformID'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->fdt['encodingTables'][$idx]['encodingID'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->fdt['encodingTables'][$idx]['offset'] = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encoding tables
|
||||
*/
|
||||
protected function getOS2Metrics(): void
|
||||
{
|
||||
$this->offset = $this->fdt['table']['OS/2']['offset'];
|
||||
$this->offset += 2; // skip version
|
||||
// xAvgCharWidth
|
||||
$this->fdt['AvgWidth'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
// usWeightClass
|
||||
$usWeightClass = round($this->fbyte->getUFWord($this->offset) * $this->fdt['urk']);
|
||||
// estimate StemV and StemH (400 = usWeightClass for Normal - Regular font)
|
||||
$this->fdt['StemV'] = (int) round((70 * $usWeightClass) / 400);
|
||||
$this->fdt['StemH'] = (int) round((30 * $usWeightClass) / 400);
|
||||
$this->offset += 2;
|
||||
$this->offset += 2; // usWidthClass
|
||||
$fsType = $this->fbyte->getShort($this->offset);
|
||||
$this->offset += 2;
|
||||
if ($fsType == 2) {
|
||||
throw new FontException(
|
||||
'This Font cannot be modified, embedded or exchanged in any manner'
|
||||
. ' without first obtaining permission of the legal owner.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFontName(): void
|
||||
{
|
||||
$this->fdt['name'] = '';
|
||||
$this->offset = $this->fdt['table']['name']['offset'];
|
||||
$this->offset += 2; // skip Format selector (=0).
|
||||
// Number of NameRecords that follow n.
|
||||
$numNameRecords = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
// Offset to start of string storage (from start of table).
|
||||
$stringStorageOffset = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
for ($idx = 0; $idx < $numNameRecords; ++$idx) {
|
||||
$this->offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
|
||||
// Name ID.
|
||||
$nameID = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
if ($nameID == 6) {
|
||||
// String length (in bytes).
|
||||
$stringLength = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
// String offset from start of storage area (in bytes).
|
||||
$stringOffset = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->offset = ($this->fdt['table']['name']['offset'] + $stringStorageOffset + $stringOffset);
|
||||
$this->fdt['name'] = substr($this->font, $this->offset, $stringLength);
|
||||
$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $this->fdt['name']);
|
||||
if (($name === null) || ($name === '')) {
|
||||
throw new FontException('Error getting font name.');
|
||||
}
|
||||
|
||||
$this->fdt['name'] = $name;
|
||||
break;
|
||||
} else {
|
||||
$this->offset += 4; // skip String length, String offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPostData(): void
|
||||
{
|
||||
$this->offset = $this->fdt['table']['post']['offset'];
|
||||
$this->offset += 4; // skip Format Type
|
||||
$this->fdt['italicAngle'] = $this->fbyte->getFixed($this->offset);
|
||||
$this->offset += 4;
|
||||
$this->fdt['underlinePosition'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$this->fdt['underlineThickness'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$isFixedPitch = ($this->fbyte->getULong($this->offset) != 0);
|
||||
$this->offset += 2;
|
||||
if ($isFixedPitch) {
|
||||
$this->fdt['Flags'] |= 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getHheaData(): void
|
||||
{
|
||||
// ---------- get hhea data ----------
|
||||
$this->offset = $this->fdt['table']['hhea']['offset'];
|
||||
$this->offset += 4; // skip Table version number
|
||||
// Ascender
|
||||
$this->fdt['Ascent'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
// Descender
|
||||
$this->fdt['Descent'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
// LineGap
|
||||
$this->fdt['Leading'] = (int) round($this->fbyte->getFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
// advanceWidthMax
|
||||
$this->fdt['MaxWidth'] = (int) round($this->fbyte->getUFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 2;
|
||||
$this->offset += 22; // skip some values
|
||||
// get the number of hMetric entries in hmtx table
|
||||
$this->fdt['numHMetrics'] = $this->fbyte->getUShort($this->offset);
|
||||
}
|
||||
|
||||
protected function getMaxpData(): void
|
||||
{
|
||||
$this->offset = $this->fdt['table']['maxp']['offset'];
|
||||
$this->offset += 4; // skip Table version number
|
||||
// get the the number of glyphs in the font.
|
||||
$this->fdt['numGlyphs'] = $this->fbyte->getUShort($this->offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font heights
|
||||
*/
|
||||
protected function getHeights(): void
|
||||
{
|
||||
// get xHeight (height of x)
|
||||
$this->fdt['XHeight'] = ($this->fdt['Ascent'] + $this->fdt['Descent']);
|
||||
if (! empty($this->fdt['ctgdata'][120])) {
|
||||
$this->offset = (
|
||||
$this->fdt['table']['glyf']['offset']
|
||||
+ $this->fdt['indexToLoc'][$this->fdt['ctgdata'][120]]
|
||||
+ 4
|
||||
);
|
||||
$yMin = $this->fbyte->getFWord($this->offset);
|
||||
$this->offset += 4;
|
||||
$yMax = $this->fbyte->getFWord($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->fdt['XHeight'] = (int) round(($yMax - $yMin) * $this->fdt['urk']);
|
||||
}
|
||||
|
||||
// get CapHeight (height of H)
|
||||
$this->fdt['CapHeight'] = (int) $this->fdt['Ascent'];
|
||||
if (! empty($this->fdt['ctgdata'][72])) {
|
||||
$this->offset = (
|
||||
$this->fdt['table']['glyf']['offset']
|
||||
+ $this->fdt['indexToLoc'][$this->fdt['ctgdata'][72]]
|
||||
+ 4
|
||||
);
|
||||
$yMin = $this->fbyte->getFWord($this->offset);
|
||||
$this->offset += 4;
|
||||
$yMax = $this->fbyte->getFWord($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->fdt['CapHeight'] = (int) round(($yMax - $yMin) * $this->fdt['urk']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font widths
|
||||
*/
|
||||
protected function getWidths(): void
|
||||
{
|
||||
// create widths array
|
||||
$chw = [];
|
||||
$this->offset = $this->fdt['table']['hmtx']['offset'];
|
||||
for ($i = 0; $i < $this->fdt['numHMetrics']; ++$i) {
|
||||
$chw[$i] = round($this->fbyte->getUFWord($this->offset) * $this->fdt['urk']);
|
||||
$this->offset += 4; // skip lsb
|
||||
}
|
||||
|
||||
if ($this->fdt['numHMetrics'] < $this->fdt['numGlyphs']) {
|
||||
// fill missing widths with the last value
|
||||
$chw = array_pad($chw, $this->fdt['numGlyphs'], $chw[($this->fdt['numHMetrics'] - 1)]);
|
||||
}
|
||||
|
||||
$this->fdt['MissingWidth'] = $chw[0];
|
||||
$this->fdt['cw'] = [];
|
||||
$this->fdt['cbbox'] = [];
|
||||
for ($cid = 0; $cid <= 65535; ++$cid) {
|
||||
if (isset($this->fdt['ctgdata'][$cid])) {
|
||||
if (isset($chw[$this->fdt['ctgdata'][$cid]])) {
|
||||
$this->fdt['cw'][$cid] = $chw[$this->fdt['ctgdata'][$cid]];
|
||||
}
|
||||
|
||||
if (isset($this->fdt['indexToLoc'][$this->fdt['ctgdata'][$cid]])) {
|
||||
$this->offset = (
|
||||
$this->fdt['table']['glyf']['offset']
|
||||
+ $this->fdt['indexToLoc'][$this->fdt['ctgdata'][$cid]]
|
||||
);
|
||||
$xMin = (int) round($this->fbyte->getFWord($this->offset + 2) * $this->fdt['urk']);
|
||||
$yMin = (int) round($this->fbyte->getFWord($this->offset + 4) * $this->fdt['urk']);
|
||||
$xMax = (int) round($this->fbyte->getFWord($this->offset + 6) * $this->fdt['urk']);
|
||||
$yMax = (int) round($this->fbyte->getFWord($this->offset + 8) * $this->fdt['urk']);
|
||||
$this->fdt['cbbox'][$cid] = [$xMin, $yMin, $xMax, $yMax];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add CTG entry
|
||||
*/
|
||||
protected function addCtgItem(int $cid, int $gid): void
|
||||
{
|
||||
$this->fdt['ctgdata'][$cid] = $gid;
|
||||
if (isset($this->subchars[$cid])) {
|
||||
$this->subglyphs[$gid] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the CID To GID Map.
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
*/
|
||||
protected function getCIDToGIDMap(): void
|
||||
{
|
||||
$this->fdt['ctgdata'] = [];
|
||||
foreach ($this->fdt['encodingTables'] as $enctable) {
|
||||
// get only specified Platform ID and Encoding ID
|
||||
if (
|
||||
($enctable['platformID'] == $this->fdt['platform_id'])
|
||||
&& ($enctable['encodingID'] == $this->fdt['encoding_id'])
|
||||
) {
|
||||
$this->offset = ($this->fdt['table']['cmap']['offset'] + $enctable['offset']);
|
||||
$format = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
match ($format) {
|
||||
0 => $this->processFormat0(),
|
||||
2 => $this->processFormat2(),
|
||||
4 => $this->processFormat4(),
|
||||
6 => $this->processFormat6(),
|
||||
8 => $this->processFormat8(),
|
||||
10 => $this->processFormat10(),
|
||||
12 => $this->processFormat12(),
|
||||
13 => $this->processFormat13(),
|
||||
14 => $this->processFormat14(),
|
||||
default => throw new FontException('Unsupported cmap format: ' . $format),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($this->fdt['ctgdata'][0])) {
|
||||
$this->fdt['ctgdata'][0] = 0;
|
||||
}
|
||||
|
||||
if ($this->fdt['type'] != 'TrueTypeUnicode') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($this->fdt['ctgdata']) != 256) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fdt['type'] = 'TrueType';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 0: Byte encoding table
|
||||
*/
|
||||
protected function processFormat0(): void
|
||||
{
|
||||
$this->offset += 4; // skip length and version/language
|
||||
for ($chr = 0; $chr < 256; ++$chr) {
|
||||
$gid = $this->fbyte->getByte($this->offset);
|
||||
$this->addCtgItem($chr, $gid);
|
||||
++$this->offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 2: High-byte mapping through table
|
||||
*/
|
||||
protected function processFormat2(): void
|
||||
{
|
||||
$this->offset += 4; // skip length and version/language
|
||||
$numSubHeaders = 0;
|
||||
for ($chr = 0; $chr < 256; ++$chr) {
|
||||
// Array that maps high bytes to subHeaders: value is subHeader index * 8.
|
||||
$subHeaderKeys[$chr] = ($this->fbyte->getUShort($this->offset) / 8);
|
||||
$this->offset += 2;
|
||||
if ($numSubHeaders < $subHeaderKeys[$chr]) {
|
||||
$numSubHeaders = $subHeaderKeys[$chr];
|
||||
}
|
||||
}
|
||||
|
||||
// the number of subHeaders is equal to the max of subHeaderKeys + 1
|
||||
++$numSubHeaders;
|
||||
// read subHeader structures
|
||||
$subHeaders = [];
|
||||
$numGlyphIndexArray = 0;
|
||||
for ($ish = 0; $ish < $numSubHeaders; ++$ish) {
|
||||
$subHeaders[$ish]['firstCode'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$subHeaders[$ish]['entryCount'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$subHeaders[$ish]['idDelta'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$subHeaders[$ish]['idRangeOffset'] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$subHeaders[$ish]['idRangeOffset'] -= (2 + (($numSubHeaders - $ish - 1) * 8));
|
||||
$subHeaders[$ish]['idRangeOffset'] /= 2;
|
||||
$numGlyphIndexArray += $subHeaders[$ish]['entryCount'];
|
||||
}
|
||||
|
||||
$glyphIndexArray = [
|
||||
0 => 0,
|
||||
];
|
||||
for ($gid = 0; $gid < $numGlyphIndexArray; ++$gid) {
|
||||
$glyphIndexArray[$gid] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
for ($chr = 0; $chr < 256; ++$chr) {
|
||||
$shk = $subHeaderKeys[$chr];
|
||||
if ($shk == 0) {
|
||||
// one byte code
|
||||
$cdx = $chr;
|
||||
$gid = $glyphIndexArray[0];
|
||||
$this->addCtgItem($cdx, $gid);
|
||||
} else {
|
||||
// two bytes code
|
||||
$start_byte = $subHeaders[$shk]['firstCode'];
|
||||
$end_byte = $start_byte + $subHeaders[$shk]['entryCount'];
|
||||
for ($jdx = $start_byte; $jdx < $end_byte; ++$jdx) {
|
||||
// combine high and low bytes
|
||||
$cdx = (($chr << 8) + $jdx);
|
||||
$idRangeOffset = ($subHeaders[$shk]['idRangeOffset'] + $jdx - $subHeaders[$shk]['firstCode']);
|
||||
$gid = max(0, (($glyphIndexArray[$idRangeOffset] + $subHeaders[$shk]['idDelta']) % 65536));
|
||||
$this->addCtgItem($cdx, $gid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 4: Segment mapping to delta values
|
||||
*/
|
||||
protected function processFormat4(): void
|
||||
{
|
||||
$length = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->offset += 2; // skip version/language
|
||||
$segCount = floor($this->fbyte->getUShort($this->offset) / 2);
|
||||
$this->offset += 2;
|
||||
$this->offset += 6; // skip searchRange, entrySelector, rangeShift
|
||||
$endCount = []; // array of end character codes for each segment
|
||||
for ($kdx = 0; $kdx < $segCount; ++$kdx) {
|
||||
$endCount[$kdx] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
$this->offset += 2; // skip reservedPad
|
||||
$startCount = []; // array of start character codes for each segment
|
||||
for ($kdx = 0; $kdx < $segCount; ++$kdx) {
|
||||
$startCount[$kdx] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
$idDelta = []; // delta for all character codes in segment
|
||||
for ($kdx = 0; $kdx < $segCount; ++$kdx) {
|
||||
$idDelta[$kdx] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
$idRangeOffset = []; // Offsets into glyphIdArray or 0
|
||||
for ($kdx = 0; $kdx < $segCount; ++$kdx) {
|
||||
$idRangeOffset[$kdx] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
|
||||
$glyphIdArray = []; // glyph index array
|
||||
for ($kdx = 0; $kdx < $gidlen; ++$kdx) {
|
||||
$glyphIdArray[$kdx] = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
for ($kdx = 0; $kdx < $segCount; ++$kdx) {
|
||||
for ($chr = $startCount[$kdx]; $chr <= $endCount[$kdx]; ++$chr) {
|
||||
if ($idRangeOffset[$kdx] == 0) {
|
||||
$gid = max(0, (($idDelta[$kdx] + $chr) % 65536));
|
||||
} else {
|
||||
$gid = (floor($idRangeOffset[$kdx] / 2) + ($chr - $startCount[$kdx]) - ($segCount - $kdx));
|
||||
$gid = max(0, (($glyphIdArray[$gid] + $idDelta[$kdx]) % 65536));
|
||||
}
|
||||
|
||||
$this->addCtgItem($chr, $gid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 6: Trimmed table mapping
|
||||
*/
|
||||
protected function processFormat6(): void
|
||||
{
|
||||
$this->offset += 4; // skip length and version/language
|
||||
$firstCode = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$entryCount = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
for ($kdx = 0; $kdx < $entryCount; ++$kdx) {
|
||||
$chr = ($kdx + $firstCode);
|
||||
$gid = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$this->addCtgItem($chr, $gid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 8: Mixed 16-bit and 32-bit coverage
|
||||
*/
|
||||
protected function processFormat8(): void
|
||||
{
|
||||
$this->offset += 10; // skip reserved, length and version/language
|
||||
for ($kdx = 0; $kdx < 8192; ++$kdx) {
|
||||
$is32[$kdx] = $this->fbyte->getByte($this->offset);
|
||||
++$this->offset;
|
||||
}
|
||||
|
||||
$nGroups = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
for ($idx = 0; $idx < $nGroups; ++$idx) {
|
||||
$startCharCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$endCharCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$startGlyphID = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
for ($cpw = $startCharCode; $cpw <= $endCharCode; ++$cpw) {
|
||||
$is32idx = floor($cpw / 8);
|
||||
if ((isset($is32[$is32idx])) && (($is32[$is32idx] & (1 << (7 - ($cpw % 8)))) == 0)) {
|
||||
$chr = $cpw;
|
||||
} else {
|
||||
// 32 bit format
|
||||
// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
|
||||
//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
|
||||
//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
|
||||
$chr = (((55232 + ($cpw >> 10)) << 10) + (0xDC00 + ($cpw & 0x3FF)) - 56_613_888);
|
||||
}
|
||||
|
||||
$this->addCtgItem($chr, $startGlyphID);
|
||||
$this->fdt['ctgdata'][$chr] = 0; // overwrite
|
||||
++$startGlyphID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 10: Trimmed array
|
||||
*/
|
||||
protected function processFormat10(): void
|
||||
{
|
||||
$this->offset += 10; // skip reserved, length and version/language
|
||||
$startCharCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$numChars = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
for ($kdx = 0; $kdx < $numChars; ++$kdx) {
|
||||
$chr = ($kdx + $startCharCode);
|
||||
$gid = $this->fbyte->getUShort($this->offset);
|
||||
$this->addCtgItem($chr, $gid);
|
||||
$this->offset += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 12: Segmented coverage
|
||||
*/
|
||||
protected function processFormat12(): void
|
||||
{
|
||||
$this->offset += 10; // skip length and version/language
|
||||
$nGroups = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
for ($kdx = 0; $kdx < $nGroups; ++$kdx) {
|
||||
$startCharCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$endCharCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
$startGlyphCode = $this->fbyte->getULong($this->offset);
|
||||
$this->offset += 4;
|
||||
for ($chr = $startCharCode; $chr <= $endCharCode; ++$chr) {
|
||||
$this->addCtgItem($chr, $startGlyphCode);
|
||||
++$startGlyphCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 13: Many-to-one range mappings
|
||||
*
|
||||
* @TODO: TO BE IMPLEMENTED
|
||||
*/
|
||||
protected function processFormat13(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Format 14: Unicode Variation Sequences
|
||||
*
|
||||
* @TODO: TO BE IMPLEMENTED
|
||||
*/
|
||||
protected function processFormat14(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
360
vendor/tecnickcom/tc-lib-pdf-font/src/Import/TypeOne.php
vendored
Normal file
360
vendor/tecnickcom/tc-lib-pdf-font/src/Import/TypeOne.php
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TypeOne.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\Import;
|
||||
|
||||
use Com\Tecnick\File\File;
|
||||
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-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
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
class TypeOne extends \Com\Tecnick\Pdf\Font\Import\Core
|
||||
{
|
||||
/**
|
||||
* Store font data
|
||||
*/
|
||||
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'];
|
||||
$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'];
|
||||
$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';
|
||||
$file = new File();
|
||||
$fpt = $file->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
|
||||
*/
|
||||
protected function extractFontInfo(): void
|
||||
{
|
||||
if (preg_match('#/FontName[\s]*+\/([^\s]*+)#', $this->font, $matches) !== 1) {
|
||||
preg_match('#/FullName[\s]*+\(([^\)]*+)#', $this->font, $matches);
|
||||
}
|
||||
|
||||
$name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]);
|
||||
if ($name === null) {
|
||||
throw new FontException('Unable to extract font name');
|
||||
}
|
||||
|
||||
$this->fdt['name'] = $name;
|
||||
preg_match('#/FontBBox[\s]*+{([^}]*+)#', $this->font, $matches);
|
||||
$rawbvl = explode(' ', trim($matches[1]));
|
||||
$bvl = [(int) $rawbvl[0], (int) $rawbvl[1], (int) $rawbvl[2], (int) $rawbvl[3]];
|
||||
$this->fdt['bbox'] = implode(' ', $bvl);
|
||||
$this->fdt['Ascent'] = $bvl[3];
|
||||
$this->fdt['Descent'] = $bvl[1];
|
||||
preg_match('#/ItalicAngle[\s]*+([0-9\+\-]*+)#', $this->font, $matches);
|
||||
$this->fdt['italicAngle'] = (int) $matches[1];
|
||||
|
||||
if ($this->fdt['italicAngle'] != 0) {
|
||||
$this->fdt['Flags'] |= 64;
|
||||
}
|
||||
|
||||
preg_match('#/UnderlinePosition[\s]*+([0-9\+\-]*+)#', $this->font, $matches);
|
||||
$this->fdt['underlinePosition'] = (int) $matches[1];
|
||||
preg_match('#/UnderlineThickness[\s]*+([0-9\+\-]*+)#', $this->font, $matches);
|
||||
$this->fdt['underlineThickness'] = (int) $matches[1];
|
||||
preg_match('#/isFixedPitch[\s]*+([^\s]*+)#', $this->font, $matches);
|
||||
if ($matches[1] == 'true') {
|
||||
$this->fdt['Flags'] = (((int) $this->fdt['Flags']) | 1);
|
||||
}
|
||||
|
||||
preg_match('#/Weight[\s]*+\(([^\)]*+)#', $this->font, $matches);
|
||||
if (! empty($matches[1])) {
|
||||
$this->fdt['weight'] = strtolower($matches[1]);
|
||||
}
|
||||
|
||||
$this->fdt['weight'] = 'Book';
|
||||
$this->fdt['Leading'] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Font information
|
||||
*
|
||||
* @return array<string, int>
|
||||
*/
|
||||
protected function getInternalMap(): array
|
||||
{
|
||||
$imap = [];
|
||||
if (preg_match_all('#dup[\s]([0-9]+)[\s]*+/([^\s]*+)[\s]put#sU', $this->font, $fmap, PREG_SET_ORDER) > 0) {
|
||||
foreach ($fmap as $val) {
|
||||
$imap[$val[2]] = (int) $val[1];
|
||||
}
|
||||
}
|
||||
|
||||
return $imap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt eexec encrypted part
|
||||
*/
|
||||
protected function getEplain(): string
|
||||
{
|
||||
$csr = 55665; // eexec encryption constant
|
||||
$cc1 = 52845;
|
||||
$cc2 = 22719;
|
||||
$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) % 65536);
|
||||
}
|
||||
|
||||
return $eplain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract eexec info
|
||||
*
|
||||
* @return array<int, array<int, string>>
|
||||
*/
|
||||
protected function extractEplainInfo(): array
|
||||
{
|
||||
$eplain = $this->getEplain();
|
||||
if (preg_match('#/ForceBold[\s]*+([^\s]*+)#', $eplain, $matches) > 0 && $matches[1] == 'true') {
|
||||
$this->fdt['Flags'] |= 0x40000;
|
||||
}
|
||||
|
||||
$this->extractStem($eplain);
|
||||
if (preg_match('#/BlueValues[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
|
||||
$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
|
||||
{
|
||||
if (preg_match('#/StdVW[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
|
||||
$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) > 0 ? (int) $matches[1] : 30;
|
||||
|
||||
if (preg_match('#/Cap[X]?Height[\s]*+\[([^\]]*+)#', $eplain, $matches) > 0) {
|
||||
$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;
|
||||
if (preg_match('#/lenIV[\s]*+([\d]*+)#', $eplain, $matches) > 0) {
|
||||
$this->fdt['lenIV'] = (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<int, string>>
|
||||
*/
|
||||
protected function getCharstringData(string $eplain): array
|
||||
{
|
||||
$this->fdt['enc_map'] = [];
|
||||
$eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1));
|
||||
preg_match_all('#/([A-Za-z0-9\.]*+)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER);
|
||||
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]];
|
||||
}
|
||||
|
||||
if ($this->fdt['enc_map'] === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$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
|
||||
*/
|
||||
protected function decodeNumber(
|
||||
int $idx,
|
||||
int &$cck,
|
||||
int &$cid,
|
||||
array &$ccom,
|
||||
array &$cdec,
|
||||
array &$cwidths
|
||||
): int {
|
||||
if ($ccom[$idx] == 255) {
|
||||
$sval = chr($ccom[($idx + 1)]) . chr($ccom[($idx + 2)]) . chr($ccom[($idx + 3)]) . chr($ccom[($idx + 4)]);
|
||||
$vsval = unpack('li', $sval);
|
||||
if (($vsval === false) || (!is_numeric($vsval['i']))) {
|
||||
throw new FontException('Unable to unpack number');
|
||||
}
|
||||
|
||||
$cdec[$cck] = (int) $vsval['i'];
|
||||
return ($idx + 5);
|
||||
}
|
||||
|
||||
if ($ccom[$idx] >= 251) {
|
||||
$cdec[$cck] = ((-($ccom[$idx] - 251) * 256) - $ccom[($idx + 1)] - 108);
|
||||
return ($idx + 2);
|
||||
}
|
||||
|
||||
if ($ccom[$idx] >= 247) {
|
||||
$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
|
||||
*/
|
||||
protected function process(): void
|
||||
{
|
||||
$this->storeFontData();
|
||||
$this->extractFontInfo();
|
||||
$imap = $this->getInternalMap();
|
||||
$matches = $this->extractEplainInfo();
|
||||
$cwidths = [];
|
||||
$cc1 = 52845;
|
||||
$cc2 = 22719;
|
||||
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) % 65536);
|
||||
}
|
||||
|
||||
// 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