22
33//============================================================+
44// File name : tcpdf.php
5- // Version : 7.0.12
5+ // Version : 7.0.13
66// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
77// License : GNU-LGPL v3 (https://www.gnu.org/copyleft/lesser.html)
88// Copyright (C): 2002-2026 Nicola Asuni - Tecnick.com LTD
1515 * See: https://tcpdf.org
1616 * @package com.tecnick.tcpdf
1717 * @author Nicola Asuni
18- * @version 7.0.12
18+ * @version 7.0.13
1919 */
2020
2121// TCPDF configuration
@@ -1710,6 +1710,7 @@ protected function insertPageContent(int $pid, int $start, string $content): voi
17101710 protected function stretchedCellText (
17111711 int $ mode ,
17121712 string $ txt ,
1713+ float $ cellx ,
17131714 float $ width ,
17141715 float $ height ,
17151716 float $ textw ,
@@ -1729,33 +1730,16 @@ protected function stretchedCellText(
17291730 $ rawwidth =
17301731 $ eng ->font ->getOrdArrDims (array_values ($ eng ->uniconv ->strToOrdArr ($ txt )))['totwidth ' ] / $ this ->kratio ;
17311732 $ naturalbox = max ($ rawwidth , $ textw ) + $ padl + $ padr ;
1732- $ boxx = $ this ->rtlmode ? $ this -> posx + $ width - $ naturalbox : $ this -> posx ;
1733+ $ boxx = $ this ->rtlmode ? $ cellx + $ width - $ naturalbox : $ cellx ;
17331734
1734- $ text = $ eng -> getTextCell (
1735+ $ text = $ this -> cellTextRun (
17351736 $ txt ,
17361737 $ boxx ,
1737- $ this ->posy ,
17381738 $ naturalbox ,
17391739 $ height ,
1740- 0 ,
1741- 0 ,
17421740 $ valign ,
17431741 $ this ->rtlmode ? 'R ' : 'L ' ,
17441742 null ,
1745- ['all ' => ['lineWidth ' => 0.0 ]],
1746- $ this ->textrendermode ['stroke ' ],
1747- 0 ,
1748- 0 ,
1749- 0 ,
1750- true ,
1751- $ this ->textrendermode ['fill ' ],
1752- $ this ->textrendermode ['stroke ' ] > 0 ,
1753- $ this ->fontdecor ['U ' ],
1754- $ this ->fontdecor ['D ' ],
1755- $ this ->fontdecor ['O ' ],
1756- $ this ->textrendermode ['clip ' ],
1757- false ,
1758- $ this ->forcedTextDir (),
17591743 $ shadow ,
17601744 );
17611745 $ eng ->font ->popLastFont ();
@@ -1769,7 +1753,7 @@ protected function stretchedCellText(
17691753 // Glyph scaling anchored at the text start edge: the ambient
17701754 // spacing/stretching apply inside the saved graphics state and
17711755 // the CTM brings the rendered width up to the available width.
1772- $ anchor = $ this ->rtlmode ? $ this -> posx + $ width - $ padr : $ this -> posx + $ padl ;
1756+ $ anchor = $ this ->rtlmode ? $ cellx + $ width - $ padr : $ cellx + $ padl ;
17731757 return (
17741758 $ basestate
17751759 . $ eng ->graph ->getStartTransform ()
@@ -1793,6 +1777,128 @@ protected function stretchedCellText(
17931777 return $ spacingstate . $ text . $ reset ;
17941778 }
17951779
1780+ /**
1781+ * PDF code for a cell whose text is drawn on one line regardless of the
1782+ * cell width.
1783+ *
1784+ * The box is drawn by the engine with the requested geometry and the
1785+ * border-adjusted cell definition used by getTextCell(). The text is
1786+ * drawn in a box wider than the text, anchored to the cell so the
1787+ * engine alignment places it as in the cell: left edge for L, right
1788+ * edge for R, center for C.
1789+ *
1790+ * @param array{margin: array{T: float, R: float, B: float, L: float}, padding: array{T: float, R: float, B: float, L: float}, borderpos: float}|null $engcell
1791+ * @param array<int|string, array<string, mixed>> $styles
1792+ */
1793+ protected function overflowCellOutput (
1794+ string $ txt ,
1795+ float $ cellx ,
1796+ float $ width ,
1797+ float $ height ,
1798+ float $ txtw ,
1799+ string $ halign ,
1800+ string $ valign ,
1801+ ?array $ engcell ,
1802+ array $ styles ,
1803+ ): string {
1804+ $ eng = $ this ->engine ();
1805+ /** @var array{margin: array{T: float, R: float, B: float, L: float}, padding: array{T: float, R: float, B: float, L: float}, borderpos: float} $cell */
1806+ $ cell = $ this ->engineMethod ('adjustMinCellPadding ' )->invoke ($ eng , $ styles , $ engcell );
1807+ $ box = $ this ->engineMethod ('drawCell ' )->invoke (
1808+ $ eng ,
1809+ $ eng ->toPoints ($ cellx ) + $ cell ['margin ' ]['L ' ],
1810+ $ eng ->toYPoints ($ this ->posy ) - $ cell ['margin ' ]['T ' ],
1811+ $ eng ->toPoints ($ width ),
1812+ $ eng ->toPoints ($ height ),
1813+ $ styles ,
1814+ $ cell ,
1815+ );
1816+
1817+ if ($ halign === 'J ' ) {
1818+ $ halign = $ this ->isRTLTextDir () ? 'R ' : 'L ' ;
1819+ }
1820+
1821+ // Twice the estimated text width leaves the engine enough room for
1822+ // the prepared text, whose width can differ from the estimate.
1823+ $ boxw = $ width + (2 * $ txtw ) + $ eng ->toUnit ($ cell ['padding ' ]['L ' ] + $ cell ['padding ' ]['R ' ]);
1824+ $ boxx = match ($ halign ) {
1825+ 'R ' => $ cellx + $ width - $ boxw ,
1826+ 'C ' => $ cellx + (($ width - $ boxw ) / 2 ),
1827+ default => $ cellx ,
1828+ };
1829+
1830+ return (is_string ($ box ) ? $ box : '' ) . $ this ->cellTextRun ($ txt , $ boxx , $ boxw , $ height , $ valign , $ halign , $ cell , $ this ->engineShadow ());
1831+ }
1832+
1833+ /**
1834+ * Text-only engine cell run (no box) with the current text render mode
1835+ * and decorations.
1836+ *
1837+ * @param array{margin: array{T: float, R: float, B: float, L: float}, padding: array{T: float, R: float, B: float, L: float}, borderpos: float}|null $cell
1838+ * @param array{xoffset: float, yoffset: float, opacity: float, mode: string, color: string}|null $shadow
1839+ */
1840+ protected function cellTextRun (
1841+ string $ txt ,
1842+ float $ posx ,
1843+ float $ width ,
1844+ float $ height ,
1845+ string $ valign ,
1846+ string $ halign ,
1847+ ?array $ cell ,
1848+ ?array $ shadow ,
1849+ ): string {
1850+ return $ this ->engine ()->getTextCell (
1851+ $ txt ,
1852+ $ posx ,
1853+ $ this ->posy ,
1854+ $ width ,
1855+ $ height ,
1856+ 0 ,
1857+ 0 ,
1858+ $ valign ,
1859+ $ halign ,
1860+ $ cell ,
1861+ ['all ' => ['lineWidth ' => 0.0 ]],
1862+ $ this ->textrendermode ['stroke ' ],
1863+ 0 ,
1864+ 0 ,
1865+ 0 ,
1866+ true ,
1867+ $ this ->textrendermode ['fill ' ],
1868+ $ this ->textrendermode ['stroke ' ] > 0 ,
1869+ $ this ->fontdecor ['U ' ],
1870+ $ this ->fontdecor ['D ' ],
1871+ $ this ->fontdecor ['O ' ],
1872+ $ this ->textrendermode ['clip ' ],
1873+ false ,
1874+ $ this ->forcedTextDir (),
1875+ $ shadow ,
1876+ );
1877+ }
1878+
1879+ /**
1880+ * Protected engine method made callable from the facade.
1881+ */
1882+ protected function engineMethod (string $ name ): \ReflectionMethod
1883+ {
1884+ static $ methods = [];
1885+ if (!isset ($ methods [$ name ])) {
1886+ $ methods [$ name ] = new \ReflectionMethod (\Com \Tecnick \Pdf \Tcpdf::class, $ name );
1887+ }
1888+
1889+ return $ methods [$ name ];
1890+ }
1891+
1892+ /**
1893+ * Text width in user units measured with the current engine font,
1894+ * including its spacing and stretching.
1895+ */
1896+ protected function engineTextWidth (string $ txt ): float
1897+ {
1898+ $ eng = $ this ->engine ();
1899+ return $ eng ->toUnit ($ eng ->font ->getOrdArrDims (array_values ($ eng ->uniconv ->strToOrdArr ($ txt )))['totwidth ' ]);
1900+ }
1901+
17961902 /**
17971903 * Re-apply the current font to the engine font stack so it carries the
17981904 * up-to-date spacing/stretching.
@@ -3396,13 +3502,40 @@ public function Cell(
33963502 }
33973503 }
33983504
3399- if ($ stretchmode > 0 && $ txt !== '' ) {
3505+ // Legacy Cell never wraps. Text that may not fit the inner cell
3506+ // width is drawn on one line by overflowCellOutput(). The width
3507+ // estimate skips the engine text preparation (cleanup, Bidi,
3508+ // shaping, hyphenation), so near-fitting text takes the same path;
3509+ // its rendering is unchanged when the text fits.
3510+ $ txtw = 0.0 ;
3511+ if ($ stretchmode === 0 && $ txt !== '' ) {
3512+ $ txtw = $ stretchtextw > 0 ? $ stretchtextw : $ this ->engineTextWidth ($ txt );
3513+ $ innerw = $ width - $ borderpadding ['L ' ] - $ borderpadding ['R ' ];
3514+ if ($ txtw <= $ innerw * 0.95 ) {
3515+ $ txtw = 0.0 ;
3516+ }
3517+ }
3518+
3519+ if ($ txtw > 0 ) {
3520+ $ out .= $ this ->overflowCellOutput (
3521+ $ txt ,
3522+ $ cellx ,
3523+ $ width ,
3524+ $ height ,
3525+ $ txtw ,
3526+ $ this ->halignToEngine ($ _align ),
3527+ $ this ->valignToEngine ($ _valign === 'M ' ? 'C ' : $ _valign ),
3528+ $ engcell ,
3529+ $ styles ,
3530+ );
3531+ } elseif ($ stretchmode > 0 && $ txt !== '' ) {
34003532 // Box and text drawn separately: the border must keep the
34013533 // requested geometry while the text run is scaled or spaced.
34023534 $ out .= $ this ->cellBoxOutput ($ cellx , $ this ->posy , $ width , $ height , $ styles , (bool ) $ _fill );
34033535 $ out .= $ this ->stretchedCellText (
34043536 $ stretchmode ,
34053537 $ txt ,
3538+ $ cellx ,
34063539 $ width ,
34073540 $ height ,
34083541 $ stretchtextw ,
0 commit comments