generated from jric11/baseProject
581 lines
18 KiB
PHP
581 lines
18 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Subset.php
|
||
|
|
*
|
||
|
|
* @since 2011-05-23
|
||
|
|
* @category Library
|
||
|
|
* @package PdfFont
|
||
|
|
* @author Nicola Asuni <info@tecnick.com>
|
||
|
|
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
|
||
|
|
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||
|
|
* @link https://github.com/tecnickcom/tc-lib-pdf-font
|
||
|
|
*
|
||
|
|
* This file is part of tc-lib-pdf-font software library.
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace Com\Tecnick\Pdf\Font;
|
||
|
|
|
||
|
|
use Com\Tecnick\File\Byte;
|
||
|
|
use Com\Tecnick\File\File as ObjFile;
|
||
|
|
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-2026 Nicola Asuni - Tecnick.com LTD
|
||
|
|
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||
|
|
* @link https://github.com/tecnickcom/tc-lib-pdf-font
|
||
|
|
*
|
||
|
|
* @phpstan-import-type TFontData from 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' => [],
|
||
|
|
'cwu' => [],
|
||
|
|
'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;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* File helper used to load font definition files.
|
||
|
|
*/
|
||
|
|
protected ObjFile $fileHelper;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process TrueType font
|
||
|
|
*
|
||
|
|
* @param string $font Content of the input font file
|
||
|
|
* @param TFontData $fdt Extracted font metrics
|
||
|
|
* @param ObjFile $fileHelper Optional file helper for font loading.
|
||
|
|
* @param array<int, bool> $subchars Array containing subset chars
|
||
|
|
*
|
||
|
|
* @throws FontException in case of error
|
||
|
|
*/
|
||
|
|
public function __construct(string $font, array $fdt, ObjFile $fileHelper, array $subchars = [])
|
||
|
|
{
|
||
|
|
$this->fileHelper = $fileHelper;
|
||
|
|
$this->font = $font;
|
||
|
|
$this->fbyte = new Byte($font);
|
||
|
|
$trueType = new TrueType(
|
||
|
|
font: $font,
|
||
|
|
fdt: $fdt,
|
||
|
|
fileHelper: $this->fileHelper,
|
||
|
|
fbyte: $this->fbyte,
|
||
|
|
subchars: $subchars,
|
||
|
|
// Subsetting only needs the glyph program (loca/glyf); per-glyph bounding
|
||
|
|
// boxes are never read here, so skip computing them.
|
||
|
|
withCbbox: false,
|
||
|
|
);
|
||
|
|
$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;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the checksum of a TTF table.
|
||
|
|
*
|
||
|
|
* @param string $table Table to check
|
||
|
|
* @param int $length Length of table in bytes
|
||
|
|
*
|
||
|
|
* @return int checksum
|
||
|
|
*
|
||
|
|
* @throws FontException
|
||
|
|
*/
|
||
|
|
protected function getTableChecksum(string $table, int $length): int
|
||
|
|
{
|
||
|
|
$sum = 0;
|
||
|
|
$tlen = (int) floor(($length + 3) / 4);
|
||
|
|
$offset = 0;
|
||
|
|
for ($idx = 0; $idx < $tlen; ++$idx) {
|
||
|
|
$chunk = \substr($table, $offset, 4);
|
||
|
|
if (\strlen($chunk) < 4) {
|
||
|
|
// OpenType checksums use zero-padding for trailing partial words.
|
||
|
|
$chunk = \str_pad($chunk, 4, "\0", STR_PAD_RIGHT);
|
||
|
|
}
|
||
|
|
|
||
|
|
$val = \unpack('Ni', $chunk);
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($new_sga as $gid => $enabled) {
|
||
|
|
if (!$enabled) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->subglyphs[$gid] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// sort glyphs by key
|
||
|
|
\ksort($this->subglyphs);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find composite glyphs
|
||
|
|
*
|
||
|
|
* @param array<int, bool> $new_sga
|
||
|
|
* @param int $key
|
||
|
|
*
|
||
|
|
* @return array<int, bool>
|
||
|
|
*/
|
||
|
|
protected function findCompositeGlyphs(array $new_sga, int $key): array
|
||
|
|
{
|
||
|
|
if (isset($this->fdt['indexToLoc'][$key])) {
|
||
|
|
/**
|
||
|
|
* Glyph Header
|
||
|
|
* - int16 numberOfContours Normal glyph if >= 0 or composite glyph if negative (should be -1)
|
||
|
|
* - int16 xMin Minimum x for coordinate data.
|
||
|
|
* - int16 yMin Minimum y for coordinate data.
|
||
|
|
* - int16 xMax Maximum x for coordinate data.
|
||
|
|
* - int16 yMax Maximum y for coordinate data.
|
||
|
|
*/
|
||
|
|
|
||
|
|
$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
|
||
|
|
/**
|
||
|
|
* ComponentGlyph record
|
||
|
|
* - uint16 flags Normal glyph if >= 0 or composite glyph if negative (should be -1)
|
||
|
|
* - uint16 glyphIndex glyph index of component
|
||
|
|
* - u/int8|u/int16 argument1 x-offset for component or point number; type depends on bits 0 and 1 in component flags
|
||
|
|
* - u/int8|u/int16 argument2 y-offset for component or point number; type depends on bits 0 and 1 in component flags
|
||
|
|
* - [transform data] optional transform data
|
||
|
|
*/
|
||
|
|
$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
|
||
|
|
// ARG_1_AND_2_ARE_WORDS (bit 0): [u]int32 if set and [u]int16 if not set
|
||
|
|
if (($flags & 1) !== 0) {
|
||
|
|
$this->offset += 4;
|
||
|
|
} else {
|
||
|
|
$this->offset += 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (($flags & 8) !== 0) {
|
||
|
|
// WE_HAVE_A_SCALE (bit 3): Adds 1 * F2DOT14 field
|
||
|
|
$this->offset += 2;
|
||
|
|
} elseif (($flags & 64) !== 0) {
|
||
|
|
// WE_HAVE_AN_X_AND_Y_SCALE (bit 6): Adds 2 * F2DOT14 fields
|
||
|
|
$this->offset += 4;
|
||
|
|
} elseif (($flags & 128) !== 0) {
|
||
|
|
// WE_HAVE_A_TWO_BY_TWO (bit 7): Adds 4 * F2DOT14 fields
|
||
|
|
$this->offset += 8;
|
||
|
|
}
|
||
|
|
} while ($flags & 32); // MORE_COMPONENTS (bit 5)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
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 (!isset($this->fdt['table'][$tag])) {
|
||
|
|
$this->fdt['table'][$tag] = [
|
||
|
|
'checkSum' => 0,
|
||
|
|
'data' => '',
|
||
|
|
'length' => 0,
|
||
|
|
'offset' => 0,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
$isSubsetTable = $tag === 'loca' || $tag === 'glyf';
|
||
|
|
if (!$isSubsetTable) {
|
||
|
|
$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", max(0, $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) {
|
||
|
|
$nextidx = $this->getNextLocaIndex($i + 1);
|
||
|
|
if (isset($this->subglyphs[$i], $this->fdt['indexToLoc'][$i]) && $nextidx !== null) {
|
||
|
|
$length = $this->fdt['indexToLoc'][$nextidx] - $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 (!isset($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 (!isset($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'],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns the first available loca index from $start, or null if none exists.
|
||
|
|
*/
|
||
|
|
protected function getNextLocaIndex(int $start): ?int
|
||
|
|
{
|
||
|
|
for ($idx = $start; $idx <= $this->fdt['tot_num_glyphs']; ++$idx) {
|
||
|
|
if (isset($this->fdt['indexToLoc'][$idx])) {
|
||
|
|
return $idx;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* build new subset font
|
||
|
|
*
|
||
|
|
* @throws FontException
|
||
|
|
*/
|
||
|
|
protected function buildSubsetFont(): void
|
||
|
|
{
|
||
|
|
$this->subfont = '';
|
||
|
|
$this->subfont .= \pack('N', 0x1_0000); // 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
|
||
|
|
// Table offsets stored in $this->fdt start after the 12-byte sfnt header.
|
||
|
|
// The full output adds the table directory immediately after that header,
|
||
|
|
// so both directory offsets and in-buffer table positions must include this base.
|
||
|
|
$tableDataBaseOffset = $numTables * 16;
|
||
|
|
$this->offset = $tableDataBaseOffset;
|
||
|
|
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 = 0xB1B0_AFBA - $this->getTableChecksum($this->subfont, \strlen($this->subfont));
|
||
|
|
$headAdjustmentPos = $tableDataBaseOffset + $this->fdt['table']['head']['offset'] + 8;
|
||
|
|
$this->subfont =
|
||
|
|
\substr($this->subfont, 0, $headAdjustmentPos)
|
||
|
|
. \pack('N', $checkSumAdjustment)
|
||
|
|
. \substr($this->subfont, $headAdjustmentPos + 4);
|
||
|
|
}
|
||
|
|
}
|