Describe the bug
Write() lays the text out from a fixed origin and expresses the current cursor as an offset from it:
$originx = $this->lmargin;
...
// Legacy wraps the lines inside the horizontal cell padding and
// starts them at the padded edge.
$originx += $this->cellpadding['L'];
$width = max(0.0, $width - $this->cellpadding['L'] - $this->cellpadding['R']);
$offset = max(0.0, $this->posx - $originx);
So the text is drawn at max($posx, $lmargin + $padding). Legacy TCPDF drew it at
$posx + $padding.
At the left margin the two agree by coincidence: the clamp lands on exactly the abscissa the addition
would have produced. After any setX() beyond the margin they diverge, and the text is drawn one
padding to the left of where legacy drew it.
Anything that positions text by arithmetic on GetX() is affected — indented blocks, hanging labels,
cc: lists, signature blocks. In our appointment letters it shifted every such element left by
exactly 1.00mm, which is the default left cell padding.
To Reproduce
<?php
require __DIR__ . '/vendor/autoload.php';
define('K_PATH_FONTS', __DIR__ . '/vendor/tecnickcom/tc-lib-pdf-font/target/fonts/');
$pdf = new TCPDF('P', 'mm', 'LETTER', true, 'UTF-8');
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->setMargins(20, 20, 20);
$pdf->AddPage();
$pdf->setFont('helvetica', '', 11);
printf("left cell padding: %.2f\n", $pdf->getCellPaddings()['L']);
$pdf->Write(5, "first\n"); // cursor is at the left margin, 20.00
$pdf->setX(40.0); // an explicit indent
$pdf->Write(5, "second\n");
file_put_contents(__DIR__ . '/cursor-padding.pdf', $pdf->Output(null, 'S'));
Reading the Td operators back out of the page gives the two text origins. A self-contained script
that does this and exits non-zero while they are inconsistent is attached; it inflates the streams
itself, so it needs no parser.
Expected behavior
Write() applies the horizontal cell padding the same way wherever the cursor is, so the distance
between the cursor and the first glyph is constant. Legacy applied it at the cursor.
Logs
On 7.0.6:
left cell padding : 1.00 mm
cursor 20.00 at the margin -> text at 21.00 (padding applied: 1.00)
cursor 40.00 after setX() -> text at 40.00 (padding applied: 0.00)
With the fix below:
cursor 20.00 at the margin -> text at 21.00 (padding applied: 1.00)
cursor 40.00 after setX() -> text at 41.00 (padding applied: 1.00)
Environment:
- PHP version: 8.5.9
- Version: 7.0.6
Suggested fix
Add the padding to the cursor rather than clamping the cursor to the padded origin. The margin case
is unchanged, because the addition and the clamp produce the same result there.
- $offset = max(0.0, $this->posx - $originx);
+ // Legacy starts the text at the cursor plus the padding; clamping to the padded
+ // origin instead loses the padding for every cursor beyond the left margin.
+ $offset = max(0.0, $this->posx + $this->cellpadding['L'] - $originx);
Patch attached. Verified against 7.0.6: the padding is then applied consistently, and text written at
the left margin lands exactly where it does today.
Additional context
MultiCell() and writeHTMLCell() compute their origins separately and were not examined, so I
cannot say whether the same clamp appears there.
Describe the bug
Write()lays the text out from a fixed origin and expresses the current cursor as an offset from it:So the text is drawn at
max($posx, $lmargin + $padding). Legacy TCPDF drew it at$posx + $padding.At the left margin the two agree by coincidence: the clamp lands on exactly the abscissa the addition
would have produced. After any
setX()beyond the margin they diverge, and the text is drawn onepadding to the left of where legacy drew it.
Anything that positions text by arithmetic on
GetX()is affected — indented blocks, hanging labels,cc:lists, signature blocks. In our appointment letters it shifted every such element left byexactly 1.00mm, which is the default left cell padding.
To Reproduce
Reading the
Tdoperators back out of the page gives the two text origins. A self-contained scriptthat does this and exits non-zero while they are inconsistent is attached; it inflates the streams
itself, so it needs no parser.
Expected behavior
Write()applies the horizontal cell padding the same way wherever the cursor is, so the distancebetween the cursor and the first glyph is constant. Legacy applied it at the cursor.
Logs
On 7.0.6:
With the fix below:
Environment:
Suggested fix
Add the padding to the cursor rather than clamping the cursor to the padded origin. The margin case
is unchanged, because the addition and the clamp produce the same result there.
Patch attached. Verified against 7.0.6: the padding is then applied consistently, and text written at
the left margin lands exactly where it does today.
Additional context
MultiCell()andwriteHTMLCell()compute their origins separately and were not examined, so Icannot say whether the same clamp appears there.