* @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 = '

' . $body . '

'; $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);