Files
putnamrec/vendor/tecnickcom/tc-lib-pdf/examples/E080_no_write_regions.php
T
2026-08-20 11:41:17 +00:00

378 lines
14 KiB
PHP

<?php
/**
* E080_no_write_regions.php
*
* @since 2026-06-16
* @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.
*
* No-write page regions (legacy TCPDF example_064 equivalent).
*
* A no-write region is a portion of the page that flowing text will avoid. Each region is
* defined exactly as in the legacy library: a vertical, possibly slanted, segment plus the
* page side it blocks ('L' or 'R'). tc-lib-pdf-page approximates the resulting writable area
* with a stack of rectangular regions (one per horizontal band), so text flows down the page
* hugging the obstacle. Several adjacent segments approximate curved shapes (see the circle on
* the second scenario). See Com\Tecnick\Pdf\Page\Region::setNoWriteRegions().
*
* Each obstacle scenario is rendered twice, on consecutive pages, so the two text engines can be
* compared side by side:
* - with addTextCell() (plain flowing text);
* - with addHTMLCell() (HTML cell), producing the SAME visual result.
* Both engines hug the rectangular, trapezoidal and circular obstacles band by band and, when
* the text overflows the last region, continue on a fresh full-width page (no-write regions are
* page-specific, so the continuation page has no obstacles).
*
* NOTE: the band height must be at least one text line tall; with a 12pt font (~4.9mm line
* height) a 5mm band fits exactly one line, giving the finest (per-line) staircase.
*/
// 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'));
// autoloader when using RPM or DEB package installation
//require ('/usr/share/php/Com/Tecnick/Pdf/autoload.php');
// main TCPDF object
$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: 080');
$pdf->setTitle('No-write page regions');
$pdf->setKeywords('TCPDF tc-lib-pdf example no-write page regions wrap text shape');
$pdf->setPDFFilename('080_no_write_regions.pdf');
$pdf->setViewerPreferences(['DisplayDocTitle' => true]);
// ----------
// Fonts (matching legacy example_064: 12pt body, small 8pt annotation labels).
// NOTE: the font engine keeps the LAST inserted font as the current one, so each font is
// (re)inserted with insertFont() right before it is used, making it the active font.
$insertFont = static function (string $style, float $size) use ($pdf): void {
$font = $pdf->font->insert($pdf->pon, 'helvetica', $style, $size);
$pdf->page->addContent($font['out']);
};
// ----------
// Colours (matching legacy example_064).
$black = $pdf->color->getPdfColor('rgb(0,0,0)'); // titles and labels (single-line, no page break)
$bodyBlue = 'rgb(0,0,255)'; // flowing body text (justified, as in example_064)
// Graphic styles: light-green fill, the no-write edge in red, the other edges in grey dashed.
$fillStyle = [
'lineWidth' => 0.254,
'lineCap' => 'butt',
'lineJoin' => 'miter',
'dashArray' => [1, 1],
'dashPhase' => 0,
'lineColor' => 'rgb(127,127,127)',
'fillColor' => 'rgb(220,255,220)',
];
$edgeGrey = [
'lineWidth' => 0.254,
'lineCap' => 'butt',
'lineJoin' => 'miter',
'dashArray' => [1, 1],
'dashPhase' => 0,
'lineColor' => 'rgb(127,127,127)',
];
$edgeRed = [
'lineWidth' => 0.254,
'lineCap' => 'butt',
'lineJoin' => 'miter',
'dashArray' => [],
'dashPhase' => 0,
'lineColor' => 'rgb(255,0,0)',
];
$arrowStyle = [
'lineWidth' => 0.254,
'lineCap' => 'butt',
'lineJoin' => 'miter',
'dashArray' => [],
'dashPhase' => 0,
'lineColor' => 'rgb(0,0,0)',
];
// Image used as a rectangular obstacle (legacy example used images/image_demo.jpg).
$imgFile = \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-image/test/images/200x100_RGBICC.jpg');
$imgId = $pdf->image->add($imgFile);
// Flowing body text (kept blue and justified as in example_064).
$intro =
'TEST PAGE REGIONS: a no-write region is a portion of the page with a rectangular or '
. 'trapezium shape that will not be covered when writing text. A region is aligned on the '
. 'left or right side of the page and is defined using a vertical segment. You can set '
. 'multiple regions for the same page, and combine several adjacent regions to approximate '
. 'curved shapes.';
$lorem =
'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. Cras eget velit nulla, eu sagittis elit. Nunc ac arcu est, in '
. 'lobortis tellus. Praesent condimentum rhoncus sodales. In hac habitasse platea dictumst. Proin '
. 'porta eros pharetra enim tincidunt dignissim nec vel dolor. Cras sapien elit, ornare ac '
. 'dignissim eu, ultricies ac eros. Maecenas augue magna, ultrices a congue in, mollis eu nulla. '
. 'Nunc venenatis massa at est eleifend faucibus. Vivamus sed risus lectus, nec interdum nunc. '
. 'Fusce et felis vitae diam lobortis sollicitudin. Aenean tincidunt accumsan nisi, id vehicula '
. 'quam laoreet elementum. Phasellus egestas interdum erat, et viverra ipsum ultricies ac.';
// More text than fits on a no-write page: with automatic page break enabled the flow continues
// on a fresh page. No-write regions are page-specific, so that continuation page has no
// obstacles and the text returns to the full page width.
$body = $intro . ' ' . \str_repeat($lorem . ' ', 5);
$margin = [
'PL' => 15.0,
'PR' => 15.0,
'CT' => 25.0,
'CB' => 12.0,
];
$bandHeight = 5.0;
// =============================================================
// Reusable building blocks, so each obstacle scenario can be rendered identically with the
// plain-text engine (addTextCell) and the HTML engine (addHTMLCell).
// Add a page and print its (black) title.
$startPage = static function (string $title) use ($pdf, $margin, $black, $insertFont): array {
$page = $pdf->addPage(['margin' => $margin]);
$pdf->page->addContent($black);
$insertFont('B', 13);
$pdf->addTextCellXY(txt: $title, posx: 15, posy: 12, drawcell: false);
return $page;
};
// Draw the two image obstacles plus the two trapezoids (with xt,yt / xb,yb / side labels and
// direction arrows, as in the legacy example) and register the matching no-write regions.
$setupTrapezoids = static function (float $pageHeight) use (
$pdf,
$imgId,
$fillStyle,
$edgeGrey,
$edgeRed,
$arrowStyle,
$black,
$insertFont,
$bandHeight,
): void {
// Two image obstacles: top-right and bottom-left.
$pdf->page->addContent($pdf->image->getSetImage($imgId, 155, 30, 40, 40, $pageHeight));
$pdf->page->addContent($pdf->image->getSetImage($imgId, 15, 230, 40, 40, $pageHeight));
// Left trapezoid: the slanted right edge (57,90)-(67,140) is the no-write boundary (red).
$pdf->page->addContent($pdf->graph->getPolygon([15, 90, 57, 90, 67, 140, 15, 140, 15, 90], 'DF', [
'all' => $fillStyle,
0 => $edgeGrey,
1 => $edgeRed,
2 => $edgeGrey,
3 => $edgeGrey,
]));
// Right trapezoid: the slanted left edge (155,180)-(145,130) is the no-write boundary (red).
$pdf->page->addContent($pdf->graph->getPolygon([145, 130, 195, 130, 195, 180, 155, 180, 145, 130], 'DF', [
'all' => $fillStyle,
0 => $edgeGrey,
1 => $edgeGrey,
2 => $edgeGrey,
3 => $edgeRed,
]));
// Annotation labels and direction arrows (8pt black), as in the legacy example.
$pdf->page->addContent($black);
$insertFont('', 8);
$pdf->addTextCellXY(
txt: 'xt,yt',
posx: 38,
posy: 90,
width: 18,
halign: \Com\Tecnick\Pdf\TextHAlign::Right,
drawcell: false,
);
$pdf->addTextCellXY(
txt: 'xb,yb',
posx: 48,
posy: 136,
width: 18,
halign: \Com\Tecnick\Pdf\TextHAlign::Right,
drawcell: false,
);
$pdf->addTextCellXY(
txt: 'side',
posx: 38,
posy: 111,
width: 16,
halign: \Com\Tecnick\Pdf\TextHAlign::Right,
drawcell: false,
);
$pdf->page->addContent($pdf->graph->getArrow(60, 115, 40, 115, 2, 4, 15, $arrowStyle));
$pdf->addTextCellXY(
txt: 'xt,yt',
posx: 147,
posy: 130,
width: 18,
halign: \Com\Tecnick\Pdf\TextHAlign::Left,
drawcell: false,
);
$pdf->addTextCellXY(
txt: 'xb,yb',
posx: 156,
posy: 176,
width: 18,
halign: \Com\Tecnick\Pdf\TextHAlign::Left,
drawcell: false,
);
$pdf->addTextCellXY(
txt: 'side',
posx: 158,
posy: 151,
width: 16,
halign: \Com\Tecnick\Pdf\TextHAlign::Left,
drawcell: false,
);
$pdf->page->addContent($pdf->graph->getArrow(152, 155, 172, 155, 2, 4, 15, $arrowStyle));
// No-write regions: each is a segment (xt,yt)-(xb,yb) plus the blocked page side.
$pdf->page->setNoWriteRegions(
[
['xt' => 153, 'yt' => 30, 'xb' => 153, 'yb' => 70, 'side' => 'R'], // top-right image
['xt' => 60, 'yt' => 90, 'xb' => 70, 'yb' => 140, 'side' => 'L'], // left trapezoid
['xt' => 143, 'yt' => 130, 'xb' => 153, 'yb' => 180, 'side' => 'R'], // right trapezoid
['xt' => 58, 'yt' => 230, 'xb' => 58, 'yb' => 270, 'side' => 'L'], // bottom-left image
],
$bandHeight,
);
};
// Draw the circular obstacle (left half-disc against the right content edge) traced by eight
// adjacent right-side segments, and register the matching no-write regions.
$setupCircle = static function () use ($pdf, $fillStyle, $bandHeight): void {
$pdf->page->addContent($pdf->graph->getPolygon(
[
195,
110,
179.693,
113.045,
166.716,
121.716,
158.045,
134.693,
155,
150,
158.045,
165.307,
166.716,
178.284,
179.693,
186.955,
195,
190,
],
'DF',
['all' => $fillStyle],
));
$pdf->page->setNoWriteRegions([
['xt' => 195, 'yt' => 110, 'xb' => 179.693, 'yb' => 113.045, 'side' => 'R'],
['xt' => 179.693, 'yt' => 113.045, 'xb' => 166.716, 'yb' => 121.716, 'side' => 'R'],
['xt' => 166.716, 'yt' => 121.716, 'xb' => 158.045, 'yb' => 134.693, 'side' => 'R'],
['xt' => 158.045, 'yt' => 134.693, 'xb' => 155, 'yb' => 150, 'side' => 'R'],
['xt' => 155, 'yt' => 150, 'xb' => 158.045, 'yb' => 165.307, 'side' => 'R'],
['xt' => 158.045, 'yt' => 165.307, 'xb' => 166.716, 'yb' => 178.284, 'side' => 'R'],
['xt' => 166.716, 'yt' => 178.284, 'xb' => 179.693, 'yb' => 186.955, 'side' => 'R'],
['xt' => 179.693, 'yt' => 186.955, 'xb' => 195, 'yb' => 190, 'side' => 'R'],
], $bandHeight);
};
// Flow the body with the plain-text engine. width 0 makes each band use its own writable width.
// The fill colour is set through the graph so it is tracked and re-applied automatically when the
// flow continues on an automatically added page (the colour resets per page).
$renderTextBody = static function () use ($pdf, $body, $bodyBlue, $insertFont): void {
$pdf->page->addContent($pdf->graph->add(['fillColor' => $bodyBlue]));
$insertFont('', 12);
$pdf->addTextCell(
txt: $body,
posx: 0,
posy: 0,
width: 0,
halign: \Com\Tecnick\Pdf\TextHAlign::Justify,
drawcell: false,
);
};
// Flow the SAME body with the HTML engine. A single justified, coloured paragraph hugs the
// obstacle band by band exactly like addTextCell, and overflows onto a fresh full-width page.
// A font must be active before addHTMLCell(). getHTMLCell() returns the same rendering as a
// string instead of emitting it (e.g. $code = $pdf->getHTMLCell(...); $pdf->page->addContent($code);).
$renderHtmlBody = static function () use ($pdf, $body, $bodyBlue, $insertFont): void {
$insertFont('', 12);
$region = $pdf->page->getRegion();
$html = '<p style="text-align:justify;color:' . $bodyBlue . ';">' . $body . '</p>';
$pdf->addHTMLCell(
html: $html,
posx: (float) $region['RX'],
posy: (float) $region['RY'],
width: (float) $region['RW'],
);
};
// =============================================================
// SCENARIO 1: rectangular and trapezoidal no-write regions around two images.
$page = $startPage('No-write page regions: addTextCell flowing around rectangular and trapezoidal shapes');
$pageHeight = (float) $page['height'];
$setupTrapezoids($pageHeight);
$renderTextBody();
$startPage('No-write page regions: addHTMLCell flowing around rectangular and trapezoidal shapes');
$setupTrapezoids($pageHeight);
$renderHtmlBody();
// =============================================================
// SCENARIO 2: eight adjacent regions approximating a circle (text wraps its left side).
$startPage('No-write page regions: addTextCell flowing around a circular shape (8 adjacent segments)');
$setupCircle();
$renderTextBody();
$startPage('No-write page regions: addHTMLCell flowing around a circular shape (8 adjacent segments)');
$setupCircle();
$renderHtmlBody();
// =============================================================
$rawpdf = $pdf->getOutPDFString();
$pdf->renderPDF(rawpdf: $rawpdf);