Example E079 : font stretch spacing matrix

Font stretching (Tz) and character spacing (Tc) combined in one matrix, driven from both the API and CSS.

Font stretching (Tz) and character spacing (Tc) combined in one matrix, driven from both the API and CSS.

PDF

Page 1 of E079_font_stretch_spacing_matrix.pdf

Source Code

<?php

declare(strict_types=1);

/**
 * E079_font_stretch_spacing_matrix.php
 *
 * @since       2026-06-15
 * @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.
 */

// NOTE: local file reads (images, fonts, attachments) are restricted to an allowlist of
// trusted paths that covers this package tree, so run the examples in place. To read assets
// from other locations, list them in the 'allowedPaths' entry of the fileOptions constructor
// parameter (see E047_remote_resources_security.php).

// NOTE: run make fonts in the project root to generate the dependencies and example fonts.

// autoloader when using Composer
require __DIR__ . '/../vendor/autoload.php';

// define fonts directory
\define('K_PATH_FONTS', \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'));

// This example exercises horizontal font stretching (Tz) and
// character spacing (Tc) through both the direct text API (Section 1) and the
// HTML/CSS engine (Section 2), across every combination of alignment,
// stretching and spacing for two fonts (times and dejavuserif).
$pdf = new \Com\Tecnick\Pdf\Tcpdf(
    unit: \Com\Tecnick\Pdf\Page\Unit::Millimeter,
    isunicode: true,
    subsetfont: false,
    compress: true,
    mode: \Com\Tecnick\Pdf\PdfConformance::None,
    objEncrypt: null,
);

$pdf->setCreator('tc-lib-pdf');
$pdf->setAuthor('Nicola Asuni');
$pdf->setSubject('tc-lib-pdf example: 079');
$pdf->setTitle('Font stretching and spacing matrix');
$pdf->setKeywords('TCPDF tc-lib-pdf example font stretch Tz spacing Tc letter-spacing font-stretch html css');
$pdf->setPDFFilename('079_font_stretch_spacing_matrix.pdf');
$pdf->setViewerPreferences(['DisplayDocTitle' => true]);

// ---------- shared layout constants ----------

$marginLeft = 15.0;
$contentWidth = 180.0;
$topY = 15.0;
$bottomLimit = 285.0;
$rowHeight = 8.0;

$boxStyle = [
    'all' => [
        'lineWidth' => 0.2,
        'lineCap' => 'butt',
        'lineJoin' => 'miter',
        'dashArray' => [],
        'dashPhase' => 0,
        'lineColor' => 'gray',
        'fillColor' => '',
    ],
];

// Matrix dimensions (mirror example_063: 90/100/110% and -0.254/0/+0.254mm).
$alignments = ['L' => 'LEFT', 'C' => 'CENTER', 'R' => 'RIGHT', 'J' => 'JUSTIFY'];
$cssAlign = ['L' => 'left', 'C' => 'center', 'R' => 'right', 'J' => 'justify'];
$stretchRatios = [0.90, 1.00, 1.10]; // font-stack stretching ratios (1.0 = 100%)
$spacingPoints = [-0.72, 0.0, 0.72]; // direct API character spacing (points)
$spacingMillim = [-0.254, 0.0, 0.254]; // CSS letter-spacing (mm); ~ the spacing above
$fonts = ['times', 'dejavuserif'];

// ---------- helpers ----------

$setFont = static function (
    \Com\Tecnick\Pdf\Tcpdf $pdf,
    string $family,
    string $style,
    float $size,
    float $spacing = 0.0,
    float $stretching = 1.0,
): void {
    $font = $pdf->font->insert($pdf->pon, $family, $style, $size, $spacing, $stretching);
    $pdf->page->addContent($font['out']);
};

// Register a neutral current font without writing to any page; used before
// addPage() so the new page is created with a valid current font.
$ensureFont = static function (\Com\Tecnick\Pdf\Tcpdf $pdf): void {
    $pdf->font->insert($pdf->pon, 'helvetica', '', 10, 0.0, 1.0);
};

$heading = static function (
    \Com\Tecnick\Pdf\Tcpdf $pdf,
    string $title,
    string $subtitle,
    float $marginLeft,
    float $contentWidth,
    float $y,
) use ($setFont): float {
    $setFont($pdf, 'helvetica', 'B', 13);
    $pdf->page->addContent($pdf->getTextCell(
        txt: $title,
        posx: $marginLeft,
        posy: $y,
        width: $contentWidth,
        height: 0,
        valign: \Com\Tecnick\Pdf\TextVAlign::Top,
        halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    ));
    $setFont($pdf, 'helvetica', '', 9);
    $pdf->page->addContent($pdf->getTextCell(
        txt: $subtitle,
        posx: $marginLeft,
        posy: $y + 6.5,
        width: $contentWidth,
        height: 0,
        valign: \Com\Tecnick\Pdf\TextVAlign::Top,
        halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    ));

    return $y + 15.0;
};

// ========== SECTION 1 - DIRECT TEXT API ==========

foreach ($fonts as $family) {
    $ensureFont($pdf); // ensure a current font exists before addPage
    $pdf->addPage();
    $y = $heading(
        $pdf,
        'Example 079 - Section 1: direct text API (font: ' . $family . ')',
        'Each row inserts the font with a stretching ratio and character spacing, then renders the '
        . 'label via getTextCell() using the L/C/R/J alignment shown.',
        $marginLeft,
        $contentWidth,
        $topY,
    );

    foreach ($alignments as $code => $name) {
        foreach ($stretchRatios as $ratio) {
            foreach ($spacingPoints as $spacing) {
                if (($y + $rowHeight) > $bottomLimit) {
                    $ensureFont($pdf);
                    $pdf->addPage();
                    $y = $topY;
                }

                $setFont($pdf, $family, '', 14, $spacing, $ratio);
                $pdf->page->addContent($pdf->graph->getRect(
                    $marginLeft,
                    $y,
                    $contentWidth,
                    $rowHeight,
                    'D',
                    $boxStyle,
                ));
                $label = \sprintf(
                    '%s | Stretching = %d%% | Spacing = %+.3fpt',
                    $name,
                    (int) \round($ratio * 100.0),
                    $spacing,
                );
                $pdf->page->addContent($pdf->getTextCell(
                    txt: $label,
                    posx: $marginLeft,
                    posy: $y,
                    width: $contentWidth,
                    height: $rowHeight,
                    valign: \Com\Tecnick\Pdf\TextVAlign::Center,
                    halign: $code,
                    drawcell: false,
                ));
                $y += $rowHeight + 1.0;
            }
        }

        $y += 1.5; // gap between alignment groups
    }
}

// ========== SECTION 2 - HTML / CSS API ==========

foreach ($fonts as $family) {
    $ensureFont($pdf); // ensure a current font exists before addPage
    $pdf->addPage();
    $y = $heading(
        $pdf,
        'Example 079 - Section 2: HTML/CSS API (font: ' . $family . ')',
        'Each bordered block is an HTML span carrying CSS font-stretch and letter-spacing; the '
        . 'wrapping paragraph must stay inside its cell with the stretch/spacing applied.',
        $marginLeft,
        $contentWidth,
        $topY,
    );

    // Base font for the HTML engine (neutral stretch/spacing root).
    $setFont($pdf, $family, '', 11);

    $html = '';
    foreach ($alignments as $code => $name) {
        foreach ($stretchRatios as $ratio) {
            $pct = (int) \round($ratio * 100.0);
            foreach ($spacingMillim as $spacing) {
                $spacingLabel = \sprintf('%+.3F', $spacing);
                $html .=
                    '<table border="1" cellpadding="3" cellspacing="0" style="width:100%;"><tr><td>'
                    . '<div style="text-align:'
                    . $cssAlign[$code]
                    . ';">'
                    . '<span style="font-stretch:'
                    . $pct
                    . '%;letter-spacing:'
                    . $spacing
                    . 'mm;">'
                    . '<span style="color:red;">'
                    . $name
                    . '</span> | '
                    . '<span style="color:green;">Stretching = '
                    . $pct
                    . '%</span> | '
                    . '<span style="color:blue;">Spacing = '
                    . $spacingLabel
                    . 'mm</span><br />'
                    . 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sed imperdiet lectus. '
                    . 'Phasellus quis velit velit, non condimentum quam. Sed neque urna, ultrices ac '
                    . 'volutpat vel, laoreet vitae augue. Sed vel velit erat. Class aptent taciti sociosqu '
                    . 'ad litora torquent per conubia nostra, per inceptos himenaeos.'
                    . '</span></div></td></tr></table>';
            }
        }
    }

    $pdf->addHTMLCell(html: $html, posx: $marginLeft, posy: $y, width: $contentWidth, height: 0);
}

// ----------

// get PDF document as raw string
$rawpdf = $pdf->getOutPDFString();

// Various output modes:

//$pdf->savePDF(\dirname(__DIR__) . '/target', $rawpdf);
$pdf->renderPDF(rawpdf: $rawpdf);

//$pdf->downloadPDF($rawpdf);
//echo $pdf->getMIMEAttachmentPDF($rawpdf);