Example E030 : svg

SVG rendering: path parsing, markers, text anchoring, and style inheritance.

SVG rendering: path parsing, markers, text anchoring, and style inheritance.

PDF

Page 1 of E030_svg.pdf

Source Code

<?php

declare(strict_types=1);

/**
 * E030_svg.php
 *
 * Dedicated SVG rendering example.
 *
 * Renders every SVG image file shipped in the examples/images/ directory.
 *
 * @since       2026-04-26
 * @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('OUTPUT_FILE', \realpath(__DIR__ . '/../target') . '/example_svg.pdf');

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

// autoloader when using RPM or DEB package installation
//require ('/usr/share/php/Com/Tecnick/Pdf/autoload.php');

// ---------------------------------------------------------------------------
// SVG directory
$svgdir = __DIR__ . '/images';

// ---------------------------------------------------------------------------
// PDF object

$pdf = new \Com\Tecnick\Pdf\Tcpdf(
    unit: \Com\Tecnick\Pdf\Page\Unit::Millimeter,
    isunicode: true,
    subsetfont: false,
    compress: true,
);

$pdf->setCreator('tc-lib-pdf');
$pdf->setAuthor('Nicola Asuni');
$pdf->setSubject('tc-lib-pdf example: 030 — SVG');
$pdf->setTitle('SVG Rendering Example');
$pdf->setKeywords('TCPDF tc-lib-pdf SVG example');
$pdf->setPDFFilename('030_svg.pdf');

$pdf->setViewerPreferences(['DisplayDocTitle' => true]);

$pdf->enableDefaultPageContent();

// Insert a font for page labels
$bfont = $pdf->font->insert($pdf->pon, 'helvetica', '', 10);

// ---------------------------------------------------------------------------
// Page 1 - tcpdf_box.svg
//
// A multi-path box illustration that exercises fill, stroke, fill-rule and
// transform handling.
// ---------------------------------------------------------------------------

$page01 = $pdf->addPage();
$pdf->setBookmark(name: 'tcpdf_box.svg', link: '', level: 0, page: -1, posx: 0, posy: 0, fstyle: 'B', color: 'blue');

$pdf->page->addContent($bfont['out']);

// Label
$pdf->addTextCell(
    txt: 'tcpdf_box.svg — paths, fill, stroke, fill-rule',
    pid: -1,
    posx: 10,
    posy: 10,
    width: 190,
    height: 6,
    offset: 0,
    linespace: 0,
    valign: \Com\Tecnick\Pdf\TextVAlign::Top,
    halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    cell: null,
    styles: [],
    strokewidth: 0,
    wordspacing: 0,
    leading: 0,
    rise: 0,
    jlast: true,
    fill: true,
    stroke: false,
    underline: false,
    linethrough: false,
    overline: false,
    clip: false,
    drawcell: false,
);

// Render at two different sizes to check scaling
$svg01a = $pdf->addSVG(
    img: $svgdir . '/tcpdf_box.svg',
    posx: 10,
    posy: 20,
    width: 180,
    height: 120,
    pageheight: $page01['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg01a));

$svg01b = $pdf->addSVG(
    img: $svgdir . '/tcpdf_box.svg',
    posx: 10,
    posy: 148,
    width: 90,
    height: 60,
    pageheight: $page01['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg01b));

$svg01c = $pdf->addSVG(
    img: $svgdir . '/tcpdf_box.svg',
    posx: 105,
    posy: 148,
    width: 45,
    height: 30,
    pageheight: $page01['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg01c));

// ---------------------------------------------------------------------------
// Page 2 - testsvg.svg
//
// Exercises linear and radial gradients, clip-paths, complex paths, text and
// image embedding inside an SVG document.
// ---------------------------------------------------------------------------

$page02 = $pdf->addPage();
$pdf->setBookmark(name: 'testsvg.svg', link: '', level: 0, page: -1, posx: 0, posy: 0, fstyle: 'B', color: 'green');

$pdf->page->addContent($bfont['out']);

$pdf->addTextCell(
    txt: 'testsvg.svg — gradients, clip-paths, text, embedded image',
    pid: -1,
    posx: 10,
    posy: 10,
    width: 190,
    height: 6,
    offset: 0,
    linespace: 0,
    valign: \Com\Tecnick\Pdf\TextVAlign::Top,
    halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    cell: null,
    styles: [],
    strokewidth: 0,
    wordspacing: 0,
    leading: 0,
    rise: 0,
    jlast: true,
    fill: true,
    stroke: false,
    underline: false,
    linethrough: false,
    overline: false,
    clip: false,
    drawcell: false,
);

// Full-width render
$svg02a = $pdf->addSVG(
    img: $svgdir . '/testsvg.svg',
    posx: 10,
    posy: 20,
    width: 190,
    height: 95,
    pageheight: $page02['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg02a));

// Smaller render to verify scaling
$svg02b = $pdf->addSVG(
    img: $svgdir . '/testsvg.svg',
    posx: 10,
    posy: 120,
    width: 95,
    height: 47,
    pageheight: $page02['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg02b));

$svg02c = $pdf->addSVG(
    img: $svgdir . '/testsvg.svg',
    posx: 110,
    posy: 120,
    width: 47,
    height: 23,
    pageheight: $page02['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg02c));

// ---------------------------------------------------------------------------
// Page 3 - testsvgblend.svg
//
// Exercises mix-blend-mode (multiply, screen, darken …) and opacity.
// ---------------------------------------------------------------------------

$page03 = $pdf->addPage();
$pdf->setBookmark(name: 'testsvgblend.svg', link: '', level: 0, page: -1, posx: 0, posy: 0, fstyle: 'B', color: 'red');

$pdf->page->addContent($bfont['out']);

$pdf->addTextCell(
    txt: 'testsvgblend.svg — blend modes, opacity',
    pid: -1,
    posx: 10,
    posy: 10,
    width: 190,
    height: 6,
    offset: 0,
    linespace: 0,
    valign: \Com\Tecnick\Pdf\TextVAlign::Top,
    halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    cell: null,
    styles: [],
    strokewidth: 0,
    wordspacing: 0,
    leading: 0,
    rise: 0,
    jlast: true,
    fill: true,
    stroke: false,
    underline: false,
    linethrough: false,
    overline: false,
    clip: false,
    drawcell: false,
);

$svg03a = $pdf->addSVG(
    img: $svgdir . '/testsvgblend.svg',
    posx: 10,
    posy: 20,
    width: 190,
    height: 127,
    pageheight: $page03['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg03a));

// Side-by-side scaled copies
$svg03b = $pdf->addSVG(
    img: $svgdir . '/testsvgblend.svg',
    posx: 10,
    posy: 155,
    width: 90,
    height: 60,
    pageheight: $page03['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg03b));

$svg03c = $pdf->addSVG(
    img: $svgdir . '/testsvgblend.svg',
    posx: 105,
    posy: 155,
    width: 90,
    height: 60,
    pageheight: $page03['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg03c));

// ---------------------------------------------------------------------------
// Page 4 - tux.svg
//
// The Tux penguin - a complex multi-path SVG with fine detail and many style
// attributes (fill, stroke, stroke-width, opacity).
//
// Copyright: Larry Ewing - permits any use with proper attribution.
// ---------------------------------------------------------------------------

$page04 = $pdf->addPage();
$pdf->setBookmark(name: 'tux.svg', link: '', level: 0, page: -1, posx: 0, posy: 0, fstyle: 'B', color: 'black');

$pdf->page->addContent($bfont['out']);

$pdf->addTextCell(
    txt: 'tux.svg — complex multi-path illustration (The copyright holder of the Tux image is Larry Ewing)',
    pid: -1,
    posx: 10,
    posy: 10,
    width: 190,
    height: 6,
    offset: 0,
    linespace: 0,
    valign: \Com\Tecnick\Pdf\TextVAlign::Top,
    halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    cell: null,
    styles: [],
    strokewidth: 0,
    wordspacing: 0,
    leading: 0,
    rise: 0,
    jlast: true,
    fill: true,
    stroke: false,
    underline: false,
    linethrough: false,
    overline: false,
    clip: false,
    drawcell: false,
);

// Large centred render
$svg04a = $pdf->addSVG(
    img: $svgdir . '/tux.svg',
    posx: 55,
    posy: 20,
    width: 100,
    height: 121,
    pageheight: $page04['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg04a));

// Three scaled thumbnails
$svg04b = $pdf->addSVG(
    img: $svgdir . '/tux.svg',
    posx: 10,
    posy: 148,
    width: 55,
    height: 66,
    pageheight: $page04['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg04b));

$svg04c = $pdf->addSVG(
    img: $svgdir . '/tux.svg',
    posx: 70,
    posy: 148,
    width: 40,
    height: 48,
    pageheight: $page04['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg04c));

$svg04d = $pdf->addSVG(
    img: $svgdir . '/tux.svg',
    posx: 115,
    posy: 148,
    width: 25,
    height: 30,
    pageheight: $page04['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg04d));

// ---------------------------------------------------------------------------
// Page 5 - all four images together (overview grid)
// ---------------------------------------------------------------------------

$page05 = $pdf->addPage();
$pdf->setBookmark(
    name: 'SVG overview grid',
    link: '',
    level: 0,
    page: -1,
    posx: 0,
    posy: 0,
    fstyle: 'B',
    color: 'purple',
);

$pdf->page->addContent($bfont['out']);

$pdf->addTextCell(
    txt: 'SVG overview grid — all four images at equal size',
    pid: -1,
    posx: 10,
    posy: 10,
    width: 190,
    height: 6,
    offset: 0,
    linespace: 0,
    valign: \Com\Tecnick\Pdf\TextVAlign::Top,
    halign: \Com\Tecnick\Pdf\TextHAlign::Left,
    cell: null,
    styles: [],
    strokewidth: 0,
    wordspacing: 0,
    leading: 0,
    rise: 0,
    jlast: true,
    fill: true,
    stroke: false,
    underline: false,
    linethrough: false,
    overline: false,
    clip: false,
    drawcell: false,
);

// Row 1
$s1 = $pdf->addSVG(
    img: $svgdir . '/tcpdf_box.svg',
    posx: 10,
    posy: 20,
    width: 90,
    height: 60,
    pageheight: $page05['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $s1));

$s2 = $pdf->addSVG(
    img: $svgdir . '/testsvg.svg',
    posx: 105,
    posy: 20,
    width: 90,
    height: 60,
    pageheight: $page05['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $s2));

// Row 2
$s3 = $pdf->addSVG(
    img: $svgdir . '/testsvgblend.svg',
    posx: 10,
    posy: 88,
    width: 90,
    height: 60,
    pageheight: $page05['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $s3));

$s4 = $pdf->addSVG(
    img: $svgdir . '/tux.svg',
    posx: 105,
    posy: 88,
    width: 90,
    height: 109,
    pageheight: $page05['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $s4));

// ---------------------------------------------------------------------------
// Page 6 - abstract_a4_features.svg (full-page portrait)
//
// Dedicated A4 portrait abstract composition that concentrates most SVG
// features currently supported by this library in one single visual sample.
// ---------------------------------------------------------------------------

$page06 = $pdf->addPage();
$pdf->setBookmark(
    name: 'abstract_a4_features.svg',
    link: '',
    level: 0,
    page: -1,
    posx: 0,
    posy: 0,
    fstyle: 'B',
    color: 'orange',
);

$svg06a = $pdf->addSVG(
    img: $svgdir . '/abstract_a4_features.svg',
    posx: 0,
    posy: 0,
    width: $page06['width'],
    height: $page06['height'],
    pageheight: $page06['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg06a));

// ---------------------------------------------------------------------------
// Page 7 - path_text_regressions.svg (full-page portrait)
//
// Sheet covering the path and text parsing rules: numbers with no leading
// digit, packed decimals, scientific notation,
// elliptical arc flags packed against their neighbours, the smooth curve
// commands 'S' and 'T', compound subpaths under both fill rules, text
// anchoring, XML white space handling and multi chunk text layout.
//
// Every shape is drawn twice, as a pale fill from a verbose path and as a dark
// outline from the compact equivalent: the outline must trace the fill exactly.
// ---------------------------------------------------------------------------

$page07 = $pdf->addPage();
$pdf->setBookmark(
    name: 'path_text_regressions.svg',
    link: '',
    level: 0,
    page: -1,
    posx: 0,
    posy: 0,
    fstyle: 'B',
    color: 'purple',
);

$svg07a = $pdf->addSVG(
    img: $svgdir . '/path_text_regressions.svg',
    posx: 0,
    posy: 0,
    width: $page07['width'],
    height: $page07['height'],
    pageheight: $page07['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg07a));

// ---------------------------------------------------------------------------
// Page 8 - style_cascade_regressions.svg (full-page portrait)
//
// Sheet covering the style cascade, opacity, length units and clipping rules:
// a style declaration against the presentation attribute of the same element,
// a fully transparent group, lengths with and without a unit suffix,
// percentage alpha values, and a clipPath applied to a shape, to a group and
// to nothing at all.
//
// Each case is drawn next to a reference shape painted directly in the
// expected form: the two must be identical.
// ---------------------------------------------------------------------------

$page08 = $pdf->addPage();
$pdf->setBookmark(
    name: 'style_cascade_regressions.svg',
    link: '',
    level: 0,
    page: -1,
    posx: 0,
    posy: 0,
    fstyle: 'B',
    color: 'green',
);

$svg08a = $pdf->addSVG(
    img: $svgdir . '/style_cascade_regressions.svg',
    posx: 0,
    posy: 0,
    width: $page08['width'],
    height: $page08['height'],
    pageheight: $page08['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg08a));

// ---------------------------------------------------------------------------
// Page 9 - style_sheet_use_regressions.svg (full-page portrait)
//
// Sheet covering the rules a document stylesheet contributes to the cascade,
// the compositing of a translucent group, the dash pattern, the dash offset
// and the miter limit, and the use element against a shape, a group and a
// symbol target.
//
// Each case is drawn next to a reference shape painted directly in the
// expected form: the two must be identical.
// ---------------------------------------------------------------------------

$page09 = $pdf->addPage();
$pdf->setBookmark(
    name: 'style_sheet_use_regressions.svg',
    link: '',
    level: 0,
    page: -1,
    posx: 0,
    posy: 0,
    fstyle: 'B',
    color: 'orange',
);

$svg09a = $pdf->addSVG(
    img: $svgdir . '/style_sheet_use_regressions.svg',
    posx: 0,
    posy: 0,
    width: $page09['width'],
    height: $page09['height'],
    pageheight: $page09['height'],
);
$pdf->page->addContent($pdf->getSetSVG(soid: $svg09a));

// ---------------------------------------------------------------------------
// Output
// ---------------------------------------------------------------------------

$rawpdf = $pdf->getOutPDFString();
$pdf->renderPDF(rawpdf: $rawpdf);