Files
baseProject/vendor/tecnickcom/tc-lib-pdf/test/FontCloneStyleTest.php
T

214 lines
7.8 KiB
PHP

<?php
/**
* FontCloneStyleTest.php
*
* @since 2026-07-12
* @category Library
* @package Pdf
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2002-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
*
* This file is part of tc-lib-pdf software library.
*/
namespace Test;
/**
* Regression tests for the font style cloning.
*
* Com\Tecnick\Pdf\Font\Stack::cloneFont() used to forward the definition file of the source font
* to the requested style, so a style that was not already loaded was silently rendered with the
* glyphs and the metrics of the source style.
* See: https://github.com/tecnickcom/tc-lib-pdf-font/issues/19
*
* @since 2026-07-12
* @category Library
* @package Pdf
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2002-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
*
* @phpstan-import-type TFontData from \Com\Tecnick\Pdf\Font\Load
*/
class FontCloneStyleTest extends TestUtil
{
/** @throws \Throwable */
protected function getTestObject(): \Com\Tecnick\Pdf\Tcpdf
{
self::setUpFontsPath();
return new \Com\Tecnick\Pdf\Tcpdf(unit: 'mm', isunicode: true);
}
/**
* Returns the raw buffer data of the given font key.
*
* @return TFontData
*
* @throws \Throwable
*/
private function getFontData(\Com\Tecnick\Pdf\Tcpdf $obj, string $key): array
{
$this->assertArrayHasKey($key, $obj->font->getFonts(), 'The font ' . $key . ' has not been loaded');
return $obj->font->getFont($key);
}
/** @throws \Throwable */
public function testCloneFontLoadsTheDefinitionFileOfTheRequestedStyle(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$obj->font->insert($obj->pon, 'freesans', '', 12);
$bold = $obj->font->cloneFont($obj->pon, null, 'B', 12);
$this->assertSame('freesansB', $bold['key']);
$this->assertSame('B', $bold['style']);
$data = $this->getFontData($obj, 'freesansB');
$this->assertSame('FreeSansBold', $data['name']);
$this->assertStringEndsWith('freesansb.json', $data['ifile']);
$this->assertFalse($data['fakestyle'], 'The real bold definition file must be used');
}
/** @throws \Throwable */
public function testCloneFontLoadsTheDefinitionFileOfCombinedStyles(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$obj->font->insert($obj->pon, 'freesans', '', 12);
$italic = $obj->font->cloneFont($obj->pon, null, 'I', 12);
$this->assertSame('freesansI', $italic['key']);
$this->assertSame('FreeSansOblique', $this->getFontData($obj, 'freesansI')['name']);
$bolditalic = $obj->font->cloneFont($obj->pon, null, 'BI', 12);
$this->assertSame('freesansBI', $bolditalic['key']);
$this->assertSame('FreeSansBoldOblique', $this->getFontData($obj, 'freesansBI')['name']);
}
/**
* Cloning a style that was not loaded before must return the same font as cloning a style
* that was already loaded: this is the inconsistency reported in the upstream issue.
*
* @throws \Throwable
*/
public function testCloneFontIsConsistentWhenTheStyleIsAlreadyLoaded(): void
{
$preloaded = $this->getTestObject();
$preloaded->addPage();
$preloaded->font->insert($preloaded->pon, 'freesans', '', 12);
$preloaded->font->insert($preloaded->pon, 'freesans', 'B', 12);
$preloaded->font->insert($preloaded->pon, 'freesans', '', 12);
$expected = $preloaded->font->cloneFont($preloaded->pon, null, 'B', 12);
$cloned = $this->getTestObject();
$cloned->addPage();
$cloned->font->insert($cloned->pon, 'freesans', '', 12);
$actual = $cloned->font->cloneFont($cloned->pon, null, 'B', 12);
$this->assertSame($expected['key'], $actual['key']);
$this->assertSame($expected['cw'], $actual['cw'], 'The cloned font must have the bold glyph widths');
$this->assertSame($expected['height'], $actual['height']);
}
/**
* The bold glyphs of FreeSans are wider than the regular ones: a wrongly loaded bold font
* would return the regular widths.
*
* @throws \Throwable
*/
public function testCloneFontUsesTheMetricsOfTheRequestedStyle(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$regular = $obj->font->insert($obj->pon, 'freesans', '', 12);
$regularwidth = $obj->font->getCharWidth(0x48); // 'H'
$bold = $obj->font->cloneFont($obj->pon, null, 'B', 12);
$boldwidth = $obj->font->getCharWidth(0x48); // 'H'
$this->assertNotSame($regular['key'], $bold['key']);
$this->assertGreaterThan($regularwidth, $boldwidth, 'The bold glyph must be wider than the regular one');
}
/**
* The source font is loaded with an explicit definition file (as tc-lib-pdf does for the
* default font): the style variant must still be resolved.
*
* @throws \Throwable
*/
public function testCloneFontResolvesTheStyleOfAFontLoadedWithAnExplicitFile(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$ifile = (string) \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts/core/helvetica.json');
$obj->font->insert($obj->pon, 'helvetica', '', 10, null, null, $ifile);
$bold = $obj->font->cloneFont($obj->pon, null, 'B', 10);
$this->assertSame('helveticaB', $bold['key']);
$data = $this->getFontData($obj, 'helveticaB');
$this->assertSame('Helvetica-Bold', $data['name']);
$this->assertStringEndsWith('helveticab.json', $data['ifile']);
}
/**
* When no definition file exists for the requested style, the artificial style must be used.
*
* @throws \Throwable
*/
public function testCloneFontFallsBackToTheArtificialStyle(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$obj->font->insert($obj->pon, 'dejavumathtexgyre', '', 12);
$bold = $obj->font->cloneFont($obj->pon, null, 'B', 12);
$this->assertSame('dejavumathtexgyreB', $bold['key']);
$data = $this->getFontData($obj, 'dejavumathtexgyreB');
$this->assertTrue($data['fakestyle'], 'The artificial bold style must be enabled');
$this->assertTrue($data['mode']['bold']);
}
/** @throws \Throwable */
public function testCloneFontReturnsToTheRegularStyle(): void
{
$obj = $this->getTestObject();
$obj->addPage();
$obj->font->insert($obj->pon, 'freesans', 'B', 12);
$regular = $obj->font->cloneFont($obj->pon, null, '', 12);
$this->assertSame('freesans', $regular['key']);
$this->assertSame('FreeSans', $this->getFontData($obj, 'freesans')['name']);
}
/**
* Tcpdf::addTOC() clones the current font to render the top level bookmarks in bold.
*
* @throws \Throwable
*/
public function testAddTOCRendersTopLevelBookmarksWithTheRealBoldFont(): void
{
$obj = $this->getTestObject();
$page = $obj->addPage();
if (!isset($page['pid']) || !\is_int($page['pid'])) {
$this->fail('Unexpected addPage() return shape.');
}
$obj->font->insert($obj->pon, 'freesans', '', 12);
$obj->setBookmark('Chapter One', '', 0, $page['pid']);
$obj->addTOC($page['pid']);
$data = $this->getFontData($obj, 'freesansB');
$this->assertSame('FreeSansBold', $data['name']);
$this->assertStringEndsWith('freesansb.json', $data['ifile']);
}
}