* @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\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 * @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 */ 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 * * @throws FontException */ protected function getFontFullPath(string $fontdir, string $file): string { $dirobj = new Dir(); $kpathfonts = \defined('K_PATH_FONTS') ? (string) \constant('K_PATH_FONTS') : ''; // directories where to search for the font definition file // ('.' searches the current working directory) $dirs = \array_unique([ '.', $fontdir, $kpathfonts, $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 TFontData $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 TFontData $font Font to process * @param int $cidoffset Offset for CID values * * @return array> */ 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; } } /** @var array> $range */ return $this->optimizeWidthRanges($range); } /** * Optimize width ranges * * @param array> $range Width Ranges * * @return array> */ 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; } }