Feature: Labelprint für Kistenetiketten hinzugefügt
This commit is contained in:
298
vendor/tecnickcom/tc-lib-pdf-font/src/Buffer.php
vendored
Normal file
298
vendor/tecnickcom/tc-lib-pdf-font/src/Buffer.php
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Buffer.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\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Buffer
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
abstract class Buffer
|
||||
{
|
||||
/**
|
||||
* Array containing all fonts data
|
||||
*
|
||||
* @var array<string, TFontData>
|
||||
*/
|
||||
protected array $font = [];
|
||||
|
||||
/**
|
||||
* Font counter
|
||||
*/
|
||||
protected int $numfonts = 0;
|
||||
|
||||
/**
|
||||
* Array containing encoding differences
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $encdiff = [];
|
||||
|
||||
/**
|
||||
* Index for Encoding differences
|
||||
*/
|
||||
protected int $numdiffs = 0;
|
||||
|
||||
/**
|
||||
* Array containing font definitions grouped by file
|
||||
*
|
||||
* @var array<string, array{
|
||||
* 'dir': string,
|
||||
* 'keys': array<string>,
|
||||
* 'length1': int,
|
||||
* 'length2': int,
|
||||
* 'subset': bool,
|
||||
* }>
|
||||
*/
|
||||
protected array $file = [];
|
||||
|
||||
/**
|
||||
* Initialize fonts buffer
|
||||
*
|
||||
* @param float $kunit Unit of measure conversion ratio.
|
||||
* @param bool $subset If true embedd only a subset of the fonts
|
||||
* (stores only the information related to
|
||||
* the used characters); If false embedd
|
||||
* full font; This option is valid only for
|
||||
* TrueTypeUnicode fonts and it is disabled
|
||||
* for PDF/A. If you want to enable users to
|
||||
* modify the document, set this parameter
|
||||
* to false. If you subset the font, the
|
||||
* person who receives your PDF would need
|
||||
* to have your same font in order to make
|
||||
* changes to your PDF. The file size of the
|
||||
* PDF would also be smaller because you are
|
||||
* embedding only a subset. NOTE: This
|
||||
* option is computational and memory
|
||||
* intensive.
|
||||
* @param bool $unicode True if we are in Unicode mode, False otherwhise.
|
||||
* @param bool $pdfa True if we are in PDF/A mode.
|
||||
*
|
||||
* @return string Font key
|
||||
*/
|
||||
public function __construct(
|
||||
protected float $kunit,
|
||||
protected bool $subset = false,
|
||||
protected bool $unicode = true,
|
||||
protected bool $pdfa = false
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default subset mode
|
||||
*/
|
||||
public function isSubsetMode(): bool
|
||||
{
|
||||
return $this->subset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fonts buffer
|
||||
*
|
||||
* @return array<string, TFontData>
|
||||
*/
|
||||
public function getFonts(): array
|
||||
{
|
||||
return $this->font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fonts buffer
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function getEncDiffs(): array
|
||||
{
|
||||
return $this->encdiff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified font key exist on buffer
|
||||
*
|
||||
* @param string $key Font key
|
||||
*/
|
||||
public function isValidKey(string $key): bool
|
||||
{
|
||||
return isset($this->font[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get font by key
|
||||
*
|
||||
* @param string $key Font key
|
||||
*
|
||||
* @return TFontData Returns the fonts array.
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function getFont(string $key): array
|
||||
{
|
||||
if (! isset($this->font[$key])) {
|
||||
throw new FontException('The font ' . $key . ' has not been loaded');
|
||||
}
|
||||
|
||||
return $this->font[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a character to the subset list
|
||||
*
|
||||
* @param string $key The font key
|
||||
* @param int $char The Unicode character value to add
|
||||
*/
|
||||
public function addSubsetChar(string $key, int $char): void
|
||||
{
|
||||
if (! isset($this->font[$key])) {
|
||||
throw new FontException('The font ' . $key . ' has not been loaded');
|
||||
}
|
||||
|
||||
$this->font[$key]['subsetchars'][$char] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new font to the fonts buffer
|
||||
*
|
||||
* The definition file (and the font file itself when embedding) must be present either in the current directory
|
||||
* or in the one indicated by K_PATH_FONTS if the constant is defined.
|
||||
*
|
||||
* @param int $objnum Current PDF object number
|
||||
* @param string $font Font family.
|
||||
* If it is a standard family name, it will override the corresponding font.
|
||||
* @param string $style Font style.
|
||||
* Possible values are (case insensitive):
|
||||
* regular (default)
|
||||
* B: bold
|
||||
* I: italic
|
||||
* U: underline
|
||||
* D: strikeout (linethrough)
|
||||
* O: overline
|
||||
* @param string $ifile The font definition file (or empty for autodetect).
|
||||
* By default, the name is built from the family and style, in lower case with no spaces.
|
||||
* @param ?bool $subset If true embed only a subset of the font
|
||||
* (stores only the information related to
|
||||
* the used characters); If false embed
|
||||
* full font; This option is valid only
|
||||
* for TrueTypeUnicode fonts and it is
|
||||
* disabled for PDF/A. If you want to
|
||||
* enable users to modify the document,
|
||||
* set this parameter to false. If you
|
||||
* subset the font, the person who
|
||||
* receives your PDF would need to have
|
||||
* your same font in order to make changes
|
||||
* to your PDF. The file size of the PDF
|
||||
* would also be smaller because you are
|
||||
* embedding only a subset. Set this to
|
||||
* null to use the default value. NOTE:
|
||||
* This option is computational and memory
|
||||
* intensive.
|
||||
*
|
||||
* @return string Font key
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function add(
|
||||
int &$objnum,
|
||||
string $font,
|
||||
string $style = '',
|
||||
string $ifile = '',
|
||||
?bool $subset = null
|
||||
) {
|
||||
if ($subset === null) {
|
||||
$subset = $this->subset;
|
||||
}
|
||||
|
||||
$fobj = new Font($font, $style, $ifile, $subset, $this->unicode, $this->pdfa);
|
||||
$key = $fobj->getFontkey();
|
||||
|
||||
if (isset($this->font[$key])) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$fobj->load();
|
||||
$this->font[$key] = $fobj->getFontData();
|
||||
|
||||
$this->setFontFile($key);
|
||||
$this->setFontDiff($key);
|
||||
|
||||
$this->font[$key]['i'] = ++$this->numfonts;
|
||||
$this->font[$key]['n'] = ++$objnum;
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set font file and subset
|
||||
*
|
||||
* @param string $key Font key
|
||||
*/
|
||||
protected function setFontFile(string $key): void
|
||||
{
|
||||
if (empty($this->font[$key]['file'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $this->font[$key]['file'];
|
||||
if (! isset($this->file[$file])) {
|
||||
$this->file[$file] = [
|
||||
'dir' => '',
|
||||
'keys' => [],
|
||||
'length1' => 0,
|
||||
'length2' => 0,
|
||||
'subset' => false,
|
||||
];
|
||||
}
|
||||
|
||||
if (! in_array($key, $this->file[$file]['keys'])) {
|
||||
$this->file[$file]['keys'][] = $key;
|
||||
}
|
||||
|
||||
$this->file[$file]['dir'] = $this->font[$key]['dir'];
|
||||
$this->file[$file]['length1'] = $this->font[$key]['length1'];
|
||||
$this->file[$file]['length2'] = $this->font[$key]['length2'];
|
||||
$this->file[$file]['subset'] = ($this->file[$file]['subset'] && $this->font[$key]['subset']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set font diff
|
||||
*
|
||||
* @param string $key Font key
|
||||
*/
|
||||
protected function setFontDiff(string $key): void
|
||||
{
|
||||
if (empty($this->font[$key]['diff'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$diffid = array_search($this->font[$key]['diff'], $this->encdiff, true);
|
||||
if ($diffid === false) {
|
||||
$diffid = ++$this->numdiffs;
|
||||
$this->encdiff[$diffid] = $this->font[$key]['diff'];
|
||||
}
|
||||
|
||||
$this->font[$key]['diffid'] = $diffid;
|
||||
}
|
||||
}
|
||||
60
vendor/tecnickcom/tc-lib-pdf-font/src/Core.php
vendored
Normal file
60
vendor/tecnickcom/tc-lib-pdf-font/src/Core.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\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
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
/**
|
||||
* Core fonts
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public const FONT = [
|
||||
'courier' => 'Courier',
|
||||
'courierB' => 'Courier-Bold',
|
||||
'courierI' => 'Courier-Oblique',
|
||||
'courierBI' => 'Courier-BoldOblique',
|
||||
'helvetica' => 'Helvetica',
|
||||
'helveticaB' => 'Helvetica-Bold',
|
||||
'helveticaI' => 'Helvetica-Oblique',
|
||||
'helveticaBI' => 'Helvetica-BoldOblique',
|
||||
'timesroman' => 'Times-Roman',
|
||||
'times' => 'Times-Roman',
|
||||
'timesB' => 'Times-Bold',
|
||||
'timesI' => 'Times-Italic',
|
||||
'timesBI' => 'Times-BoldItalic',
|
||||
'symbol' => 'Symbol',
|
||||
'symbolB' => 'Symbol',
|
||||
'symbolI' => 'Symbol',
|
||||
'symbolBI' => 'Symbol',
|
||||
'zapfdingbats' => 'ZapfDingbats',
|
||||
'zapfdingbatsB' => 'ZapfDingbats',
|
||||
'zapfdingbatsI' => 'ZapfDingbats',
|
||||
'zapfdingbatsBI' => 'ZapfDingbats',
|
||||
];
|
||||
}
|
||||
34
vendor/tecnickcom/tc-lib-pdf-font/src/Exception.php
vendored
Normal file
34
vendor/tecnickcom/tc-lib-pdf-font/src/Exception.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception.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;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Exception
|
||||
*
|
||||
* Custom Exception class
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
207
vendor/tecnickcom/tc-lib-pdf-font/src/Font.php
vendored
Normal file
207
vendor/tecnickcom/tc-lib-pdf-font/src/Font.php
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Font.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\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\File\File;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Font
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class Font extends \Com\Tecnick\Pdf\Font\Load
|
||||
{
|
||||
/**
|
||||
* Load an imported font
|
||||
*
|
||||
* The definition file (and the font file itself when embedding) must be present either in the current directory
|
||||
* or in the one indicated by K_PATH_FONTS if the constant is defined.
|
||||
*
|
||||
* @param string $font Font family.
|
||||
* If it is a
|
||||
* standard
|
||||
* family name,
|
||||
* it will
|
||||
* override the
|
||||
* corresponding
|
||||
* font.
|
||||
* @param string $style Font style.
|
||||
* Possible
|
||||
* values are
|
||||
* (case
|
||||
* insensitive):
|
||||
* regular
|
||||
* (default)
|
||||
* B: bold I:
|
||||
* italic U:
|
||||
* underline
|
||||
* D:
|
||||
* strikeout
|
||||
* (linethrough)
|
||||
* O: overline
|
||||
* @param string $ifile The font definition file (or empty for autodetect).
|
||||
* By default, the name is built from the family and
|
||||
* style, in lower case with no spaces.
|
||||
* @param bool $subset If true embedd only a subset of the font
|
||||
* (stores only the information related to
|
||||
* the used characters); If false embedd
|
||||
* full font; This option is valid only for
|
||||
* TrueTypeUnicode fonts and it is disabled
|
||||
* for PDF/A. If you want to enable users
|
||||
* to modify the document, set this
|
||||
* parameter to false. If you subset the
|
||||
* font, the person who receives your PDF
|
||||
* would need to have your same font in
|
||||
* order to make changes to your PDF. The
|
||||
* file size of the PDF would also be
|
||||
* smaller because you are embedding only a
|
||||
* subset.
|
||||
* @param bool $unicode True if we are in Unicode mode, False otherwhise.
|
||||
* @param bool $pdfa True if we are in PDF/A mode.
|
||||
* @param bool $compress Set to false to disable stream compression.
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function __construct(
|
||||
string $font,
|
||||
string $style = '',
|
||||
string $ifile = '',
|
||||
bool $subset = false,
|
||||
bool $unicode = true,
|
||||
bool $pdfa = false,
|
||||
bool $compress = true
|
||||
) {
|
||||
if ($font === '') {
|
||||
throw new FontException('empty font family name');
|
||||
}
|
||||
|
||||
if (FILE::hasDoubleDots($ifile) || FILE::hasForbiddenProtocol($ifile)) {
|
||||
throw new FontException('Invalid font ifile: ' . $ifile);
|
||||
}
|
||||
|
||||
$this->data['ifile'] = $ifile;
|
||||
$this->data['family'] = $font;
|
||||
$this->data['unicode'] = $unicode;
|
||||
$this->data['pdfa'] = $pdfa;
|
||||
$this->data['compress'] = $compress;
|
||||
$this->data['subset'] = $subset;
|
||||
$this->data['subsetchars'] = array_fill(0, 255, true);
|
||||
|
||||
// generate the font key and set styles
|
||||
$this->setStyle($style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font key
|
||||
*/
|
||||
public function getFontkey(): string
|
||||
{
|
||||
return $this->data['key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the font data
|
||||
*
|
||||
* @return TFontData
|
||||
*/
|
||||
public function getFontData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set style and normalize the font name
|
||||
*
|
||||
* @param string $style Style
|
||||
*/
|
||||
protected function setStyle(string $style): void
|
||||
{
|
||||
$style = strtoupper($style);
|
||||
if (str_ends_with($this->data['family'], 'I')) {
|
||||
$style .= 'I';
|
||||
$this->data['family'] = substr($this->data['family'], 0, -1);
|
||||
}
|
||||
|
||||
if (str_ends_with($this->data['family'], 'B')) {
|
||||
$style .= 'B';
|
||||
$this->data['family'] = substr($this->data['family'], 0, -1);
|
||||
}
|
||||
|
||||
// normalize family name
|
||||
$this->data['family'] = strtolower($this->data['family']);
|
||||
if ((! $this->data['unicode']) && ($this->data['family'] == 'arial')) {
|
||||
$this->data['family'] = 'helvetica';
|
||||
}
|
||||
|
||||
if (($this->data['family'] == 'symbol') || ($this->data['family'] == 'zapfdingbats')) {
|
||||
$style = '';
|
||||
}
|
||||
|
||||
if ($this->data['pdfa'] && (isset(Core::FONT[$this->data['family']]))) {
|
||||
// core fonts must be embedded in PDF/A
|
||||
$this->data['family'] = 'pdfa' . $this->data['family'];
|
||||
}
|
||||
|
||||
$this->setStyleMode($style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set style mode properties
|
||||
*
|
||||
* @param string $style Style
|
||||
*/
|
||||
protected function setStyleMode(string $style): void
|
||||
{
|
||||
$suffix = '';
|
||||
if (str_contains($style, 'B')) {
|
||||
$this->data['mode']['bold'] = true;
|
||||
$suffix .= 'B';
|
||||
}
|
||||
|
||||
if (str_contains($style, 'I')) {
|
||||
$this->data['mode']['italic'] = true;
|
||||
$suffix .= 'I';
|
||||
}
|
||||
|
||||
$this->data['style'] = (string) $suffix;
|
||||
if (str_contains($style, 'U')) {
|
||||
$this->data['style'] .= 'U';
|
||||
$this->data['mode']['underline'] = true;
|
||||
}
|
||||
|
||||
if (str_contains($style, 'D')) {
|
||||
$this->data['style'] .= 'D';
|
||||
$this->data['mode']['linethrough'] = true;
|
||||
}
|
||||
|
||||
if (str_contains($style, 'O')) {
|
||||
$this->data['style'] .= 'O';
|
||||
$this->data['mode']['overline'] = true;
|
||||
}
|
||||
|
||||
$this->data['key'] = $this->data['family'] . $suffix;
|
||||
}
|
||||
}
|
||||
598
vendor/tecnickcom/tc-lib-pdf-font/src/Import.php
vendored
Normal file
598
vendor/tecnickcom/tc-lib-pdf-font/src/Import.php
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
506
vendor/tecnickcom/tc-lib-pdf-font/src/Load.php
vendored
Normal file
506
vendor/tecnickcom/tc-lib-pdf-font/src/Load.php
vendored
Normal file
@@ -0,0 +1,506 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Load.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\Dir;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Load
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <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-type TFontDataCidInfo array{
|
||||
* 'Ordering': string,
|
||||
* 'Registry': string,
|
||||
* 'Supplement': int,
|
||||
* 'uni2cid': array<int, int>,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontDataDesc array{
|
||||
* 'Ascent': int,
|
||||
* 'AvgWidth': int,
|
||||
* 'CapHeight': int,
|
||||
* 'Descent': int,
|
||||
* 'Flags': int,
|
||||
* 'FontBBox': string,
|
||||
* 'ItalicAngle': int,
|
||||
* 'Leading': int,
|
||||
* 'MaxWidth': int,
|
||||
* 'MissingWidth': int,
|
||||
* 'StemH': int,
|
||||
* 'StemV': int,
|
||||
* 'XHeight': int,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontDataEncTable array{
|
||||
* 'encodingID': int,
|
||||
* 'offset': int,
|
||||
* 'platformID': int,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontDataMode array{
|
||||
* 'bold': bool,
|
||||
* 'italic': bool,
|
||||
* 'linethrough': bool,
|
||||
* 'overline': bool,
|
||||
* 'underline': bool,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontDataTableItem array{
|
||||
* 'checkSum': int,
|
||||
* 'data': string,
|
||||
* 'length': int,
|
||||
* 'offset': int,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontData array{
|
||||
* 'Ascender': int,
|
||||
* 'Ascent': int,
|
||||
* 'AvgWidth': float,
|
||||
* 'CapHeight': int,
|
||||
* 'CharacterSet': string,
|
||||
* 'Descender': int,
|
||||
* 'Descent': int,
|
||||
* 'EncodingScheme': string,
|
||||
* 'FamilyName': string,
|
||||
* 'Flags': int,
|
||||
* 'FontBBox': array<int>,
|
||||
* 'FontName': string,
|
||||
* 'FullName': string,
|
||||
* 'IsFixedPitch': bool,
|
||||
* 'ItalicAngle': int,
|
||||
* 'Leading': int,
|
||||
* 'MaxWidth': int,
|
||||
* 'MissingWidth': int,
|
||||
* 'StdHW': int,
|
||||
* 'StdVW': int,
|
||||
* 'StemH': int,
|
||||
* 'StemV': int,
|
||||
* 'UnderlinePosition': int,
|
||||
* 'UnderlineThickness': int,
|
||||
* 'Version': string,
|
||||
* 'Weight': string,
|
||||
* 'XHeight': int,
|
||||
* 'bbox': string,
|
||||
* 'cbbox': array<int, array<int, int>>,
|
||||
* 'cidinfo': TFontDataCidInfo,
|
||||
* 'compress': bool,
|
||||
* 'ctg': string,
|
||||
* 'ctgdata': array<int, int>,
|
||||
* 'cw': array<int, int>,
|
||||
* 'datafile': string,
|
||||
* 'desc': TFontDataDesc,
|
||||
* 'diff': string,
|
||||
* 'diff_n': int,
|
||||
* 'dir': string,
|
||||
* 'dw': int,
|
||||
* 'enc': string,
|
||||
* 'enc_map': array<int, string>,
|
||||
* 'encodingTables': array<int, TFontDataEncTable>,
|
||||
* 'encoding_id': int,
|
||||
* 'encrypted': string,
|
||||
* 'fakestyle': bool,
|
||||
* 'family': string,
|
||||
* 'file': string,
|
||||
* 'file_n': int,
|
||||
* 'file_name': string,
|
||||
* 'i': int,
|
||||
* 'ifile': string,
|
||||
* 'indexToLoc': array<int, int>,
|
||||
* 'input_file': string,
|
||||
* 'isUnicode': bool,
|
||||
* 'italicAngle': float,
|
||||
* 'key': string,
|
||||
* 'lenIV': int,
|
||||
* 'length1': int,
|
||||
* 'length2': int,
|
||||
* 'linked': bool,
|
||||
* 'mode': TFontDataMode,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* 'numGlyphs': int,
|
||||
* 'numHMetrics': int,
|
||||
* 'originalsize': int,
|
||||
* 'pdfa': bool,
|
||||
* 'platform_id': int,
|
||||
* 'settype': string,
|
||||
* 'short_offset': bool,
|
||||
* 'size1': int,
|
||||
* 'size2': int,
|
||||
* 'style': string,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* 'table': array<string, TFontDataTableItem>,
|
||||
* 'tot_num_glyphs': int,
|
||||
* 'type': string,
|
||||
* 'underlinePosition': int,
|
||||
* 'underlineThickness': int,
|
||||
* 'unicode': bool,
|
||||
* 'unitsPerEm': int,
|
||||
* 'up': int,
|
||||
* 'urk': float,
|
||||
* 'ut': int,
|
||||
* 'weight': string,
|
||||
* }
|
||||
*/
|
||||
abstract class Load
|
||||
{
|
||||
/**
|
||||
* Valid Font types
|
||||
*
|
||||
* @var array<string, bool> Font types
|
||||
*/
|
||||
protected const FONTTYPES = [
|
||||
'Core' => true,
|
||||
'TrueType' => true,
|
||||
'TrueTypeUnicode' => true,
|
||||
'Type1' => true,
|
||||
'cidfont0' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* Font data
|
||||
*
|
||||
* @var TFontData
|
||||
*/
|
||||
protected array $data = [
|
||||
'Ascender' => 0,
|
||||
'Ascent' => 0,
|
||||
'AvgWidth' => 0.0,
|
||||
'CapHeight' => 0,
|
||||
'CharacterSet' => '',
|
||||
'Descender' => 0,
|
||||
'Descent' => 0,
|
||||
'EncodingScheme' => '',
|
||||
'FamilyName' => '',
|
||||
'Flags' => 0,
|
||||
'FontBBox' => [],
|
||||
'FontName' => '',
|
||||
'FullName' => '',
|
||||
'IsFixedPitch' => false,
|
||||
'ItalicAngle' => 0,
|
||||
'Leading' => 0,
|
||||
'MaxWidth' => 0,
|
||||
'MissingWidth' => 0,
|
||||
'StdHW' => 0,
|
||||
'StdVW' => 0,
|
||||
'StemH' => 0,
|
||||
'StemV' => 0,
|
||||
'UnderlinePosition' => 0,
|
||||
'UnderlineThickness' => 0,
|
||||
'Version' => '',
|
||||
'Weight' => '',
|
||||
'XHeight' => 0,
|
||||
'bbox' => '',
|
||||
'cbbox' => [],
|
||||
'cidinfo' => [
|
||||
'Ordering' => '',
|
||||
'Registry' => '',
|
||||
'Supplement' => 0,
|
||||
'uni2cid' => [],
|
||||
],
|
||||
'compress' => false,
|
||||
'ctg' => '',
|
||||
'ctgdata' => [],
|
||||
'cw' => [],
|
||||
'datafile' => '',
|
||||
'desc' => [
|
||||
'Ascent' => 0,
|
||||
'AvgWidth' => 0,
|
||||
'CapHeight' => 0,
|
||||
'Descent' => 0,
|
||||
'Flags' => 0,
|
||||
'FontBBox' => '',
|
||||
'ItalicAngle' => 0,
|
||||
'Leading' => 0,
|
||||
'MaxWidth' => 0,
|
||||
'MissingWidth' => 0,
|
||||
'StemH' => 0,
|
||||
'StemV' => 0,
|
||||
'XHeight' => 0,
|
||||
],
|
||||
'diff' => '',
|
||||
'diff_n' => 0,
|
||||
'dir' => '',
|
||||
'dw' => 0,
|
||||
'enc' => '',
|
||||
'enc_map' => [],
|
||||
'encodingTables' => [],
|
||||
'encoding_id' => 0,
|
||||
'encrypted' => '',
|
||||
'fakestyle' => false,
|
||||
'family' => '',
|
||||
'file' => '',
|
||||
'file_n' => 0,
|
||||
'file_name' => '',
|
||||
'i' => 0,
|
||||
'ifile' => '',
|
||||
'indexToLoc' => [],
|
||||
'input_file' => '',
|
||||
'isUnicode' => false,
|
||||
'italicAngle' => 0,
|
||||
'key' => '',
|
||||
'lenIV' => 0,
|
||||
'length1' => 0,
|
||||
'length2' => 0,
|
||||
'linked' => false,
|
||||
'mode' => [
|
||||
'bold' => false,
|
||||
'italic' => false,
|
||||
'linethrough' => false,
|
||||
'overline' => false,
|
||||
'underline' => false,
|
||||
],
|
||||
'n' => 0,
|
||||
'name' => '',
|
||||
'numGlyphs' => 0,
|
||||
'numHMetrics' => 0,
|
||||
'originalsize' => 0,
|
||||
'pdfa' => false,
|
||||
'platform_id' => 0,
|
||||
'settype' => '',
|
||||
'short_offset' => false,
|
||||
'size1' => 0,
|
||||
'size2' => 0,
|
||||
'style' => '',
|
||||
'subset' => false,
|
||||
'subsetchars' => [],
|
||||
'table' => [],
|
||||
'tot_num_glyphs' => 0,
|
||||
'type' => '',
|
||||
'underlinePosition' => 0,
|
||||
'underlineThickness' => 0,
|
||||
'unicode' => false,
|
||||
'unitsPerEm' => 0,
|
||||
'up' => 0,
|
||||
'urk' => 0.0,
|
||||
'ut' => 0,
|
||||
'weight' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Load the font data
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function load(): void
|
||||
{
|
||||
$this->getFontInfo();
|
||||
$this->checkType();
|
||||
$this->setName();
|
||||
$this->setDefaultWidth();
|
||||
if ($this->data['fakestyle']) {
|
||||
$this->setArtificialStyles();
|
||||
}
|
||||
|
||||
$this->setFileData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the font data
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
*/
|
||||
protected function getFontInfo(): void
|
||||
{
|
||||
$this->findFontFile();
|
||||
|
||||
// read the font definition file
|
||||
if (! @is_readable($this->data['ifile'])) {
|
||||
throw new FontException('unable to read file: ' . $this->data['ifile']);
|
||||
}
|
||||
|
||||
$fdt = @file_get_contents($this->data['ifile']);
|
||||
if ($fdt === false) {
|
||||
throw new FontException('unable to read file: ' . $this->data['ifile']);
|
||||
}
|
||||
|
||||
$fdtdata = @json_decode($fdt, true, 5, JSON_OBJECT_AS_ARRAY);
|
||||
if ($fdtdata === null) {
|
||||
throw new FontException('JSON decoding error [' . json_last_error() . ']');
|
||||
}
|
||||
|
||||
if (! is_array($fdtdata) || (! isset($fdtdata['type']))) {
|
||||
throw new FontException('fhe font definition file has a bad format: ' . $this->data['ifile']);
|
||||
}
|
||||
|
||||
$this->data = array_replace_recursive($this->data, $fdtdata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of font directories
|
||||
*
|
||||
* @return array<string> Font directories
|
||||
*/
|
||||
protected function findFontDirectories(): array
|
||||
{
|
||||
$dir = new Dir();
|
||||
$dirs = [''];
|
||||
if (defined('K_PATH_FONTS')) {
|
||||
$dirs[] = K_PATH_FONTS;
|
||||
$glb = glob(K_PATH_FONTS . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
|
||||
if ($glb !== false) {
|
||||
$dirs = [...$dirs, ...$glb];
|
||||
}
|
||||
}
|
||||
|
||||
$parent_font_dir = $dir->findParentDir('fonts', __DIR__);
|
||||
if (($parent_font_dir !== '') && ($parent_font_dir !== '/')) {
|
||||
$dirs[] = $parent_font_dir;
|
||||
$glb = glob($parent_font_dir . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
|
||||
if ($glb !== false) {
|
||||
$dirs = array_merge($dirs, $glb);
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($dirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the font data
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
protected function findFontFile(): void
|
||||
{
|
||||
if (! empty($this->data['ifile'])) {
|
||||
$this->data['dir'] = dirname($this->data['ifile']);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data['ifile'] = strtolower($this->data['key']) . '.json';
|
||||
|
||||
// find font definition file names
|
||||
$files = array_unique(
|
||||
[strtolower($this->data['key']) . '.json', strtolower($this->data['family']) . '.json']
|
||||
);
|
||||
|
||||
// directories where to search for the font definition file
|
||||
$dirs = $this->findFontDirectories();
|
||||
|
||||
foreach ($files as $file) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (@is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
|
||||
$this->data['ifile'] = $dir . DIRECTORY_SEPARATOR . $file;
|
||||
$this->data['dir'] = $dir;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
// we haven't found the version with style variations
|
||||
$this->data['fakestyle'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setDefaultWidth(): void
|
||||
{
|
||||
if (! empty($this->data['dw'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->data['desc']['MissingWidth'] > 0) {
|
||||
$this->data['dw'] = $this->data['desc']['MissingWidth'];
|
||||
} elseif (! empty($this->data['cw'][32])) {
|
||||
$this->data['dw'] = $this->data['cw'][32];
|
||||
} else {
|
||||
$this->data['dw'] = 600;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Font Type
|
||||
*/
|
||||
protected function checkType(): void
|
||||
{
|
||||
if (isset(self::FONTTYPES[$this->data['type']])) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new FontException('Unknow font type: ' . $this->data['type']);
|
||||
}
|
||||
|
||||
protected function setName(): void
|
||||
{
|
||||
if ($this->data['type'] == 'Core') {
|
||||
$this->data['name'] = (string) Core::FONT[$this->data['key']];
|
||||
$this->data['subset'] = false;
|
||||
} elseif (($this->data['type'] == 'Type1') || ($this->data['type'] == 'TrueType')) {
|
||||
$this->data['subset'] = false;
|
||||
} elseif ($this->data['type'] == 'TrueTypeUnicode') {
|
||||
$this->data['enc'] = 'Identity-H';
|
||||
} elseif (($this->data['type'] == 'cidfont0') && ($this->data['pdfa'])) {
|
||||
throw new FontException('CID0 fonts are not supported, all fonts must be embedded in PDF/A mode!');
|
||||
}
|
||||
|
||||
if (empty($this->data['name'])) {
|
||||
$this->data['name'] = (string) $this->data['key'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set artificial styles if the font variation file is missing
|
||||
*/
|
||||
protected function setArtificialStyles(): void
|
||||
{
|
||||
// artificial bold
|
||||
if ($this->data['mode']['bold']) {
|
||||
$this->data['name'] .= 'Bold';
|
||||
$this->data['desc']['StemV'] = empty($this->data['desc']['StemV'])
|
||||
? 123 : (int) round($this->data['desc']['StemV'] * 1.75);
|
||||
}
|
||||
|
||||
// artificial italic
|
||||
if ($this->data['mode']['italic']) {
|
||||
$this->data['name'] .= 'Italic';
|
||||
if (! empty($this->data['desc']['ItalicAngle'])) {
|
||||
$this->data['desc']['ItalicAngle'] -= 11;
|
||||
} else {
|
||||
$this->data['desc']['ItalicAngle'] = -11;
|
||||
}
|
||||
|
||||
if (! empty($this->data['desc']['Flags'])) {
|
||||
$this->data['desc']['Flags'] |= 64; //bit 7
|
||||
} else {
|
||||
$this->data['desc']['Flags'] = 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setFileData(): void
|
||||
{
|
||||
if (empty($this->data['file'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_contains($this->data['type'], 'TrueType')) {
|
||||
$this->data['length1'] = $this->data['originalsize'];
|
||||
$this->data['length2'] = 0;
|
||||
} elseif ($this->data['type'] != 'Core') {
|
||||
$this->data['length1'] = $this->data['size1'];
|
||||
$this->data['length2'] = $this->data['size2'];
|
||||
}
|
||||
}
|
||||
}
|
||||
411
vendor/tecnickcom/tc-lib-pdf-font/src/OutFont.php
vendored
Normal file
411
vendor/tecnickcom/tc-lib-pdf-font/src/OutFont.php
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OutFont.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\Pdf\Encrypt\Encrypt;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\Unicode\Data\Identity;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\OutFont
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfFont
|
||||
* @author Nicola Asuni <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 TFontDataCidInfo from Load
|
||||
* @phpstan-import-type TFontDataDesc from Load
|
||||
*/
|
||||
abstract class OutFont extends \Com\Tecnick\Pdf\Font\OutUtil
|
||||
{
|
||||
/**
|
||||
* Current PDF object number
|
||||
*/
|
||||
protected int $pon;
|
||||
|
||||
/**
|
||||
* Encrypt object
|
||||
*/
|
||||
protected Encrypt $enc;
|
||||
|
||||
/**
|
||||
* Get the PDF output string for a CID-0 font.
|
||||
* A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
|
||||
*
|
||||
* @param array{
|
||||
* 'cidinfo': TFontDataCidInfo,
|
||||
* 'cw': array<int, int>,
|
||||
* 'desc': TFontDataDesc,
|
||||
* 'dw': int,
|
||||
* 'enc': string,
|
||||
* 'i': int,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* } $font Font to process
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getCid0(array $font): string
|
||||
{
|
||||
$cidoffset = 0;
|
||||
if (! isset($font['cw'][1])) {
|
||||
$cidoffset = 31;
|
||||
}
|
||||
|
||||
$this->uniToCid($font, $cidoffset);
|
||||
$name = $font['name'];
|
||||
$longname = $name;
|
||||
if (! empty($font['enc'])) {
|
||||
$longname .= '-' . $font['enc'];
|
||||
}
|
||||
|
||||
// obj 1
|
||||
$out = $font['n'] . ' 0 obj' . "\n"
|
||||
. '<</Type /Font'
|
||||
. ' /Subtype /Type0'
|
||||
. ' /BaseFont /' . $longname
|
||||
. ' /Name /F' . $font['i'];
|
||||
if (! empty($font['enc'])) {
|
||||
$out .= ' /Encoding /' . $font['enc'];
|
||||
}
|
||||
|
||||
$out .= ' /DescendantFonts [' . ($this->pon + 1) . ' 0 R]'
|
||||
. ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// obj 2
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<</Type /Font'
|
||||
. ' /Subtype /CIDFontType0'
|
||||
. ' /BaseFont /' . $name;
|
||||
$cidinfo = '/Registry ' . $this->enc->escapeDataString($font['cidinfo']['Registry'], $this->pon)
|
||||
. ' /Ordering ' . $this->enc->escapeDataString($font['cidinfo']['Ordering'], $this->pon)
|
||||
. ' /Supplement ' . $font['cidinfo']['Supplement'];
|
||||
$out .= ' /CIDSystemInfo <<' . $cidinfo . '>>'
|
||||
. ' /FontDescriptor ' . ($this->pon + 1) . ' 0 R'
|
||||
. ' /DW ' . $font['dw'] . "\n"
|
||||
. $this->getCharWidths($font, $cidoffset)
|
||||
. ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// obj 3
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<</Type /FontDescriptor /FontName /' . $name;
|
||||
foreach ($font['desc'] as $key => $val) {
|
||||
if ($key !== 'Style') { // @phpstan-ignore-line
|
||||
$out .= $this->getKeyValOut($key, $val);
|
||||
}
|
||||
}
|
||||
|
||||
return $out . ('>>' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Unicode to CID
|
||||
*
|
||||
* @param array{
|
||||
* 'cidinfo': TFontDataCidInfo,
|
||||
* 'cw': array<int, int>,
|
||||
* 'desc': TFontDataDesc,
|
||||
* 'dw': int,
|
||||
* 'enc': string,
|
||||
* 'i': int,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* } $font Font to process
|
||||
* @param int $cidoffset Offset for CID values
|
||||
*/
|
||||
protected function uniToCid(array &$font, int $cidoffset): void
|
||||
{
|
||||
// convert unicode to cid.
|
||||
$uni2cid = $font['cidinfo']['uni2cid'];
|
||||
$chw = [];
|
||||
foreach ($font['cw'] as $uni => $width) {
|
||||
if (isset($uni2cid[$uni])) {
|
||||
$chw[($uni2cid[$uni] + $cidoffset)] = $width;
|
||||
} elseif ($uni < 256) {
|
||||
$chw[$uni] = $width;
|
||||
} // else unknown character
|
||||
}
|
||||
|
||||
$font['cw'] = array_merge($font['cw'], $chw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for a TrueTypeUnicode font.
|
||||
* Based on PDF Reference 1.3 (section 5)
|
||||
*
|
||||
* @param array{
|
||||
* 'cidinfo': TFontDataCidInfo,
|
||||
* 'compress': bool,
|
||||
* 'ctg': string,
|
||||
* 'cw': array<int, int>,
|
||||
* 'desc': TFontDataDesc,
|
||||
* 'dir': string,
|
||||
* 'dw': int,
|
||||
* 'enc': string,
|
||||
* 'file_n': int,
|
||||
* 'i': int,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* } $font Font to process
|
||||
*
|
||||
* return string
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
* @SuppressWarnings("PHPMD.NPathComplexity")
|
||||
*/
|
||||
protected function getTrueTypeUnicode(array $font): string
|
||||
{
|
||||
$fontname = '';
|
||||
if ($font['subset']) {
|
||||
// change name for font subsetting
|
||||
$subtag = sprintf('%06u', $font['i']);
|
||||
$subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
|
||||
$fontname .= $subtag . '+';
|
||||
}
|
||||
|
||||
$fontname .= $font['name'];
|
||||
|
||||
// Type0 Font
|
||||
// A composite font composed of other fonts, organized hierarchically
|
||||
|
||||
// obj 1
|
||||
$out = $font['n'] . ' 0 obj' . "\n"
|
||||
. '<< /Type /Font'
|
||||
. ' /Subtype /Type0'
|
||||
. ' /BaseFont /' . $fontname
|
||||
. ' /Name /F' . $font['i']
|
||||
. ' /Encoding /' . $font['enc']
|
||||
. ' /ToUnicode ' . ($this->pon + 1) . ' 0 R'
|
||||
. ' /DescendantFonts [' . ($this->pon + 2) . ' 0 R]'
|
||||
. ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// ToUnicode Object
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<<';
|
||||
$cidhmap = Identity::CIDHMAP;
|
||||
if ($font['compress']) {
|
||||
$out .= ' /Filter /FlateDecode';
|
||||
$cidhmap = gzcompress($cidhmap);
|
||||
if ($cidhmap === false) {
|
||||
throw new \RuntimeException('Unable to compress CIDHMAP');
|
||||
}
|
||||
}
|
||||
|
||||
$stream = $this->enc->encryptString($cidhmap, $this->pon); // ToUnicode map for Identity-H
|
||||
$out .= ' /Length ' . strlen($stream)
|
||||
. ' >>'
|
||||
. ' stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// CIDFontType2
|
||||
// A CIDFont whose glyph descriptions are based on TrueType font technology
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<< /Type /Font'
|
||||
. ' /Subtype /CIDFontType2'
|
||||
. ' /BaseFont /' . $fontname;
|
||||
// A dictionary containing entries that define the character collection of the CIDFont.
|
||||
$cidinfo = '/Registry ' . $this->enc->escapeDataString($font['cidinfo']['Registry'], $this->pon)
|
||||
. ' /Ordering ' . $this->enc->escapeDataString($font['cidinfo']['Ordering'], $this->pon)
|
||||
. ' /Supplement ' . $font['cidinfo']['Supplement'];
|
||||
$out .= ' /CIDSystemInfo << ' . $cidinfo . ' >>'
|
||||
. ' /FontDescriptor ' . ($this->pon + 1) . ' 0 R'
|
||||
. ' /DW ' . $font['dw'] . "\n"
|
||||
. $this->getCharWidths($font, 0);
|
||||
if (! empty($font['ctg'])) {
|
||||
$out .= "\n" . '/CIDToGIDMap ' . ($this->pon + 2) . ' 0 R';
|
||||
}
|
||||
|
||||
$out .= ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// Font descriptor
|
||||
// A font descriptor describing the CIDFont default metrics other than its glyph widths
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<< /Type /FontDescriptor'
|
||||
. ' /FontName /' . $fontname;
|
||||
foreach ($font['desc'] as $key => $val) {
|
||||
$out .= $this->getKeyValOut($key, $val);
|
||||
}
|
||||
|
||||
if (! empty($font['file_n'])) {
|
||||
// A stream containing a TrueType font
|
||||
$out .= ' /FontFile2 ' . $font['file_n'] . ' 0 R';
|
||||
}
|
||||
|
||||
$out .= ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
if (! empty($font['ctg'])) {
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n";
|
||||
// Embed CIDToGIDMap
|
||||
// A specification of the mapping from CIDs to glyph indices
|
||||
// search and get CTG font file to embedd
|
||||
$ctgfile = strtolower($font['ctg']);
|
||||
// search and get ctg font file to embedd
|
||||
$fontfile = $this->getFontFullPath($font['dir'], $ctgfile);
|
||||
$content = file_get_contents($fontfile);
|
||||
if ($content === false) {
|
||||
throw new FontException('Unable to read font file: ' . $fontfile);
|
||||
}
|
||||
|
||||
$stream = $this->enc->encryptString($content, $this->pon);
|
||||
$out .= '<< /Length ' . strlen($stream) . '';
|
||||
if (str_ends_with($fontfile, '.z')) { // check file extension
|
||||
// Decompresses data encoded using the public-domain
|
||||
// zlib/deflate compression method, reproducing the
|
||||
// original text or binary data
|
||||
$out .= ' /Filter /FlateDecode';
|
||||
}
|
||||
|
||||
$out .= ' >> stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for a Core font.
|
||||
*
|
||||
* @param array{
|
||||
* 'family': string,
|
||||
* 'i': int,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* } $font Font to process
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getCore(array $font): string
|
||||
{
|
||||
$out = $font['n'] . ' 0 obj' . "\n"
|
||||
. '<</Type /Font'
|
||||
. ' /Subtype /Type1'
|
||||
. ' /BaseFont /' . $font['name']
|
||||
. ' /Name /F' . $font['i'];
|
||||
if (($font['family'] != 'symbol') && ($font['family'] != 'zapfdingbats')) {
|
||||
$out .= ' /Encoding /WinAnsiEncoding';
|
||||
}
|
||||
|
||||
return $out . (' >>' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for a Core font.
|
||||
*
|
||||
* @param array{
|
||||
* 'cw': array<int, int>,
|
||||
* 'desc': TFontDataDesc,
|
||||
* 'diff_n': int,
|
||||
* 'dw': int,
|
||||
* 'enc': string,
|
||||
* 'file': string,
|
||||
* 'file_n': int,
|
||||
* 'i': int,
|
||||
* 'n': int,
|
||||
* 'name': string,
|
||||
* 'type': string,
|
||||
* } $font Font to process
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getTrueType(array $font): string
|
||||
{
|
||||
// obj 1
|
||||
$out = $font['n'] . ' 0 obj' . "\n"
|
||||
. '<</Type /Font'
|
||||
. ' /Subtype /' . $font['type']
|
||||
. ' /BaseFont /' . $font['name']
|
||||
. ' /Name /F' . $font['i']
|
||||
. ' /FirstChar 32 /LastChar 255'
|
||||
. ' /Widths ' . ($this->pon + 1) . ' 0 R'
|
||||
. ' /FontDescriptor ' . ($this->pon + 2) . ' 0 R';
|
||||
if (! empty($font['enc'])) {
|
||||
if (isset($font['diff_n']) && $font['diff_n'] !== 0) {
|
||||
$out .= ' /Encoding ' . $font['diff_n'] . ' 0 R';
|
||||
} else {
|
||||
$out .= ' /Encoding /WinAnsiEncoding';
|
||||
}
|
||||
}
|
||||
|
||||
$out .= ' >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// obj 2 - Widths
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '[';
|
||||
for ($idx = 32; $idx < 256; ++$idx) {
|
||||
if (isset($font['cw'][$idx])) {
|
||||
$out .= $font['cw'][$idx] . ' ';
|
||||
} else {
|
||||
$out .= $font['dw'] . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
$out .= ']' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
|
||||
// obj 3 - Descriptor
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<</Type /FontDescriptor /FontName /' . $font['name'];
|
||||
foreach ($font['desc'] as $fdk => $fdv) {
|
||||
$out .= $this->getKeyValOut($fdk, $fdv);
|
||||
}
|
||||
|
||||
if (! empty($font['file'])) {
|
||||
$out .= ' /FontFile' . ($font['type'] == 'Type1' ? '' : '2') . ' ' . $font['file_n'] . ' 0 R';
|
||||
}
|
||||
|
||||
return $out . ('>>' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted key/value PDF string
|
||||
*
|
||||
* @param string $key Key name
|
||||
* @param mixed $val Value
|
||||
*/
|
||||
protected function getKeyValOut(string $key, mixed $val): string
|
||||
{
|
||||
if (is_float($val)) {
|
||||
$val = sprintf('%F', $val);
|
||||
}
|
||||
|
||||
return ' /' . $key . ' ' . $val . '';
|
||||
}
|
||||
}
|
||||
197
vendor/tecnickcom/tc-lib-pdf-font/src/OutUtil.php
vendored
Normal file
197
vendor/tecnickcom/tc-lib-pdf-font/src/OutUtil.php
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OutUtil.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\Dir;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\OutUtil
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
abstract class OutUtil
|
||||
{
|
||||
/**
|
||||
* Return font full path
|
||||
*
|
||||
* @param string $fontdir Original font directory
|
||||
* @param string $file Font file name.
|
||||
*
|
||||
* @return string Font full path or empty string
|
||||
*/
|
||||
protected function getFontFullPath(string $fontdir, string $file): string
|
||||
{
|
||||
$dirobj = new Dir();
|
||||
// directories where to search for the font definition file
|
||||
$dirs = array_unique(
|
||||
['', $fontdir, (defined('K_PATH_FONTS') ? K_PATH_FONTS : ''), $dirobj->findParentDir('fonts', __DIR__)]
|
||||
);
|
||||
foreach ($dirs as $dir) {
|
||||
if (@is_readable($dir . DIRECTORY_SEPARATOR . $file)) {
|
||||
return $dir . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FontException('Unable to locate the file: ' . $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs font widths
|
||||
*
|
||||
* @param array{
|
||||
* 'cw': array<int, int>,
|
||||
* 'dw': int,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* } $font Font to process
|
||||
* @param int $cidoffset Offset for CID values
|
||||
*
|
||||
* @return string PDF command string for font widths
|
||||
*/
|
||||
protected function getCharWidths(array $font, int $cidoffset = 0): string
|
||||
{
|
||||
ksort($font['cw']);
|
||||
$range = $this->getWidthRanges($font, $cidoffset);
|
||||
// output data
|
||||
$wdt = '';
|
||||
foreach ($range as $kdx => $wds) {
|
||||
if (count(array_count_values($wds)) == 1) {
|
||||
// interval mode is more compact
|
||||
$wdt .= ' ' . $kdx . ' ' . ($kdx + count($wds) - 1) . ' ' . $wds[0];
|
||||
} else {
|
||||
// range mode
|
||||
$wdt .= ' ' . $kdx . ' [ ' . implode(' ', $wds) . ' ]';
|
||||
}
|
||||
}
|
||||
|
||||
return '/W [' . $wdt . ' ]';
|
||||
}
|
||||
|
||||
/**
|
||||
* get width ranges of characters
|
||||
*
|
||||
* @param array{
|
||||
* 'cw': array<int, int>,
|
||||
* 'dw': int,
|
||||
* 'subset': bool,
|
||||
* 'subsetchars': array<int, bool>,
|
||||
* } $font Font to process
|
||||
* @param int $cidoffset Offset for CID values
|
||||
*
|
||||
* @return array<int, array<int, int>>
|
||||
*/
|
||||
protected function getWidthRanges(array $font, int $cidoffset = 0): array
|
||||
{
|
||||
$range = [];
|
||||
$rangeid = 0;
|
||||
$prevcid = -2;
|
||||
$prevwidth = -1;
|
||||
$interval = false;
|
||||
// for each character
|
||||
foreach ($font['cw'] as $cid => $width) {
|
||||
$cid -= $cidoffset;
|
||||
if ($font['subset'] && (! isset($font['subsetchars'][$cid]))) {
|
||||
// ignore the unused characters (font subsetting)
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($width != $font['dw']) {
|
||||
if ($cid === $prevcid + 1) {
|
||||
// consecutive CID
|
||||
if ($width == $prevwidth) {
|
||||
if ($width === $range[$rangeid][0]) {
|
||||
$range[$rangeid][] = $width;
|
||||
} else {
|
||||
array_pop($range[$rangeid]);
|
||||
// new range
|
||||
$rangeid = $prevcid;
|
||||
$range[$rangeid] = [];
|
||||
$range[$rangeid][] = $prevwidth;
|
||||
$range[$rangeid][] = $width;
|
||||
}
|
||||
|
||||
$interval = true;
|
||||
$range[$rangeid][-1] = -1;
|
||||
} else {
|
||||
if ($interval) {
|
||||
// new range
|
||||
$rangeid = $cid;
|
||||
$range[$rangeid] = [];
|
||||
$range[$rangeid][] = $width;
|
||||
} else {
|
||||
$range[$rangeid][] = $width;
|
||||
}
|
||||
|
||||
$interval = false;
|
||||
}
|
||||
} else {
|
||||
// new range
|
||||
$rangeid = $cid;
|
||||
$range[$rangeid] = [];
|
||||
$range[$rangeid][] = $width;
|
||||
$interval = false;
|
||||
}
|
||||
|
||||
$prevcid = $cid;
|
||||
$prevwidth = $width;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->optimizeWidthRanges($range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize width ranges
|
||||
*
|
||||
* @param array<int, array<int, int>> $range Widht Ranges
|
||||
*
|
||||
* @return array<int, array<int, int>>
|
||||
*/
|
||||
protected function optimizeWidthRanges(array $range): array
|
||||
{
|
||||
$prevk = -1;
|
||||
$nextk = -1;
|
||||
$prevint = false;
|
||||
foreach ($range as $kdx => $wds) {
|
||||
$cws = count($wds);
|
||||
if (($kdx == $nextk) && (! $prevint) && ((! isset($wds[-1])) || ($cws < 4))) {
|
||||
unset($range[$kdx][-1]);
|
||||
$range[$prevk] = [...$range[$prevk], ...$range[$kdx]];
|
||||
unset($range[$kdx]);
|
||||
} else {
|
||||
$prevk = $kdx;
|
||||
}
|
||||
|
||||
$prevint = false;
|
||||
$nextk = $kdx + $cws;
|
||||
if (isset($wds[-1])) {
|
||||
unset($range[$kdx][-1]);
|
||||
$prevint = ($cws > 3);
|
||||
--$nextk;
|
||||
}
|
||||
}
|
||||
|
||||
return $range;
|
||||
}
|
||||
}
|
||||
258
vendor/tecnickcom/tc-lib-pdf-font/src/Output.php
vendored
Normal file
258
vendor/tecnickcom/tc-lib-pdf-font/src/Output.php
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Output.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\Pdf\Encrypt\Encrypt;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Output
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class Output extends \Com\Tecnick\Pdf\Font\OutFont
|
||||
{
|
||||
/**
|
||||
* Array of character subsets for each font file
|
||||
*
|
||||
* @var array<string, array<int, bool>>
|
||||
*/
|
||||
protected array $subchars = [];
|
||||
|
||||
/**
|
||||
* PDF string block with the fonts definitions
|
||||
*/
|
||||
protected string $out = '';
|
||||
|
||||
/**
|
||||
* Initialize font data
|
||||
*
|
||||
* @param array<string, TFontData> $fonts Array of imported fonts data
|
||||
* @param int $pon Current PDF Object Number
|
||||
* @param Encrypt $encrypt Encrypt object
|
||||
*/
|
||||
public function __construct(
|
||||
protected array $fonts,
|
||||
int $pon,
|
||||
Encrypt $encrypt
|
||||
) {
|
||||
$this->pon = $pon;
|
||||
$this->enc = $encrypt;
|
||||
|
||||
$this->out = $this->getEncodingDiffs();
|
||||
$this->out .= $this->getFontFiles();
|
||||
$this->out .= $this->getFontDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current PDF object number
|
||||
*/
|
||||
public function getObjectNumber(): int
|
||||
{
|
||||
return $this->pon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PDF fonts block
|
||||
*/
|
||||
public function getFontsBlock(): string
|
||||
{
|
||||
return $this->out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for Font resources dictionary.
|
||||
*
|
||||
* @param array<string, array{'i': int, 'n': int}> $data Font data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getOutFontResources(array $data): string
|
||||
{
|
||||
if (empty($data)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$out = ' /Font <<';
|
||||
|
||||
foreach ($data as $font) {
|
||||
$out .= ' /F' . $font['i'] . ' ' . $font['n'] . ' 0 R';
|
||||
}
|
||||
|
||||
return $out . ' >>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the PDF output string for Font resources dictionary.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutFontDict(): string
|
||||
{
|
||||
return $this->getOutFontResources($this->fonts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for XOBject Font resources dictionary.
|
||||
*
|
||||
* @param array<string> $keys Array of font keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutFontDictByKeys(array $keys): string
|
||||
{
|
||||
if (empty($keys)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$data = [];
|
||||
foreach ($keys as $key) {
|
||||
$data[$key] = [
|
||||
'i' => $this->fonts[$key]['i'],
|
||||
'n' => $this->fonts[$key]['n'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->getOutFontResources($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for font encoding diffs
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getEncodingDiffs(): string
|
||||
{
|
||||
$out = '';
|
||||
$done = []; // store processed items to avoid duplication
|
||||
foreach ($this->fonts as $fkey => $font) {
|
||||
if (! empty($font['diff'])) {
|
||||
$dkey = md5($font['diff']);
|
||||
if (! isset($done[$dkey])) {
|
||||
$out .= (++$this->pon) . ' 0 obj' . "\n"
|
||||
. '<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['
|
||||
. $font['diff'] . '] >>' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
$done[$dkey] = $this->pon;
|
||||
}
|
||||
|
||||
$this->fonts[$fkey]['diff_n'] = $done[$dkey];
|
||||
}
|
||||
|
||||
// extract the character subset
|
||||
if (! empty($font['file'])) {
|
||||
$file_key = md5($font['file']);
|
||||
if (empty($this->subchars[$file_key])) {
|
||||
$this->subchars[$file_key] = $font['subsetchars'];
|
||||
} else {
|
||||
$this->subchars[$file_key] = array_merge($this->subchars[$file_key], $font['subsetchars']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for font files
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getFontFiles(): string
|
||||
{
|
||||
$out = '';
|
||||
$done = []; // store processed items to avoid duplication
|
||||
foreach ($this->fonts as $fkey => $font) {
|
||||
if (! empty($font['file'])) {
|
||||
$dkey = md5($font['file']);
|
||||
if (! isset($done[$dkey])) {
|
||||
$fontfile = $this->getFontFullPath($font['dir'], $font['file']);
|
||||
$font_data = file_get_contents($fontfile);
|
||||
if ($font_data === false) {
|
||||
throw new FontException('Unable to read font file: ' . $fontfile);
|
||||
}
|
||||
|
||||
if ($font['subset']) {
|
||||
$font_data = gzuncompress($font_data);
|
||||
if ($font_data === false) {
|
||||
throw new FontException('Unable to uncompress font file: ' . $fontfile);
|
||||
}
|
||||
|
||||
$sub = new Subset($font_data, $font, $this->subchars[md5($font['file'])]);
|
||||
$font_data = $sub->getSubsetFont();
|
||||
$font['length1'] = strlen($font_data);
|
||||
$font_data = gzcompress($font_data);
|
||||
if ($font_data === false) {
|
||||
throw new FontException('Unable to compress font file: ' . $fontfile);
|
||||
}
|
||||
}
|
||||
|
||||
++$this->pon;
|
||||
$stream = $this->enc->encryptString($font_data, $this->pon);
|
||||
$out .= $this->pon . ' 0 obj' . "\n"
|
||||
. '<<'
|
||||
. ' /Filter /FlateDecode'
|
||||
. ' /Length ' . strlen($stream)
|
||||
. ' /Length1 ' . $font['length1'];
|
||||
$out .= ' /Length2 ' . $font['length2']
|
||||
. ' /Length3 0';
|
||||
|
||||
$out .= ' >> stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n"
|
||||
. 'endobj' . "\n";
|
||||
$done[$dkey] = $this->pon;
|
||||
}
|
||||
|
||||
$this->fonts[$fkey]['file_n'] = $done[$dkey];
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for fonts
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
protected function getFontDefinitions(): string
|
||||
{
|
||||
$out = '';
|
||||
foreach ($this->fonts as $font) {
|
||||
$out .= match (strtolower($font['type'])) {
|
||||
'core' => $this->getCore($font),
|
||||
'cidfont0' => $this->getCid0($font),
|
||||
'type1' => $this->getTrueType($font),
|
||||
'truetype' => $this->getTrueType($font),
|
||||
'truetypeunicode' => $this->getTrueTypeUnicode($font),
|
||||
default => throw new FontException('Unsupported font type: ' . $font['type']),
|
||||
};
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
758
vendor/tecnickcom/tc-lib-pdf-font/src/Stack.php
vendored
Normal file
758
vendor/tecnickcom/tc-lib-pdf-font/src/Stack.php
vendored
Normal file
@@ -0,0 +1,758 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Stack.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\Unicode\Data\Type as UnicodeType;
|
||||
use Com\Tecnick\Pdf\Font\Exception as FontException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Stack
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @phpstan-type TTextSplit array{
|
||||
* 'pos': int,
|
||||
* 'ord': int,
|
||||
* 'spaces': int,
|
||||
* 'septype': string,
|
||||
* 'wordwidth': float,
|
||||
* 'totwidth': float,
|
||||
* 'totspacewidth': float,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TTextDims array{
|
||||
* 'chars': int,
|
||||
* 'spaces': int,
|
||||
* 'words': int,
|
||||
* 'totwidth': float,
|
||||
* 'totspacewidth': float,
|
||||
* 'split': array<int, TTextSplit>,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TBBox array{float, float, float, float}
|
||||
*
|
||||
* @phpstan-type TStackItem array{
|
||||
* 'key': string,
|
||||
* 'style': string,
|
||||
* 'size': float,
|
||||
* 'spacing': float,
|
||||
* 'stretching': float,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type TFontMetric array{
|
||||
* 'ascent': float,
|
||||
* 'avgwidth': float,
|
||||
* 'capheight': float,
|
||||
* 'cbbox': array<int, TBBox>,
|
||||
* 'cratio': float,
|
||||
* 'cw': array<int, float>,
|
||||
* 'descent': float,
|
||||
* 'dw': float,
|
||||
* 'fbbox': array<int, float>,
|
||||
* 'height': float,
|
||||
* 'idx': int,
|
||||
* 'key': string,
|
||||
* 'maxwidth': float,
|
||||
* 'midpoint': float,
|
||||
* 'missingwidth': float,
|
||||
* 'out': string,
|
||||
* 'outraw': string,
|
||||
* 'size': float,
|
||||
* 'spacing': float,
|
||||
* 'stretching': float,
|
||||
* 'style': string,
|
||||
* 'type': string,
|
||||
* 'up': float,
|
||||
* 'usize': float,
|
||||
* 'ut': float,
|
||||
* 'xheight': float,
|
||||
* }
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
class Stack extends \Com\Tecnick\Pdf\Font\Buffer
|
||||
{
|
||||
/**
|
||||
* Default font size in points
|
||||
*/
|
||||
public const DEFAULT_SIZE = 10;
|
||||
|
||||
/**
|
||||
* Array (stack) containing fonts in order of insertion.
|
||||
* The last item is the current font.
|
||||
*
|
||||
* @var array<int, TStackItem>
|
||||
*/
|
||||
protected array $stack = [];
|
||||
|
||||
/**
|
||||
* Current font index
|
||||
*/
|
||||
protected int $index = -1;
|
||||
|
||||
/**
|
||||
* Array containing font metrics for each fontkey-size combination.
|
||||
*
|
||||
* @var array<string, TFontMetric>
|
||||
*/
|
||||
protected array $metric = [];
|
||||
|
||||
/**
|
||||
* Insert a font into the stack
|
||||
*
|
||||
* The definition file (and the font file itself when embedding) must be present either in the current directory
|
||||
* or in the one indicated by K_PATH_FONTS if the constant is defined.
|
||||
*
|
||||
* @param int $objnum Current PDF object number
|
||||
* @param string $font Font family, or comma separated list of font families
|
||||
* If it is a standard family name, it will override the corresponding font.
|
||||
* @param string $style Font style.
|
||||
* Possible values are (case insensitive):
|
||||
* regular (default)
|
||||
* B: bold
|
||||
* I: italic
|
||||
* U: underline
|
||||
* D: strikeout (linethrough)
|
||||
* O: overline
|
||||
* @param ?int $size Font size in points (set to null to inherit the last font size).
|
||||
* @param ?float $spacing Extra spacing between characters.
|
||||
* @param ?float $stretching Horizontal character stretching ratio.
|
||||
* @param string $ifile The font definition file (or empty for autodetect).
|
||||
* By default, the name is built from the family and style, in lower case with no spaces.
|
||||
* @param ?bool $subset If true embedd only a subset of the font
|
||||
* (stores only the information related to
|
||||
* the used characters); If false embedd
|
||||
* full font; This option is valid only for
|
||||
* TrueTypeUnicode fonts and it is disabled
|
||||
* for PDF/A. If you want to enable users
|
||||
* to modify the document, set this
|
||||
* parameter to false. If you subset the
|
||||
* font, the person who receives your PDF
|
||||
* would need to have your same font in
|
||||
* order to make changes to your PDF. The
|
||||
* file size of the PDF would also be
|
||||
* smaller because you are embedding only a
|
||||
* subset. Set this to null to use the
|
||||
* default value. NOTE: This option is
|
||||
* computational and memory intensive.
|
||||
*
|
||||
* @return TFontMetric Font data
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function insert(
|
||||
int &$objnum,
|
||||
string $font,
|
||||
string $style = '',
|
||||
?int $size = null,
|
||||
?float $spacing = null,
|
||||
?float $stretching = null,
|
||||
string $ifile = '',
|
||||
?bool $subset = null
|
||||
): array {
|
||||
if ($subset === null) {
|
||||
$subset = $this->subset;
|
||||
}
|
||||
|
||||
$size = $this->getInputSize($size);
|
||||
$spacing = $this->getInputSpacing($spacing);
|
||||
$stretching = $this->getInputStretching($stretching);
|
||||
|
||||
// try to load the corresponding imported font
|
||||
$err = null;
|
||||
$keys = $this->getNormalizedFontKeys($font);
|
||||
$fontkey = '';
|
||||
foreach ($keys as $key) {
|
||||
try {
|
||||
$fontkey = $this->add($objnum, $key, $style, $ifile, $subset);
|
||||
$err = null;
|
||||
break;
|
||||
} catch (FontException $exc) {
|
||||
$err = $exc;
|
||||
}
|
||||
}
|
||||
|
||||
if ($err instanceof \Com\Tecnick\Pdf\Font\Exception) {
|
||||
throw new FontException($err->getMessage());
|
||||
}
|
||||
|
||||
// add this font in the stack
|
||||
$data = $this->getFont($fontkey);
|
||||
|
||||
$this->stack[++$this->index] = [
|
||||
'key' => $fontkey,
|
||||
'style' => $data['style'],
|
||||
'size' => $size,
|
||||
'spacing' => $spacing,
|
||||
'stretching' => $stretching,
|
||||
];
|
||||
|
||||
return $this->getFontMetric($this->index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current font data array.
|
||||
*
|
||||
* @return TFontMetric
|
||||
*/
|
||||
public function getCurrentFont(): array
|
||||
{
|
||||
return $this->getFontMetric($this->index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clone of the specified font with new parameters.
|
||||
*
|
||||
* @param int $objnum Current PDF object number.
|
||||
* @param ?int $idx Font index. Leave it null to use the current font.
|
||||
* @param ?string $style Font style.
|
||||
* Possible values are (case insensitive):
|
||||
* regular (default)
|
||||
* B: bold
|
||||
* I: italic
|
||||
* U: underline
|
||||
* D: strikeout (linethrough)
|
||||
* O: overline
|
||||
* @param ?int $size Font size in points (set to null to inherit the last font size).
|
||||
* @param ?float $spacing Extra spacing between characters.
|
||||
* @param ?float $stretching Horizontal character stretching ratio.
|
||||
*
|
||||
* @return TFontMetric
|
||||
*/
|
||||
public function cloneFont(
|
||||
int &$objnum,
|
||||
?int $idx = null,
|
||||
?string $style = null,
|
||||
?int $size = null,
|
||||
?float $spacing = null,
|
||||
?float $stretching = null,
|
||||
): array {
|
||||
if ($idx === null) {
|
||||
$idx = $this->index;
|
||||
} elseif (($idx < 0) || ($idx > $this->index)) {
|
||||
throw new FontException('Invalid font index');
|
||||
}
|
||||
|
||||
$curfont = $this->stack[$idx];
|
||||
|
||||
if (($style === null) || ($style == $curfont['style'])) {
|
||||
$size = $this->getInputSize($size);
|
||||
$spacing = $this->getInputSpacing($spacing);
|
||||
$stretching = $this->getInputStretching($stretching);
|
||||
|
||||
$this->stack[++$this->index] = [
|
||||
'key' => $curfont['key'],
|
||||
'style' => $curfont['style'],
|
||||
'size' => $size,
|
||||
'spacing' => $spacing,
|
||||
'stretching' => $stretching,
|
||||
];
|
||||
|
||||
return $this->getFontMetric($this->index);
|
||||
}
|
||||
|
||||
$data = $this->getFont($curfont['key']);
|
||||
|
||||
return $this->insert(
|
||||
$objnum,
|
||||
$data['family'],
|
||||
$style,
|
||||
$size,
|
||||
$spacing,
|
||||
$stretching,
|
||||
$data['ifile'],
|
||||
$data['subset'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current font key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentFontKey(): string
|
||||
{
|
||||
return $this->stack[$this->index]['key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current font type (i.e.: Core, TrueType, TrueTypeUnicode, Type1).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrentFontType(): string
|
||||
{
|
||||
return $this->getFont($this->stack[$this->index]['key'])['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PDF code to use the current font.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutCurrentFont(): string
|
||||
{
|
||||
return $this->getFontMetric($this->index)['out'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current font type is Core, TrueType or Type1.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrentByteFont(): bool
|
||||
{
|
||||
$currentFontType = $this->getCurrentFontType();
|
||||
return (($currentFontType == 'Core') || ($currentFontType == 'TrueType') || ($currentFontType == 'Type1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current font type is TrueTypeUnicode or cidfont0.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrentUnicodeFont(): bool
|
||||
{
|
||||
$currentFontType = $this->getCurrentFontType();
|
||||
return (($currentFontType == 'TrueTypeUnicode') || ($currentFontType == 'cidfont0'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove and return the last inserted font
|
||||
*
|
||||
* @return TFontMetric
|
||||
*/
|
||||
public function popLastFont(): array
|
||||
{
|
||||
if (($this->index < 0) || ($this->stack === [])) {
|
||||
throw new FontException('The font stack is empty');
|
||||
}
|
||||
|
||||
$font = $this->getFontMetric($this->index);
|
||||
array_pop($this->stack);
|
||||
--$this->index;
|
||||
return $font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace missing characters with selected substitutions
|
||||
*
|
||||
* @param array<int, int> $uniarr Array of character codepoints.
|
||||
* @param array<int, array<int>> $subs Array of possible character substitutions.
|
||||
* The key is the character to check (integer value),
|
||||
* the value is an array of possible substitutes.
|
||||
*
|
||||
* @return array<int, int> Array of character codepoints.
|
||||
*/
|
||||
public function replaceMissingChars(array $uniarr, array $subs = []): array
|
||||
{
|
||||
$font = $this->getFontMetric($this->index);
|
||||
foreach ($uniarr as $pos => $uni) {
|
||||
if (isset($font['cw'][$uni])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! isset($subs[$uni])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($subs[$uni] as $alt) {
|
||||
if (isset($font['cw'][$alt])) {
|
||||
$uniarr[$pos] = $alt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $uniarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified unicode value is defined in the current font
|
||||
*
|
||||
* @param int $ord Unicode character value to convert
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isCharDefined(int $ord): bool
|
||||
{
|
||||
$font = $this->getFontMetric($this->index);
|
||||
return isset($font['cw'][$ord]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the specified character
|
||||
*
|
||||
* @param int $ord Unicode character value.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getCharWidth(int $ord): float
|
||||
{
|
||||
if (($ord == 173) || ($ord == 8203)) {
|
||||
// 173 = SHY character is not printed, as it is used for text hyphenation
|
||||
// 8203 = ZWSP character
|
||||
return 0;
|
||||
}
|
||||
|
||||
$font = $this->getFontMetric($this->index);
|
||||
return $font['cw'][$ord] ?? $font['dw'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lenght of the string specified using an array of codepoints.
|
||||
*
|
||||
* @param array<int, int> $uniarr Array of character codepoints.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getOrdArrWidth(array $uniarr): float
|
||||
{
|
||||
return $this->getOrdArrDims($uniarr)['totwidth'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns various dimensions of the string specified using an array of codepoints.
|
||||
*
|
||||
* @param array<int, int> $uniarr Array of character codepoints.
|
||||
*
|
||||
* @return TTextDims
|
||||
*/
|
||||
public function getOrdArrDims(array $uniarr): array
|
||||
{
|
||||
$chars = count($uniarr); // total number of chars
|
||||
$spaces = 0; // total number of spaces
|
||||
$totwidth = 0; // total string width
|
||||
$totspacewidth = 0; // total space width
|
||||
$words = 0; // total number of words
|
||||
$fact = ($this->stack[$this->index]['spacing'] * $this->stack[$this->index]['stretching']);
|
||||
$uniarr[] = 8203; // add null at the end to ensure that the last word is processed
|
||||
$split = [];
|
||||
foreach ($uniarr as $idx => $ord) {
|
||||
$unitype = UnicodeType::UNI[$ord];
|
||||
$chrwidth = $this->getCharWidth($ord);
|
||||
// 'B' Paragraph Separator
|
||||
// 'S' Segment Separator
|
||||
// 'WS' Whitespace
|
||||
// 'BN' Boundary Neutral
|
||||
if (($unitype == 'B') || ($unitype == 'S') || ($unitype == 'WS') || ($unitype == 'BN')) {
|
||||
$split[$words] = [
|
||||
'pos' => $idx,
|
||||
'ord' => $ord,
|
||||
'spaces' => $spaces,
|
||||
'septype' => $unitype,
|
||||
'wordwidth' => 0,
|
||||
'totwidth' => ($totwidth + ($fact * ($idx - 1))),
|
||||
'totspacewidth' => ($totspacewidth + ($fact * ($spaces - 1))),
|
||||
];
|
||||
if ($words > 0) {
|
||||
$split[$words]['wordwidth'] = ($split[$words]['totwidth'] - $split[($words - 1)]['totwidth']);
|
||||
}
|
||||
$words++;
|
||||
if ($unitype == 'WS') {
|
||||
++$spaces;
|
||||
$totspacewidth += $chrwidth;
|
||||
}
|
||||
}
|
||||
$totwidth += $chrwidth;
|
||||
}
|
||||
$totwidth += ($fact * ($chars - 1));
|
||||
$totspacewidth += ($fact * ($spaces - 1));
|
||||
return [
|
||||
'chars' => $chars,
|
||||
'spaces' => $spaces,
|
||||
'words' => $words,
|
||||
'totwidth' => $totwidth,
|
||||
'totspacewidth' => $totspacewidth,
|
||||
'split' => $split,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the glyph bounding box of the specified character in the current font in user units.
|
||||
*
|
||||
* @param int $ord Unicode character value.
|
||||
*
|
||||
* @return TBBox (xMin, yMin, xMax, yMax)
|
||||
*/
|
||||
public function getCharBBox(int $ord): array
|
||||
{
|
||||
$font = $this->getFontMetric($this->index);
|
||||
return $font['cbbox'][$ord] ?? [0.0, 0.0, 0.0, 0.0]; // glyph without outline
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a char if it is defined on the current font.
|
||||
*
|
||||
* @param int $oldchar Integer code (unicode) of the character to replace.
|
||||
* @param int $newchar Integer code (unicode) of the new character.
|
||||
*
|
||||
* @return int the replaced char or the old char in case the new char i not defined
|
||||
*/
|
||||
public function replaceChar(int $oldchar, int $newchar): int
|
||||
{
|
||||
if ($this->isCharDefined($newchar)) {
|
||||
// add the new char on the subset list
|
||||
$this->addSubsetChar($this->stack[$this->index]['key'], $newchar);
|
||||
// return the new character
|
||||
return $newchar;
|
||||
}
|
||||
|
||||
// return the old char
|
||||
return $oldchar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the font metrics associated to the input key.
|
||||
*
|
||||
* @param int $idx Font index in the stack.
|
||||
*
|
||||
* @return TFontMetric
|
||||
*/
|
||||
protected function getFontMetric(int $idx): array
|
||||
{
|
||||
$font = $this->stack[$idx];
|
||||
$mkey = md5(serialize($font));
|
||||
if (! empty($this->metric[$mkey])) {
|
||||
return $this->metric[$mkey];
|
||||
}
|
||||
|
||||
$size = $font['size'];
|
||||
$usize = ((float) $size / $this->kunit);
|
||||
$cratio = ((float) $size / 1000);
|
||||
$wratio = ($cratio * $font['stretching']); // horizontal ratio
|
||||
$data = $this->getFont($font['key']);
|
||||
$outfont = sprintf('/F%d %F Tf', $data['i'], $font['size']); // PDF output string
|
||||
// add this font in the stack wit metrics in internal units
|
||||
$this->metric[$mkey] = [
|
||||
'ascent' => ((float) $data['desc']['Ascent'] * $cratio),
|
||||
'avgwidth' => ((float) $data['desc']['AvgWidth'] * $cratio * $font['stretching']),
|
||||
'capheight' => ((float) $data['desc']['CapHeight'] * $cratio),
|
||||
'cbbox' => [],
|
||||
'cratio' => $cratio,
|
||||
'cw' => [],
|
||||
'descent' => ((float) $data['desc']['Descent'] * $cratio),
|
||||
'dw' => ((float) $data['dw'] * $cratio * $font['stretching']),
|
||||
'fbbox' => [],
|
||||
'height' => ((float) ($data['desc']['Ascent'] - $data['desc']['Descent']) * $cratio),
|
||||
'idx' => $idx,
|
||||
'key' => $font['key'],
|
||||
'maxwidth' => ((float) $data['desc']['MaxWidth'] * $cratio * $font['stretching']),
|
||||
'midpoint' => ((float) ($data['desc']['Ascent'] + $data['desc']['Descent']) * $cratio / 2),
|
||||
'missingwidth' => ((float) $data['desc']['MissingWidth'] * $cratio * $font['stretching']),
|
||||
'out' => 'BT ' . $outfont . ' ET' . "\r",
|
||||
'outraw' => $outfont,
|
||||
'size' => $size,
|
||||
'spacing' => $font['spacing'],
|
||||
'stretching' => $font['stretching'],
|
||||
'style' => $font['style'],
|
||||
'type' => $data['type'],
|
||||
'up' => ((float) $data['up'] * $cratio),
|
||||
'usize' => $usize,
|
||||
'ut' => ((float) $data['ut'] * $cratio),
|
||||
'xheight' => ((float) $data['desc']['XHeight'] * $cratio),
|
||||
];
|
||||
$tbox = explode(' ', substr($data['desc']['FontBBox'], 1, -1));
|
||||
$this->metric[$mkey]['fbbox'] = [
|
||||
// left
|
||||
((float) $tbox[0] * $wratio),
|
||||
// bottom
|
||||
((float) $tbox[1] * $cratio),
|
||||
// right
|
||||
((float) $tbox[2] * $wratio),
|
||||
// top
|
||||
((float) $tbox[3] * $cratio),
|
||||
];
|
||||
//left, bottom, right, and top edges
|
||||
foreach ($data['cw'] as $cid => $width) {
|
||||
$this->metric[$mkey]['cw'][(int) $cid] = ((float) $width * $wratio);
|
||||
}
|
||||
|
||||
if (is_array($data['cbbox'])) {
|
||||
foreach ($data['cbbox'] as $cid => $val) {
|
||||
if (! is_array($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($val) != 4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->metric[$mkey]['cbbox'][(int) $cid] = [
|
||||
// left
|
||||
((float) $val[0] * $wratio),
|
||||
// bottom
|
||||
((float) $val[1] * $cratio),
|
||||
// right
|
||||
((float) $val[2] * $wratio),
|
||||
// top
|
||||
((float) $val[3] * $cratio),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->metric[$mkey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the input size
|
||||
*
|
||||
* @param ?int $size Font size in points (set to null to inherit the last font size).
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function getInputSize(?int $size = null): float
|
||||
{
|
||||
if (($size === null) || ($size < 0)) {
|
||||
if ($this->index >= 0) {
|
||||
// inherit the size of the last inserted font
|
||||
return $this->stack[$this->index]['size'] ?? 0;
|
||||
}
|
||||
|
||||
return self::DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
return max(0, (float) $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the input spacing
|
||||
*
|
||||
* @param ?float $spacing Extra spacing between characters.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function getInputSpacing(?float $spacing = null): float
|
||||
{
|
||||
if ($spacing === null) {
|
||||
if ($this->index >= 0) {
|
||||
// inherit the size of the last inserted font
|
||||
return $this->stack[$this->index]['spacing'] ?? 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($spacing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the input stretching
|
||||
*
|
||||
* @param ?float $stretching Horizontal character stretching ratio.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function getInputStretching(?float $stretching = null): float
|
||||
{
|
||||
if ($stretching === null) {
|
||||
if ($this->index >= 0) {
|
||||
// inherit the size of the last inserted font
|
||||
return $this->stack[$this->index]['stretching'] ?? 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return ($stretching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return normalized font keys
|
||||
*
|
||||
* @param string $fontfamily Property string containing comma-separated font family names
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
protected function getNormalizedFontKeys(string $fontfamily): array
|
||||
{
|
||||
if ($fontfamily === '') {
|
||||
throw new FontException('Empty font family name');
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
// remove spaces and symbols
|
||||
$fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
|
||||
if (($fontfamily === null) || (! is_string($fontfamily))) {
|
||||
throw new FontException('Invalid font family name: ' . $fontfamily);
|
||||
}
|
||||
|
||||
// extract all font names
|
||||
$fontslist = preg_split('/[,]/', $fontfamily);
|
||||
if ($fontslist === false) {
|
||||
throw new FontException('Invalid font family name: ' . $fontfamily);
|
||||
}
|
||||
|
||||
// replacement patterns
|
||||
|
||||
$fontpattern = ['/regular$/', '/italic$/', '/oblique$/', '/bold([I]?)$/'];
|
||||
$fontreplacement = ['', 'I', 'I', 'B\\1'];
|
||||
|
||||
$keypattern = ['/^serif|^cursive|^fantasy|^timesnewroman/', '/^sansserif/', '/^monospace/'];
|
||||
$keyreplacement = ['times', 'helvetica', 'courier'];
|
||||
|
||||
// find first valid font name
|
||||
foreach ($fontslist as $font) {
|
||||
$font = preg_replace($fontpattern, $fontreplacement, $font);
|
||||
if ($font === null) {
|
||||
throw new FontException('Invalid font family name: ' . $fontfamily);
|
||||
}
|
||||
|
||||
// replace common family names and core fonts
|
||||
$fontkey = preg_replace($keypattern, $keyreplacement, $font);
|
||||
if ($fontkey === null) {
|
||||
throw new FontException('Invalid font family name: ' . $fontfamily);
|
||||
}
|
||||
|
||||
$keys[] = $fontkey;
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nomalized font family name or the current font name (key.
|
||||
*
|
||||
* @param string $fontfamily Raw font family name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFontFamilyName(string $fontfamily): string
|
||||
{
|
||||
$fkeys = $this->getNormalizedFontKeys($fontfamily);
|
||||
foreach ($fkeys as $fkey) {
|
||||
if ($this->isValidKey($fkey)) {
|
||||
return $fkey;
|
||||
}
|
||||
|
||||
$pdfakey = 'pdfa' . $fkey;
|
||||
if ($this->isValidKey($pdfakey)) {
|
||||
return $pdfakey;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getCurrentFontKey();
|
||||
}
|
||||
}
|
||||
498
vendor/tecnickcom/tc-lib-pdf-font/src/Subset.php
vendored
Normal file
498
vendor/tecnickcom/tc-lib-pdf-font/src/Subset.php
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Subset.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\Pdf\Font\Exception as FontException;
|
||||
use Com\Tecnick\Pdf\Font\Import\TrueType;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Font\Subset
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
class Subset
|
||||
{
|
||||
/**
|
||||
* array of table names to preserve (loca and glyf tables will be added later)
|
||||
* the cmap table is not needed and shall not be present,
|
||||
* since the mapping from character codes to glyph descriptions is provided separately
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
protected const TABLENAMES = [
|
||||
'head' => true,
|
||||
'hhea' => true,
|
||||
'hmtx' => true,
|
||||
'maxp' => true,
|
||||
'cvt ' => true,
|
||||
'fpgm' => true,
|
||||
'prep' => true,
|
||||
'glyf' => true,
|
||||
'loca' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* 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' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array containing subset glyphs indexes of chars from cmap table
|
||||
*
|
||||
* @var array<int, bool>
|
||||
*/
|
||||
protected array $subglyphs = [];
|
||||
|
||||
/**
|
||||
* Subset font
|
||||
*/
|
||||
protected string $subfont = '';
|
||||
|
||||
/**
|
||||
* 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 array<int, bool> $subchars Array containing subset chars
|
||||
*
|
||||
* @throws FontException in case of error
|
||||
*/
|
||||
public function __construct(string $font, array $fdt, array $subchars = [])
|
||||
{
|
||||
$this->fbyte = new Byte($font);
|
||||
$trueType = new TrueType($font, $fdt, $this->fbyte, $subchars);
|
||||
$this->fdt = $trueType->getFontMetrics();
|
||||
$this->subglyphs = $trueType->getSubGlyphs();
|
||||
$this->addCompositeGlyphs();
|
||||
$this->addProcessedTables();
|
||||
$this->removeUnusedTables();
|
||||
$this->buildSubsetFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the extracted font metrics
|
||||
*/
|
||||
public function getSubsetFont(): string
|
||||
{
|
||||
return $this->subfont;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returs the checksum of a TTF table.
|
||||
*
|
||||
* @param string $table Table to check
|
||||
* @param int $length Length of table in bytes
|
||||
*
|
||||
* @return int checksum
|
||||
*/
|
||||
protected function getTableChecksum(string $table, int $length): int
|
||||
{
|
||||
$sum = 0;
|
||||
$tlen = ($length / 4);
|
||||
$offset = 0;
|
||||
for ($idx = 0; $idx < $tlen; ++$idx) {
|
||||
$val = unpack('Ni', substr($table, $offset, 4));
|
||||
if ($val === false) {
|
||||
throw new FontException('Unable to unpack table data');
|
||||
}
|
||||
|
||||
$sum += $val['i'];
|
||||
$offset += 4;
|
||||
}
|
||||
|
||||
$sum = unpack('Ni', pack('N', $sum));
|
||||
if ($sum === false) {
|
||||
throw new FontException('Unable to unpack checksum');
|
||||
}
|
||||
|
||||
return $sum['i'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add composite glyphs
|
||||
*/
|
||||
protected function addCompositeGlyphs(): void
|
||||
{
|
||||
$new_sga = $this->subglyphs;
|
||||
while ($new_sga !== []) {
|
||||
$sga = array_keys($new_sga);
|
||||
$new_sga = [];
|
||||
foreach ($sga as $key) {
|
||||
$new_sga = $this->findCompositeGlyphs($new_sga, $key);
|
||||
}
|
||||
|
||||
$this->subglyphs = [...$this->subglyphs, ...$new_sga];
|
||||
}
|
||||
|
||||
// sort glyphs by key (and remove duplicates)
|
||||
ksort($this->subglyphs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add composite glyphs
|
||||
*
|
||||
* @param array<int, bool> $new_sga
|
||||
*
|
||||
* @return array<int, bool>
|
||||
*/
|
||||
protected function findCompositeGlyphs(array $new_sga, int $key): array
|
||||
{
|
||||
if (isset($this->fdt['indexToLoc'][$key])) {
|
||||
$this->offset = ($this->fdt['table']['glyf']['offset'] + $this->fdt['indexToLoc'][$key]);
|
||||
$numberOfContours = $this->fbyte->getShort($this->offset);
|
||||
$this->offset += 2;
|
||||
if ($numberOfContours < 0) { // composite glyph
|
||||
$this->offset += 8; // skip xMin, yMin, xMax, yMax
|
||||
do {
|
||||
$flags = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
$glyphIndex = $this->fbyte->getUShort($this->offset);
|
||||
$this->offset += 2;
|
||||
if (! isset($this->subglyphs[$glyphIndex])) {
|
||||
// add missing glyphs
|
||||
$new_sga[$glyphIndex] = true;
|
||||
}
|
||||
|
||||
// skip some bytes by case
|
||||
if (($flags & 1) !== 0) {
|
||||
$this->offset += 4;
|
||||
} else {
|
||||
$this->offset += 2;
|
||||
}
|
||||
|
||||
if (($flags & 8) !== 0) {
|
||||
$this->offset += 2;
|
||||
} elseif (($flags & 64) !== 0) {
|
||||
$this->offset += 4;
|
||||
} elseif (($flags & 128) !== 0) {
|
||||
$this->offset += 8;
|
||||
}
|
||||
} while ($flags & 32);
|
||||
}
|
||||
}
|
||||
|
||||
return $new_sga;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unused tables
|
||||
*/
|
||||
protected function removeUnusedTables(): void
|
||||
{
|
||||
// get the tables to preserve
|
||||
$this->offset = 12;
|
||||
$tabname = array_keys($this->fdt['table']);
|
||||
foreach ($tabname as $tag) {
|
||||
if (! isset(self::TABLENAMES[$tag])) {
|
||||
// remove the table
|
||||
unset($this->fdt['table'][$tag]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($this->fdt['table'][$tag])) {
|
||||
$this->fdt['table'][$tag] = [
|
||||
'checkSum' => 0,
|
||||
'data' => '',
|
||||
'length' => 0,
|
||||
'offset' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$this->fdt['table'][$tag]['data'] = substr(
|
||||
$this->font,
|
||||
$this->fdt['table'][$tag]['offset'],
|
||||
$this->fdt['table'][$tag]['length']
|
||||
);
|
||||
if ($tag == 'head') {
|
||||
// set the checkSumAdjustment to 0
|
||||
$this->fdt['table'][$tag]['data'] = substr($this->fdt['table'][$tag]['data'], 0, 8)
|
||||
. "\x0\x0\x0\x0" . substr($this->fdt['table'][$tag]['data'], 12);
|
||||
}
|
||||
|
||||
$pad = 4 - ((int) $this->fdt['table'][$tag]['length'] % 4);
|
||||
if ($pad != 4) {
|
||||
// the length of a table must be a multiple of four bytes
|
||||
$this->fdt['table'][$tag]['length'] += (int) $pad;
|
||||
$this->fdt['table'][$tag]['data'] .= str_repeat("\x0", $pad);
|
||||
}
|
||||
|
||||
$this->fdt['table'][$tag]['offset'] = $this->offset;
|
||||
$this->offset += $this->fdt['table'][$tag]['length'];
|
||||
// check sum is not changed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add glyf and loca tables
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
* @SuppressWarnings("PHPMD.NPathComplexity")
|
||||
*/
|
||||
protected function addProcessedTables(): void
|
||||
{
|
||||
// build new glyf and loca tables
|
||||
$glyf = '';
|
||||
$loca = '';
|
||||
$this->offset = 0;
|
||||
$glyf_offset = $this->fdt['table']['glyf']['offset'];
|
||||
for ($i = 0; $i < $this->fdt['tot_num_glyphs']; ++$i) {
|
||||
if (
|
||||
isset($this->subglyphs[$i])
|
||||
&& isset($this->fdt['indexToLoc'][$i])
|
||||
&& isset($this->fdt['indexToLoc'][($i + 1)])
|
||||
) {
|
||||
$length = ($this->fdt['indexToLoc'][($i + 1)] - $this->fdt['indexToLoc'][$i]);
|
||||
$glyf .= substr($this->font, ($glyf_offset + $this->fdt['indexToLoc'][$i]), $length);
|
||||
} else {
|
||||
$length = 0;
|
||||
}
|
||||
|
||||
if ($this->fdt['short_offset']) {
|
||||
$loca .= pack('n', floor($this->offset / 2));
|
||||
} else {
|
||||
$loca .= pack('N', $this->offset);
|
||||
}
|
||||
|
||||
$this->offset += $length;
|
||||
}
|
||||
|
||||
// add loca
|
||||
if (empty($this->fdt['table']['loca'])) {
|
||||
$this->fdt['table']['loca'] = [
|
||||
'checkSum' => 0,
|
||||
'data' => '',
|
||||
'length' => 0,
|
||||
'offset' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$this->fdt['table']['loca']['data'] = $loca;
|
||||
$this->fdt['table']['loca']['length'] = strlen($loca);
|
||||
$this->fdt['table']['loca']['offset'] = $this->offset;
|
||||
$pad = 4 - ($this->fdt['table']['loca']['length'] % 4);
|
||||
if ($pad != 4) {
|
||||
// the length of a table must be a multiple of four bytes
|
||||
$this->fdt['table']['loca']['length'] += $pad;
|
||||
$this->fdt['table']['loca']['data'] .= str_repeat("\x0", $pad);
|
||||
}
|
||||
|
||||
$this->fdt['table']['loca']['checkSum'] = $this->getTableChecksum(
|
||||
$this->fdt['table']['loca']['data'],
|
||||
$this->fdt['table']['loca']['length']
|
||||
);
|
||||
|
||||
$this->offset += $this->fdt['table']['loca']['length'];
|
||||
|
||||
// add glyf
|
||||
if (empty($this->fdt['table']['glyf'])) {
|
||||
$this->fdt['table']['glyf'] = [
|
||||
'checkSum' => 0,
|
||||
'data' => '',
|
||||
'length' => 0,
|
||||
'offset' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$this->fdt['table']['glyf']['data'] = $glyf;
|
||||
$this->fdt['table']['glyf']['length'] = strlen($glyf);
|
||||
$this->fdt['table']['glyf']['offset'] = $this->offset;
|
||||
$pad = 4 - ($this->fdt['table']['glyf']['length'] % 4);
|
||||
if ($pad != 4) {
|
||||
// the length of a table must be a multiple of four bytes
|
||||
$this->fdt['table']['glyf']['length'] += $pad;
|
||||
$this->fdt['table']['glyf']['data'] .= str_repeat("\x0", $pad);
|
||||
}
|
||||
|
||||
$this->fdt['table']['glyf']['checkSum'] = $this->getTableChecksum(
|
||||
$this->fdt['table']['glyf']['data'],
|
||||
$this->fdt['table']['glyf']['length']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* build new subset font
|
||||
*/
|
||||
protected function buildSubsetFont(): void
|
||||
{
|
||||
$this->subfont = '';
|
||||
$this->subfont .= pack('N', 0x10000); // sfnt version
|
||||
$numTables = count($this->fdt['table']);
|
||||
$this->subfont .= pack('n', $numTables); // numTables
|
||||
$entrySelector = floor(log($numTables, 2));
|
||||
$searchRange = 2 ** $entrySelector * 16;
|
||||
$rangeShift = ($numTables * 16) - $searchRange;
|
||||
$this->subfont .= pack('n', $searchRange); // searchRange
|
||||
$this->subfont .= pack('n', $entrySelector); // entrySelector
|
||||
$this->subfont .= pack('n', $rangeShift); // rangeShift
|
||||
$this->offset = ($numTables * 16);
|
||||
foreach ($this->fdt['table'] as $tag => $data) {
|
||||
$this->subfont .= $tag; // tag
|
||||
$this->subfont .= pack('N', $data['checkSum']); // checkSum
|
||||
$this->subfont .= pack('N', ($data['offset'] + $this->offset)); // offset
|
||||
$this->subfont .= pack('N', $data['length']); // length
|
||||
}
|
||||
|
||||
foreach ($this->fdt['table'] as $data) {
|
||||
$this->subfont .= $data['data'];
|
||||
}
|
||||
|
||||
// set checkSumAdjustment on head table
|
||||
$checkSumAdjustment = (0xB1B0AFBA - $this->getTableChecksum($this->subfont, strlen($this->subfont)));
|
||||
$this->subfont = substr($this->subfont, 0, $this->fdt['table']['head']['offset'] + 8)
|
||||
. pack('N', $checkSumAdjustment)
|
||||
. substr($this->subfont, $this->fdt['table']['head']['offset'] + 12);
|
||||
}
|
||||
}
|
||||
52
vendor/tecnickcom/tc-lib-pdf-font/src/UniToCid.php
vendored
Normal file
52
vendor/tecnickcom/tc-lib-pdf-font/src/UniToCid.php
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user