Revisited: Tcpdf – Variable Height Table Rows With MultiCell

A few weeks ago I wrote an article about creating variable height table rows using tcpdf. It was a neat solution and I liked it, which was the reason I blogged about it in the first place. However, it turns out that ‘sharing the love’ is not the only reason to blog about these things. It can also be a learning experience thanks to the commenting from you out there in the interwebs.

The particular comment in question, from mike, made me realise that there was a bit of a flaw in my neat idea – page breaks. Because I was first drawing the text, the automatic page breaks played havoc with the positioning when it came to drawing the borders. If the text was so long that it wrapped across enough lines to cause a page break, when I tried to reposition to draw the borders I was in completely the wrong place and it all went a little Pete Tong.

Still, not deterred by this little setback, I re-looked at the problem and have come up with a different approach that solves this problem and adds the ability to do a few more things.

When I relooked at the documentation, I discovered the useful little function: getNumLines(). This actually allows us to determine how many lines a string of text will take up, given a particular width. In effect, it allows us to do what I was using MultiCell to return, without actually drawing anything. This lets us to determined the maximum cell height with one line of code:

$linecount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80),$pdf->getNumLines($row['cell3data'], 80));

Now that we know the cell height before we have drawn anything, we can use MultiCell() to draw the text and the borders at the same time.

This was not the end, however. My particular implementation was a grid of data that had borders between the column and not between the rows. All very pretty until that automatic page break happened and gave us a grid of data without a border at the bottom. To add insult to injury, the new page ended up with a grid of data with no border at the top!

To solve this, I needed to know if the row was going to be the first of the page, so I could draw the bottom border on the previous row and give the new row a top border. With the use of the page dimensions I could work this out:

$dimensions = $pdf->getPageDimensions();
$hasBorder = false; //flag for fringe case
foreach($data as $row) {
	$rowcount = 0;
	//work out the number of lines required
	$rowcount = max($pdf->getNumLines($row['cell1data'], 80),$pdf->getNumLines($row['cell2data'], 80),$pdf->getNumLines($row['cell3data'], 80));
	$startY = $pdf->GetY();
	if (($startY + $rowcount * 6) + $dimensions['bm'] > ($dimensions['hk'])) {
		//this row will cause a page break, draw the bottom border on previous row and give this a top border
		//we could force a page break and rewrite grid headings here
		if ($hasborder) {
			$hasborder = false;
		} else {
			$pdf->Cell(240,0,'','T'); //draw bottom border on previous row
		$borders = 'LTR';
	} elseif ((ceil($startY) + $rowcount * 6) + $dimensions['bm'] == floor($dimensions['hk'])) {
		//fringe case where this cell will just reach the page break
		//draw the cell with a bottom border as we cannot draw it otherwise
		$borders = 'LRB';	
		$hasborder = true; //stops the attempt to draw the bottom border on the next row
	} else {
		//normal cell
		$borders = 'LR';
	//now draw it
	$pdf->MultiCell(80,$rowcount * 6,$row['cell1data'],$borders,'L',0,0);
	$pdf->MultiCell(80,$rowcount * 6,$row['cell2data'],$borders,'L',0,0);
	$pdf->MultiCell(80,$rowcount * 6,$row['cell3data'],$borders,'L',0,0);
$pdf->Cell(240,0,'','T');  //last bottom border

Note that there is a fringe case that I came across. This was when the row would not cause a page break as it was exactly the height of the remaining space. Any attempt to draw the bottom border with a separate call would cause a page break. I solved this by ensuring in that particular case, the cell was drawn with the bottom border already.

What gives this method more power (apart from actually working) is that we know when there is going to be a new page and we could choose to force a new page and if we wanted. We could then re-plot the grid headings before creating the row. That way we can have the grid headings on every page.

I am determined to get a water-tight solution to this problem and I think this gets us closer. If you do find any problems with it, let me know and I will see if we can solve them. Thanks for your feedback.

Update: Bretton Eveleigh has written a class that encapsulates this and other helper methods into a format that makes creating tables with Tcpdf much easier. See the comments below for the link. Thanks Bretton.


54 thoughts on “Revisited: Tcpdf – Variable Height Table Rows With MultiCell

  1. Hi James, it is 7 years since I wrote this but if you look in the comments there is discussion about this. It is a fixed line height I chose. If you page through the comments you will find a response that explains how to do this dynamically.

  2. What is the significance of the magic number 6 in your calculations? ie: $startY + $rowcount * 6
    Where did the 6 come from, what does it mean?

  3. Thanks Dan! This worked a treat for outputting rows of multicells in a table format with subtotals etc. Many thanks for this article!

Leave a Reply

Your email address will not be published. Required fields are marked *