Skip to content

Write() drops the left cell padding once the cursor has been moved [7.x] #874

Description

@wayne530

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions